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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// decides whether (and how) to rename a Discord thread based on an
|
|
3
3
|
// OpenCode session title. Kept focused and deterministic; no Discord mocks.
|
|
4
4
|
import { describe, test, expect } from 'vitest';
|
|
5
|
-
import { deriveThreadNameFromSessionTitle,
|
|
5
|
+
import { deriveThreadNameFromSessionTitle, } from './session-handler/thread-session-runtime.js';
|
|
6
6
|
describe('deriveThreadNameFromSessionTitle', () => {
|
|
7
7
|
test('returns trimmed title for plain thread', () => {
|
|
8
8
|
expect(deriveThreadNameFromSessionTitle({
|
|
@@ -67,6 +67,30 @@ describe('deriveThreadNameFromSessionTitle', () => {
|
|
|
67
67
|
currentName: 'seed',
|
|
68
68
|
})).toMatchInlineSnapshot(`undefined`);
|
|
69
69
|
});
|
|
70
|
+
test('ignores a title containing only OpenCode callout markup', () => {
|
|
71
|
+
expect(deriveThreadNameFromSessionTitle({
|
|
72
|
+
sessionTitle: '<callout accent="#ef4444">',
|
|
73
|
+
currentName: 'Useful existing title',
|
|
74
|
+
})).toMatchInlineSnapshot(`undefined`);
|
|
75
|
+
});
|
|
76
|
+
test('ignores paired OpenCode callout markup with no title text', () => {
|
|
77
|
+
expect(deriveThreadNameFromSessionTitle({
|
|
78
|
+
sessionTitle: '<callout accent="#ef4444"></callout>',
|
|
79
|
+
currentName: 'Useful existing title',
|
|
80
|
+
})).toMatchInlineSnapshot(`undefined`);
|
|
81
|
+
});
|
|
82
|
+
test('keeps useful text while removing OpenCode callout wrappers', () => {
|
|
83
|
+
expect(deriveThreadNameFromSessionTitle({
|
|
84
|
+
sessionTitle: '<callout accent="#ef4444">Useful title</callout>',
|
|
85
|
+
currentName: 'seed',
|
|
86
|
+
})).toMatchInlineSnapshot(`"Useful title"`);
|
|
87
|
+
});
|
|
88
|
+
test('preserves a thread prefix around a multiline uppercase callout', () => {
|
|
89
|
+
expect(deriveThreadNameFromSessionTitle({
|
|
90
|
+
sessionTitle: '<CALLOUT icon="info">\nUseful title\n</CALLOUT>',
|
|
91
|
+
currentName: 'btw: original title',
|
|
92
|
+
})).toMatchInlineSnapshot(`"btw: Useful title"`);
|
|
93
|
+
});
|
|
70
94
|
test('preserves btw: prefix from current name', () => {
|
|
71
95
|
expect(deriveThreadNameFromSessionTitle({
|
|
72
96
|
sessionTitle: 'Side question about auth',
|
|
@@ -79,6 +103,18 @@ describe('deriveThreadNameFromSessionTitle', () => {
|
|
|
79
103
|
currentName: 'Fork: old session title',
|
|
80
104
|
})).toMatchInlineSnapshot(`"Fork: Forked task title"`);
|
|
81
105
|
});
|
|
106
|
+
test('does not double a worktree prefix copied from the Discord thread name', () => {
|
|
107
|
+
expect(deriveThreadNameFromSessionTitle({
|
|
108
|
+
sessionTitle: '⬦ Fix queue draining',
|
|
109
|
+
currentName: '⬦ Old name',
|
|
110
|
+
})).toMatchInlineSnapshot(`"⬦ Fix queue draining"`);
|
|
111
|
+
});
|
|
112
|
+
test('preserves the worktree prefix', () => {
|
|
113
|
+
expect(deriveThreadNameFromSessionTitle({
|
|
114
|
+
sessionTitle: 'Refactor queue',
|
|
115
|
+
currentName: '⬦ old worktree title',
|
|
116
|
+
})).toMatchInlineSnapshot(`"⬦ Refactor queue"`);
|
|
117
|
+
});
|
|
82
118
|
test('returns undefined for null/undefined title', () => {
|
|
83
119
|
expect(deriveThreadNameFromSessionTitle({
|
|
84
120
|
sessionTitle: null,
|
|
@@ -90,41 +126,3 @@ describe('deriveThreadNameFromSessionTitle', () => {
|
|
|
90
126
|
})).toMatchInlineSnapshot(`undefined`);
|
|
91
127
|
});
|
|
92
128
|
});
|
|
93
|
-
describe('deriveThreadRenameFromSessionUpdate', () => {
|
|
94
|
-
test('skips auto-rename after the thread differs from the persisted synced name', () => {
|
|
95
|
-
expect(deriveThreadRenameFromSessionUpdate({
|
|
96
|
-
sessionTitle: 'New OpenCode title',
|
|
97
|
-
currentName: 'custom name from user',
|
|
98
|
-
lastSyncedName: 'Old OpenCode title',
|
|
99
|
-
})).toMatchInlineSnapshot(`
|
|
100
|
-
{
|
|
101
|
-
"desiredName": null,
|
|
102
|
-
"nextSyncedName": "Old OpenCode title",
|
|
103
|
-
}
|
|
104
|
-
`);
|
|
105
|
-
});
|
|
106
|
-
test('returns desired name while thread still matches the persisted synced name', () => {
|
|
107
|
-
expect(deriveThreadRenameFromSessionUpdate({
|
|
108
|
-
sessionTitle: 'New OpenCode title',
|
|
109
|
-
currentName: 'Old OpenCode title',
|
|
110
|
-
lastSyncedName: 'Old OpenCode title',
|
|
111
|
-
})).toMatchInlineSnapshot(`
|
|
112
|
-
{
|
|
113
|
-
"desiredName": "New OpenCode title",
|
|
114
|
-
"nextSyncedName": "New OpenCode title",
|
|
115
|
-
}
|
|
116
|
-
`);
|
|
117
|
-
});
|
|
118
|
-
test('remembers a no-op matching title as synced for later manual rename detection', () => {
|
|
119
|
-
expect(deriveThreadRenameFromSessionUpdate({
|
|
120
|
-
sessionTitle: 'Old OpenCode title',
|
|
121
|
-
currentName: 'Old OpenCode title',
|
|
122
|
-
lastSyncedName: null,
|
|
123
|
-
})).toMatchInlineSnapshot(`
|
|
124
|
-
{
|
|
125
|
-
"desiredName": null,
|
|
126
|
-
"nextSyncedName": "Old OpenCode title",
|
|
127
|
-
}
|
|
128
|
-
`);
|
|
129
|
-
});
|
|
130
|
-
});
|
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
// E2e coverage for the durable kimaki_sleep tool.
|
|
2
|
+
// Verifies the tool persists a wake row, the thread shows the sleep line,
|
|
3
|
+
// the task runner wake resumes the same session, and a user message cancels
|
|
4
|
+
// a planned sleep so the wake never fires.
|
|
5
|
+
import { describe, test, expect } from 'vitest';
|
|
6
|
+
import { setupQueueAdvancedSuite, TEST_USER_ID, } from './queue-advanced-e2e-setup.js';
|
|
7
|
+
import { waitForBotMessageContaining, waitForFooterMessage, } from './test-utils.js';
|
|
8
|
+
import { getSessionSleep, getThreadSession } from './database.js';
|
|
9
|
+
import { wakeDueSessionSleeps } from './task-runner.js';
|
|
10
|
+
const TEXT_CHANNEL_ID = '200000000000001031';
|
|
11
|
+
// Fixed wake instant so the wake message stays snapshot-stable across runs.
|
|
12
|
+
const SLEEP_UNTIL = '2030-01-01T09:00:00Z';
|
|
13
|
+
const AFTER_SLEEP_UNTIL = new Date('2030-01-01T09:00:01Z');
|
|
14
|
+
function createSleepMatchers() {
|
|
15
|
+
const sleepCallMatcher = {
|
|
16
|
+
id: 'sleep-tool-call',
|
|
17
|
+
priority: 130,
|
|
18
|
+
when: {
|
|
19
|
+
lastMessageRole: 'user',
|
|
20
|
+
latestUserTextIncludes: 'SLEEP_TOOL_MARKER',
|
|
21
|
+
},
|
|
22
|
+
then: {
|
|
23
|
+
parts: [
|
|
24
|
+
{ type: 'stream-start', warnings: [] },
|
|
25
|
+
{ type: 'text-start', id: 'sleep-call-text' },
|
|
26
|
+
{
|
|
27
|
+
type: 'text-delta',
|
|
28
|
+
id: 'sleep-call-text',
|
|
29
|
+
delta: 'waiting for the deploy',
|
|
30
|
+
},
|
|
31
|
+
{ type: 'text-end', id: 'sleep-call-text' },
|
|
32
|
+
{
|
|
33
|
+
type: 'tool-call',
|
|
34
|
+
toolCallId: 'sleep-call-1',
|
|
35
|
+
toolName: 'kimaki_sleep',
|
|
36
|
+
input: JSON.stringify({
|
|
37
|
+
until: SLEEP_UNTIL,
|
|
38
|
+
reason: 'waiting for the deploy',
|
|
39
|
+
}),
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
type: 'finish',
|
|
43
|
+
finishReason: 'tool-calls',
|
|
44
|
+
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
const sleepAcknowledgeMatcher = {
|
|
50
|
+
id: 'sleep-tool-ack',
|
|
51
|
+
priority: 129,
|
|
52
|
+
when: {
|
|
53
|
+
latestUserTextIncludes: 'SLEEP_TOOL_MARKER',
|
|
54
|
+
rawPromptIncludes: 'Sleeping until',
|
|
55
|
+
},
|
|
56
|
+
then: {
|
|
57
|
+
parts: [
|
|
58
|
+
{ type: 'stream-start', warnings: [] },
|
|
59
|
+
{ type: 'text-start', id: 'sleep-ack-text' },
|
|
60
|
+
{ type: 'text-delta', id: 'sleep-ack-text', delta: 'sleep-started' },
|
|
61
|
+
{ type: 'text-end', id: 'sleep-ack-text' },
|
|
62
|
+
{
|
|
63
|
+
type: 'finish',
|
|
64
|
+
finishReason: 'stop',
|
|
65
|
+
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
const sleepWakeMatcher = {
|
|
71
|
+
id: 'sleep-tool-wake',
|
|
72
|
+
priority: 131,
|
|
73
|
+
when: {
|
|
74
|
+
latestUserTextIncludes: 'Woke after sleeping until',
|
|
75
|
+
},
|
|
76
|
+
then: {
|
|
77
|
+
parts: [
|
|
78
|
+
{ type: 'stream-start', warnings: [] },
|
|
79
|
+
{ type: 'text-start', id: 'sleep-wake-text' },
|
|
80
|
+
{ type: 'text-delta', id: 'sleep-wake-text', delta: 'sleep-wake-done' },
|
|
81
|
+
{ type: 'text-end', id: 'sleep-wake-text' },
|
|
82
|
+
{
|
|
83
|
+
type: 'finish',
|
|
84
|
+
finishReason: 'stop',
|
|
85
|
+
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
const sleepCancelCallMatcher = {
|
|
91
|
+
id: 'sleep-cancel-call',
|
|
92
|
+
priority: 132,
|
|
93
|
+
when: {
|
|
94
|
+
lastMessageRole: 'user',
|
|
95
|
+
latestUserTextIncludes: 'SLEEP_CANCEL_MARKER',
|
|
96
|
+
},
|
|
97
|
+
then: {
|
|
98
|
+
parts: [
|
|
99
|
+
{ type: 'stream-start', warnings: [] },
|
|
100
|
+
{
|
|
101
|
+
type: 'tool-call',
|
|
102
|
+
toolCallId: 'sleep-cancel-call-1',
|
|
103
|
+
toolName: 'kimaki_sleep',
|
|
104
|
+
input: JSON.stringify({ duration: '2h', reason: 'cancel me' }),
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
type: 'finish',
|
|
108
|
+
finishReason: 'tool-calls',
|
|
109
|
+
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
const sleepCancelAckMatcher = {
|
|
115
|
+
id: 'sleep-cancel-ack',
|
|
116
|
+
priority: 133,
|
|
117
|
+
when: {
|
|
118
|
+
latestUserTextIncludes: 'SLEEP_CANCEL_MARKER',
|
|
119
|
+
rawPromptIncludes: 'Sleeping until',
|
|
120
|
+
},
|
|
121
|
+
then: {
|
|
122
|
+
parts: [
|
|
123
|
+
{ type: 'stream-start', warnings: [] },
|
|
124
|
+
{ type: 'text-start', id: 'sleep-cancel-ack-text' },
|
|
125
|
+
{
|
|
126
|
+
type: 'text-delta',
|
|
127
|
+
id: 'sleep-cancel-ack-text',
|
|
128
|
+
delta: 'cancel-sleep-started',
|
|
129
|
+
},
|
|
130
|
+
{ type: 'text-end', id: 'sleep-cancel-ack-text' },
|
|
131
|
+
{
|
|
132
|
+
type: 'finish',
|
|
133
|
+
finishReason: 'stop',
|
|
134
|
+
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
const sleepQueueCallMatcher = {
|
|
140
|
+
id: 'sleep-queue-call',
|
|
141
|
+
priority: 135,
|
|
142
|
+
when: {
|
|
143
|
+
lastMessageRole: 'user',
|
|
144
|
+
latestUserTextIncludes: 'SLEEP_QUEUE_MARKER',
|
|
145
|
+
},
|
|
146
|
+
then: {
|
|
147
|
+
parts: [
|
|
148
|
+
{ type: 'stream-start', warnings: [] },
|
|
149
|
+
{
|
|
150
|
+
type: 'tool-call',
|
|
151
|
+
toolCallId: 'sleep-queue-call-1',
|
|
152
|
+
toolName: 'kimaki_sleep',
|
|
153
|
+
input: JSON.stringify({ duration: '2h', reason: 'queue supersedes' }),
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
type: 'finish',
|
|
157
|
+
finishReason: 'tool-calls',
|
|
158
|
+
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
const sleepQueueAckMatcher = {
|
|
164
|
+
id: 'sleep-queue-ack',
|
|
165
|
+
priority: 136,
|
|
166
|
+
when: {
|
|
167
|
+
latestUserTextIncludes: 'SLEEP_QUEUE_MARKER',
|
|
168
|
+
rawPromptIncludes: 'Sleeping until',
|
|
169
|
+
},
|
|
170
|
+
then: {
|
|
171
|
+
parts: [
|
|
172
|
+
{ type: 'stream-start', warnings: [] },
|
|
173
|
+
{ type: 'text-start', id: 'sleep-queue-ack-text' },
|
|
174
|
+
{
|
|
175
|
+
type: 'text-delta',
|
|
176
|
+
id: 'sleep-queue-ack-text',
|
|
177
|
+
delta: 'queue-sleep-started',
|
|
178
|
+
},
|
|
179
|
+
{ type: 'text-end', id: 'sleep-queue-ack-text' },
|
|
180
|
+
{
|
|
181
|
+
type: 'finish',
|
|
182
|
+
finishReason: 'stop',
|
|
183
|
+
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
const sleepQueuedMessageMatcher = {
|
|
189
|
+
id: 'sleep-queued-message',
|
|
190
|
+
priority: 137,
|
|
191
|
+
when: {
|
|
192
|
+
lastMessageRole: 'user',
|
|
193
|
+
latestUserTextIncludes: 'SLEEP_QUEUE_FOLLOWUP',
|
|
194
|
+
},
|
|
195
|
+
then: {
|
|
196
|
+
parts: [
|
|
197
|
+
{ type: 'stream-start', warnings: [] },
|
|
198
|
+
{ type: 'text-start', id: 'sleep-queued-text' },
|
|
199
|
+
{
|
|
200
|
+
type: 'text-delta',
|
|
201
|
+
id: 'sleep-queued-text',
|
|
202
|
+
delta: 'queue-followup-done',
|
|
203
|
+
},
|
|
204
|
+
{ type: 'text-end', id: 'sleep-queued-text' },
|
|
205
|
+
{
|
|
206
|
+
type: 'finish',
|
|
207
|
+
finishReason: 'stop',
|
|
208
|
+
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
const sleepCancelFollowupMatcher = {
|
|
214
|
+
id: 'sleep-cancel-followup',
|
|
215
|
+
priority: 134,
|
|
216
|
+
when: {
|
|
217
|
+
lastMessageRole: 'user',
|
|
218
|
+
latestUserTextIncludes: 'SLEEP_CANCEL_FOLLOWUP',
|
|
219
|
+
},
|
|
220
|
+
then: {
|
|
221
|
+
parts: [
|
|
222
|
+
{ type: 'stream-start', warnings: [] },
|
|
223
|
+
{ type: 'text-start', id: 'sleep-cancel-followup-text' },
|
|
224
|
+
{
|
|
225
|
+
type: 'text-delta',
|
|
226
|
+
id: 'sleep-cancel-followup-text',
|
|
227
|
+
delta: 'cancel-followup-done',
|
|
228
|
+
},
|
|
229
|
+
{ type: 'text-end', id: 'sleep-cancel-followup-text' },
|
|
230
|
+
{
|
|
231
|
+
type: 'finish',
|
|
232
|
+
finishReason: 'stop',
|
|
233
|
+
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
return [
|
|
239
|
+
sleepCallMatcher,
|
|
240
|
+
sleepAcknowledgeMatcher,
|
|
241
|
+
sleepWakeMatcher,
|
|
242
|
+
sleepCancelCallMatcher,
|
|
243
|
+
sleepCancelAckMatcher,
|
|
244
|
+
sleepCancelFollowupMatcher,
|
|
245
|
+
sleepQueueCallMatcher,
|
|
246
|
+
sleepQueueAckMatcher,
|
|
247
|
+
sleepQueuedMessageMatcher,
|
|
248
|
+
];
|
|
249
|
+
}
|
|
250
|
+
describe('kimaki_sleep', () => {
|
|
251
|
+
const ctx = setupQueueAdvancedSuite({
|
|
252
|
+
channelId: TEXT_CHANNEL_ID,
|
|
253
|
+
channelName: 'sleep-e2e',
|
|
254
|
+
dirName: 'sleep-e2e',
|
|
255
|
+
username: 'sleep-tester',
|
|
256
|
+
extraMatchers: createSleepMatchers(),
|
|
257
|
+
});
|
|
258
|
+
test('persists a wake row, then the runner wakes the same session', async () => {
|
|
259
|
+
await ctx.discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
260
|
+
content: 'SLEEP_TOOL_MARKER wait for the deploy',
|
|
261
|
+
});
|
|
262
|
+
const thread = await ctx.discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
263
|
+
timeout: 4_000,
|
|
264
|
+
predicate: (t) => {
|
|
265
|
+
return t.name === 'SLEEP_TOOL_MARKER wait for the deploy';
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
const th = ctx.discord.thread(thread.id);
|
|
269
|
+
await waitForBotMessageContaining({
|
|
270
|
+
discord: ctx.discord,
|
|
271
|
+
threadId: thread.id,
|
|
272
|
+
text: 'sleep-started',
|
|
273
|
+
timeout: 4_000,
|
|
274
|
+
});
|
|
275
|
+
// Let the first turn finish emitting its footer before waking, otherwise
|
|
276
|
+
// the footer can land after the wake message and reorder the snapshot.
|
|
277
|
+
await waitForFooterMessage({
|
|
278
|
+
discord: ctx.discord,
|
|
279
|
+
threadId: thread.id,
|
|
280
|
+
timeout: 4_000,
|
|
281
|
+
afterMessageIncludes: 'sleep-started',
|
|
282
|
+
afterAuthorId: ctx.discord.botUserId,
|
|
283
|
+
});
|
|
284
|
+
const sessionId = await getThreadSession(thread.id);
|
|
285
|
+
if (!sessionId) {
|
|
286
|
+
throw new Error('Expected a thread session after the sleep tool ran');
|
|
287
|
+
}
|
|
288
|
+
const plannedSleep = await getSessionSleep({ sessionId });
|
|
289
|
+
expect(plannedSleep?.status).toBe('planned');
|
|
290
|
+
expect(plannedSleep?.reason).toBe('waiting for the deploy');
|
|
291
|
+
// The row is years out, so pass a future `now` instead of waiting on the tick.
|
|
292
|
+
await wakeDueSessionSleeps({
|
|
293
|
+
rest: ctx.botClient.rest,
|
|
294
|
+
now: AFTER_SLEEP_UNTIL,
|
|
295
|
+
});
|
|
296
|
+
await waitForBotMessageContaining({
|
|
297
|
+
discord: ctx.discord,
|
|
298
|
+
threadId: thread.id,
|
|
299
|
+
text: 'sleep-wake-done',
|
|
300
|
+
timeout: 4_000,
|
|
301
|
+
});
|
|
302
|
+
await waitForFooterMessage({
|
|
303
|
+
discord: ctx.discord,
|
|
304
|
+
threadId: thread.id,
|
|
305
|
+
timeout: 4_000,
|
|
306
|
+
afterMessageIncludes: 'sleep-wake-done',
|
|
307
|
+
afterAuthorId: ctx.discord.botUserId,
|
|
308
|
+
});
|
|
309
|
+
expect(await th.text()).toMatchInlineSnapshot(`
|
|
310
|
+
"--- from: user (sleep-tester)
|
|
311
|
+
SLEEP_TOOL_MARKER wait for the deploy
|
|
312
|
+
--- from: assistant (TestBot)
|
|
313
|
+
> *using deterministic-provider/deterministic-v2*
|
|
314
|
+
> waiting for the deploy
|
|
315
|
+
|
|
316
|
+
┣ kimaki_sleep until 2030-01-01T09:00:00Z _waiting for the deploy_
|
|
317
|
+
|
|
318
|
+
> sleep-started
|
|
319
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2*
|
|
320
|
+
⬦ Woke after sleeping until 2030-01-01 09:00 UTC
|
|
321
|
+
Reason: waiting for the deploy
|
|
322
|
+
Continue the work you were waiting for.
|
|
323
|
+
[embed]
|
|
324
|
+
> sleep-wake-done
|
|
325
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000991>"
|
|
326
|
+
`);
|
|
327
|
+
// The wake must continue the SAME OpenCode session, not start a new one:
|
|
328
|
+
// that is the whole point of sleeping instead of scheduling a new prompt.
|
|
329
|
+
expect(await getThreadSession(thread.id)).toBe(sessionId);
|
|
330
|
+
// `consumed` proves ingress actually turned the wake into a turn. The row
|
|
331
|
+
// only reaches this state through the ingress commit point.
|
|
332
|
+
const wokenSleep = await getSessionSleep({ sessionId });
|
|
333
|
+
expect(wokenSleep?.status).toBe('consumed');
|
|
334
|
+
// Replaying the tick must not produce a second wake turn.
|
|
335
|
+
const messagesBeforeReplay = (await th.getMessages()).length;
|
|
336
|
+
await wakeDueSessionSleeps({
|
|
337
|
+
rest: ctx.botClient.rest,
|
|
338
|
+
now: AFTER_SLEEP_UNTIL,
|
|
339
|
+
});
|
|
340
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
341
|
+
expect((await th.getMessages()).length).toBe(messagesBeforeReplay);
|
|
342
|
+
await new Promise((resolve) => {
|
|
343
|
+
setTimeout(resolve, 20);
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}, 20_000);
|
|
347
|
+
test('a user message cancels a planned sleep so no wake fires', async () => {
|
|
348
|
+
await ctx.discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
349
|
+
content: 'SLEEP_CANCEL_MARKER wait for something',
|
|
350
|
+
});
|
|
351
|
+
const thread = await ctx.discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
352
|
+
timeout: 4_000,
|
|
353
|
+
predicate: (t) => {
|
|
354
|
+
return t.name === 'SLEEP_CANCEL_MARKER wait for something';
|
|
355
|
+
},
|
|
356
|
+
});
|
|
357
|
+
const th = ctx.discord.thread(thread.id);
|
|
358
|
+
await waitForBotMessageContaining({
|
|
359
|
+
discord: ctx.discord,
|
|
360
|
+
threadId: thread.id,
|
|
361
|
+
text: 'cancel-sleep-started',
|
|
362
|
+
timeout: 4_000,
|
|
363
|
+
});
|
|
364
|
+
await waitForFooterMessage({
|
|
365
|
+
discord: ctx.discord,
|
|
366
|
+
threadId: thread.id,
|
|
367
|
+
timeout: 4_000,
|
|
368
|
+
afterMessageIncludes: 'cancel-sleep-started',
|
|
369
|
+
afterAuthorId: ctx.discord.botUserId,
|
|
370
|
+
});
|
|
371
|
+
const sessionId = await getThreadSession(thread.id);
|
|
372
|
+
if (!sessionId) {
|
|
373
|
+
throw new Error('Expected a thread session after the sleep tool ran');
|
|
374
|
+
}
|
|
375
|
+
expect((await getSessionSleep({ sessionId }))?.status).toBe('planned');
|
|
376
|
+
await th.user(TEST_USER_ID).sendMessage({
|
|
377
|
+
content: 'SLEEP_CANCEL_FOLLOWUP never mind, keep going',
|
|
378
|
+
});
|
|
379
|
+
await waitForBotMessageContaining({
|
|
380
|
+
discord: ctx.discord,
|
|
381
|
+
threadId: thread.id,
|
|
382
|
+
text: 'cancel-followup-done',
|
|
383
|
+
timeout: 4_000,
|
|
384
|
+
});
|
|
385
|
+
await waitForFooterMessage({
|
|
386
|
+
discord: ctx.discord,
|
|
387
|
+
threadId: thread.id,
|
|
388
|
+
timeout: 4_000,
|
|
389
|
+
afterMessageIncludes: 'cancel-followup-done',
|
|
390
|
+
afterAuthorId: ctx.discord.botUserId,
|
|
391
|
+
});
|
|
392
|
+
// Cancellation happens inside runtime.enqueueIncoming, so it covers
|
|
393
|
+
// slash commands and CLI-injected prompts, not just chat messages.
|
|
394
|
+
expect((await getSessionSleep({ sessionId }))?.status).toBe('cancelled');
|
|
395
|
+
await wakeDueSessionSleeps({
|
|
396
|
+
rest: ctx.botClient.rest,
|
|
397
|
+
now: AFTER_SLEEP_UNTIL,
|
|
398
|
+
});
|
|
399
|
+
// Everything is deterministic, so a short poll proves the wake never posts.
|
|
400
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
401
|
+
const messages = await th.getMessages();
|
|
402
|
+
const woke = messages.some((message) => {
|
|
403
|
+
return message.content.includes('Woke after sleeping until');
|
|
404
|
+
});
|
|
405
|
+
expect(woke).toBe(false);
|
|
406
|
+
await new Promise((resolve) => {
|
|
407
|
+
setTimeout(resolve, 20);
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
expect(await th.text()).toMatchInlineSnapshot(`
|
|
411
|
+
"--- from: user (sleep-tester)
|
|
412
|
+
SLEEP_CANCEL_MARKER wait for something
|
|
413
|
+
--- from: assistant (TestBot)
|
|
414
|
+
> *using deterministic-provider/deterministic-v2*
|
|
415
|
+
┣ kimaki_sleep for 2h _cancel me_
|
|
416
|
+
|
|
417
|
+
> cancel-sleep-started
|
|
418
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2*
|
|
419
|
+
--- from: user (sleep-tester)
|
|
420
|
+
SLEEP_CANCEL_FOLLOWUP never mind, keep going
|
|
421
|
+
--- from: assistant (TestBot)
|
|
422
|
+
> cancel-followup-done
|
|
423
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000991>"
|
|
424
|
+
`);
|
|
425
|
+
}, 20_000);
|
|
426
|
+
test('/queue also supersedes a pending sleep', async () => {
|
|
427
|
+
// Slash commands never reach the Discord MessageCreate handler, so this
|
|
428
|
+
// proves cancellation really lives in the shared ingress path.
|
|
429
|
+
await ctx.discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
430
|
+
content: 'SLEEP_QUEUE_MARKER wait for something',
|
|
431
|
+
});
|
|
432
|
+
const thread = await ctx.discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
433
|
+
timeout: 4_000,
|
|
434
|
+
predicate: (t) => {
|
|
435
|
+
return t.name === 'SLEEP_QUEUE_MARKER wait for something';
|
|
436
|
+
},
|
|
437
|
+
});
|
|
438
|
+
const th = ctx.discord.thread(thread.id);
|
|
439
|
+
await waitForBotMessageContaining({
|
|
440
|
+
discord: ctx.discord,
|
|
441
|
+
threadId: thread.id,
|
|
442
|
+
text: 'queue-sleep-started',
|
|
443
|
+
timeout: 4_000,
|
|
444
|
+
});
|
|
445
|
+
await waitForFooterMessage({
|
|
446
|
+
discord: ctx.discord,
|
|
447
|
+
threadId: thread.id,
|
|
448
|
+
timeout: 4_000,
|
|
449
|
+
afterMessageIncludes: 'queue-sleep-started',
|
|
450
|
+
afterAuthorId: ctx.discord.botUserId,
|
|
451
|
+
});
|
|
452
|
+
const sessionId = await getThreadSession(thread.id);
|
|
453
|
+
if (!sessionId) {
|
|
454
|
+
throw new Error('Expected a thread session after the sleep tool ran');
|
|
455
|
+
}
|
|
456
|
+
expect((await getSessionSleep({ sessionId }))?.status).toBe('planned');
|
|
457
|
+
const { id: queueInteractionId } = await th.user(TEST_USER_ID)
|
|
458
|
+
.runSlashCommand({
|
|
459
|
+
name: 'queue',
|
|
460
|
+
options: [
|
|
461
|
+
{
|
|
462
|
+
name: 'message',
|
|
463
|
+
type: 3,
|
|
464
|
+
value: 'SLEEP_QUEUE_FOLLOWUP handle this instead',
|
|
465
|
+
},
|
|
466
|
+
],
|
|
467
|
+
});
|
|
468
|
+
await th.waitForInteractionAck({
|
|
469
|
+
interactionId: queueInteractionId,
|
|
470
|
+
timeout: 4_000,
|
|
471
|
+
});
|
|
472
|
+
await waitForBotMessageContaining({
|
|
473
|
+
discord: ctx.discord,
|
|
474
|
+
threadId: thread.id,
|
|
475
|
+
text: 'queue-followup-done',
|
|
476
|
+
timeout: 4_000,
|
|
477
|
+
});
|
|
478
|
+
expect((await getSessionSleep({ sessionId }))?.status).toBe('cancelled');
|
|
479
|
+
await wakeDueSessionSleeps({
|
|
480
|
+
rest: ctx.botClient.rest,
|
|
481
|
+
now: AFTER_SLEEP_UNTIL,
|
|
482
|
+
});
|
|
483
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
484
|
+
const messages = await th.getMessages();
|
|
485
|
+
const woke = messages.some((message) => {
|
|
486
|
+
return message.content.includes('Woke after sleeping until');
|
|
487
|
+
});
|
|
488
|
+
expect(woke).toBe(false);
|
|
489
|
+
await new Promise((resolve) => {
|
|
490
|
+
setTimeout(resolve, 20);
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
}, 20_000);
|
|
494
|
+
});
|
|
@@ -81,7 +81,8 @@ function createMinimalMatchers() {
|
|
|
81
81
|
}
|
|
82
82
|
const TEST_USER_ID = '900000000000000777';
|
|
83
83
|
const TEXT_CHANNEL_ID = '900000000000000778';
|
|
84
|
-
describe('
|
|
84
|
+
const startupBenchmark = describe.skipIf(process.env['KIMAKI_STARTUP_BENCHMARK'] !== '1');
|
|
85
|
+
startupBenchmark('startup time measurement', () => {
|
|
85
86
|
let directories;
|
|
86
87
|
let discord;
|
|
87
88
|
let botClient = null;
|
package/dist/store.js
CHANGED
|
@@ -9,10 +9,13 @@ export const store = createStore(() => ({
|
|
|
9
9
|
defaultVerbosity: 'text_and_essential_tools',
|
|
10
10
|
defaultMentionMode: false,
|
|
11
11
|
critiqueEnabled: true,
|
|
12
|
+
footerMentionsEnabled: true,
|
|
12
13
|
enabledSkills: [],
|
|
13
14
|
disabledSkills: [],
|
|
14
15
|
allowedMentions: ['users'],
|
|
15
16
|
allowAllUsers: false,
|
|
17
|
+
opencodeHostname: null,
|
|
18
|
+
opencodePort: null,
|
|
16
19
|
restrictExternalDirectories: false,
|
|
17
20
|
permissionTimeoutMs: 10 * 60 * 1000,
|
|
18
21
|
useWorktrees: false,
|