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
|
@@ -0,0 +1,565 @@
|
|
|
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
|
+
|
|
6
|
+
import { describe, test, expect } from 'vitest'
|
|
7
|
+
import type { DeterministicMatcher } from 'opencode-deterministic-provider'
|
|
8
|
+
import {
|
|
9
|
+
setupQueueAdvancedSuite,
|
|
10
|
+
TEST_USER_ID,
|
|
11
|
+
} from './queue-advanced-e2e-setup.js'
|
|
12
|
+
import {
|
|
13
|
+
waitForBotMessageContaining,
|
|
14
|
+
waitForFooterMessage,
|
|
15
|
+
} from './test-utils.js'
|
|
16
|
+
import { getSessionSleep, getThreadSession } from './database.js'
|
|
17
|
+
import { wakeDueSessionSleeps } from './task-runner.js'
|
|
18
|
+
|
|
19
|
+
const TEXT_CHANNEL_ID = '200000000000001031'
|
|
20
|
+
|
|
21
|
+
// Fixed wake instant so the wake message stays snapshot-stable across runs.
|
|
22
|
+
const SLEEP_UNTIL = '2030-01-01T09:00:00Z'
|
|
23
|
+
const AFTER_SLEEP_UNTIL = new Date('2030-01-01T09:00:01Z')
|
|
24
|
+
|
|
25
|
+
function createSleepMatchers(): DeterministicMatcher[] {
|
|
26
|
+
const sleepCallMatcher: DeterministicMatcher = {
|
|
27
|
+
id: 'sleep-tool-call',
|
|
28
|
+
priority: 130,
|
|
29
|
+
when: {
|
|
30
|
+
lastMessageRole: 'user',
|
|
31
|
+
latestUserTextIncludes: 'SLEEP_TOOL_MARKER',
|
|
32
|
+
},
|
|
33
|
+
then: {
|
|
34
|
+
parts: [
|
|
35
|
+
{ type: 'stream-start', warnings: [] },
|
|
36
|
+
{ type: 'text-start', id: 'sleep-call-text' },
|
|
37
|
+
{
|
|
38
|
+
type: 'text-delta',
|
|
39
|
+
id: 'sleep-call-text',
|
|
40
|
+
delta: 'waiting for the deploy',
|
|
41
|
+
},
|
|
42
|
+
{ type: 'text-end', id: 'sleep-call-text' },
|
|
43
|
+
{
|
|
44
|
+
type: 'tool-call',
|
|
45
|
+
toolCallId: 'sleep-call-1',
|
|
46
|
+
toolName: 'kimaki_sleep',
|
|
47
|
+
input: JSON.stringify({
|
|
48
|
+
until: SLEEP_UNTIL,
|
|
49
|
+
reason: 'waiting for the deploy',
|
|
50
|
+
}),
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
type: 'finish',
|
|
54
|
+
finishReason: 'tool-calls',
|
|
55
|
+
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const sleepAcknowledgeMatcher: DeterministicMatcher = {
|
|
62
|
+
id: 'sleep-tool-ack',
|
|
63
|
+
priority: 129,
|
|
64
|
+
when: {
|
|
65
|
+
latestUserTextIncludes: 'SLEEP_TOOL_MARKER',
|
|
66
|
+
rawPromptIncludes: 'Sleeping until',
|
|
67
|
+
},
|
|
68
|
+
then: {
|
|
69
|
+
parts: [
|
|
70
|
+
{ type: 'stream-start', warnings: [] },
|
|
71
|
+
{ type: 'text-start', id: 'sleep-ack-text' },
|
|
72
|
+
{ type: 'text-delta', id: 'sleep-ack-text', delta: 'sleep-started' },
|
|
73
|
+
{ type: 'text-end', id: 'sleep-ack-text' },
|
|
74
|
+
{
|
|
75
|
+
type: 'finish',
|
|
76
|
+
finishReason: 'stop',
|
|
77
|
+
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const sleepWakeMatcher: DeterministicMatcher = {
|
|
84
|
+
id: 'sleep-tool-wake',
|
|
85
|
+
priority: 131,
|
|
86
|
+
when: {
|
|
87
|
+
latestUserTextIncludes: 'Woke after sleeping until',
|
|
88
|
+
},
|
|
89
|
+
then: {
|
|
90
|
+
parts: [
|
|
91
|
+
{ type: 'stream-start', warnings: [] },
|
|
92
|
+
{ type: 'text-start', id: 'sleep-wake-text' },
|
|
93
|
+
{ type: 'text-delta', id: 'sleep-wake-text', delta: 'sleep-wake-done' },
|
|
94
|
+
{ type: 'text-end', id: 'sleep-wake-text' },
|
|
95
|
+
{
|
|
96
|
+
type: 'finish',
|
|
97
|
+
finishReason: 'stop',
|
|
98
|
+
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const sleepCancelCallMatcher: DeterministicMatcher = {
|
|
105
|
+
id: 'sleep-cancel-call',
|
|
106
|
+
priority: 132,
|
|
107
|
+
when: {
|
|
108
|
+
lastMessageRole: 'user',
|
|
109
|
+
latestUserTextIncludes: 'SLEEP_CANCEL_MARKER',
|
|
110
|
+
},
|
|
111
|
+
then: {
|
|
112
|
+
parts: [
|
|
113
|
+
{ type: 'stream-start', warnings: [] },
|
|
114
|
+
{
|
|
115
|
+
type: 'tool-call',
|
|
116
|
+
toolCallId: 'sleep-cancel-call-1',
|
|
117
|
+
toolName: 'kimaki_sleep',
|
|
118
|
+
input: JSON.stringify({ duration: '2h', reason: 'cancel me' }),
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
type: 'finish',
|
|
122
|
+
finishReason: 'tool-calls',
|
|
123
|
+
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const sleepCancelAckMatcher: DeterministicMatcher = {
|
|
130
|
+
id: 'sleep-cancel-ack',
|
|
131
|
+
priority: 133,
|
|
132
|
+
when: {
|
|
133
|
+
latestUserTextIncludes: 'SLEEP_CANCEL_MARKER',
|
|
134
|
+
rawPromptIncludes: 'Sleeping until',
|
|
135
|
+
},
|
|
136
|
+
then: {
|
|
137
|
+
parts: [
|
|
138
|
+
{ type: 'stream-start', warnings: [] },
|
|
139
|
+
{ type: 'text-start', id: 'sleep-cancel-ack-text' },
|
|
140
|
+
{
|
|
141
|
+
type: 'text-delta',
|
|
142
|
+
id: 'sleep-cancel-ack-text',
|
|
143
|
+
delta: 'cancel-sleep-started',
|
|
144
|
+
},
|
|
145
|
+
{ type: 'text-end', id: 'sleep-cancel-ack-text' },
|
|
146
|
+
{
|
|
147
|
+
type: 'finish',
|
|
148
|
+
finishReason: 'stop',
|
|
149
|
+
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const sleepQueueCallMatcher: DeterministicMatcher = {
|
|
156
|
+
id: 'sleep-queue-call',
|
|
157
|
+
priority: 135,
|
|
158
|
+
when: {
|
|
159
|
+
lastMessageRole: 'user',
|
|
160
|
+
latestUserTextIncludes: 'SLEEP_QUEUE_MARKER',
|
|
161
|
+
},
|
|
162
|
+
then: {
|
|
163
|
+
parts: [
|
|
164
|
+
{ type: 'stream-start', warnings: [] },
|
|
165
|
+
{
|
|
166
|
+
type: 'tool-call',
|
|
167
|
+
toolCallId: 'sleep-queue-call-1',
|
|
168
|
+
toolName: 'kimaki_sleep',
|
|
169
|
+
input: JSON.stringify({ duration: '2h', reason: 'queue supersedes' }),
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
type: 'finish',
|
|
173
|
+
finishReason: 'tool-calls',
|
|
174
|
+
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
},
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const sleepQueueAckMatcher: DeterministicMatcher = {
|
|
181
|
+
id: 'sleep-queue-ack',
|
|
182
|
+
priority: 136,
|
|
183
|
+
when: {
|
|
184
|
+
latestUserTextIncludes: 'SLEEP_QUEUE_MARKER',
|
|
185
|
+
rawPromptIncludes: 'Sleeping until',
|
|
186
|
+
},
|
|
187
|
+
then: {
|
|
188
|
+
parts: [
|
|
189
|
+
{ type: 'stream-start', warnings: [] },
|
|
190
|
+
{ type: 'text-start', id: 'sleep-queue-ack-text' },
|
|
191
|
+
{
|
|
192
|
+
type: 'text-delta',
|
|
193
|
+
id: 'sleep-queue-ack-text',
|
|
194
|
+
delta: 'queue-sleep-started',
|
|
195
|
+
},
|
|
196
|
+
{ type: 'text-end', id: 'sleep-queue-ack-text' },
|
|
197
|
+
{
|
|
198
|
+
type: 'finish',
|
|
199
|
+
finishReason: 'stop',
|
|
200
|
+
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
},
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const sleepQueuedMessageMatcher: DeterministicMatcher = {
|
|
207
|
+
id: 'sleep-queued-message',
|
|
208
|
+
priority: 137,
|
|
209
|
+
when: {
|
|
210
|
+
lastMessageRole: 'user',
|
|
211
|
+
latestUserTextIncludes: 'SLEEP_QUEUE_FOLLOWUP',
|
|
212
|
+
},
|
|
213
|
+
then: {
|
|
214
|
+
parts: [
|
|
215
|
+
{ type: 'stream-start', warnings: [] },
|
|
216
|
+
{ type: 'text-start', id: 'sleep-queued-text' },
|
|
217
|
+
{
|
|
218
|
+
type: 'text-delta',
|
|
219
|
+
id: 'sleep-queued-text',
|
|
220
|
+
delta: 'queue-followup-done',
|
|
221
|
+
},
|
|
222
|
+
{ type: 'text-end', id: 'sleep-queued-text' },
|
|
223
|
+
{
|
|
224
|
+
type: 'finish',
|
|
225
|
+
finishReason: 'stop',
|
|
226
|
+
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
},
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const sleepCancelFollowupMatcher: DeterministicMatcher = {
|
|
233
|
+
id: 'sleep-cancel-followup',
|
|
234
|
+
priority: 134,
|
|
235
|
+
when: {
|
|
236
|
+
lastMessageRole: 'user',
|
|
237
|
+
latestUserTextIncludes: 'SLEEP_CANCEL_FOLLOWUP',
|
|
238
|
+
},
|
|
239
|
+
then: {
|
|
240
|
+
parts: [
|
|
241
|
+
{ type: 'stream-start', warnings: [] },
|
|
242
|
+
{ type: 'text-start', id: 'sleep-cancel-followup-text' },
|
|
243
|
+
{
|
|
244
|
+
type: 'text-delta',
|
|
245
|
+
id: 'sleep-cancel-followup-text',
|
|
246
|
+
delta: 'cancel-followup-done',
|
|
247
|
+
},
|
|
248
|
+
{ type: 'text-end', id: 'sleep-cancel-followup-text' },
|
|
249
|
+
{
|
|
250
|
+
type: 'finish',
|
|
251
|
+
finishReason: 'stop',
|
|
252
|
+
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
},
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return [
|
|
259
|
+
sleepCallMatcher,
|
|
260
|
+
sleepAcknowledgeMatcher,
|
|
261
|
+
sleepWakeMatcher,
|
|
262
|
+
sleepCancelCallMatcher,
|
|
263
|
+
sleepCancelAckMatcher,
|
|
264
|
+
sleepCancelFollowupMatcher,
|
|
265
|
+
sleepQueueCallMatcher,
|
|
266
|
+
sleepQueueAckMatcher,
|
|
267
|
+
sleepQueuedMessageMatcher,
|
|
268
|
+
]
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
describe('kimaki_sleep', () => {
|
|
272
|
+
const ctx = setupQueueAdvancedSuite({
|
|
273
|
+
channelId: TEXT_CHANNEL_ID,
|
|
274
|
+
channelName: 'sleep-e2e',
|
|
275
|
+
dirName: 'sleep-e2e',
|
|
276
|
+
username: 'sleep-tester',
|
|
277
|
+
extraMatchers: createSleepMatchers(),
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
test(
|
|
281
|
+
'persists a wake row, then the runner wakes the same session',
|
|
282
|
+
async () => {
|
|
283
|
+
await ctx.discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
284
|
+
content: 'SLEEP_TOOL_MARKER wait for the deploy',
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
const thread = await ctx.discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
288
|
+
timeout: 4_000,
|
|
289
|
+
predicate: (t) => {
|
|
290
|
+
return t.name === 'SLEEP_TOOL_MARKER wait for the deploy'
|
|
291
|
+
},
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
const th = ctx.discord.thread(thread.id)
|
|
295
|
+
|
|
296
|
+
await waitForBotMessageContaining({
|
|
297
|
+
discord: ctx.discord,
|
|
298
|
+
threadId: thread.id,
|
|
299
|
+
text: 'sleep-started',
|
|
300
|
+
timeout: 4_000,
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
// Let the first turn finish emitting its footer before waking, otherwise
|
|
304
|
+
// the footer can land after the wake message and reorder the snapshot.
|
|
305
|
+
await waitForFooterMessage({
|
|
306
|
+
discord: ctx.discord,
|
|
307
|
+
threadId: thread.id,
|
|
308
|
+
timeout: 4_000,
|
|
309
|
+
afterMessageIncludes: 'sleep-started',
|
|
310
|
+
afterAuthorId: ctx.discord.botUserId,
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
const sessionId = await getThreadSession(thread.id)
|
|
314
|
+
if (!sessionId) {
|
|
315
|
+
throw new Error('Expected a thread session after the sleep tool ran')
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const plannedSleep = await getSessionSleep({ sessionId })
|
|
319
|
+
expect(plannedSleep?.status).toBe('planned')
|
|
320
|
+
expect(plannedSleep?.reason).toBe('waiting for the deploy')
|
|
321
|
+
|
|
322
|
+
// The row is years out, so pass a future `now` instead of waiting on the tick.
|
|
323
|
+
await wakeDueSessionSleeps({
|
|
324
|
+
rest: ctx.botClient.rest,
|
|
325
|
+
now: AFTER_SLEEP_UNTIL,
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
await waitForBotMessageContaining({
|
|
329
|
+
discord: ctx.discord,
|
|
330
|
+
threadId: thread.id,
|
|
331
|
+
text: 'sleep-wake-done',
|
|
332
|
+
timeout: 4_000,
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
await waitForFooterMessage({
|
|
336
|
+
discord: ctx.discord,
|
|
337
|
+
threadId: thread.id,
|
|
338
|
+
timeout: 4_000,
|
|
339
|
+
afterMessageIncludes: 'sleep-wake-done',
|
|
340
|
+
afterAuthorId: ctx.discord.botUserId,
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
expect(await th.text()).toMatchInlineSnapshot(`
|
|
344
|
+
"--- from: user (sleep-tester)
|
|
345
|
+
SLEEP_TOOL_MARKER wait for the deploy
|
|
346
|
+
--- from: assistant (TestBot)
|
|
347
|
+
> *using deterministic-provider/deterministic-v2*
|
|
348
|
+
> waiting for the deploy
|
|
349
|
+
|
|
350
|
+
┣ kimaki_sleep until 2030-01-01T09:00:00Z _waiting for the deploy_
|
|
351
|
+
|
|
352
|
+
> sleep-started
|
|
353
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2*
|
|
354
|
+
⬦ Woke after sleeping until 2030-01-01 09:00 UTC
|
|
355
|
+
Reason: waiting for the deploy
|
|
356
|
+
Continue the work you were waiting for.
|
|
357
|
+
[embed]
|
|
358
|
+
> sleep-wake-done
|
|
359
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000991>"
|
|
360
|
+
`)
|
|
361
|
+
|
|
362
|
+
// The wake must continue the SAME OpenCode session, not start a new one:
|
|
363
|
+
// that is the whole point of sleeping instead of scheduling a new prompt.
|
|
364
|
+
expect(await getThreadSession(thread.id)).toBe(sessionId)
|
|
365
|
+
|
|
366
|
+
// `consumed` proves ingress actually turned the wake into a turn. The row
|
|
367
|
+
// only reaches this state through the ingress commit point.
|
|
368
|
+
const wokenSleep = await getSessionSleep({ sessionId })
|
|
369
|
+
expect(wokenSleep?.status).toBe('consumed')
|
|
370
|
+
|
|
371
|
+
// Replaying the tick must not produce a second wake turn.
|
|
372
|
+
const messagesBeforeReplay = (await th.getMessages()).length
|
|
373
|
+
await wakeDueSessionSleeps({
|
|
374
|
+
rest: ctx.botClient.rest,
|
|
375
|
+
now: AFTER_SLEEP_UNTIL,
|
|
376
|
+
})
|
|
377
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
378
|
+
expect((await th.getMessages()).length).toBe(messagesBeforeReplay)
|
|
379
|
+
await new Promise((resolve) => {
|
|
380
|
+
setTimeout(resolve, 20)
|
|
381
|
+
})
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
20_000,
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
test(
|
|
388
|
+
'a user message cancels a planned sleep so no wake fires',
|
|
389
|
+
async () => {
|
|
390
|
+
await ctx.discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
391
|
+
content: 'SLEEP_CANCEL_MARKER wait for something',
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
const thread = await ctx.discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
395
|
+
timeout: 4_000,
|
|
396
|
+
predicate: (t) => {
|
|
397
|
+
return t.name === 'SLEEP_CANCEL_MARKER wait for something'
|
|
398
|
+
},
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
const th = ctx.discord.thread(thread.id)
|
|
402
|
+
|
|
403
|
+
await waitForBotMessageContaining({
|
|
404
|
+
discord: ctx.discord,
|
|
405
|
+
threadId: thread.id,
|
|
406
|
+
text: 'cancel-sleep-started',
|
|
407
|
+
timeout: 4_000,
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
await waitForFooterMessage({
|
|
411
|
+
discord: ctx.discord,
|
|
412
|
+
threadId: thread.id,
|
|
413
|
+
timeout: 4_000,
|
|
414
|
+
afterMessageIncludes: 'cancel-sleep-started',
|
|
415
|
+
afterAuthorId: ctx.discord.botUserId,
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
const sessionId = await getThreadSession(thread.id)
|
|
419
|
+
if (!sessionId) {
|
|
420
|
+
throw new Error('Expected a thread session after the sleep tool ran')
|
|
421
|
+
}
|
|
422
|
+
expect((await getSessionSleep({ sessionId }))?.status).toBe('planned')
|
|
423
|
+
|
|
424
|
+
await th.user(TEST_USER_ID).sendMessage({
|
|
425
|
+
content: 'SLEEP_CANCEL_FOLLOWUP never mind, keep going',
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
await waitForBotMessageContaining({
|
|
429
|
+
discord: ctx.discord,
|
|
430
|
+
threadId: thread.id,
|
|
431
|
+
text: 'cancel-followup-done',
|
|
432
|
+
timeout: 4_000,
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
await waitForFooterMessage({
|
|
436
|
+
discord: ctx.discord,
|
|
437
|
+
threadId: thread.id,
|
|
438
|
+
timeout: 4_000,
|
|
439
|
+
afterMessageIncludes: 'cancel-followup-done',
|
|
440
|
+
afterAuthorId: ctx.discord.botUserId,
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
// Cancellation happens inside runtime.enqueueIncoming, so it covers
|
|
444
|
+
// slash commands and CLI-injected prompts, not just chat messages.
|
|
445
|
+
expect((await getSessionSleep({ sessionId }))?.status).toBe('cancelled')
|
|
446
|
+
|
|
447
|
+
await wakeDueSessionSleeps({
|
|
448
|
+
rest: ctx.botClient.rest,
|
|
449
|
+
now: AFTER_SLEEP_UNTIL,
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
// Everything is deterministic, so a short poll proves the wake never posts.
|
|
453
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
454
|
+
const messages = await th.getMessages()
|
|
455
|
+
const woke = messages.some((message) => {
|
|
456
|
+
return message.content.includes('Woke after sleeping until')
|
|
457
|
+
})
|
|
458
|
+
expect(woke).toBe(false)
|
|
459
|
+
await new Promise((resolve) => {
|
|
460
|
+
setTimeout(resolve, 20)
|
|
461
|
+
})
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
expect(await th.text()).toMatchInlineSnapshot(`
|
|
465
|
+
"--- from: user (sleep-tester)
|
|
466
|
+
SLEEP_CANCEL_MARKER wait for something
|
|
467
|
+
--- from: assistant (TestBot)
|
|
468
|
+
> *using deterministic-provider/deterministic-v2*
|
|
469
|
+
┣ kimaki_sleep for 2h _cancel me_
|
|
470
|
+
|
|
471
|
+
> cancel-sleep-started
|
|
472
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2*
|
|
473
|
+
--- from: user (sleep-tester)
|
|
474
|
+
SLEEP_CANCEL_FOLLOWUP never mind, keep going
|
|
475
|
+
--- from: assistant (TestBot)
|
|
476
|
+
> cancel-followup-done
|
|
477
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000991>"
|
|
478
|
+
`)
|
|
479
|
+
},
|
|
480
|
+
20_000,
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
test(
|
|
484
|
+
'/queue also supersedes a pending sleep',
|
|
485
|
+
async () => {
|
|
486
|
+
// Slash commands never reach the Discord MessageCreate handler, so this
|
|
487
|
+
// proves cancellation really lives in the shared ingress path.
|
|
488
|
+
await ctx.discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
489
|
+
content: 'SLEEP_QUEUE_MARKER wait for something',
|
|
490
|
+
})
|
|
491
|
+
|
|
492
|
+
const thread = await ctx.discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
493
|
+
timeout: 4_000,
|
|
494
|
+
predicate: (t) => {
|
|
495
|
+
return t.name === 'SLEEP_QUEUE_MARKER wait for something'
|
|
496
|
+
},
|
|
497
|
+
})
|
|
498
|
+
|
|
499
|
+
const th = ctx.discord.thread(thread.id)
|
|
500
|
+
|
|
501
|
+
await waitForBotMessageContaining({
|
|
502
|
+
discord: ctx.discord,
|
|
503
|
+
threadId: thread.id,
|
|
504
|
+
text: 'queue-sleep-started',
|
|
505
|
+
timeout: 4_000,
|
|
506
|
+
})
|
|
507
|
+
|
|
508
|
+
await waitForFooterMessage({
|
|
509
|
+
discord: ctx.discord,
|
|
510
|
+
threadId: thread.id,
|
|
511
|
+
timeout: 4_000,
|
|
512
|
+
afterMessageIncludes: 'queue-sleep-started',
|
|
513
|
+
afterAuthorId: ctx.discord.botUserId,
|
|
514
|
+
})
|
|
515
|
+
|
|
516
|
+
const sessionId = await getThreadSession(thread.id)
|
|
517
|
+
if (!sessionId) {
|
|
518
|
+
throw new Error('Expected a thread session after the sleep tool ran')
|
|
519
|
+
}
|
|
520
|
+
expect((await getSessionSleep({ sessionId }))?.status).toBe('planned')
|
|
521
|
+
|
|
522
|
+
const { id: queueInteractionId } = await th.user(TEST_USER_ID)
|
|
523
|
+
.runSlashCommand({
|
|
524
|
+
name: 'queue',
|
|
525
|
+
options: [
|
|
526
|
+
{
|
|
527
|
+
name: 'message',
|
|
528
|
+
type: 3,
|
|
529
|
+
value: 'SLEEP_QUEUE_FOLLOWUP handle this instead',
|
|
530
|
+
},
|
|
531
|
+
],
|
|
532
|
+
})
|
|
533
|
+
await th.waitForInteractionAck({
|
|
534
|
+
interactionId: queueInteractionId,
|
|
535
|
+
timeout: 4_000,
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
await waitForBotMessageContaining({
|
|
539
|
+
discord: ctx.discord,
|
|
540
|
+
threadId: thread.id,
|
|
541
|
+
text: 'queue-followup-done',
|
|
542
|
+
timeout: 4_000,
|
|
543
|
+
})
|
|
544
|
+
|
|
545
|
+
expect((await getSessionSleep({ sessionId }))?.status).toBe('cancelled')
|
|
546
|
+
|
|
547
|
+
await wakeDueSessionSleeps({
|
|
548
|
+
rest: ctx.botClient.rest,
|
|
549
|
+
now: AFTER_SLEEP_UNTIL,
|
|
550
|
+
})
|
|
551
|
+
|
|
552
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
553
|
+
const messages = await th.getMessages()
|
|
554
|
+
const woke = messages.some((message) => {
|
|
555
|
+
return message.content.includes('Woke after sleeping until')
|
|
556
|
+
})
|
|
557
|
+
expect(woke).toBe(false)
|
|
558
|
+
await new Promise((resolve) => {
|
|
559
|
+
setTimeout(resolve, 20)
|
|
560
|
+
})
|
|
561
|
+
}
|
|
562
|
+
},
|
|
563
|
+
20_000,
|
|
564
|
+
)
|
|
565
|
+
})
|
|
@@ -106,7 +106,11 @@ function createMinimalMatchers(): DeterministicMatcher[] {
|
|
|
106
106
|
const TEST_USER_ID = '900000000000000777'
|
|
107
107
|
const TEXT_CHANNEL_ID = '900000000000000778'
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
const startupBenchmark = describe.skipIf(
|
|
110
|
+
process.env['KIMAKI_STARTUP_BENCHMARK'] !== '1',
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
startupBenchmark('startup time measurement', () => {
|
|
110
114
|
let directories: ReturnType<typeof createRunDirectories>
|
|
111
115
|
let discord: DigitalDiscord
|
|
112
116
|
let botClient: Client | null = null
|
package/src/store.ts
CHANGED
|
@@ -25,6 +25,7 @@ export type RegisteredUserCommand = {
|
|
|
25
25
|
export type DeterministicTranscriptionConfig = {
|
|
26
26
|
transcription: string
|
|
27
27
|
queueMessage: boolean
|
|
28
|
+
sessionAction?: 'btw' | 'new-session'
|
|
28
29
|
/** Exercise the production API-key prompt before returning the canned result. */
|
|
29
30
|
requireApiKey?: boolean
|
|
30
31
|
/** Agent name extracted from voice message. Only set if user explicitly requested an agent. */
|
|
@@ -67,6 +68,11 @@ export type KimakiState = {
|
|
|
67
68
|
// Read by: system-message.ts (conditionally appends critique instructions).
|
|
68
69
|
critiqueEnabled: boolean
|
|
69
70
|
|
|
71
|
+
// Whether final session footers mention the thread creator.
|
|
72
|
+
// Changes: set once at startup from --skip-footer-mentions.
|
|
73
|
+
// Read by: ThreadSessionRuntime.emitFooter().
|
|
74
|
+
footerMentionsEnabled: boolean
|
|
75
|
+
|
|
70
76
|
// User-specified skill whitelist. When non-empty, only these skill names
|
|
71
77
|
// are injected into the model's system prompt (all others are hidden
|
|
72
78
|
// behind an opencode permission.skill deny-all rule). Mutually exclusive
|
|
@@ -96,6 +102,19 @@ export type KimakiState = {
|
|
|
96
102
|
// Read by: discord-utils.ts hasKimakiBotPermission().
|
|
97
103
|
allowAllUsers: boolean
|
|
98
104
|
|
|
105
|
+
// Hostname passed to `opencode serve --hostname`. Null means OpenCode's
|
|
106
|
+
// default (127.0.0.1). Set from --opencode-hostname. Kimaki still talks
|
|
107
|
+
// to 127.0.0.1 even when the server binds 0.0.0.0. Does not bind the
|
|
108
|
+
// Kimaki hrana/lock server.
|
|
109
|
+
// Changes: set once at startup from --opencode-hostname CLI flag.
|
|
110
|
+
// Read by: opencode.ts startSingleServer().
|
|
111
|
+
opencodeHostname: string | null
|
|
112
|
+
|
|
113
|
+
// Port passed to `opencode serve --port`. Null means pick a free port.
|
|
114
|
+
// Changes: set once at startup from --opencode-port CLI flag.
|
|
115
|
+
// Read by: opencode.ts startSingleServer().
|
|
116
|
+
opencodePort: number | null
|
|
117
|
+
|
|
99
118
|
// When true, the agent may only touch the session working directory and a
|
|
100
119
|
// small set of known-safe paths; anything else triggers an external_directory
|
|
101
120
|
// permission prompt. When false (default), every directory is allowed and the
|
|
@@ -185,10 +204,13 @@ export const store = createStore<KimakiState>(() => ({
|
|
|
185
204
|
defaultVerbosity: 'text_and_essential_tools',
|
|
186
205
|
defaultMentionMode: false,
|
|
187
206
|
critiqueEnabled: true,
|
|
207
|
+
footerMentionsEnabled: true,
|
|
188
208
|
enabledSkills: [],
|
|
189
209
|
disabledSkills: [],
|
|
190
210
|
allowedMentions: ['users'],
|
|
191
211
|
allowAllUsers: false,
|
|
212
|
+
opencodeHostname: null,
|
|
213
|
+
opencodePort: null,
|
|
192
214
|
restrictExternalDirectories: false,
|
|
193
215
|
permissionTimeoutMs: 10 * 60 * 1000,
|
|
194
216
|
useWorktrees: false,
|