kimaki 0.26.0 → 0.28.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/dist/agent-model.e2e.test.js +250 -164
- package/dist/anthropic-account-identity.js +9 -1
- package/dist/anthropic-auth-plugin.js +10 -13
- package/dist/anthropic-auth-plugin.test.js +17 -0
- package/dist/anthropic-auth-state.js +1 -0
- package/dist/bash-tool-schema-plugin.js +32 -0
- package/dist/bash-tool-schema-plugin.test.js +49 -0
- package/dist/bin.js +73 -19
- package/dist/cli-commands/maintenance.js +46 -7
- package/dist/cli-commands/project.js +11 -27
- package/dist/cli-commands/send.js +30 -1
- package/dist/cli-commands/session.js +322 -90
- package/dist/cli-commands/task.js +34 -15
- package/dist/cli-parsing.test.js +83 -4
- package/dist/cli-runner.js +44 -8
- package/dist/cli-runner.test.js +27 -0
- package/dist/cli-send-thread.e2e.test.js +9 -9
- package/dist/cli.js +32 -0
- package/dist/commands/abort.js +5 -1
- package/dist/commands/action-buttons.js +2 -1
- package/dist/commands/add-project.js +4 -7
- package/dist/commands/agent.js +164 -32
- package/dist/commands/ask-question.js +23 -33
- package/dist/commands/ask-question.test.js +43 -1
- package/dist/commands/btw.js +28 -6
- package/dist/commands/file-upload.js +2 -1
- package/dist/commands/fork-subagent.js +2 -4
- package/dist/commands/fork.js +4 -4
- package/dist/commands/last-sessions.js +17 -10
- package/dist/commands/login.js +6 -0
- package/dist/commands/merge-worktree.js +47 -14
- package/dist/commands/model-variant.js +8 -1
- package/dist/commands/model.js +16 -9
- package/dist/commands/multioauth.js +1 -0
- package/dist/commands/new-worktree.js +170 -102
- package/dist/commands/queue.js +3 -2
- package/dist/commands/resume.js +4 -4
- package/dist/commands/session.js +78 -63
- package/dist/commands/user-command.js +6 -8
- package/dist/commands/worktrees.js +5 -13
- package/dist/config.js +16 -1
- package/dist/cpu-profiler.js +211 -0
- package/dist/cpu-profiler.test.js +160 -0
- package/dist/database.js +152 -3
- package/dist/db.js +33 -0
- package/dist/db.test.js +267 -1
- package/dist/default-channel-provisioning.test.js +148 -0
- package/dist/discord-bot.js +517 -488
- package/dist/discord-command-registration.js +25 -10
- package/dist/discord-command-registration.test.js +17 -0
- package/dist/discord-utils.js +97 -5
- package/dist/discord-utils.test.js +151 -1
- package/dist/errors.js +10 -0
- package/dist/exec-async.js +17 -5
- package/dist/external-directory-permissions.e2e.test.js +11 -7
- package/dist/external-directory-project-deny.e2e.test.js +5 -3
- package/dist/external-opencode-sync.js +94 -17
- package/dist/external-opencode-sync.test.js +206 -0
- package/dist/extract-sdk-error-message.test.js +13 -0
- package/dist/file-edit-log.js +197 -0
- package/dist/file-edit-log.test.js +258 -0
- package/dist/format-tables.js +52 -0
- package/dist/format-tables.test.js +93 -0
- package/dist/gateway-proxy-reconnect.e2e.test.js +28 -8
- package/dist/gateway-proxy.e2e.test.js +18 -18
- package/dist/git-worktree-core.js +182 -17
- package/dist/interaction-handler.js +416 -398
- package/dist/ipc-tools-plugin.js +68 -3
- package/dist/kimaki-opencode-plugin-loading.e2e.test.js +122 -58
- package/dist/kimaki-opencode-plugin.js +9 -3
- package/dist/kimaki-workspace-adaptor.js +33 -10
- package/dist/logger.js +11 -1
- package/dist/markdown.js +3 -1
- package/dist/markdown.test.js +11 -3
- package/dist/message-formatting.js +181 -51
- package/dist/message-formatting.test.js +529 -22
- package/dist/message-preprocessing.js +105 -1
- package/dist/oauth-rotation-shared.js +10 -0
- package/dist/openai-auth-plugin.js +1 -0
- package/dist/openai-auth-state.js +1 -0
- package/dist/opencode-command.js +28 -0
- package/dist/opencode-command.test.js +109 -1
- package/dist/opencode-interrupt-plugin.js +4 -0
- package/dist/opencode-interrupt-plugin.test.js +24 -0
- package/dist/opencode.js +82 -15
- package/dist/project-add.test.js +62 -0
- package/dist/question-abort-clears-dropdown.e2e.test.js +89 -0
- package/dist/question-abort-retry-text-before-dropdown.e2e.test.js +150 -0
- package/dist/question-text-before-dropdown.e2e.test.js +52 -0
- package/dist/queue-advanced-abort.e2e.test.js +8 -10
- package/dist/queue-advanced-action-buttons.e2e.test.js +8 -8
- package/dist/queue-advanced-e2e-setup.js +78 -5
- package/dist/queue-advanced-footer.e2e.test.js +60 -269
- package/dist/queue-advanced-model-switch.e2e.test.js +9 -11
- package/dist/queue-advanced-permissions-typing.e2e.test.js +15 -15
- package/dist/queue-advanced-question.e2e.test.js +32 -27
- package/dist/queue-advanced-typing-interrupt.e2e.test.js +10 -12
- package/dist/queue-advanced-typing.e2e.test.js +7 -9
- package/dist/queue-delete-message.e2e.test.js +3 -3
- package/dist/queue-drain-after-interactive-ui.e2e.test.js +5 -5
- package/dist/queue-interrupt-drain.e2e.test.js +122 -12
- package/dist/queue-question-select-drain.e2e.test.js +34 -132
- package/dist/runtime-lifecycle.e2e.test.js +21 -51
- package/dist/schema.js +35 -1
- package/dist/session-handler/event-stream-state.js +391 -50
- package/dist/session-handler/event-stream-state.test.js +736 -8
- package/dist/session-handler/model-utils.js +172 -5
- package/dist/session-handler/model-utils.test.js +445 -0
- package/dist/session-handler/opencode-session-event-log.js +30 -1
- package/dist/session-handler/thread-ingress.test.js +41 -0
- package/dist/session-handler/thread-runtime-state.js +2 -0
- package/dist/session-handler/thread-session-runtime-summary.test.js +121 -0
- package/dist/session-handler/thread-session-runtime.js +574 -299
- package/dist/session-search.js +123 -4
- package/dist/session-search.test.js +248 -1
- package/dist/session-sleep.test.js +224 -0
- package/dist/session-title-rename.test.js +37 -39
- package/dist/sleep.e2e.test.js +494 -0
- package/dist/startup-time.e2e.test.js +2 -1
- package/dist/store.js +3 -0
- package/dist/system-message.js +102 -16
- package/dist/system-message.test.js +192 -15
- package/dist/task-runner.js +96 -5
- package/dist/task-schedule.js +87 -1
- package/dist/task-schedule.test.js +149 -1
- package/dist/test-utils.js +17 -9
- package/dist/thinking-utils.js +10 -0
- package/dist/thinking-utils.test.js +35 -0
- package/dist/thread-message-queue.e2e.test.js +65 -213
- package/dist/undo-redo.e2e.test.js +17 -9
- package/dist/voice-attachment.js +23 -1
- package/dist/voice-handler.js +21 -4
- package/dist/voice-message.e2e.test.js +144 -30
- package/dist/voice-routing-input.e2e.test.js +195 -0
- package/dist/voice-routing-question.e2e.test.js +125 -0
- package/dist/voice.js +28 -8
- package/dist/voice.test.js +96 -30
- package/dist/worktree-clone-isolation.e2e.test.js +188 -0
- package/dist/worktree-lifecycle.e2e.test.js +55 -36
- package/dist/worktrees.js +226 -443
- package/dist/worktrees.test.js +435 -58
- package/dist/xai-auth-plugin.js +1 -0
- package/dist/xai-auth-state.js +1 -0
- package/package.json +8 -6
- package/skills/bump-holocron-consumers/SKILL.md +137 -0
- package/skills/critique/SKILL.md +4 -4
- package/skills/errore/SKILL.md +60 -218
- package/skills/goke/SKILL.md +150 -10
- package/skills/holocron/SKILL.md +154 -17
- package/skills/npm-package/SKILL.md +55 -35
- package/skills/profano/SKILL.md +17 -2
- package/skills/spiceflow/SKILL.md +39 -3
- package/skills/strada/SKILL.md +39 -0
- package/skills/zele/SKILL.md +19 -0
- package/src/agent-model.e2e.test.ts +287 -186
- package/src/anthropic-account-identity.ts +20 -1
- package/src/anthropic-auth-plugin.test.ts +19 -0
- package/src/anthropic-auth-plugin.ts +12 -16
- package/src/anthropic-auth-state.ts +1 -0
- package/src/bash-tool-schema-plugin.test.ts +55 -0
- package/src/bash-tool-schema-plugin.ts +46 -0
- package/src/bin.ts +78 -26
- package/src/cli-commands/maintenance.ts +59 -13
- package/src/cli-commands/project.ts +14 -35
- package/src/cli-commands/send.ts +31 -1
- package/src/cli-commands/session.ts +374 -114
- package/src/cli-commands/task.ts +34 -15
- package/src/cli-parsing.test.ts +103 -4
- package/src/cli-runner.test.ts +49 -0
- package/src/cli-runner.ts +44 -7
- package/src/cli-send-thread.e2e.test.ts +9 -11
- package/src/cli.ts +53 -0
- package/src/commands/abort.ts +6 -1
- package/src/commands/action-buttons.ts +2 -1
- package/src/commands/add-project.ts +4 -8
- package/src/commands/agent.ts +205 -29
- package/src/commands/ask-question.test.ts +48 -1
- package/src/commands/ask-question.ts +23 -33
- package/src/commands/btw.ts +40 -5
- package/src/commands/file-upload.ts +2 -1
- package/src/commands/fork-subagent.ts +2 -3
- package/src/commands/fork.ts +4 -3
- package/src/commands/last-sessions.ts +10 -4
- package/src/commands/login.ts +6 -0
- package/src/commands/merge-worktree.ts +54 -16
- package/src/commands/model-variant.ts +9 -1
- package/src/commands/model.ts +20 -9
- package/src/commands/multioauth.ts +1 -0
- package/src/commands/new-worktree.ts +138 -55
- package/src/commands/queue.ts +3 -2
- package/src/commands/resume.ts +4 -3
- package/src/commands/session.ts +94 -70
- package/src/commands/user-command.ts +6 -6
- package/src/commands/worktrees.ts +5 -12
- package/src/config.ts +18 -1
- package/src/cpu-profiler.test.ts +183 -0
- package/src/cpu-profiler.ts +293 -0
- package/src/database.ts +235 -3
- package/src/db.test.ts +293 -0
- package/src/db.ts +38 -0
- package/src/default-channel-provisioning.test.ts +155 -0
- package/src/discord-bot.ts +78 -36
- package/src/discord-command-registration.test.ts +20 -0
- package/src/discord-command-registration.ts +42 -12
- package/src/discord-utils.test.ts +176 -0
- package/src/discord-utils.ts +152 -7
- package/src/errors.ts +12 -0
- package/src/exec-async.ts +20 -11
- package/src/external-directory-permissions.e2e.test.ts +11 -7
- package/src/external-directory-project-deny.e2e.test.ts +5 -3
- package/src/external-opencode-sync.test.ts +248 -0
- package/src/external-opencode-sync.ts +122 -19
- package/src/extract-sdk-error-message.test.ts +18 -0
- package/src/file-edit-log.test.ts +287 -0
- package/src/file-edit-log.ts +283 -0
- package/src/format-tables.test.ts +101 -0
- package/src/format-tables.ts +75 -0
- package/src/gateway-proxy-reconnect.e2e.test.ts +38 -8
- package/src/gateway-proxy.e2e.test.ts +18 -17
- package/src/git-worktree-core.ts +247 -20
- package/src/interaction-handler.ts +36 -13
- package/src/ipc-tools-plugin.ts +82 -5
- package/src/kimaki-opencode-plugin-loading.e2e.test.ts +188 -97
- package/src/kimaki-opencode-plugin.ts +9 -3
- package/src/kimaki-workspace-adaptor.ts +40 -11
- package/src/logger.ts +14 -4
- package/src/markdown.test.ts +13 -3
- package/src/markdown.ts +4 -1
- package/src/message-formatting.test.ts +672 -25
- package/src/message-formatting.ts +242 -53
- package/src/message-preprocessing.ts +116 -1
- package/src/oauth-rotation-shared.ts +10 -0
- package/src/openai-auth-plugin.ts +1 -0
- package/src/openai-auth-state.ts +1 -0
- package/src/opencode-command.test.ts +140 -0
- package/src/opencode-command.ts +38 -0
- package/src/opencode-interrupt-plugin.test.ts +32 -0
- package/src/opencode-interrupt-plugin.ts +3 -0
- package/src/opencode.ts +115 -15
- package/src/project-add.test.ts +80 -0
- package/src/question-abort-clears-dropdown.e2e.test.ts +109 -0
- package/src/question-abort-retry-text-before-dropdown.e2e.test.ts +172 -0
- package/src/question-text-before-dropdown.e2e.test.ts +67 -0
- package/src/queue-advanced-abort.e2e.test.ts +8 -9
- package/src/queue-advanced-action-buttons.e2e.test.ts +8 -8
- package/src/queue-advanced-e2e-setup.ts +83 -4
- package/src/queue-advanced-footer.e2e.test.ts +67 -315
- package/src/queue-advanced-model-switch.e2e.test.ts +9 -10
- package/src/queue-advanced-permissions-typing.e2e.test.ts +15 -15
- package/src/queue-advanced-question.e2e.test.ts +36 -30
- package/src/queue-advanced-typing-interrupt.e2e.test.ts +11 -11
- package/src/queue-advanced-typing.e2e.test.ts +8 -8
- package/src/queue-delete-message.e2e.test.ts +3 -3
- package/src/queue-drain-after-interactive-ui.e2e.test.ts +5 -5
- package/src/queue-interrupt-drain.e2e.test.ts +133 -12
- package/src/queue-question-select-drain.e2e.test.ts +36 -161
- package/src/runtime-lifecycle.e2e.test.ts +21 -54
- package/src/schema.sql +15 -2
- package/src/schema.ts +37 -1
- package/src/session-handler/event-stream-state.test.ts +790 -7
- package/src/session-handler/event-stream-state.ts +568 -53
- package/src/session-handler/model-utils.test.ts +537 -0
- package/src/session-handler/model-utils.ts +281 -5
- package/src/session-handler/opencode-session-event-log.ts +31 -1
- package/src/session-handler/thread-ingress.test.ts +54 -0
- package/src/session-handler/thread-runtime-state.ts +3 -0
- package/src/session-handler/thread-session-runtime-summary.test.ts +169 -0
- package/src/session-handler/thread-session-runtime.ts +712 -358
- package/src/session-search.test.ts +281 -0
- package/src/session-search.ts +241 -7
- package/src/session-sleep.test.ts +294 -0
- package/src/session-title-rename.test.ts +54 -48
- package/src/sleep.e2e.test.ts +565 -0
- package/src/startup-time.e2e.test.ts +5 -1
- package/src/store.ts +22 -0
- package/src/system-message.test.ts +226 -15
- package/src/system-message.ts +130 -16
- package/src/task-runner.ts +138 -3
- package/src/task-schedule.test.ts +170 -0
- package/src/task-schedule.ts +141 -1
- package/src/test-utils.ts +21 -10
- package/src/thinking-utils.test.ts +46 -0
- package/src/thinking-utils.ts +21 -0
- package/src/thread-message-queue.e2e.test.ts +65 -245
- package/src/undo-redo.e2e.test.ts +17 -9
- package/src/voice-attachment.ts +29 -1
- package/src/voice-handler.ts +23 -4
- package/src/voice-message.e2e.test.ts +149 -29
- package/src/voice-routing-input.e2e.test.ts +203 -0
- package/src/voice-routing-question.e2e.test.ts +131 -0
- package/src/voice.test.ts +107 -29
- package/src/voice.ts +34 -6
- package/src/worktree-clone-isolation.e2e.test.ts +213 -0
- package/src/worktree-lifecycle.e2e.test.ts +55 -35
- package/src/worktrees.test.ts +505 -64
- package/src/worktrees.ts +257 -603
- package/src/xai-auth-plugin.ts +1 -0
- package/src/xai-auth-state.ts +1 -0
- package/dist/message-finish-field.e2e.test.js +0 -169
- package/dist/subagent-rate-limit-plugin.js +0 -184
- package/src/message-finish-field.e2e.test.ts +0 -196
- package/src/subagent-rate-limit-plugin.ts +0 -228
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
// E2e test
|
|
2
|
-
// via the Discord dropdown select menu. Reproduces a bug where answering via
|
|
3
|
-
// select (not text) leaves queued messages stuck because the session continues
|
|
4
|
-
// processing after the answer and may enter another blocking state.
|
|
1
|
+
// E2e test for ordered queue drain after a pending question is answered via select.
|
|
5
2
|
|
|
6
3
|
import { describe, test, expect } from 'vitest'
|
|
7
4
|
import {
|
|
@@ -53,10 +50,7 @@ async function expectNoBotMessageContaining({
|
|
|
53
50
|
while (Date.now() - start < timeout) {
|
|
54
51
|
const messages = await discord.thread(threadId).getMessages()
|
|
55
52
|
const match = messages.find((message) => {
|
|
56
|
-
return (
|
|
57
|
-
message.author.id === discord.botUserId
|
|
58
|
-
&& message.content.includes(text)
|
|
59
|
-
)
|
|
53
|
+
return message.author.id === discord.botUserId && message.content.includes(text)
|
|
60
54
|
})
|
|
61
55
|
if (match) {
|
|
62
56
|
throw new Error(
|
|
@@ -78,132 +72,19 @@ describe('queue drain after question select answer', () => {
|
|
|
78
72
|
})
|
|
79
73
|
|
|
80
74
|
test(
|
|
81
|
-
'queued
|
|
82
|
-
async () => {
|
|
83
|
-
// 1. Send a message that triggers the question tool
|
|
84
|
-
await ctx.discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
85
|
-
content: 'QUESTION_SELECT_QUEUE_MARKER',
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
const thread = await ctx.discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
89
|
-
timeout: 8_000,
|
|
90
|
-
predicate: (t) => {
|
|
91
|
-
return t.name === 'QUESTION_SELECT_QUEUE_MARKER'
|
|
92
|
-
},
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
const th = ctx.discord.thread(thread.id)
|
|
96
|
-
|
|
97
|
-
// 2. Wait for the question dropdown message to appear in Discord.
|
|
98
|
-
// Uses visible message wait instead of internal Map polling which
|
|
99
|
-
// is too timing-sensitive on CI.
|
|
100
|
-
const questionMessages = await waitForBotMessageContaining({
|
|
101
|
-
discord: ctx.discord,
|
|
102
|
-
threadId: thread.id,
|
|
103
|
-
text: 'How to proceed?',
|
|
104
|
-
timeout: 12_000,
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
// Get the pending question context hash from the internal map.
|
|
108
|
-
// By this point the question message is visible so the context must exist.
|
|
109
|
-
const pending = await waitForPendingQuestion({
|
|
110
|
-
threadId: thread.id,
|
|
111
|
-
timeoutMs: 8_000,
|
|
112
|
-
})
|
|
113
|
-
const questionMsg = questionMessages.find((m) => {
|
|
114
|
-
return m.content.includes('How to proceed?')
|
|
115
|
-
})!
|
|
116
|
-
expect(questionMsg).toBeTruthy()
|
|
117
|
-
|
|
118
|
-
// 3. Queue a message while question is pending
|
|
119
|
-
const { id: queueInteractionId } = await th.user(TEST_USER_ID)
|
|
120
|
-
.runSlashCommand({
|
|
121
|
-
name: 'queue',
|
|
122
|
-
options: [{ name: 'message', type: 3, value: 'Reply with exactly: post-question-drain' }],
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
const queueAck = await th.waitForInteractionAck({
|
|
126
|
-
interactionId: queueInteractionId,
|
|
127
|
-
timeout: 8_000,
|
|
128
|
-
})
|
|
129
|
-
if (!queueAck.messageId) {
|
|
130
|
-
throw new Error('Expected /queue response message id')
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// 4. The first queued item should be handed off immediately even while
|
|
134
|
-
// the question is still pending, so the visible dispatch indicator
|
|
135
|
-
// appears before the user answers the dropdown.
|
|
136
|
-
await waitForBotMessageContaining({
|
|
137
|
-
discord: ctx.discord,
|
|
138
|
-
threadId: thread.id,
|
|
139
|
-
text: '» **question-select-tester:** Reply with exactly: post-question-drain',
|
|
140
|
-
timeout: 8_000,
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
// 5. Answer the question via dropdown select (pick first option "Alpha")
|
|
144
|
-
const interaction = await th.user(TEST_USER_ID).selectMenu({
|
|
145
|
-
messageId: questionMsg.id,
|
|
146
|
-
customId: `ask_question:${pending.contextHash}:0`,
|
|
147
|
-
values: ['0'],
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
await th.waitForInteractionAck({
|
|
151
|
-
interactionId: interaction.id,
|
|
152
|
-
timeout: 8_000,
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
// 6. Wait for footer from the drained queued message
|
|
156
|
-
await waitForFooterMessage({
|
|
157
|
-
discord: ctx.discord,
|
|
158
|
-
threadId: thread.id,
|
|
159
|
-
timeout: 8_000,
|
|
160
|
-
afterMessageIncludes: '» **question-select-tester:**',
|
|
161
|
-
afterAuthorId: ctx.discord.botUserId,
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
const timeline = await th.text({ showInteractions: true })
|
|
165
|
-
expect(timeline).toMatchInlineSnapshot(`
|
|
166
|
-
"--- from: user (question-select-tester)
|
|
167
|
-
QUESTION_SELECT_QUEUE_MARKER
|
|
168
|
-
--- from: assistant (TestBot)
|
|
169
|
-
*using deterministic-provider/deterministic-v2*
|
|
170
|
-
**Select action**
|
|
171
|
-
How to proceed?
|
|
172
|
-
✓ _Alpha_
|
|
173
|
-
[user interaction]
|
|
174
|
-
» **question-select-tester:** Reply with exactly: post-question-drain
|
|
175
|
-
Queued message (position 1)
|
|
176
|
-
[user selects dropdown: 0]
|
|
177
|
-
» **question-select-tester:** Alpha
|
|
178
|
-
⬥ ok
|
|
179
|
-
*project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
180
|
-
`)
|
|
181
|
-
expect(timeline).toContain('QUESTION_SELECT_QUEUE_MARKER')
|
|
182
|
-
expect(timeline).toContain('How to proceed?')
|
|
183
|
-
expect(timeline).toContain('[user selects dropdown: 0]')
|
|
184
|
-
expect(timeline).toContain('» **question-select-tester:** Reply with exactly: post-question-drain')
|
|
185
|
-
expect(timeline).toContain('⬥ ok')
|
|
186
|
-
expect(timeline).toContain('*project ⋅ main ⋅')
|
|
187
|
-
},
|
|
188
|
-
20_000,
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
test(
|
|
192
|
-
'only the first queued message is handed off after dropdown answer',
|
|
75
|
+
'queued messages drain in order after answering via dropdown select',
|
|
193
76
|
async () => {
|
|
194
|
-
const marker = 'QUESTION_SELECT_QUEUE_MARKER
|
|
195
|
-
|
|
77
|
+
const marker = 'QUESTION_SELECT_QUEUE_MARKER'
|
|
196
78
|
await ctx.discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
197
79
|
content: marker,
|
|
198
80
|
})
|
|
199
81
|
|
|
200
82
|
const thread = await ctx.discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
201
83
|
timeout: 8_000,
|
|
202
|
-
predicate: (
|
|
203
|
-
return
|
|
84
|
+
predicate: (candidate) => {
|
|
85
|
+
return candidate.name === marker
|
|
204
86
|
},
|
|
205
87
|
})
|
|
206
|
-
|
|
207
88
|
const th = ctx.discord.thread(thread.id)
|
|
208
89
|
|
|
209
90
|
const questionMessages = await waitForBotMessageContaining({
|
|
@@ -212,21 +93,18 @@ describe('queue drain after question select answer', () => {
|
|
|
212
93
|
text: 'How to proceed?',
|
|
213
94
|
timeout: 12_000,
|
|
214
95
|
})
|
|
215
|
-
|
|
216
96
|
const pending = await waitForPendingQuestion({
|
|
217
97
|
threadId: thread.id,
|
|
218
98
|
timeoutMs: 8_000,
|
|
219
99
|
})
|
|
220
|
-
|
|
221
|
-
const questionMsg = questionMessages.find((message) => {
|
|
100
|
+
const questionMessage = questionMessages.find((message) => {
|
|
222
101
|
return message.content.includes('How to proceed?')
|
|
223
102
|
})
|
|
224
|
-
|
|
225
|
-
if (!questionMsg) {
|
|
103
|
+
if (!questionMessage) {
|
|
226
104
|
throw new Error('Expected question message')
|
|
227
105
|
}
|
|
228
106
|
|
|
229
|
-
const firstQueuedPrompt = '
|
|
107
|
+
const firstQueuedPrompt = 'QUESTION_SELECT_DRAIN_FIRST_MARKER'
|
|
230
108
|
const secondQueuedPrompt = 'Reply with exactly: post-question-second'
|
|
231
109
|
|
|
232
110
|
const { id: firstQueueInteractionId } = await th.user(TEST_USER_ID)
|
|
@@ -234,48 +112,45 @@ describe('queue drain after question select answer', () => {
|
|
|
234
112
|
name: 'queue',
|
|
235
113
|
options: [{ name: 'message', type: 3, value: firstQueuedPrompt }],
|
|
236
114
|
})
|
|
237
|
-
|
|
238
115
|
await th.waitForInteractionAck({
|
|
239
116
|
interactionId: firstQueueInteractionId,
|
|
240
117
|
timeout: 8_000,
|
|
241
118
|
})
|
|
242
119
|
|
|
120
|
+
await waitForBotMessageContaining({
|
|
121
|
+
discord: ctx.discord,
|
|
122
|
+
threadId: thread.id,
|
|
123
|
+
text: `» **question-select-tester:** ${firstQueuedPrompt}`,
|
|
124
|
+
timeout: 8_000,
|
|
125
|
+
})
|
|
126
|
+
|
|
243
127
|
const { id: secondQueueInteractionId } = await th.user(TEST_USER_ID)
|
|
244
128
|
.runSlashCommand({
|
|
245
129
|
name: 'queue',
|
|
246
130
|
options: [{ name: 'message', type: 3, value: secondQueuedPrompt }],
|
|
247
131
|
})
|
|
248
|
-
|
|
249
132
|
await th.waitForInteractionAck({
|
|
250
133
|
interactionId: secondQueueInteractionId,
|
|
251
134
|
timeout: 8_000,
|
|
252
135
|
})
|
|
253
136
|
|
|
137
|
+
await expectNoBotMessageContaining({
|
|
138
|
+
discord: ctx.discord,
|
|
139
|
+
threadId: thread.id,
|
|
140
|
+
text: `» **question-select-tester:** ${secondQueuedPrompt}`,
|
|
141
|
+
timeout: 200,
|
|
142
|
+
})
|
|
143
|
+
|
|
254
144
|
const interaction = await th.user(TEST_USER_ID).selectMenu({
|
|
255
|
-
messageId:
|
|
145
|
+
messageId: questionMessage.id,
|
|
256
146
|
customId: `ask_question:${pending.contextHash}:0`,
|
|
257
147
|
values: ['0'],
|
|
258
148
|
})
|
|
259
|
-
|
|
260
149
|
await th.waitForInteractionAck({
|
|
261
150
|
interactionId: interaction.id,
|
|
262
151
|
timeout: 8_000,
|
|
263
152
|
})
|
|
264
153
|
|
|
265
|
-
await waitForBotMessageContaining({
|
|
266
|
-
discord: ctx.discord,
|
|
267
|
-
threadId: thread.id,
|
|
268
|
-
text: `» **question-select-tester:** ${firstQueuedPrompt}`,
|
|
269
|
-
timeout: 8_000,
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
await expectNoBotMessageContaining({
|
|
273
|
-
discord: ctx.discord,
|
|
274
|
-
threadId: thread.id,
|
|
275
|
-
text: `» **question-select-tester:** ${secondQueuedPrompt}`,
|
|
276
|
-
timeout: 200,
|
|
277
|
-
})
|
|
278
|
-
|
|
279
154
|
await waitForFooterMessage({
|
|
280
155
|
discord: ctx.discord,
|
|
281
156
|
threadId: thread.id,
|
|
@@ -283,14 +158,12 @@ describe('queue drain after question select answer', () => {
|
|
|
283
158
|
afterMessageIncludes: `» **question-select-tester:** ${firstQueuedPrompt}`,
|
|
284
159
|
afterAuthorId: ctx.discord.botUserId,
|
|
285
160
|
})
|
|
286
|
-
|
|
287
161
|
await waitForBotMessageContaining({
|
|
288
162
|
discord: ctx.discord,
|
|
289
163
|
threadId: thread.id,
|
|
290
164
|
text: `» **question-select-tester:** ${secondQueuedPrompt}`,
|
|
291
165
|
timeout: 8_000,
|
|
292
166
|
})
|
|
293
|
-
|
|
294
167
|
await waitForFooterMessage({
|
|
295
168
|
discord: ctx.discord,
|
|
296
169
|
threadId: thread.id,
|
|
@@ -302,30 +175,32 @@ describe('queue drain after question select answer', () => {
|
|
|
302
175
|
const timeline = await th.text({ showInteractions: true })
|
|
303
176
|
expect(timeline).toMatchInlineSnapshot(`
|
|
304
177
|
"--- from: user (question-select-tester)
|
|
305
|
-
QUESTION_SELECT_QUEUE_MARKER
|
|
178
|
+
QUESTION_SELECT_QUEUE_MARKER
|
|
306
179
|
--- from: assistant (TestBot)
|
|
307
|
-
*using deterministic-provider/deterministic-v2*
|
|
180
|
+
> *using deterministic-provider/deterministic-v2*
|
|
308
181
|
**Select action**
|
|
309
182
|
How to proceed?
|
|
310
183
|
✓ _Alpha_
|
|
311
184
|
[user interaction]
|
|
312
|
-
» **question-select-tester:**
|
|
185
|
+
» **question-select-tester:** QUESTION_SELECT_DRAIN_FIRST_MARKER
|
|
313
186
|
Queued message (position 1)
|
|
314
187
|
[user interaction]
|
|
315
188
|
Queued message (position 1)
|
|
316
189
|
[user selects dropdown: 0]
|
|
317
190
|
» **question-select-tester:** Alpha
|
|
318
|
-
|
|
319
|
-
*project ⋅ main ⋅
|
|
191
|
+
> question-drain-first
|
|
192
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2*
|
|
320
193
|
» **question-select-tester:** Reply with exactly: post-question-second
|
|
321
|
-
|
|
322
|
-
*project ⋅ main ⋅
|
|
194
|
+
> ok
|
|
195
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000991>"
|
|
323
196
|
`)
|
|
197
|
+
expect(timeline).toContain('How to proceed?')
|
|
198
|
+
expect(timeline).toContain('[user selects dropdown: 0]')
|
|
324
199
|
expect(timeline).toContain(`» **question-select-tester:** ${firstQueuedPrompt}`)
|
|
325
|
-
expect(timeline).toContain('
|
|
200
|
+
expect(timeline).toContain('question-drain-first')
|
|
326
201
|
expect(timeline).toContain(`» **question-select-tester:** ${secondQueuedPrompt}`)
|
|
327
|
-
expect(timeline).toContain('
|
|
202
|
+
expect(timeline).toContain('ok')
|
|
328
203
|
},
|
|
329
|
-
|
|
204
|
+
15_000,
|
|
330
205
|
)
|
|
331
206
|
})
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
chooseLockPort,
|
|
40
40
|
cleanupTestSessions,
|
|
41
41
|
initTestGitRepo,
|
|
42
|
+
isFooterMessage,
|
|
42
43
|
waitForBotMessageContaining,
|
|
43
44
|
waitForBotReplyAfterUserMessage,
|
|
44
45
|
} from './test-utils.js'
|
|
@@ -264,11 +265,11 @@ describe('runtime lifecycle', () => {
|
|
|
264
265
|
}, 5_000)
|
|
265
266
|
|
|
266
267
|
test(
|
|
267
|
-
'
|
|
268
|
+
'two sequential completions reuse same runtime and listener',
|
|
268
269
|
async () => {
|
|
269
|
-
// Sends A, waits for full completion (footer), sends B
|
|
270
|
-
//
|
|
271
|
-
//
|
|
270
|
+
// Sends A, waits for full completion (footer), then sends B and waits
|
|
271
|
+
// for its footer. Proves the listener stays alive across full run cycles
|
|
272
|
+
// without any interrupt/queue involvement.
|
|
272
273
|
// This is the "calm" path — no abort, no queue, just sequential use.
|
|
273
274
|
|
|
274
275
|
// 1. Send first message → thread created, session established
|
|
@@ -323,51 +324,20 @@ describe('runtime lifecycle', () => {
|
|
|
323
324
|
|
|
324
325
|
// Same runtime instance — listener was not recreated
|
|
325
326
|
const runtimeAfterB = getRuntime(thread.id)
|
|
326
|
-
expect(runtimeAfterB).toBe(runtimeAfterA)
|
|
327
|
-
|
|
328
|
-
// 3. Send C after B fully completed
|
|
329
|
-
await th.user(TEST_USER_ID).sendMessage({
|
|
330
|
-
content: 'Reply with exactly: seq-gamma',
|
|
331
|
-
})
|
|
332
|
-
|
|
333
|
-
await waitForBotReplyAfterUserMessage({
|
|
334
|
-
discord,
|
|
335
|
-
threadId: thread.id,
|
|
336
|
-
userId: TEST_USER_ID,
|
|
337
|
-
userMessageIncludes: 'seq-gamma',
|
|
338
|
-
timeout: 4_000,
|
|
339
|
-
})
|
|
340
|
-
|
|
341
|
-
await waitForBotMessageContaining({
|
|
342
|
-
discord,
|
|
343
|
-
threadId: thread.id,
|
|
344
|
-
userId: TEST_USER_ID,
|
|
345
|
-
text: '*project',
|
|
346
|
-
afterUserMessageIncludes: 'seq-gamma',
|
|
347
|
-
timeout: 4_000,
|
|
348
|
-
})
|
|
349
|
-
|
|
350
|
-
// Still the same runtime — three full cycles, one runtime, one listener
|
|
351
|
-
const runtimeAfterC = getRuntime(thread.id)
|
|
352
327
|
expect(await th.text()).toMatchInlineSnapshot(`
|
|
353
328
|
"--- from: user (lifecycle-tester)
|
|
354
329
|
Reply with exactly: seq-alpha
|
|
355
330
|
--- from: assistant (TestBot)
|
|
356
|
-
*using deterministic-provider/deterministic-v2*
|
|
357
|
-
|
|
358
|
-
*project ⋅ main ⋅
|
|
331
|
+
> *using deterministic-provider/deterministic-v2*
|
|
332
|
+
> ok
|
|
333
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000888>
|
|
359
334
|
--- from: user (lifecycle-tester)
|
|
360
335
|
Reply with exactly: seq-beta
|
|
361
336
|
--- from: assistant (TestBot)
|
|
362
|
-
|
|
363
|
-
*project ⋅ main ⋅
|
|
364
|
-
--- from: user (lifecycle-tester)
|
|
365
|
-
Reply with exactly: seq-gamma
|
|
366
|
-
--- from: assistant (TestBot)
|
|
367
|
-
⬥ ok
|
|
368
|
-
*project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
337
|
+
> ok
|
|
338
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000888>"
|
|
369
339
|
`)
|
|
370
|
-
expect(
|
|
340
|
+
expect(runtimeAfterB).toBe(runtimeAfterA)
|
|
371
341
|
},
|
|
372
342
|
15_000,
|
|
373
343
|
)
|
|
@@ -398,10 +368,7 @@ describe('runtime lifecycle', () => {
|
|
|
398
368
|
const messages = await discord.thread(thread.id).getMessages()
|
|
399
369
|
|
|
400
370
|
const footerMessage = messages.find((message) => {
|
|
401
|
-
if (message
|
|
402
|
-
return false
|
|
403
|
-
}
|
|
404
|
-
if (!message.content.startsWith('*')) {
|
|
371
|
+
if (!isFooterMessage({ message, botUserId: discord.botUserId })) {
|
|
405
372
|
return false
|
|
406
373
|
}
|
|
407
374
|
return message.content.includes('deterministic-v2') && message.content.includes('%')
|
|
@@ -411,9 +378,9 @@ describe('runtime lifecycle', () => {
|
|
|
411
378
|
"--- from: user (lifecycle-tester)
|
|
412
379
|
Reply with exactly: footer-check
|
|
413
380
|
--- from: assistant (TestBot)
|
|
414
|
-
*using deterministic-provider/deterministic-v2*
|
|
415
|
-
|
|
416
|
-
*project ⋅ main ⋅
|
|
381
|
+
> *using deterministic-provider/deterministic-v2*
|
|
382
|
+
> ok
|
|
383
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000888>"
|
|
417
384
|
`)
|
|
418
385
|
expect(footerMessage).toBeDefined()
|
|
419
386
|
if (!footerMessage) {
|
|
@@ -483,14 +450,14 @@ describe('runtime lifecycle', () => {
|
|
|
483
450
|
"--- from: user (lifecycle-tester)
|
|
484
451
|
Reply with exactly: reconnect-alpha
|
|
485
452
|
--- from: assistant (TestBot)
|
|
486
|
-
*using deterministic-provider/deterministic-v2*
|
|
487
|
-
|
|
488
|
-
*project ⋅ main ⋅
|
|
453
|
+
> *using deterministic-provider/deterministic-v2*
|
|
454
|
+
> ok
|
|
455
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000888>
|
|
489
456
|
--- from: user (lifecycle-tester)
|
|
490
457
|
Reply with exactly: reconnect-beta
|
|
491
458
|
--- from: assistant (TestBot)
|
|
492
|
-
|
|
493
|
-
*project ⋅ main ⋅
|
|
459
|
+
> ok
|
|
460
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000888>"
|
|
494
461
|
`)
|
|
495
462
|
|
|
496
463
|
const runtimeAfterRestart = getRuntime(thread.id)
|
|
@@ -531,7 +498,7 @@ describe('runtime lifecycle', () => {
|
|
|
531
498
|
"--- from: user (lifecycle-tester)
|
|
532
499
|
Reply with exactly: footer-high-usage
|
|
533
500
|
--- from: assistant (TestBot)
|
|
534
|
-
*using deterministic-provider/deterministic-v2*"
|
|
501
|
+
> *using deterministic-provider/deterministic-v2*"
|
|
535
502
|
`)
|
|
536
503
|
|
|
537
504
|
const threadText = await discord.thread(thread.id).text()
|
package/src/schema.sql
CHANGED
|
@@ -105,7 +105,7 @@ CREATE TABLE IF NOT EXISTS `part_messages` (
|
|
|
105
105
|
`message_id` text NOT NULL,
|
|
106
106
|
`thread_id` text NOT NULL,
|
|
107
107
|
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
|
|
108
|
-
CONSTRAINT `fk_part_messages_thread_id_thread_sessions_thread_id_fk` FOREIGN KEY (`thread_id`) REFERENCES `thread_sessions`(`thread_id`) ON UPDATE CASCADE
|
|
108
|
+
CONSTRAINT `fk_part_messages_thread_id_thread_sessions_thread_id_fk` FOREIGN KEY (`thread_id`) REFERENCES `thread_sessions`(`thread_id`) ON UPDATE CASCADE ON DELETE CASCADE
|
|
109
109
|
);
|
|
110
110
|
|
|
111
111
|
CREATE TABLE IF NOT EXISTS `scheduled_task_runs` (
|
|
@@ -168,6 +168,17 @@ CREATE TABLE IF NOT EXISTS `session_models` (
|
|
|
168
168
|
`created_at` datetime DEFAULT CURRENT_TIMESTAMP
|
|
169
169
|
);
|
|
170
170
|
|
|
171
|
+
CREATE TABLE IF NOT EXISTS `session_sleeps` (
|
|
172
|
+
`session_id` text PRIMARY KEY,
|
|
173
|
+
`wake_at` datetime NOT NULL,
|
|
174
|
+
`reason` text,
|
|
175
|
+
`status` text DEFAULT 'planned' NOT NULL,
|
|
176
|
+
`delivery_id` text NOT NULL,
|
|
177
|
+
`attempts` integer DEFAULT 0 NOT NULL,
|
|
178
|
+
`last_attempt_at` datetime,
|
|
179
|
+
`created_at` datetime DEFAULT CURRENT_TIMESTAMP
|
|
180
|
+
);
|
|
181
|
+
|
|
171
182
|
CREATE TABLE IF NOT EXISTS `session_start_sources` (
|
|
172
183
|
`session_id` text PRIMARY KEY,
|
|
173
184
|
`schedule_kind` text NOT NULL,
|
|
@@ -183,7 +194,8 @@ CREATE TABLE IF NOT EXISTS `thread_sessions` (
|
|
|
183
194
|
`source` text DEFAULT 'kimaki' NOT NULL,
|
|
184
195
|
`last_synced_name` text,
|
|
185
196
|
`parent_session_id` text,
|
|
186
|
-
`created_at` datetime DEFAULT CURRENT_TIMESTAMP
|
|
197
|
+
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
|
|
198
|
+
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP
|
|
187
199
|
);
|
|
188
200
|
|
|
189
201
|
CREATE TABLE IF NOT EXISTS `thread_workspaces` (
|
|
@@ -219,4 +231,5 @@ CREATE INDEX IF NOT EXISTS `scheduled_tasks_channel_id_status_idx` ON `scheduled
|
|
|
219
231
|
CREATE INDEX IF NOT EXISTS `scheduled_tasks_thread_id_status_idx` ON `scheduled_tasks` (`thread_id`,`status`);
|
|
220
232
|
CREATE INDEX IF NOT EXISTS `session_events_session_id_timestamp_event_index_id_idx` ON `session_events` (`session_id`,`timestamp`,`event_index`,`id`);
|
|
221
233
|
CREATE INDEX IF NOT EXISTS `session_events_thread_id_timestamp_event_index_id_idx` ON `session_events` (`thread_id`,`timestamp`,`event_index`,`id`);
|
|
234
|
+
CREATE INDEX IF NOT EXISTS `session_sleeps_status_wake_at_idx` ON `session_sleeps` (`status`,`wake_at`);
|
|
222
235
|
CREATE INDEX IF NOT EXISTS `session_start_sources_scheduled_task_id_idx` ON `session_start_sources` (`scheduled_task_id`);
|
package/src/schema.ts
CHANGED
|
@@ -33,6 +33,10 @@ export const thread_sessions = sqliteCore.sqliteTable('thread_sessions', {
|
|
|
33
33
|
// Survives bot restarts so child multi-turn system prompts keep the parent ID.
|
|
34
34
|
parent_session_id: sqliteCore.text('parent_session_id'),
|
|
35
35
|
created_at: datetime('created_at').default(orm.sql`CURRENT_TIMESTAMP`),
|
|
36
|
+
// Bumped on every (re)binding of a session to this thread. /resume can map one
|
|
37
|
+
// session to several threads, so session_id -> thread_id needs a tiebreaker.
|
|
38
|
+
// Without it the reverse lookup is arbitrary and tools can target a dead thread.
|
|
39
|
+
updated_at: datetime('updated_at').default(orm.sql`CURRENT_TIMESTAMP`).$onUpdate(() => new Date()),
|
|
36
40
|
})
|
|
37
41
|
|
|
38
42
|
export const session_events = sqliteCore.sqliteTable('session_events', {
|
|
@@ -50,7 +54,7 @@ export const session_events = sqliteCore.sqliteTable('session_events', {
|
|
|
50
54
|
export const part_messages = sqliteCore.sqliteTable('part_messages', {
|
|
51
55
|
part_id: sqliteCore.text('part_id').primaryKey().notNull(),
|
|
52
56
|
message_id: sqliteCore.text('message_id').notNull(),
|
|
53
|
-
thread_id: sqliteCore.text('thread_id').notNull().references(() => thread_sessions.thread_id, { onUpdate: 'cascade' }),
|
|
57
|
+
thread_id: sqliteCore.text('thread_id').notNull().references(() => thread_sessions.thread_id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
|
54
58
|
created_at: datetime('created_at').default(orm.sql`CURRENT_TIMESTAMP`),
|
|
55
59
|
})
|
|
56
60
|
|
|
@@ -224,6 +228,36 @@ export const forum_sync_configs = sqliteCore.sqliteTable('forum_sync_configs', {
|
|
|
224
228
|
sqliteCore.uniqueIndex('forum_sync_configs_app_id_forum_channel_id_key').on(table.app_id, table.forum_channel_id),
|
|
225
229
|
])
|
|
226
230
|
|
|
231
|
+
// Durable "wake this session later" rows for the kimaki_sleep tool.
|
|
232
|
+
//
|
|
233
|
+
// Delivery is at-least-once, and ingress makes it at-most-once-per-turn:
|
|
234
|
+
//
|
|
235
|
+
// planned ──► consumed cancelled / failed are terminal
|
|
236
|
+
// ▲ │
|
|
237
|
+
// └───┘ retried until ingress consumes it
|
|
238
|
+
//
|
|
239
|
+
// A row stays `planned` until ingress turns the wake into a turn, so ANY loss
|
|
240
|
+
// (crash before posting, crash after posting, missed gateway event) is covered
|
|
241
|
+
// by the same retry. `delivery_id` is the Discord message nonce, so a retry
|
|
242
|
+
// returns the existing message instead of waking the session twice.
|
|
243
|
+
//
|
|
244
|
+
// There is no thread_id: the owning thread is resolved from session_id at wake
|
|
245
|
+
// time, so a session rebound by /resume wakes in its current thread.
|
|
246
|
+
export const session_sleeps = sqliteCore.sqliteTable('session_sleeps', {
|
|
247
|
+
session_id: sqliteCore.text('session_id').primaryKey().notNull(),
|
|
248
|
+
wake_at: datetime('wake_at').notNull(),
|
|
249
|
+
reason: sqliteCore.text('reason'),
|
|
250
|
+
status: sqliteCore.text('status', { enum: ['planned', 'consumed', 'cancelled', 'failed'] }).notNull().default('planned'),
|
|
251
|
+
// Regenerated per sleep occurrence. Doubles as the Discord nonce and as a
|
|
252
|
+
// generation guard so a stale in-flight wake cannot mutate a newer sleep.
|
|
253
|
+
delivery_id: sqliteCore.text('delivery_id').notNull().$defaultFn(() => crypto.randomUUID()),
|
|
254
|
+
attempts: sqliteCore.integer('attempts', { mode: 'number' }).notNull().default(0),
|
|
255
|
+
last_attempt_at: datetime('last_attempt_at'),
|
|
256
|
+
created_at: datetime('created_at').default(orm.sql`CURRENT_TIMESTAMP`),
|
|
257
|
+
}, (table) => [
|
|
258
|
+
sqliteCore.index('session_sleeps_status_wake_at_idx').on(table.status, table.wake_at),
|
|
259
|
+
])
|
|
260
|
+
|
|
227
261
|
export const ipc_requests = sqliteCore.sqliteTable('ipc_requests', {
|
|
228
262
|
id: sqliteCore.text('id').primaryKey().notNull().$defaultFn(() => crypto.randomUUID()),
|
|
229
263
|
type: sqliteCore.text('type', { enum: ['file_upload', 'action_buttons'] }).notNull(),
|
|
@@ -259,6 +293,7 @@ export const relations = defineRelations({
|
|
|
259
293
|
scheduled_task_runs,
|
|
260
294
|
session_start_sources,
|
|
261
295
|
forum_sync_configs,
|
|
296
|
+
session_sleeps,
|
|
262
297
|
ipc_requests,
|
|
263
298
|
}, (r) => ({
|
|
264
299
|
thread_sessions: {
|
|
@@ -340,6 +375,7 @@ export const relations = defineRelations({
|
|
|
340
375
|
export type BotMode = typeof bot_tokens.$inferSelect.bot_mode
|
|
341
376
|
export type ChannelType = typeof channel_directories.$inferSelect.channel_type
|
|
342
377
|
export type IpcRequestType = typeof ipc_requests.$inferSelect.type
|
|
378
|
+
export type SessionSleepStatus = typeof session_sleeps.$inferSelect.status
|
|
343
379
|
export type SessionEvent = typeof session_events.$inferSelect
|
|
344
380
|
export type ThreadSessionSource = typeof thread_sessions.$inferSelect.source
|
|
345
381
|
export type VerbosityLevel = typeof channel_verbosity.$inferSelect.verbosity
|