@otto-assistant/bridge 0.4.92 → 0.4.96
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 +2 -1
- package/dist/anthropic-auth-plugin.js +30 -9
- package/dist/anthropic-auth-plugin.test.js +7 -1
- package/dist/anthropic-auth-state.js +17 -1
- package/dist/cli.js +2 -3
- package/dist/commands/agent.js +2 -2
- package/dist/commands/merge-worktree.js +11 -8
- package/dist/context-awareness-plugin.js +1 -1
- package/dist/context-awareness-plugin.test.js +2 -2
- package/dist/discord-utils.js +5 -2
- package/dist/kimaki-opencode-plugin.js +1 -0
- package/dist/logger.js +8 -2
- package/dist/session-handler/thread-session-runtime.js +20 -3
- package/dist/system-message.js +13 -0
- package/dist/system-message.test.js +13 -0
- package/dist/system-prompt-drift-plugin.js +251 -0
- package/dist/utils.js +5 -1
- package/package.json +2 -1
- package/skills/npm-package/SKILL.md +14 -9
- package/src/agent-model.e2e.test.ts +2 -1
- package/src/anthropic-auth-plugin.test.ts +7 -1
- package/src/anthropic-auth-plugin.ts +29 -9
- package/src/anthropic-auth-state.ts +28 -2
- package/src/cli.ts +2 -2
- package/src/commands/agent.ts +2 -2
- package/src/commands/merge-worktree.ts +11 -8
- package/src/context-awareness-plugin.test.ts +2 -2
- package/src/context-awareness-plugin.ts +1 -1
- package/src/discord-utils.ts +19 -17
- package/src/kimaki-opencode-plugin.ts +1 -0
- package/src/logger.ts +9 -2
- package/src/session-handler/thread-session-runtime.ts +23 -3
- package/src/system-message.test.ts +13 -0
- package/src/system-message.ts +13 -0
- package/src/system-prompt-drift-plugin.ts +379 -0
- package/src/utils.ts +5 -1
|
@@ -735,7 +735,8 @@ describe('agent model resolution', () => {
|
|
|
735
735
|
--- from: assistant (TestBot)
|
|
736
736
|
⬥ ok
|
|
737
737
|
*project ⋅ main ⋅ Ns ⋅ N% ⋅ agent-model-v2 ⋅ **test-agent***
|
|
738
|
-
Switched to **plan** agent for this session
|
|
738
|
+
Switched to **plan** agent for this session (was **test-agent**)
|
|
739
|
+
The agent will change on the next message.
|
|
739
740
|
--- from: user (agent-model-tester)
|
|
740
741
|
Reply with exactly: after-switch-msg
|
|
741
742
|
--- from: assistant (TestBot)
|
|
@@ -430,15 +430,25 @@ function buildAuthorizeHandler(mode) {
|
|
|
430
430
|
function toClaudeCodeToolName(name) {
|
|
431
431
|
return OPENCODE_TO_CLAUDE_CODE_TOOL_NAME[name.toLowerCase()] ?? name;
|
|
432
432
|
}
|
|
433
|
-
function sanitizeSystemText(text) {
|
|
434
|
-
|
|
433
|
+
function sanitizeSystemText(text, onError) {
|
|
434
|
+
const startIdx = text.indexOf(OPENCODE_IDENTITY);
|
|
435
|
+
if (startIdx === -1)
|
|
436
|
+
return text;
|
|
437
|
+
const codeRefsMarker = '# Code References';
|
|
438
|
+
const endIdx = text.indexOf(codeRefsMarker, startIdx);
|
|
439
|
+
if (endIdx === -1) {
|
|
440
|
+
onError?.(`sanitizeSystemText: could not find '# Code References' after OpenCode identity`);
|
|
441
|
+
return text;
|
|
442
|
+
}
|
|
443
|
+
// Remove everything from the OpenCode identity up to (but not including) '# Code References'
|
|
444
|
+
return text.slice(0, startIdx) + text.slice(endIdx);
|
|
435
445
|
}
|
|
436
|
-
function prependClaudeCodeIdentity(system) {
|
|
446
|
+
function prependClaudeCodeIdentity(system, onError) {
|
|
437
447
|
const identityBlock = { type: 'text', text: CLAUDE_CODE_IDENTITY };
|
|
438
448
|
if (typeof system === 'undefined')
|
|
439
449
|
return [identityBlock];
|
|
440
450
|
if (typeof system === 'string') {
|
|
441
|
-
const sanitized = sanitizeSystemText(system);
|
|
451
|
+
const sanitized = sanitizeSystemText(system, onError);
|
|
442
452
|
if (sanitized === CLAUDE_CODE_IDENTITY)
|
|
443
453
|
return [identityBlock];
|
|
444
454
|
return [identityBlock, { type: 'text', text: sanitized }];
|
|
@@ -447,11 +457,11 @@ function prependClaudeCodeIdentity(system) {
|
|
|
447
457
|
return [identityBlock, system];
|
|
448
458
|
const sanitized = system.map((item) => {
|
|
449
459
|
if (typeof item === 'string')
|
|
450
|
-
return { type: 'text', text: sanitizeSystemText(item) };
|
|
460
|
+
return { type: 'text', text: sanitizeSystemText(item, onError) };
|
|
451
461
|
if (item && typeof item === 'object' && item.type === 'text') {
|
|
452
462
|
const text = item.text;
|
|
453
463
|
if (typeof text === 'string') {
|
|
454
|
-
return { ...item, text: sanitizeSystemText(text) };
|
|
464
|
+
return { ...item, text: sanitizeSystemText(text, onError) };
|
|
455
465
|
}
|
|
456
466
|
}
|
|
457
467
|
return item;
|
|
@@ -465,7 +475,7 @@ function prependClaudeCodeIdentity(system) {
|
|
|
465
475
|
}
|
|
466
476
|
return [identityBlock, ...sanitized];
|
|
467
477
|
}
|
|
468
|
-
function rewriteRequestPayload(body) {
|
|
478
|
+
function rewriteRequestPayload(body, onError) {
|
|
469
479
|
if (!body)
|
|
470
480
|
return { body, modelId: undefined, reverseToolNameMap: new Map() };
|
|
471
481
|
try {
|
|
@@ -486,7 +496,7 @@ function rewriteRequestPayload(body) {
|
|
|
486
496
|
});
|
|
487
497
|
}
|
|
488
498
|
// Rename system prompt
|
|
489
|
-
payload.system = prependClaudeCodeIdentity(payload.system);
|
|
499
|
+
payload.system = prependClaudeCodeIdentity(payload.system, onError);
|
|
490
500
|
// Rename tool_choice
|
|
491
501
|
if (payload.tool_choice &&
|
|
492
502
|
typeof payload.tool_choice === 'object' &&
|
|
@@ -658,7 +668,11 @@ const AnthropicAuthPlugin = async ({ client }) => {
|
|
|
658
668
|
.text()
|
|
659
669
|
.catch(() => undefined)
|
|
660
670
|
: undefined;
|
|
661
|
-
const rewritten = rewriteRequestPayload(originalBody)
|
|
671
|
+
const rewritten = rewriteRequestPayload(originalBody, (msg) => {
|
|
672
|
+
client.tui.showToast({
|
|
673
|
+
body: { message: msg, variant: 'error' },
|
|
674
|
+
}).catch(() => { });
|
|
675
|
+
});
|
|
662
676
|
const headers = new Headers(init?.headers);
|
|
663
677
|
if (input instanceof Request) {
|
|
664
678
|
input.headers.forEach((v, k) => {
|
|
@@ -694,6 +708,13 @@ const AnthropicAuthPlugin = async ({ client }) => {
|
|
|
694
708
|
if (shouldRotateAuth(response.status, bodyText)) {
|
|
695
709
|
const rotated = await rotateAnthropicAccount(freshAuth, client);
|
|
696
710
|
if (rotated) {
|
|
711
|
+
// Show toast notification so Discord thread shows the rotation
|
|
712
|
+
client.tui.showToast({
|
|
713
|
+
body: {
|
|
714
|
+
message: `Switching from account ${rotated.fromLabel} to account ${rotated.toLabel}`,
|
|
715
|
+
variant: 'info',
|
|
716
|
+
},
|
|
717
|
+
}).catch(() => { });
|
|
697
718
|
const retryAuth = await getFreshOAuth(getAuth, client);
|
|
698
719
|
if (retryAuth) {
|
|
699
720
|
response = await runRequest(retryAuth);
|
|
@@ -67,7 +67,13 @@ describe('rotateAnthropicAccount', () => {
|
|
|
67
67
|
const rotated = await rotateAnthropicAccount(firstAccount, client);
|
|
68
68
|
const store = await loadAccountStore();
|
|
69
69
|
const authJson = JSON.parse(await readFile(authFilePath(), 'utf8'));
|
|
70
|
-
expect(rotated).toMatchObject({
|
|
70
|
+
expect(rotated).toMatchObject({
|
|
71
|
+
auth: { refresh: 'refresh-second' },
|
|
72
|
+
fromLabel: '#1 (refresh-...irst)',
|
|
73
|
+
toLabel: '#2 (refresh-...cond)',
|
|
74
|
+
fromIndex: 0,
|
|
75
|
+
toIndex: 1,
|
|
76
|
+
});
|
|
71
77
|
expect(store.activeIndex).toBe(1);
|
|
72
78
|
expect(authJson.anthropic?.refresh).toBe('refresh-second');
|
|
73
79
|
expect(authSetCalls).toEqual([
|
|
@@ -94,6 +94,12 @@ export async function loadAccountStore() {
|
|
|
94
94
|
export async function saveAccountStore(store) {
|
|
95
95
|
await writeJson(accountsFilePath(), normalizeAccountStore(store));
|
|
96
96
|
}
|
|
97
|
+
/** Short label for an account: first 8 + last 4 chars of refresh token. */
|
|
98
|
+
export function accountLabel(account, index) {
|
|
99
|
+
const r = account.refresh;
|
|
100
|
+
const short = r.length > 12 ? `${r.slice(0, 8)}...${r.slice(-4)}` : r;
|
|
101
|
+
return index !== undefined ? `#${index + 1} (${short})` : short;
|
|
102
|
+
}
|
|
97
103
|
function findCurrentAccountIndex(store, auth) {
|
|
98
104
|
if (!store.accounts.length)
|
|
99
105
|
return 0;
|
|
@@ -165,10 +171,14 @@ export async function rotateAnthropicAccount(auth, client) {
|
|
|
165
171
|
if (store.accounts.length < 2)
|
|
166
172
|
return undefined;
|
|
167
173
|
const currentIndex = findCurrentAccountIndex(store, auth);
|
|
174
|
+
const currentAccount = store.accounts[currentIndex];
|
|
168
175
|
const nextIndex = (currentIndex + 1) % store.accounts.length;
|
|
169
176
|
const nextAccount = store.accounts[nextIndex];
|
|
170
177
|
if (!nextAccount)
|
|
171
178
|
return undefined;
|
|
179
|
+
const fromLabel = currentAccount
|
|
180
|
+
? accountLabel(currentAccount, currentIndex)
|
|
181
|
+
: accountLabel(auth, currentIndex);
|
|
172
182
|
nextAccount.lastUsed = Date.now();
|
|
173
183
|
store.activeIndex = nextIndex;
|
|
174
184
|
await saveAccountStore(store);
|
|
@@ -179,7 +189,13 @@ export async function rotateAnthropicAccount(auth, client) {
|
|
|
179
189
|
expires: nextAccount.expires,
|
|
180
190
|
};
|
|
181
191
|
await setAnthropicAuth(nextAuth, client);
|
|
182
|
-
return
|
|
192
|
+
return {
|
|
193
|
+
auth: nextAuth,
|
|
194
|
+
fromLabel,
|
|
195
|
+
toLabel: accountLabel(nextAccount, nextIndex),
|
|
196
|
+
fromIndex: currentIndex,
|
|
197
|
+
toIndex: nextIndex,
|
|
198
|
+
};
|
|
183
199
|
});
|
|
184
200
|
}
|
|
185
201
|
export async function removeAccount(index) {
|
package/dist/cli.js
CHANGED
|
@@ -32,7 +32,7 @@ import { backgroundUpgradeKimaki, upgrade, getCurrentVersion, } from './upgrade.
|
|
|
32
32
|
import { startHranaServer } from './hrana-server.js';
|
|
33
33
|
import { startIpcPolling, stopIpcPolling } from './ipc-polling.js';
|
|
34
34
|
import { getPromptPreview, parseSendAtValue, parseScheduledTaskPayload, serializeScheduledTaskPayload, } from './task-schedule.js';
|
|
35
|
-
import { accountsFilePath, loadAccountStore, removeAccount, } from './anthropic-auth-state.js';
|
|
35
|
+
import { accountLabel, accountsFilePath, loadAccountStore, removeAccount, } from './anthropic-auth-state.js';
|
|
36
36
|
const cliLogger = createLogger(LogPrefix.CLI);
|
|
37
37
|
// Gateway bot mode constants.
|
|
38
38
|
// KIMAKI_GATEWAY_APP_ID is the Discord Application ID of the gateway bot.
|
|
@@ -2234,8 +2234,7 @@ cli
|
|
|
2234
2234
|
}
|
|
2235
2235
|
store.accounts.forEach((account, index) => {
|
|
2236
2236
|
const active = index === store.activeIndex ? '*' : ' ';
|
|
2237
|
-
|
|
2238
|
-
console.log(`${active} ${index + 1}. ${label}`);
|
|
2237
|
+
console.log(`${active} ${index + 1}. ${accountLabel(account)}`);
|
|
2239
2238
|
});
|
|
2240
2239
|
process.exit(0);
|
|
2241
2240
|
});
|
package/dist/commands/agent.js
CHANGED
|
@@ -255,7 +255,7 @@ export async function handleAgentSelectMenu(interaction) {
|
|
|
255
255
|
await setAgentForContext({ context, agentName: selectedAgent });
|
|
256
256
|
if (context.isThread && context.sessionId) {
|
|
257
257
|
await interaction.editReply({
|
|
258
|
-
content: `Agent preference set for this session
|
|
258
|
+
content: `Agent preference set for this session: **${selectedAgent}**\nThe agent will change on the next message.`,
|
|
259
259
|
components: [],
|
|
260
260
|
});
|
|
261
261
|
}
|
|
@@ -317,7 +317,7 @@ export async function handleQuickAgentCommand({ command, appId, }) {
|
|
|
317
317
|
: '';
|
|
318
318
|
if (context.isThread && context.sessionId) {
|
|
319
319
|
await command.editReply({
|
|
320
|
-
content: `Switched to **${resolvedAgentName}** agent for this session
|
|
320
|
+
content: `Switched to **${resolvedAgentName}** agent for this session${previousText}\nThe agent will change on the next message.`,
|
|
321
321
|
});
|
|
322
322
|
}
|
|
323
323
|
else {
|
|
@@ -103,15 +103,18 @@ export async function handleMergeWorktreeCommand({ command, appId, }) {
|
|
|
103
103
|
await command.editReply('Rebase conflict detected. Asking the model to resolve...');
|
|
104
104
|
await sendPromptToModel({
|
|
105
105
|
prompt: [
|
|
106
|
-
|
|
106
|
+
`A rebase conflict occurred while merging this worktree into \`${result.target}\`.`,
|
|
107
107
|
'Rebasing multiple commits can pause on each commit that conflicts, so you may need to repeat the resolve/continue loop several times.',
|
|
108
|
-
'
|
|
109
|
-
'1. Check `git status` to see which files have conflicts',
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
'4.
|
|
113
|
-
'5.
|
|
114
|
-
'6.
|
|
108
|
+
'Before editing anything, first understand both sides so you preserve both intentions and do not drop features or fixes.',
|
|
109
|
+
'1. Check `git status` to see which files have conflicts and confirm the rebase is paused',
|
|
110
|
+
`2. Find the merge base between this worktree and \`${result.target}\`, then read the commit messages from both sides since that merge base so you understand the goal of each change`,
|
|
111
|
+
`3. Read the diffs from that merge base to both sides so you understand exactly what changed on this branch and on \`${result.target}\` before resolving conflicts`,
|
|
112
|
+
'4. Read the commit currently being replayed in the rebase so you know the intent of the specific conflicting patch',
|
|
113
|
+
'5. Edit the conflicted files to preserve both intended changes where possible instead of choosing one side wholesale',
|
|
114
|
+
'6. Stage resolved files with `git add`',
|
|
115
|
+
'7. Continue the rebase with `git rebase --continue`',
|
|
116
|
+
'8. If git reports more conflicts, repeat steps 1-7 until the rebase finishes (no more rebase in progress, `git status` is clean)',
|
|
117
|
+
'9. Once the rebase is fully complete, tell me so I can run `/merge-worktree` again',
|
|
115
118
|
].join('\n'),
|
|
116
119
|
thread,
|
|
117
120
|
projectDirectory: worktreeInfo.project_directory,
|
|
@@ -61,7 +61,7 @@ export function shouldInjectPwd({ currentDir, previousDir, announcedDir, }) {
|
|
|
61
61
|
inject: true,
|
|
62
62
|
text: `\n[working directory changed. Previous working directory: ${priorDirectory}. ` +
|
|
63
63
|
`Current working directory: ${currentDir}. ` +
|
|
64
|
-
`You
|
|
64
|
+
`You should read, write, and edit files under ${currentDir}. ` +
|
|
65
65
|
`Do NOT read, write, or edit files under ${priorDirectory}.]`,
|
|
66
66
|
};
|
|
67
67
|
}
|
|
@@ -36,7 +36,7 @@ describe('shouldInjectPwd', () => {
|
|
|
36
36
|
{
|
|
37
37
|
"inject": true,
|
|
38
38
|
"text": "
|
|
39
|
-
[working directory changed. Previous working directory: /repo/main. Current working directory: /repo/worktree. You
|
|
39
|
+
[working directory changed. Previous working directory: /repo/main. Current working directory: /repo/worktree. You should read, write, and edit files under /repo/worktree. Do NOT read, write, or edit files under /repo/main.]",
|
|
40
40
|
}
|
|
41
41
|
`);
|
|
42
42
|
});
|
|
@@ -50,7 +50,7 @@ describe('shouldInjectPwd', () => {
|
|
|
50
50
|
{
|
|
51
51
|
"inject": true,
|
|
52
52
|
"text": "
|
|
53
|
-
[working directory changed. Previous working directory: /repo/worktree-a. Current working directory: /repo/worktree-b. You
|
|
53
|
+
[working directory changed. Previous working directory: /repo/worktree-a. Current working directory: /repo/worktree-b. You should read, write, and edit files under /repo/worktree-b. Do NOT read, write, or edit files under /repo/worktree-a.]",
|
|
54
54
|
}
|
|
55
55
|
`);
|
|
56
56
|
});
|
package/dist/discord-utils.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// Discord-specific utility functions.
|
|
2
2
|
// Handles markdown splitting for Discord's 2000-char limit, code block escaping,
|
|
3
3
|
// thread message sending, and channel metadata extraction from topic tags.
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
// Use namespace import for CJS interop — discord.js is CJS and its named
|
|
5
|
+
// exports aren't detectable by all ESM loaders (e.g. tsx/esbuild) because
|
|
6
|
+
// discord.js uses tslib's __exportStar which is opaque to static analysis.
|
|
7
|
+
import * as discord from 'discord.js';
|
|
8
|
+
const { ChannelType, GuildMember, MessageFlags, PermissionsBitField, REST, Routes } = discord;
|
|
6
9
|
import { discordApiUrl } from './discord-urls.js';
|
|
7
10
|
import { Lexer } from 'marked';
|
|
8
11
|
import { splitTablesFromMarkdown } from './format-tables.js';
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
export { ipcToolsPlugin } from './ipc-tools-plugin.js';
|
|
12
12
|
export { contextAwarenessPlugin } from './context-awareness-plugin.js';
|
|
13
13
|
export { interruptOpencodeSessionOnUserMessage } from './opencode-interrupt-plugin.js';
|
|
14
|
+
export { systemPromptDriftPlugin } from './system-prompt-drift-plugin.js';
|
|
14
15
|
export { anthropicAuthPlugin } from './anthropic-auth-plugin.js';
|
|
15
16
|
export { imageOptimizerPlugin } from './image-optimizer-plugin.js';
|
|
16
17
|
export { kittyGraphicsPlugin } from 'kitty-graphics-agent';
|
package/dist/logger.js
CHANGED
|
@@ -80,12 +80,18 @@ export function setLogFilePath(dataDir) {
|
|
|
80
80
|
export function getLogFilePath() {
|
|
81
81
|
return logFilePath;
|
|
82
82
|
}
|
|
83
|
+
const MAX_LOG_ARG_LENGTH = 1000;
|
|
84
|
+
function truncate(str, max) {
|
|
85
|
+
if (str.length <= max)
|
|
86
|
+
return str;
|
|
87
|
+
return str.slice(0, max) + `… [truncated ${str.length - max} chars]`;
|
|
88
|
+
}
|
|
83
89
|
function formatArg(arg) {
|
|
84
90
|
if (typeof arg === 'string') {
|
|
85
|
-
return sanitizeSensitiveText(arg, { redactPaths: false });
|
|
91
|
+
return truncate(sanitizeSensitiveText(arg, { redactPaths: false }), MAX_LOG_ARG_LENGTH);
|
|
86
92
|
}
|
|
87
93
|
const safeArg = sanitizeUnknownValue(arg, { redactPaths: false });
|
|
88
|
-
return util.inspect(safeArg, { colors: true, depth: 4 });
|
|
94
|
+
return truncate(util.inspect(safeArg, { colors: true, depth: 4 }), MAX_LOG_ARG_LENGTH);
|
|
89
95
|
}
|
|
90
96
|
export function formatErrorWithStack(error) {
|
|
91
97
|
if (error instanceof Error) {
|
|
@@ -40,6 +40,14 @@ import { extractLeadingOpencodeCommand } from '../opencode-command-detection.js'
|
|
|
40
40
|
const logger = createLogger(LogPrefix.SESSION);
|
|
41
41
|
const discordLogger = createLogger(LogPrefix.DISCORD);
|
|
42
42
|
const DETERMINISTIC_CONTEXT_LIMIT = 100_000;
|
|
43
|
+
const TOAST_SESSION_ID_REGEX = /\b(ses_[A-Za-z0-9]+)\b\s*$/u;
|
|
44
|
+
function extractToastSessionId({ message }) {
|
|
45
|
+
const match = message.match(TOAST_SESSION_ID_REGEX);
|
|
46
|
+
return match?.[1];
|
|
47
|
+
}
|
|
48
|
+
function stripToastSessionId({ message }) {
|
|
49
|
+
return message.replace(TOAST_SESSION_ID_REGEX, '').trimEnd();
|
|
50
|
+
}
|
|
43
51
|
const shouldLogSessionEvents = process.env['KIMAKI_LOG_SESSION_EVENTS'] === '1' ||
|
|
44
52
|
process.env['KIMAKI_VITEST'] === '1';
|
|
45
53
|
// ── Registry ─────────────────────────────────────────────────────
|
|
@@ -943,6 +951,9 @@ export class ThreadSessionRuntime {
|
|
|
943
951
|
}
|
|
944
952
|
const sessionId = this.state?.sessionId;
|
|
945
953
|
const eventSessionId = getOpencodeEventSessionId(event);
|
|
954
|
+
const toastSessionId = event.type === 'tui.toast.show'
|
|
955
|
+
? extractToastSessionId({ message: event.properties.message })
|
|
956
|
+
: undefined;
|
|
946
957
|
if (shouldLogSessionEvents) {
|
|
947
958
|
const eventDetails = (() => {
|
|
948
959
|
if (event.type === 'session.error') {
|
|
@@ -970,6 +981,7 @@ export class ThreadSessionRuntime {
|
|
|
970
981
|
logger.log(`[EVENT] type=${event.type} eventSessionId=${eventSessionId || 'none'} activeSessionId=${sessionId || 'none'} ${this.formatRunStateForLog()}${eventDetails}`);
|
|
971
982
|
}
|
|
972
983
|
const isGlobalEvent = event.type === 'tui.toast.show';
|
|
984
|
+
const isScopedToastEvent = Boolean(toastSessionId);
|
|
973
985
|
// Drop events that don't match current session (stale events from
|
|
974
986
|
// previous sessions), unless it's a global event or a subtask session.
|
|
975
987
|
if (!isGlobalEvent && eventSessionId && eventSessionId !== sessionId) {
|
|
@@ -977,6 +989,11 @@ export class ThreadSessionRuntime {
|
|
|
977
989
|
return; // stale event from previous session
|
|
978
990
|
}
|
|
979
991
|
}
|
|
992
|
+
if (isScopedToastEvent && toastSessionId !== sessionId) {
|
|
993
|
+
if (!this.getSubtaskInfoForSession(toastSessionId)) {
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
980
997
|
if (isOpencodeSessionEventLogEnabled()) {
|
|
981
998
|
const eventLogResult = await appendOpencodeSessionEventLog({
|
|
982
999
|
threadId: this.threadId,
|
|
@@ -2023,7 +2040,7 @@ export class ThreadSessionRuntime {
|
|
|
2023
2040
|
if (properties.variant === 'warning') {
|
|
2024
2041
|
return;
|
|
2025
2042
|
}
|
|
2026
|
-
const toastMessage = properties.message.trim();
|
|
2043
|
+
const toastMessage = stripToastSessionId({ message: properties.message }).trim();
|
|
2027
2044
|
if (!toastMessage) {
|
|
2028
2045
|
return;
|
|
2029
2046
|
}
|
|
@@ -3132,8 +3149,8 @@ export class ThreadSessionRuntime {
|
|
|
3132
3149
|
const truncate = (s, max) => {
|
|
3133
3150
|
return s.length > max ? s.slice(0, max - 1) + '\u2026' : s;
|
|
3134
3151
|
};
|
|
3135
|
-
const truncatedFolder = truncate(folderName,
|
|
3136
|
-
const truncatedBranch = truncate(branchName,
|
|
3152
|
+
const truncatedFolder = truncate(folderName, 30);
|
|
3153
|
+
const truncatedBranch = truncate(branchName, 30);
|
|
3137
3154
|
const projectInfo = truncatedBranch
|
|
3138
3155
|
? `${truncatedFolder} ⋅ ${truncatedBranch} ⋅ `
|
|
3139
3156
|
: `${truncatedFolder} ⋅ `;
|
package/dist/system-message.js
CHANGED
|
@@ -368,10 +368,23 @@ Use --agent to specify which agent to use for the session:
|
|
|
368
368
|
kimaki send --channel ${channelId} --prompt "Plan the refactor of the auth module" --agent plan${userArg}
|
|
369
369
|
${availableAgentsContext}
|
|
370
370
|
|
|
371
|
+
## running opencode commands via kimaki send
|
|
372
|
+
|
|
373
|
+
You can trigger registered opencode commands (slash commands, skills, MCP prompts) by starting the \`--prompt\` with \`/commandname\`:
|
|
374
|
+
|
|
375
|
+
kimaki send --thread <thread_id> --prompt "/review fix the auth module"
|
|
376
|
+
kimaki send --channel ${channelId} --prompt "/build-cmd update dependencies"${userArg}
|
|
377
|
+
|
|
378
|
+
The command name must match a registered opencode command. If the command is not recognized, the prompt is sent as plain text to the model. This works for both new threads (\`--channel\`) and existing threads (\`--thread\`/\`--session\`).
|
|
379
|
+
|
|
371
380
|
## switching agents in the current session
|
|
372
381
|
|
|
373
382
|
The user can switch the active agent mid-session using the Discord slash command \`/<agentname>-agent\`. For example if you are in plan mode and the user asks you to edit files, tell them to run \`/build-agent\` to switch to the build agent first.
|
|
374
383
|
|
|
384
|
+
You can also switch agents via \`kimaki send\`:
|
|
385
|
+
|
|
386
|
+
kimaki send --thread <thread_id> --prompt "/<agentname>-agent"
|
|
387
|
+
|
|
375
388
|
## scheduled sends and task management
|
|
376
389
|
|
|
377
390
|
Use \`--send-at\` to schedule a one-time or recurring task:
|
|
@@ -135,10 +135,23 @@ describe('system-message', () => {
|
|
|
135
135
|
- \`plan\`: planning only
|
|
136
136
|
- \`build\`: edits files
|
|
137
137
|
|
|
138
|
+
## running opencode commands via kimaki send
|
|
139
|
+
|
|
140
|
+
You can trigger registered opencode commands (slash commands, skills, MCP prompts) by starting the \`--prompt\` with \`/commandname\`:
|
|
141
|
+
|
|
142
|
+
kimaki send --thread <thread_id> --prompt "/review fix the auth module"
|
|
143
|
+
kimaki send --channel chan_123 --prompt "/build-cmd update dependencies" --user "Tommy"
|
|
144
|
+
|
|
145
|
+
The command name must match a registered opencode command. If the command is not recognized, the prompt is sent as plain text to the model. This works for both new threads (\`--channel\`) and existing threads (\`--thread\`/\`--session\`).
|
|
146
|
+
|
|
138
147
|
## switching agents in the current session
|
|
139
148
|
|
|
140
149
|
The user can switch the active agent mid-session using the Discord slash command \`/<agentname>-agent\`. For example if you are in plan mode and the user asks you to edit files, tell them to run \`/build-agent\` to switch to the build agent first.
|
|
141
150
|
|
|
151
|
+
You can also switch agents via \`kimaki send\`:
|
|
152
|
+
|
|
153
|
+
kimaki send --thread <thread_id> --prompt "/<agentname>-agent"
|
|
154
|
+
|
|
142
155
|
## scheduled sends and task management
|
|
143
156
|
|
|
144
157
|
Use \`--send-at\` to schedule a one-time or recurring task:
|