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
|
@@ -9,6 +9,50 @@ export function getEventBufferSessionId(event) {
|
|
|
9
9
|
}
|
|
10
10
|
return getOpencodeEventSessionId(event);
|
|
11
11
|
}
|
|
12
|
+
function isCompactionContinuePart(part) {
|
|
13
|
+
if (part.type !== 'text') {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
return part.synthetic === true && part.metadata?.compaction_continue === true;
|
|
17
|
+
}
|
|
18
|
+
function isInternalOpenCodeUserMessageId({ events, sessionId, messageId, upToIndex, }) {
|
|
19
|
+
const end = upToIndex ?? events.length - 1;
|
|
20
|
+
for (let i = 0; i <= end; i++) {
|
|
21
|
+
const event = events[i]?.event;
|
|
22
|
+
if (event?.type !== 'message.part.updated') {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const part = event.properties.part;
|
|
26
|
+
if (part.sessionID !== sessionId || part.messageID !== messageId) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (part.type === 'compaction') {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
if (isCompactionContinuePart(part)) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
function isUserFacingAssistantMessage(message) {
|
|
39
|
+
return message.summary !== true;
|
|
40
|
+
}
|
|
41
|
+
export function getAssistantMessageKind({ events, sessionId, messageId, upToIndex, }) {
|
|
42
|
+
const end = upToIndex ?? events.length - 1;
|
|
43
|
+
for (let i = end; i >= 0; i--) {
|
|
44
|
+
const event = events[i]?.event;
|
|
45
|
+
if (event?.type !== 'message.updated') {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const info = event.properties.info;
|
|
49
|
+
if (info.sessionID !== sessionId || info.role !== 'assistant' || info.id !== messageId) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
return info.summary === true ? 'summary' : 'user-facing';
|
|
53
|
+
}
|
|
54
|
+
return 'unknown';
|
|
55
|
+
}
|
|
12
56
|
function getTaskChildSessionId({ part, }) {
|
|
13
57
|
// Event-shape reference:
|
|
14
58
|
// - cli/src/session-handler/event-stream-fixtures/real-session-task-three-parallel-sleeps.jsonl
|
|
@@ -93,6 +137,78 @@ export function didQuestionQueueHandoffSinceLatestQuestionAsked({ events, sessio
|
|
|
93
137
|
}
|
|
94
138
|
return false;
|
|
95
139
|
}
|
|
140
|
+
// OpenCode emits question.asked when the tool starts, often before the
|
|
141
|
+
// preceding text part gets time.end. Discord must wait for that end event
|
|
142
|
+
// or the question UI posts first and the text dumps later.
|
|
143
|
+
export function isAssistantTextReadyForQuestion({ events, sessionId, messageId, upToIndex, }) {
|
|
144
|
+
const end = upToIndex ?? events.length - 1;
|
|
145
|
+
for (let i = end; i >= 0; i--) {
|
|
146
|
+
const event = events[i]?.event;
|
|
147
|
+
if (!event || event.type !== 'message.part.updated') {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const part = event.properties.part;
|
|
151
|
+
if (part.sessionID !== sessionId) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (part.messageID !== messageId) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (part.type !== 'text') {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
return Boolean(part.time?.end);
|
|
161
|
+
}
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
export function deriveLatestUnansweredQuestion({ events, sessionId, upToIndex, }) {
|
|
165
|
+
const end = upToIndex ?? events.length - 1;
|
|
166
|
+
for (let i = end; i >= 0; i--) {
|
|
167
|
+
const entry = events[i];
|
|
168
|
+
if (!entry) {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const event = entry.event;
|
|
172
|
+
if (getEventBufferSessionId(event) !== sessionId) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (event.type === 'question.replied' || event.type === 'question.rejected') {
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
if (event.type === 'message.part.updated') {
|
|
179
|
+
const part = event.properties.part;
|
|
180
|
+
if (part.type === 'tool'
|
|
181
|
+
&& part.tool === 'question'
|
|
182
|
+
&& (part.state.status === 'error' || part.state.status === 'completed')) {
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (event.type === 'question.asked') {
|
|
187
|
+
const messageId = event.properties.tool?.messageID;
|
|
188
|
+
const latestUserMessage = getLatestUserMessage({
|
|
189
|
+
events,
|
|
190
|
+
sessionId,
|
|
191
|
+
upToIndex: end,
|
|
192
|
+
});
|
|
193
|
+
if (messageId
|
|
194
|
+
&& latestUserMessage
|
|
195
|
+
&& !isAssistantMessageInLatestUserTurn({
|
|
196
|
+
events,
|
|
197
|
+
sessionId,
|
|
198
|
+
messageId,
|
|
199
|
+
upToIndex: end,
|
|
200
|
+
})) {
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
id: event.properties.id,
|
|
205
|
+
questions: event.properties.questions,
|
|
206
|
+
tool: event.properties.tool,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
96
212
|
export function derivePendingPermissionRequests({ events, sessionId, }) {
|
|
97
213
|
const permissions = new Set();
|
|
98
214
|
for (const entry of events) {
|
|
@@ -112,6 +228,9 @@ export function derivePendingPermissionRequests({ events, sessionId, }) {
|
|
|
112
228
|
return [...permissions];
|
|
113
229
|
}
|
|
114
230
|
export function isAssistantMessageNaturalCompletion({ message, }) {
|
|
231
|
+
if (!isUserFacingAssistantMessage(message)) {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
115
234
|
if (typeof message.time.completed !== 'number') {
|
|
116
235
|
return false;
|
|
117
236
|
}
|
|
@@ -162,6 +281,14 @@ export function getLatestUserMessage({ events, sessionId, upToIndex, }) {
|
|
|
162
281
|
if (info.sessionID !== sessionId || info.role !== 'user') {
|
|
163
282
|
continue;
|
|
164
283
|
}
|
|
284
|
+
if (isInternalOpenCodeUserMessageId({
|
|
285
|
+
events,
|
|
286
|
+
sessionId,
|
|
287
|
+
messageId: info.id,
|
|
288
|
+
upToIndex: end,
|
|
289
|
+
})) {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
165
292
|
if (!latestUserMessage) {
|
|
166
293
|
latestUserMessage = info;
|
|
167
294
|
continue;
|
|
@@ -199,62 +326,118 @@ function emptyTurnTokenUsage() {
|
|
|
199
326
|
userMessageId: undefined,
|
|
200
327
|
};
|
|
201
328
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
329
|
+
function addAssistantTokens({ usage, message, }) {
|
|
330
|
+
if (message.tokens) {
|
|
331
|
+
usage.input += message.tokens.input;
|
|
332
|
+
usage.output += message.tokens.output;
|
|
333
|
+
usage.reasoning += message.tokens.reasoning;
|
|
334
|
+
usage.cacheRead += message.tokens.cache.read;
|
|
335
|
+
usage.cacheWrite += message.tokens.cache.write;
|
|
336
|
+
usage.total += message.tokens.total ?? getTokenTotal(message.tokens);
|
|
337
|
+
}
|
|
338
|
+
usage.cost += message.cost;
|
|
339
|
+
usage.model = message.modelID;
|
|
340
|
+
usage.providerID = message.providerID;
|
|
341
|
+
}
|
|
342
|
+
function sumAssistantMessages({ messages, userMessageId, }) {
|
|
343
|
+
if (messages.size === 0) {
|
|
344
|
+
return {
|
|
345
|
+
...emptyTurnTokenUsage(),
|
|
346
|
+
userMessageId,
|
|
347
|
+
};
|
|
214
348
|
}
|
|
215
|
-
const
|
|
349
|
+
const usage = emptyTurnTokenUsage();
|
|
350
|
+
usage.userMessageId = userMessageId;
|
|
351
|
+
usage.assistantMessageCount = messages.size;
|
|
352
|
+
for (const message of messages.values()) {
|
|
353
|
+
addAssistantTokens({ usage, message });
|
|
354
|
+
}
|
|
355
|
+
return usage;
|
|
356
|
+
}
|
|
357
|
+
function collectAssistantMessages({ events, sessionId, upToIndex, parentID, }) {
|
|
216
358
|
const latestByMessageId = new Map();
|
|
217
|
-
for (let i = 0; i <=
|
|
218
|
-
const
|
|
219
|
-
if (
|
|
220
|
-
continue;
|
|
221
|
-
}
|
|
222
|
-
const event = entry.event;
|
|
223
|
-
if (event.type !== 'message.updated') {
|
|
359
|
+
for (let i = 0; i <= upToIndex; i++) {
|
|
360
|
+
const event = events[i]?.event;
|
|
361
|
+
if (event?.type !== 'message.updated') {
|
|
224
362
|
continue;
|
|
225
363
|
}
|
|
226
364
|
const info = event.properties.info;
|
|
227
365
|
if (info.sessionID !== sessionId || info.role !== 'assistant') {
|
|
228
366
|
continue;
|
|
229
367
|
}
|
|
230
|
-
if (info.parentID !==
|
|
368
|
+
if (parentID && info.parentID !== parentID) {
|
|
231
369
|
continue;
|
|
232
370
|
}
|
|
233
371
|
latestByMessageId.set(info.id, info);
|
|
234
372
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
373
|
+
return latestByMessageId;
|
|
374
|
+
}
|
|
375
|
+
function getSessionInfoTokenUsage({ events, sessionId, upToIndex, }) {
|
|
376
|
+
for (let i = upToIndex; i >= 0; i--) {
|
|
377
|
+
const event = events[i]?.event;
|
|
378
|
+
if (event?.type !== 'session.updated' && event?.type !== 'session.created') {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
const info = event.properties.info;
|
|
382
|
+
if (info.id !== sessionId) {
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
if (!info.tokens) {
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
const usage = emptyTurnTokenUsage();
|
|
389
|
+
usage.input = info.tokens.input;
|
|
390
|
+
usage.output = info.tokens.output;
|
|
391
|
+
usage.reasoning = info.tokens.reasoning;
|
|
392
|
+
usage.cacheRead = info.tokens.cache.read;
|
|
393
|
+
usage.cacheWrite = info.tokens.cache.write;
|
|
394
|
+
usage.total = getTokenTotal(info.tokens);
|
|
395
|
+
usage.cost = info.cost ?? 0;
|
|
396
|
+
usage.model = info.model?.id;
|
|
397
|
+
usage.providerID = info.model?.providerID;
|
|
398
|
+
return usage.total > 0 || usage.cost > 0 ? usage : undefined;
|
|
240
399
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
400
|
+
return undefined;
|
|
401
|
+
}
|
|
402
|
+
// Latest billed token snapshot for the current user turn.
|
|
403
|
+
// Sums the last message.updated tokens per assistant message id so streaming
|
|
404
|
+
// updates are not double-counted. Scoped to sessionId so subagent idles
|
|
405
|
+
// report their own usage. Child task sessions often have no user message in
|
|
406
|
+
// the buffer; fall back to all assistant messages, then Session.tokens on
|
|
407
|
+
// session.updated (OpenCode projects per-session usage there).
|
|
408
|
+
export function getLatestTurnTokenUsage({ events, sessionId, upToIndex, }) {
|
|
409
|
+
const end = upToIndex ?? events.length - 1;
|
|
410
|
+
const latestUserMessage = getLatestUserMessage({
|
|
411
|
+
events,
|
|
412
|
+
sessionId,
|
|
413
|
+
upToIndex,
|
|
414
|
+
});
|
|
415
|
+
if (latestUserMessage) {
|
|
416
|
+
return sumAssistantMessages({
|
|
417
|
+
messages: collectAssistantMessages({
|
|
418
|
+
events,
|
|
419
|
+
sessionId,
|
|
420
|
+
upToIndex: end,
|
|
421
|
+
parentID: latestUserMessage.id,
|
|
422
|
+
}),
|
|
423
|
+
userMessageId: latestUserMessage.id,
|
|
424
|
+
});
|
|
256
425
|
}
|
|
257
|
-
|
|
426
|
+
const sessionAssistants = sumAssistantMessages({
|
|
427
|
+
messages: collectAssistantMessages({
|
|
428
|
+
events,
|
|
429
|
+
sessionId,
|
|
430
|
+
upToIndex: end,
|
|
431
|
+
}),
|
|
432
|
+
});
|
|
433
|
+
if (sessionAssistants.total > 0 || sessionAssistants.assistantMessageCount > 0) {
|
|
434
|
+
return sessionAssistants;
|
|
435
|
+
}
|
|
436
|
+
return getSessionInfoTokenUsage({
|
|
437
|
+
events,
|
|
438
|
+
sessionId,
|
|
439
|
+
upToIndex: end,
|
|
440
|
+
}) ?? emptyTurnTokenUsage();
|
|
258
441
|
}
|
|
259
442
|
function findFirstUserMessageIndex({ events, userMessageId, upToIndex, }) {
|
|
260
443
|
for (let i = 0; i <= upToIndex; i++) {
|
|
@@ -292,24 +475,44 @@ function subtractTokenUsage({ current, previous, }) {
|
|
|
292
475
|
userMessageId: current.userMessageId,
|
|
293
476
|
};
|
|
294
477
|
}
|
|
478
|
+
function findFirstSessionEventIndex({ events, sessionId, upToIndex, }) {
|
|
479
|
+
for (let i = 0; i <= upToIndex; i++) {
|
|
480
|
+
const event = events[i]?.event;
|
|
481
|
+
if (!event) {
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
if (getEventBufferSessionId(event) === sessionId) {
|
|
485
|
+
return i;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return 0;
|
|
489
|
+
}
|
|
295
490
|
// Tokens billed since the previous session.idle in this user turn.
|
|
296
491
|
// Survives process restart because both idles stay in the event buffer.
|
|
492
|
+
// Child task sessions may have no user message.updated; scope from the first
|
|
493
|
+
// event for that sessionId instead so their tokens still emit.
|
|
297
494
|
export function getIdleTokenUsageDelta({ events, sessionId, idleEventIndex, }) {
|
|
298
495
|
const current = getLatestTurnTokenUsage({
|
|
299
496
|
events,
|
|
300
497
|
sessionId,
|
|
301
498
|
upToIndex: idleEventIndex,
|
|
302
499
|
});
|
|
303
|
-
if (
|
|
500
|
+
if (current.total <= 0) {
|
|
304
501
|
return undefined;
|
|
305
502
|
}
|
|
306
|
-
const firstUserMessageIndex =
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
503
|
+
const firstUserMessageIndex = current.userMessageId
|
|
504
|
+
? findFirstUserMessageIndex({
|
|
505
|
+
events,
|
|
506
|
+
userMessageId: current.userMessageId,
|
|
507
|
+
upToIndex: idleEventIndex,
|
|
508
|
+
})
|
|
509
|
+
: findFirstSessionEventIndex({
|
|
510
|
+
events,
|
|
511
|
+
sessionId,
|
|
512
|
+
upToIndex: idleEventIndex,
|
|
513
|
+
});
|
|
311
514
|
if (firstUserMessageIndex === undefined) {
|
|
312
|
-
return
|
|
515
|
+
return current;
|
|
313
516
|
}
|
|
314
517
|
const previousIdleIndex = findPreviousIdleIndexInTurn({
|
|
315
518
|
events,
|
|
@@ -318,7 +521,7 @@ export function getIdleTokenUsageDelta({ events, sessionId, idleEventIndex, }) {
|
|
|
318
521
|
beforeIndex: idleEventIndex,
|
|
319
522
|
});
|
|
320
523
|
if (previousIdleIndex === undefined) {
|
|
321
|
-
return current
|
|
524
|
+
return current;
|
|
322
525
|
}
|
|
323
526
|
const previous = getLatestTurnTokenUsage({
|
|
324
527
|
events,
|
|
@@ -354,6 +557,9 @@ export function getLatestRunInfo({ events, sessionId, upToIndex, }) {
|
|
|
354
557
|
if (msg.sessionID !== sessionId || msg.role !== 'assistant') {
|
|
355
558
|
continue;
|
|
356
559
|
}
|
|
560
|
+
if (!isUserFacingAssistantMessage(msg)) {
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
357
563
|
return {
|
|
358
564
|
model: msg.modelID,
|
|
359
565
|
providerID: msg.providerID,
|
|
@@ -389,12 +595,35 @@ export function getAssistantMessageIdsForLatestUserTurn({ events, sessionId, upT
|
|
|
389
595
|
if (msg.sessionID !== sessionId || msg.role !== 'assistant') {
|
|
390
596
|
continue;
|
|
391
597
|
}
|
|
598
|
+
if (!isUserFacingAssistantMessage(msg)) {
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
392
601
|
if (msg.parentID === latestUserMessage.id) {
|
|
393
602
|
assistantMessageIds.add(msg.id);
|
|
394
603
|
}
|
|
395
604
|
}
|
|
396
605
|
return assistantMessageIds;
|
|
397
606
|
}
|
|
607
|
+
export function didLatestUserTurnUseSleepTool({ events, sessionId, upToIndex, }) {
|
|
608
|
+
const assistantMessageIds = getAssistantMessageIdsForLatestUserTurn({
|
|
609
|
+
events,
|
|
610
|
+
sessionId,
|
|
611
|
+
upToIndex,
|
|
612
|
+
});
|
|
613
|
+
const end = upToIndex ?? events.length - 1;
|
|
614
|
+
for (let i = end; i >= 0; i--) {
|
|
615
|
+
const event = events[i]?.event;
|
|
616
|
+
if (event?.type !== 'message.part.updated')
|
|
617
|
+
continue;
|
|
618
|
+
const part = event.properties.part;
|
|
619
|
+
if (part.sessionID !== sessionId || part.type !== 'tool')
|
|
620
|
+
continue;
|
|
621
|
+
if (part.tool === 'kimaki_sleep' && assistantMessageIds.has(part.messageID)) {
|
|
622
|
+
return true;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
return false;
|
|
626
|
+
}
|
|
398
627
|
export function getLatestAssistantMessageIdForLatestUserTurn({ events, sessionId, upToIndex, }) {
|
|
399
628
|
const latestUserMessage = getLatestUserMessage({
|
|
400
629
|
events,
|
|
@@ -419,6 +648,9 @@ export function getLatestAssistantMessageIdForLatestUserTurn({ events, sessionId
|
|
|
419
648
|
if (info.sessionID !== sessionId || info.role !== 'assistant') {
|
|
420
649
|
continue;
|
|
421
650
|
}
|
|
651
|
+
if (!isUserFacingAssistantMessage(info)) {
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
422
654
|
if (info.parentID !== latestUserMessage.id) {
|
|
423
655
|
continue;
|
|
424
656
|
}
|
|
@@ -549,6 +781,14 @@ export function isAssistantMessageInLatestUserTurn({ events, sessionId, messageI
|
|
|
549
781
|
});
|
|
550
782
|
return assistantMessageIds.has(messageId);
|
|
551
783
|
}
|
|
784
|
+
export function isSummaryAssistantMessage({ events, sessionId, messageId, upToIndex, }) {
|
|
785
|
+
return getAssistantMessageKind({
|
|
786
|
+
events,
|
|
787
|
+
sessionId,
|
|
788
|
+
messageId,
|
|
789
|
+
upToIndex,
|
|
790
|
+
}) === 'summary';
|
|
791
|
+
}
|
|
552
792
|
// Returns a stable 1-based subtask index for candidateSessionId.
|
|
553
793
|
// Indexing scope is the parent assistant message that spawned the task tool calls,
|
|
554
794
|
// so numbering restarts at 1 for each assistant message.
|
|
@@ -640,3 +880,104 @@ export function getDerivedSubagentSessions({ events, mainSessionId, upToIndex, }
|
|
|
640
880
|
}
|
|
641
881
|
return sessions;
|
|
642
882
|
}
|
|
883
|
+
function getParentIdFromSessionEvent(event) {
|
|
884
|
+
if (event.type !== 'session.created' && event.type !== 'session.updated') {
|
|
885
|
+
return undefined;
|
|
886
|
+
}
|
|
887
|
+
const parentID = event.properties.info.parentID;
|
|
888
|
+
if (typeof parentID !== 'string' || parentID.length === 0) {
|
|
889
|
+
return undefined;
|
|
890
|
+
}
|
|
891
|
+
return {
|
|
892
|
+
sessionId: event.properties.info.id,
|
|
893
|
+
parentID,
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
// Global SSE is broadcast to every thread. Buffer only this thread's session
|
|
897
|
+
// plus its task/subagent children. /btw fork clones history onto a new
|
|
898
|
+
// sessionId; those events must not evict the parent's latest-turn buffer.
|
|
899
|
+
export function shouldBufferSessionEvent({ event, mainSessionId, isKnownChildSession, }) {
|
|
900
|
+
if (event.type === 'session.diff' || event.type.endsWith('.delta')) {
|
|
901
|
+
return false;
|
|
902
|
+
}
|
|
903
|
+
if (event.type === 'tui.toast.show') {
|
|
904
|
+
return true;
|
|
905
|
+
}
|
|
906
|
+
const eventSessionId = getEventBufferSessionId(event);
|
|
907
|
+
if (!eventSessionId) {
|
|
908
|
+
return true;
|
|
909
|
+
}
|
|
910
|
+
// A new /btw thread registers the SSE listener before ensureSession.
|
|
911
|
+
// Until the child session id is bound, drop every scoped event so the
|
|
912
|
+
// parent clone flood cannot fill this buffer first.
|
|
913
|
+
if (!mainSessionId) {
|
|
914
|
+
return false;
|
|
915
|
+
}
|
|
916
|
+
if (eventSessionId === mainSessionId) {
|
|
917
|
+
return true;
|
|
918
|
+
}
|
|
919
|
+
if (isKnownChildSession(eventSessionId)) {
|
|
920
|
+
return true;
|
|
921
|
+
}
|
|
922
|
+
const parented = getParentIdFromSessionEvent(event);
|
|
923
|
+
if (!parented) {
|
|
924
|
+
return false;
|
|
925
|
+
}
|
|
926
|
+
return parented.parentID === mainSessionId || isKnownChildSession(parented.parentID);
|
|
927
|
+
}
|
|
928
|
+
// Child sessions of the main thread: task tool metadata.sessionId, plus
|
|
929
|
+
// session.created/updated parentID (available before task metadata lands).
|
|
930
|
+
export function getDerivedChildSessionIds({ events, mainSessionId, upToIndex, }) {
|
|
931
|
+
const end = upToIndex ?? events.length - 1;
|
|
932
|
+
const ids = new Set();
|
|
933
|
+
for (const session of getDerivedSubagentSessions({
|
|
934
|
+
events,
|
|
935
|
+
mainSessionId,
|
|
936
|
+
upToIndex,
|
|
937
|
+
})) {
|
|
938
|
+
ids.add(session.childSessionId);
|
|
939
|
+
}
|
|
940
|
+
let grew = true;
|
|
941
|
+
while (grew) {
|
|
942
|
+
grew = false;
|
|
943
|
+
for (let i = 0; i <= end; i++) {
|
|
944
|
+
const event = events[i]?.event;
|
|
945
|
+
if (!event) {
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
948
|
+
const parented = getParentIdFromSessionEvent(event);
|
|
949
|
+
if (!parented || ids.has(parented.sessionId)) {
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
if (parented.parentID !== mainSessionId && !ids.has(parented.parentID)) {
|
|
953
|
+
continue;
|
|
954
|
+
}
|
|
955
|
+
ids.add(parented.sessionId);
|
|
956
|
+
grew = true;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
return ids;
|
|
960
|
+
}
|
|
961
|
+
export function isDerivedChildSession({ events, mainSessionId, candidateSessionId, upToIndex, }) {
|
|
962
|
+
if (candidateSessionId === mainSessionId) {
|
|
963
|
+
return false;
|
|
964
|
+
}
|
|
965
|
+
return getDerivedChildSessionIds({
|
|
966
|
+
events,
|
|
967
|
+
mainSessionId,
|
|
968
|
+
upToIndex,
|
|
969
|
+
}).has(candidateSessionId);
|
|
970
|
+
}
|
|
971
|
+
export function getTokenUsageSessionIdsForIdle({ events, mainSessionId, idleSessionId, upToIndex, }) {
|
|
972
|
+
if (idleSessionId !== mainSessionId) {
|
|
973
|
+
return [idleSessionId];
|
|
974
|
+
}
|
|
975
|
+
return [
|
|
976
|
+
mainSessionId,
|
|
977
|
+
...getDerivedChildSessionIds({
|
|
978
|
+
events,
|
|
979
|
+
mainSessionId,
|
|
980
|
+
upToIndex,
|
|
981
|
+
}),
|
|
982
|
+
];
|
|
983
|
+
}
|