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,12 @@ import { processImage } from './image-utils.js';
|
|
|
9
9
|
import { parsePatchFileCounts } from './patch-text-parser.js';
|
|
10
10
|
import { getDataDir } from './config.js';
|
|
11
11
|
const logger = createLogger(LogPrefix.FORMATTING);
|
|
12
|
+
export const TOOL_PREFIX = '┣ ';
|
|
13
|
+
export const FILE_EDIT_PREFIX = '◼︎ ';
|
|
14
|
+
export const THINKING_PREFIX = '┣ ';
|
|
15
|
+
export const STATUS_PREFIX = '⬦ ';
|
|
16
|
+
export const QUEUE_PREFIX = '» ';
|
|
17
|
+
export const WORKTREE_PREFIX = STATUS_PREFIX;
|
|
12
18
|
/**
|
|
13
19
|
* Serialize Discord embeds into plain text so the AI model can read them.
|
|
14
20
|
* Each embed becomes an <embed> XML block with title, author, description,
|
|
@@ -137,6 +143,103 @@ function escapeInlineMarkdown(text) {
|
|
|
137
143
|
function normalizeWhitespace(text) {
|
|
138
144
|
return text.replace(/[\r\n]+/g, ' ').replace(/\s+/g, ' ');
|
|
139
145
|
}
|
|
146
|
+
export function sessionPartKind(part) {
|
|
147
|
+
return part.type === 'text' ? 'text' : 'tool';
|
|
148
|
+
}
|
|
149
|
+
export function shouldLeadWithBlankLine({ previousKind, nextKind, }) {
|
|
150
|
+
if (!previousKind)
|
|
151
|
+
return false;
|
|
152
|
+
return previousKind !== nextKind;
|
|
153
|
+
}
|
|
154
|
+
export function sessionPartContent({ content, leadWithBlankLine, }) {
|
|
155
|
+
if (!leadWithBlankLine)
|
|
156
|
+
return content;
|
|
157
|
+
return `\n${content}`;
|
|
158
|
+
}
|
|
159
|
+
export function asDiscordQuote(text) {
|
|
160
|
+
const lead = text.startsWith('\n') ? '\n' : '';
|
|
161
|
+
const body = lead ? text.slice(1) : text;
|
|
162
|
+
return lead + body.split('\n').map((line) => {
|
|
163
|
+
if (line.startsWith('>'))
|
|
164
|
+
return line;
|
|
165
|
+
return `> ${line}`;
|
|
166
|
+
}).join('\n');
|
|
167
|
+
}
|
|
168
|
+
function isNonEmptyTextPart(part) {
|
|
169
|
+
return part.type === 'text' && Boolean(part.text?.trim());
|
|
170
|
+
}
|
|
171
|
+
function isRenderableTurnPart(part) {
|
|
172
|
+
return isNonEmptyTextPart(part) || part.type === 'tool';
|
|
173
|
+
}
|
|
174
|
+
function nextToolNameAfter({ parts, fromIndex, }) {
|
|
175
|
+
for (const part of parts.slice(fromIndex + 1)) {
|
|
176
|
+
if (isNonEmptyTextPart(part))
|
|
177
|
+
return undefined;
|
|
178
|
+
if (part.type === 'tool' && part.tool)
|
|
179
|
+
return part.tool;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
export function shouldQuoteIntermediateTextPart({ part, isLastInTurn, nextToolName, }) {
|
|
183
|
+
if (!isNonEmptyTextPart(part) || isLastInTurn)
|
|
184
|
+
return false;
|
|
185
|
+
const text = part.text ?? '';
|
|
186
|
+
if (text.includes('<callout'))
|
|
187
|
+
return false;
|
|
188
|
+
if (text.trim().split('\n').length > 2)
|
|
189
|
+
return false;
|
|
190
|
+
if (nextToolName === 'question'
|
|
191
|
+
|| nextToolName?.endsWith('kimaki_sleep')
|
|
192
|
+
|| nextToolName?.endsWith('kimaki_action_buttons')) {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
export function planAssistantTurnFlush({ parts, mode, throughPartId, }) {
|
|
198
|
+
const lastText = parts.filter(isNonEmptyTextPart).at(-1);
|
|
199
|
+
const lastTextIndex = lastText
|
|
200
|
+
? parts.findLastIndex((part) => part.id === lastText.id)
|
|
201
|
+
: -1;
|
|
202
|
+
const throughIndex = throughPartId
|
|
203
|
+
? parts.findIndex((part) => part.id === throughPartId)
|
|
204
|
+
: -1;
|
|
205
|
+
const sendUntil = (() => {
|
|
206
|
+
if (mode === 'final')
|
|
207
|
+
return parts.length;
|
|
208
|
+
if (mode === 'interactive') {
|
|
209
|
+
if (throughIndex >= 0)
|
|
210
|
+
return throughIndex + 1;
|
|
211
|
+
return parts.length;
|
|
212
|
+
}
|
|
213
|
+
if (!lastText)
|
|
214
|
+
return parts.length;
|
|
215
|
+
// Hold unfinished last text and anything after it so tool order stays intact.
|
|
216
|
+
if (!lastText.time?.end)
|
|
217
|
+
return lastTextIndex;
|
|
218
|
+
return parts.length;
|
|
219
|
+
})();
|
|
220
|
+
const sendParts = [];
|
|
221
|
+
const holdParts = [];
|
|
222
|
+
for (const [index, part] of parts.entries()) {
|
|
223
|
+
if (index < sendUntil) {
|
|
224
|
+
sendParts.push({
|
|
225
|
+
part,
|
|
226
|
+
quoteText: shouldQuoteIntermediateTextPart({
|
|
227
|
+
part,
|
|
228
|
+
isLastInTurn: mode !== 'progress'
|
|
229
|
+
&& parts.filter(isRenderableTurnPart).at(-1)?.id === part.id,
|
|
230
|
+
nextToolName: nextToolNameAfter({ parts, fromIndex: index }),
|
|
231
|
+
}),
|
|
232
|
+
});
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
holdParts.push(part);
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
send: sendParts.map((entry) => ({ id: entry.part.id, quoteText: entry.quoteText })),
|
|
239
|
+
hold: holdParts.map((part) => ({ id: part.id, quoteText: false })),
|
|
240
|
+
sendParts,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
140
243
|
/**
|
|
141
244
|
* Collect renderable assistant parts from session messages as SessionChunks.
|
|
142
245
|
* Each non-empty formatted part becomes one chunk. Caller can batch them
|
|
@@ -161,7 +264,19 @@ export function collectSessionChunks({ messages, skipPartIds, limit, }) {
|
|
|
161
264
|
if (!content.trim()) {
|
|
162
265
|
continue;
|
|
163
266
|
}
|
|
164
|
-
|
|
267
|
+
const quote = shouldQuoteIntermediateTextPart({
|
|
268
|
+
part,
|
|
269
|
+
isLastInTurn: false,
|
|
270
|
+
nextToolName: nextToolNameAfter({
|
|
271
|
+
parts: message.parts,
|
|
272
|
+
fromIndex: message.parts.indexOf(part),
|
|
273
|
+
}),
|
|
274
|
+
});
|
|
275
|
+
allChunks.push({
|
|
276
|
+
partIds: [part.id],
|
|
277
|
+
content: (quote ? asDiscordQuote(content) : content).trimEnd(),
|
|
278
|
+
kind: sessionPartKind(part),
|
|
279
|
+
});
|
|
165
280
|
}
|
|
166
281
|
}
|
|
167
282
|
if (limit !== undefined && allChunks.length > limit) {
|
|
@@ -180,19 +295,37 @@ export function batchChunksForDiscord(chunks) {
|
|
|
180
295
|
return [];
|
|
181
296
|
}
|
|
182
297
|
const batched = [];
|
|
183
|
-
let current = {
|
|
298
|
+
let current = {
|
|
299
|
+
partIds: [...chunks[0].partIds],
|
|
300
|
+
content: chunks[0].content,
|
|
301
|
+
kind: chunks[0].kind,
|
|
302
|
+
};
|
|
184
303
|
for (let i = 1; i < chunks.length; i++) {
|
|
185
304
|
const next = chunks[i];
|
|
305
|
+
if (next.kind !== current.kind) {
|
|
306
|
+
batched.push(current);
|
|
307
|
+
current = {
|
|
308
|
+
partIds: [...next.partIds],
|
|
309
|
+
content: next.content,
|
|
310
|
+
kind: next.kind,
|
|
311
|
+
};
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
186
314
|
const merged = current.content + '\n' + next.content;
|
|
187
315
|
if (merged.length <= DISCORD_BATCH_MAX_LENGTH) {
|
|
188
316
|
current = {
|
|
189
317
|
partIds: [...current.partIds, ...next.partIds],
|
|
190
318
|
content: merged,
|
|
319
|
+
kind: current.kind,
|
|
191
320
|
};
|
|
192
321
|
}
|
|
193
322
|
else {
|
|
194
323
|
batched.push(current);
|
|
195
|
-
current = {
|
|
324
|
+
current = {
|
|
325
|
+
partIds: [...next.partIds],
|
|
326
|
+
content: next.content,
|
|
327
|
+
kind: next.kind,
|
|
328
|
+
};
|
|
196
329
|
}
|
|
197
330
|
}
|
|
198
331
|
batched.push(current);
|
|
@@ -323,22 +456,12 @@ export async function getFileAttachments(message) {
|
|
|
323
456
|
}));
|
|
324
457
|
return results.filter((r) => r !== null);
|
|
325
458
|
}
|
|
326
|
-
const MAX_BASH_COMMAND_INLINE_LENGTH =
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
* 2. Long/multiline command with description → show description
|
|
331
|
-
* 3. Long/multiline command without description → truncate first line of command
|
|
332
|
-
*
|
|
333
|
-
* The description field was removed from the opencode v2 bash tool schema but
|
|
334
|
-
* kimaki's system prompt instructs models to always send it as an extra field.
|
|
335
|
-
* Case 3 is the fallback when a model omits it.
|
|
336
|
-
*/
|
|
337
|
-
export function formatBashToolTitle({ command, description, stateTitle, }) {
|
|
338
|
-
if (!command && !description && !stateTitle)
|
|
459
|
+
const MAX_BASH_COMMAND_INLINE_LENGTH = 50;
|
|
460
|
+
export function formatBashToolTitle({ command, description, summary, stateTitle, }) {
|
|
461
|
+
const label = description || summary;
|
|
462
|
+
if (!command && !label && !stateTitle)
|
|
339
463
|
return '';
|
|
340
464
|
const isSingleLine = !command.includes('\n');
|
|
341
|
-
// Find first non-empty line to handle commands with leading blank lines
|
|
342
465
|
const firstMeaningfulLine = command
|
|
343
466
|
.split('\n')
|
|
344
467
|
.find((line) => line.trim().length > 0)
|
|
@@ -346,8 +469,8 @@ export function formatBashToolTitle({ command, description, stateTitle, }) {
|
|
|
346
469
|
if (command && isSingleLine && command.length <= MAX_BASH_COMMAND_INLINE_LENGTH) {
|
|
347
470
|
return ` _${escapeInlineMarkdown(command)}_`;
|
|
348
471
|
}
|
|
349
|
-
if (
|
|
350
|
-
return ` _${escapeInlineMarkdown(
|
|
472
|
+
if (label) {
|
|
473
|
+
return ` _${escapeInlineMarkdown(label)}_`;
|
|
351
474
|
}
|
|
352
475
|
if (firstMeaningfulLine.length > 0) {
|
|
353
476
|
const needsTruncation = firstMeaningfulLine.length > MAX_BASH_COMMAND_INLINE_LENGTH;
|
|
@@ -444,6 +567,21 @@ export function getToolSummaryText(part) {
|
|
|
444
567
|
const prompt = part.state.input?.prompt || '';
|
|
445
568
|
return prompt ? `*${escapeInlineMarkdown(prompt.slice(0, 60))}*` : '';
|
|
446
569
|
}
|
|
570
|
+
// LESSON: tool parts are sent to Discord at `running` status (see
|
|
571
|
+
// shouldSendPart in thread-session-runtime), so `state.title` from the tool
|
|
572
|
+
// result is never rendered here. Everything shown must come from state.input.
|
|
573
|
+
// `until` is already absolute; `duration` is relative, so say "for 2h" —
|
|
574
|
+
// never "until 2h".
|
|
575
|
+
if (part.tool.endsWith('kimaki_sleep')) {
|
|
576
|
+
const until = part.state.input?.until || '';
|
|
577
|
+
const duration = part.state.input?.duration || '';
|
|
578
|
+
const reason = part.state.input?.reason || '';
|
|
579
|
+
const when = until ? `until ${until}` : duration ? `for ${duration}` : '';
|
|
580
|
+
const reasonText = reason ? `_${escapeInlineMarkdown(reason)}_` : '';
|
|
581
|
+
return [when && escapeInlineMarkdown(when), reasonText]
|
|
582
|
+
.filter(Boolean)
|
|
583
|
+
.join(' ');
|
|
584
|
+
}
|
|
447
585
|
if (!part.state.input)
|
|
448
586
|
return '';
|
|
449
587
|
const inputFields = Object.entries(part.state.input)
|
|
@@ -470,23 +608,16 @@ export function formatTodoList(part) {
|
|
|
470
608
|
const activeTodo = todos[activeIndex];
|
|
471
609
|
if (activeIndex === -1 || !activeTodo)
|
|
472
610
|
return '';
|
|
473
|
-
// digit-with-period ⒈-⒛ for 1-20, fallback to regular number for 21+
|
|
474
|
-
const digitWithPeriod = '⒈⒉⒊⒋⒌⒍⒎⒏⒐⒑⒒⒓⒔⒕⒖⒗⒘⒙⒚⒛';
|
|
475
611
|
const todoNumber = activeIndex + 1;
|
|
476
|
-
const num = todoNumber <= 20 ? digitWithPeriod[todoNumber - 1] : `${todoNumber}.`;
|
|
477
612
|
const content = activeTodo.content.charAt(0).toLowerCase() + activeTodo.content.slice(1);
|
|
478
|
-
return `${
|
|
613
|
+
return `${todoNumber}. **${escapeInlineMarkdown(content)}**`;
|
|
479
614
|
}
|
|
480
615
|
export function formatTaskToolTitle(part) {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
const childSessionId = part.state.metadata?.sessionId;
|
|
484
|
-
if (typeof childSessionId !== 'string' || !childSessionId)
|
|
616
|
+
// Running only. The child session can be created later when many tasks queue.
|
|
617
|
+
if (part.tool !== 'task' || part.state.status !== 'running')
|
|
485
618
|
return '';
|
|
486
619
|
const description = part.state.input?.description;
|
|
487
|
-
const stateTitle = part.state.
|
|
488
|
-
? part.state.title
|
|
489
|
-
: undefined;
|
|
620
|
+
const stateTitle = part.state.title;
|
|
490
621
|
const title = typeof description === 'string' && description
|
|
491
622
|
? description
|
|
492
623
|
: typeof stateTitle === 'string'
|
|
@@ -496,7 +627,7 @@ export function formatTaskToolTitle(part) {
|
|
|
496
627
|
return '';
|
|
497
628
|
const subagentType = part.state.input?.subagent_type;
|
|
498
629
|
const agent = typeof subagentType === 'string' ? subagentType : 'task';
|
|
499
|
-
return
|
|
630
|
+
return `${TOOL_PREFIX}${escapeInlineMarkdown(agent)} **${escapeInlineMarkdown(title)}**`;
|
|
500
631
|
}
|
|
501
632
|
export function formatPart(part, prefix) {
|
|
502
633
|
const pfx = prefix ? `${prefix} ⋅ ` : '';
|
|
@@ -504,24 +635,15 @@ export function formatPart(part, prefix) {
|
|
|
504
635
|
const text = part.text?.trim();
|
|
505
636
|
if (!text)
|
|
506
637
|
return '';
|
|
507
|
-
// For subtask text, always use bullet with prefix
|
|
508
638
|
if (prefix) {
|
|
509
|
-
return
|
|
639
|
+
return `${pfx}${text}`;
|
|
510
640
|
}
|
|
511
|
-
|
|
512
|
-
const markdownStarters = ['#', '*', '_', '-', '>', '`', '[', '|'];
|
|
513
|
-
const startsWithMarkdown = markdownStarters.includes(firstChar) ||
|
|
514
|
-
/^\d+\./.test(text) ||
|
|
515
|
-
/^<callout[\s>]/i.test(text);
|
|
516
|
-
if (startsWithMarkdown) {
|
|
517
|
-
return `\n${text}`;
|
|
518
|
-
}
|
|
519
|
-
return `⬥ ${text}`;
|
|
641
|
+
return text;
|
|
520
642
|
}
|
|
521
643
|
if (part.type === 'reasoning') {
|
|
522
644
|
if (!part.text?.trim())
|
|
523
645
|
return '';
|
|
524
|
-
return
|
|
646
|
+
return `${THINKING_PREFIX}${pfx}thinking`;
|
|
525
647
|
}
|
|
526
648
|
if (part.type === 'file') {
|
|
527
649
|
return prefix
|
|
@@ -530,19 +652,20 @@ export function formatPart(part, prefix) {
|
|
|
530
652
|
}
|
|
531
653
|
if (part.type === 'step-start' ||
|
|
532
654
|
part.type === 'step-finish' ||
|
|
533
|
-
part.type === 'patch'
|
|
655
|
+
part.type === 'patch' ||
|
|
656
|
+
part.type === 'compaction') {
|
|
534
657
|
return '';
|
|
535
658
|
}
|
|
536
659
|
if (part.type === 'agent') {
|
|
537
|
-
return
|
|
660
|
+
return `${TOOL_PREFIX}${pfx}agent ${part.id}`;
|
|
538
661
|
}
|
|
539
662
|
if (part.type === 'snapshot') {
|
|
540
|
-
return
|
|
663
|
+
return `${TOOL_PREFIX}${pfx}snapshot ${part.snapshot}`;
|
|
541
664
|
}
|
|
542
665
|
if (part.type === 'tool') {
|
|
543
666
|
if (part.tool === 'todowrite') {
|
|
544
667
|
const formatted = formatTodoList(part);
|
|
545
|
-
return prefix && formatted ?
|
|
668
|
+
return prefix && formatted ? `${TOOL_PREFIX}${pfx}${formatted}` : formatted;
|
|
546
669
|
}
|
|
547
670
|
// Question tool is handled via Discord dropdowns, not text
|
|
548
671
|
if (part.tool === 'question') {
|
|
@@ -566,11 +689,13 @@ export function formatPart(part, prefix) {
|
|
|
566
689
|
}
|
|
567
690
|
const command = part.state.input?.command || '';
|
|
568
691
|
const description = part.state.input?.description || '';
|
|
692
|
+
const summary = part.state.input?.summary || '';
|
|
569
693
|
const toolTitle = formatBashToolTitle({
|
|
570
694
|
command,
|
|
571
695
|
description,
|
|
696
|
+
summary,
|
|
572
697
|
});
|
|
573
|
-
return
|
|
698
|
+
return `${TOOL_PREFIX}${pfx}bash${toolTitle}`;
|
|
574
699
|
}
|
|
575
700
|
const summaryText = getToolSummaryText(part);
|
|
576
701
|
const stateTitle = 'title' in part.state ? part.state.title : undefined;
|
|
@@ -581,9 +706,11 @@ export function formatPart(part, prefix) {
|
|
|
581
706
|
else if (part.tool === 'bash') {
|
|
582
707
|
const command = part.state.input?.command || '';
|
|
583
708
|
const description = part.state.input?.description || '';
|
|
709
|
+
const summary = part.state.input?.summary || '';
|
|
584
710
|
const formatted = formatBashToolTitle({
|
|
585
711
|
command,
|
|
586
712
|
description,
|
|
713
|
+
summary,
|
|
587
714
|
stateTitle,
|
|
588
715
|
});
|
|
589
716
|
toolTitle = formatted.startsWith(' ') ? formatted.slice(1) : formatted;
|
|
@@ -598,14 +725,17 @@ export function formatPart(part, prefix) {
|
|
|
598
725
|
if (part.tool === 'edit' ||
|
|
599
726
|
part.tool === 'write' ||
|
|
600
727
|
part.tool === 'apply_patch') {
|
|
601
|
-
return
|
|
728
|
+
return FILE_EDIT_PREFIX;
|
|
602
729
|
}
|
|
603
|
-
return
|
|
730
|
+
return TOOL_PREFIX;
|
|
604
731
|
})();
|
|
605
732
|
const toolParts = [part.tool, toolTitle, summaryText]
|
|
606
733
|
.filter(Boolean)
|
|
607
734
|
.join(' ');
|
|
608
|
-
|
|
735
|
+
if (icon === '⨯') {
|
|
736
|
+
return `${icon} ${pfx}${toolParts}`;
|
|
737
|
+
}
|
|
738
|
+
return `${icon}${pfx}${toolParts}`;
|
|
609
739
|
}
|
|
610
740
|
logger.warn('Unknown part type:', part);
|
|
611
741
|
return '';
|