@otto-assistant/bridge 0.4.92 → 0.4.93
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 +7 -0
- 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/session-handler/thread-session-runtime.js +2 -2
- package/dist/system-message.js +13 -0
- package/dist/system-message.test.js +13 -0
- package/package.json +1 -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 +7 -0
- 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/session-handler/thread-session-runtime.ts +2 -2
- package/src/system-message.test.ts +13 -0
- package/src/system-message.ts +13 -0
|
@@ -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)
|
|
@@ -694,6 +694,13 @@ const AnthropicAuthPlugin = async ({ client }) => {
|
|
|
694
694
|
if (shouldRotateAuth(response.status, bodyText)) {
|
|
695
695
|
const rotated = await rotateAnthropicAccount(freshAuth, client);
|
|
696
696
|
if (rotated) {
|
|
697
|
+
// Show toast notification so Discord thread shows the rotation
|
|
698
|
+
client.tui.showToast({
|
|
699
|
+
body: {
|
|
700
|
+
message: `Switching from account ${rotated.fromLabel} to account ${rotated.toLabel}`,
|
|
701
|
+
variant: 'info',
|
|
702
|
+
},
|
|
703
|
+
}).catch(() => { });
|
|
697
704
|
const retryAuth = await getFreshOAuth(getAuth, client);
|
|
698
705
|
if (retryAuth) {
|
|
699
706
|
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,
|
|
@@ -3132,8 +3132,8 @@ export class ThreadSessionRuntime {
|
|
|
3132
3132
|
const truncate = (s, max) => {
|
|
3133
3133
|
return s.length > max ? s.slice(0, max - 1) + '\u2026' : s;
|
|
3134
3134
|
};
|
|
3135
|
-
const truncatedFolder = truncate(folderName,
|
|
3136
|
-
const truncatedBranch = truncate(branchName,
|
|
3135
|
+
const truncatedFolder = truncate(folderName, 30);
|
|
3136
|
+
const truncatedBranch = truncate(branchName, 30);
|
|
3137
3137
|
const projectInfo = truncatedBranch
|
|
3138
3138
|
? `${truncatedFolder} ⋅ ${truncatedBranch} ⋅ `
|
|
3139
3139
|
: `${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:
|
package/package.json
CHANGED
|
@@ -39,15 +39,20 @@ Use this skill when scaffolding or fixing npm packages.
|
|
|
39
39
|
- any runtime-required extra files (for example `schema.prisma`)
|
|
40
40
|
- docs like `README.md` and `CHANGELOG.md`
|
|
41
41
|
- if tests are inside src and gets included in dist, it's fine. don't try to exclude them
|
|
42
|
-
10. `scripts.build` should be `
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
build
|
|
42
|
+
10. `scripts.build` should be `tsc && chmod +x dist/cli.js` (skip the chmod if
|
|
43
|
+
the package has no bin). No bundling. Do not delete `dist/` in `build` by
|
|
44
|
+
default because forcing a clean build on every local build can cause
|
|
45
|
+
issues. Optionally include running scripts with `tsx` if needed to
|
|
46
|
+
generate build artifacts.
|
|
47
|
+
11. `prepublishOnly` must always do the cleanup before `build` (optionally run
|
|
48
|
+
generation before build when required). Always add this script:
|
|
47
49
|
```json
|
|
48
|
-
{ "prepublishOnly": "pnpm build" }
|
|
50
|
+
{ "prepublishOnly": "rimraf dist \"*.tsbuildinfo\" && pnpm build" }
|
|
49
51
|
```
|
|
50
|
-
This ensures `dist/` is fresh before every `npm publish
|
|
52
|
+
This ensures `dist/` is fresh before every `npm publish`, so deleted files
|
|
53
|
+
do not accidentally stay in the published package. Use `rimraf` here
|
|
54
|
+
instead of bare shell globs so the script behaves the same in zsh, bash,
|
|
55
|
+
and Windows shells even when no `.tsbuildinfo` file exists.
|
|
51
56
|
|
|
52
57
|
## bin field
|
|
53
58
|
|
|
@@ -71,8 +76,8 @@ Add the shebang as the first line of the source file (`src/cli.ts`):
|
|
|
71
76
|
```
|
|
72
77
|
|
|
73
78
|
`tsc` preserves the shebang in the emitted `.js` file. The `chmod +x` is
|
|
74
|
-
already part of the `build` script, so `prepublishOnly
|
|
75
|
-
|
|
79
|
+
already part of the `build` script, so `prepublishOnly` still gets it through
|
|
80
|
+
`pnpm build` after the cleanup step.
|
|
76
81
|
|
|
77
82
|
## Reading package version at runtime
|
|
78
83
|
|
|
@@ -950,7 +950,8 @@ describe('agent model resolution', () => {
|
|
|
950
950
|
--- from: assistant (TestBot)
|
|
951
951
|
⬥ ok
|
|
952
952
|
*project ⋅ main ⋅ Ns ⋅ N% ⋅ agent-model-v2 ⋅ **test-agent***
|
|
953
|
-
Switched to **plan** agent for this session
|
|
953
|
+
Switched to **plan** agent for this session (was **test-agent**)
|
|
954
|
+
The agent will change on the next message.
|
|
954
955
|
--- from: user (agent-model-tester)
|
|
955
956
|
Reply with exactly: after-switch-msg
|
|
956
957
|
--- from: assistant (TestBot)
|
|
@@ -88,7 +88,13 @@ describe('rotateAnthropicAccount', () => {
|
|
|
88
88
|
anthropic?: { refresh?: string }
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
expect(rotated).toMatchObject({
|
|
91
|
+
expect(rotated).toMatchObject({
|
|
92
|
+
auth: { refresh: 'refresh-second' },
|
|
93
|
+
fromLabel: '#1 (refresh-...irst)',
|
|
94
|
+
toLabel: '#2 (refresh-...cond)',
|
|
95
|
+
fromIndex: 0,
|
|
96
|
+
toIndex: 1,
|
|
97
|
+
})
|
|
92
98
|
expect(store.activeIndex).toBe(1)
|
|
93
99
|
expect(authJson.anthropic?.refresh).toBe('refresh-second')
|
|
94
100
|
expect(authSetCalls).toEqual([
|
|
@@ -823,6 +823,13 @@ const AnthropicAuthPlugin: Plugin = async ({ client }) => {
|
|
|
823
823
|
if (shouldRotateAuth(response.status, bodyText)) {
|
|
824
824
|
const rotated = await rotateAnthropicAccount(freshAuth, client)
|
|
825
825
|
if (rotated) {
|
|
826
|
+
// Show toast notification so Discord thread shows the rotation
|
|
827
|
+
client.tui.showToast({
|
|
828
|
+
body: {
|
|
829
|
+
message: `Switching from account ${rotated.fromLabel} to account ${rotated.toLabel}`,
|
|
830
|
+
variant: 'info',
|
|
831
|
+
},
|
|
832
|
+
}).catch(() => {})
|
|
826
833
|
const retryAuth = await getFreshOAuth(getAuth, client)
|
|
827
834
|
if (retryAuth) {
|
|
828
835
|
response = await runRequest(retryAuth)
|
|
@@ -133,6 +133,21 @@ export async function saveAccountStore(store: AccountStore) {
|
|
|
133
133
|
await writeJson(accountsFilePath(), normalizeAccountStore(store))
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
/** Short label for an account: first 8 + last 4 chars of refresh token. */
|
|
137
|
+
export function accountLabel(account: OAuthStored, index?: number): string {
|
|
138
|
+
const r = account.refresh
|
|
139
|
+
const short = r.length > 12 ? `${r.slice(0, 8)}...${r.slice(-4)}` : r
|
|
140
|
+
return index !== undefined ? `#${index + 1} (${short})` : short
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export type RotationResult = {
|
|
144
|
+
auth: OAuthStored
|
|
145
|
+
fromLabel: string
|
|
146
|
+
toLabel: string
|
|
147
|
+
fromIndex: number
|
|
148
|
+
toIndex: number
|
|
149
|
+
}
|
|
150
|
+
|
|
136
151
|
function findCurrentAccountIndex(store: AccountStore, auth: OAuthStored) {
|
|
137
152
|
if (!store.accounts.length) return 0
|
|
138
153
|
const byRefresh = store.accounts.findIndex((account) => {
|
|
@@ -206,16 +221,21 @@ export async function setAnthropicAuth(
|
|
|
206
221
|
export async function rotateAnthropicAccount(
|
|
207
222
|
auth: OAuthStored,
|
|
208
223
|
client: Parameters<Plugin>[0]['client'],
|
|
209
|
-
) {
|
|
224
|
+
): Promise<RotationResult | undefined> {
|
|
210
225
|
return withAuthStateLock(async () => {
|
|
211
226
|
const store = await loadAccountStore()
|
|
212
227
|
if (store.accounts.length < 2) return undefined
|
|
213
228
|
|
|
214
229
|
const currentIndex = findCurrentAccountIndex(store, auth)
|
|
230
|
+
const currentAccount = store.accounts[currentIndex]
|
|
215
231
|
const nextIndex = (currentIndex + 1) % store.accounts.length
|
|
216
232
|
const nextAccount = store.accounts[nextIndex]
|
|
217
233
|
if (!nextAccount) return undefined
|
|
218
234
|
|
|
235
|
+
const fromLabel = currentAccount
|
|
236
|
+
? accountLabel(currentAccount, currentIndex)
|
|
237
|
+
: accountLabel(auth, currentIndex)
|
|
238
|
+
|
|
219
239
|
nextAccount.lastUsed = Date.now()
|
|
220
240
|
store.activeIndex = nextIndex
|
|
221
241
|
await saveAccountStore(store)
|
|
@@ -227,7 +247,13 @@ export async function rotateAnthropicAccount(
|
|
|
227
247
|
expires: nextAccount.expires,
|
|
228
248
|
}
|
|
229
249
|
await setAnthropicAuth(nextAuth, client)
|
|
230
|
-
return
|
|
250
|
+
return {
|
|
251
|
+
auth: nextAuth,
|
|
252
|
+
fromLabel,
|
|
253
|
+
toLabel: accountLabel(nextAccount, nextIndex),
|
|
254
|
+
fromIndex: currentIndex,
|
|
255
|
+
toIndex: nextIndex,
|
|
256
|
+
}
|
|
231
257
|
})
|
|
232
258
|
}
|
|
233
259
|
|
package/src/cli.ts
CHANGED
|
@@ -125,6 +125,7 @@ import {
|
|
|
125
125
|
type ScheduledTaskPayload,
|
|
126
126
|
} from './task-schedule.js'
|
|
127
127
|
import {
|
|
128
|
+
accountLabel,
|
|
128
129
|
accountsFilePath,
|
|
129
130
|
loadAccountStore,
|
|
130
131
|
removeAccount,
|
|
@@ -3178,8 +3179,7 @@ cli
|
|
|
3178
3179
|
|
|
3179
3180
|
store.accounts.forEach((account, index) => {
|
|
3180
3181
|
const active = index === store.activeIndex ? '*' : ' '
|
|
3181
|
-
|
|
3182
|
-
console.log(`${active} ${index + 1}. ${label}`)
|
|
3182
|
+
console.log(`${active} ${index + 1}. ${accountLabel(account)}`)
|
|
3183
3183
|
})
|
|
3184
3184
|
|
|
3185
3185
|
process.exit(0)
|
package/src/commands/agent.ts
CHANGED
|
@@ -379,7 +379,7 @@ export async function handleAgentSelectMenu(
|
|
|
379
379
|
|
|
380
380
|
if (context.isThread && context.sessionId) {
|
|
381
381
|
await interaction.editReply({
|
|
382
|
-
content: `Agent preference set for this session
|
|
382
|
+
content: `Agent preference set for this session: **${selectedAgent}**\nThe agent will change on the next message.`,
|
|
383
383
|
components: [],
|
|
384
384
|
})
|
|
385
385
|
} else {
|
|
@@ -457,7 +457,7 @@ export async function handleQuickAgentCommand({
|
|
|
457
457
|
|
|
458
458
|
if (context.isThread && context.sessionId) {
|
|
459
459
|
await command.editReply({
|
|
460
|
-
content: `Switched to **${resolvedAgentName}** agent for this session
|
|
460
|
+
content: `Switched to **${resolvedAgentName}** agent for this session${previousText}\nThe agent will change on the next message.`,
|
|
461
461
|
})
|
|
462
462
|
} else {
|
|
463
463
|
await command.editReply({
|
|
@@ -155,15 +155,18 @@ export async function handleMergeWorktreeCommand({
|
|
|
155
155
|
)
|
|
156
156
|
await sendPromptToModel({
|
|
157
157
|
prompt: [
|
|
158
|
-
|
|
158
|
+
`A rebase conflict occurred while merging this worktree into \`${result.target}\`.`,
|
|
159
159
|
'Rebasing multiple commits can pause on each commit that conflicts, so you may need to repeat the resolve/continue loop several times.',
|
|
160
|
-
'
|
|
161
|
-
'1. Check `git status` to see which files have conflicts',
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
'4.
|
|
165
|
-
'5.
|
|
166
|
-
'6.
|
|
160
|
+
'Before editing anything, first understand both sides so you preserve both intentions and do not drop features or fixes.',
|
|
161
|
+
'1. Check `git status` to see which files have conflicts and confirm the rebase is paused',
|
|
162
|
+
`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`,
|
|
163
|
+
`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`,
|
|
164
|
+
'4. Read the commit currently being replayed in the rebase so you know the intent of the specific conflicting patch',
|
|
165
|
+
'5. Edit the conflicted files to preserve both intended changes where possible instead of choosing one side wholesale',
|
|
166
|
+
'6. Stage resolved files with `git add`',
|
|
167
|
+
'7. Continue the rebase with `git rebase --continue`',
|
|
168
|
+
'8. If git reports more conflicts, repeat steps 1-7 until the rebase finishes (no more rebase in progress, `git status` is clean)',
|
|
169
|
+
'9. Once the rebase is fully complete, tell me so I can run `/merge-worktree` again',
|
|
167
170
|
].join('\n'),
|
|
168
171
|
thread,
|
|
169
172
|
projectDirectory: worktreeInfo.project_directory,
|
|
@@ -4111,8 +4111,8 @@ export class ThreadSessionRuntime {
|
|
|
4111
4111
|
const truncate = (s: string, max: number) => {
|
|
4112
4112
|
return s.length > max ? s.slice(0, max - 1) + '\u2026' : s
|
|
4113
4113
|
}
|
|
4114
|
-
const truncatedFolder = truncate(folderName,
|
|
4115
|
-
const truncatedBranch = truncate(branchName,
|
|
4114
|
+
const truncatedFolder = truncate(folderName, 30)
|
|
4115
|
+
const truncatedBranch = truncate(branchName, 30)
|
|
4116
4116
|
const projectInfo = truncatedBranch
|
|
4117
4117
|
? `${truncatedFolder} ⋅ ${truncatedBranch} ⋅ `
|
|
4118
4118
|
: `${truncatedFolder} ⋅ `
|
|
@@ -142,10 +142,23 @@ describe('system-message', () => {
|
|
|
142
142
|
- \`plan\`: planning only
|
|
143
143
|
- \`build\`: edits files
|
|
144
144
|
|
|
145
|
+
## running opencode commands via kimaki send
|
|
146
|
+
|
|
147
|
+
You can trigger registered opencode commands (slash commands, skills, MCP prompts) by starting the \`--prompt\` with \`/commandname\`:
|
|
148
|
+
|
|
149
|
+
kimaki send --thread <thread_id> --prompt "/review fix the auth module"
|
|
150
|
+
kimaki send --channel chan_123 --prompt "/build-cmd update dependencies" --user "Tommy"
|
|
151
|
+
|
|
152
|
+
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\`).
|
|
153
|
+
|
|
145
154
|
## switching agents in the current session
|
|
146
155
|
|
|
147
156
|
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.
|
|
148
157
|
|
|
158
|
+
You can also switch agents via \`kimaki send\`:
|
|
159
|
+
|
|
160
|
+
kimaki send --thread <thread_id> --prompt "/<agentname>-agent"
|
|
161
|
+
|
|
149
162
|
## scheduled sends and task management
|
|
150
163
|
|
|
151
164
|
Use \`--send-at\` to schedule a one-time or recurring task:
|
package/src/system-message.ts
CHANGED
|
@@ -477,10 +477,23 @@ Use --agent to specify which agent to use for the session:
|
|
|
477
477
|
kimaki send --channel ${channelId} --prompt "Plan the refactor of the auth module" --agent plan${userArg}
|
|
478
478
|
${availableAgentsContext}
|
|
479
479
|
|
|
480
|
+
## running opencode commands via kimaki send
|
|
481
|
+
|
|
482
|
+
You can trigger registered opencode commands (slash commands, skills, MCP prompts) by starting the \`--prompt\` with \`/commandname\`:
|
|
483
|
+
|
|
484
|
+
kimaki send --thread <thread_id> --prompt "/review fix the auth module"
|
|
485
|
+
kimaki send --channel ${channelId} --prompt "/build-cmd update dependencies"${userArg}
|
|
486
|
+
|
|
487
|
+
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\`).
|
|
488
|
+
|
|
480
489
|
## switching agents in the current session
|
|
481
490
|
|
|
482
491
|
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.
|
|
483
492
|
|
|
493
|
+
You can also switch agents via \`kimaki send\`:
|
|
494
|
+
|
|
495
|
+
kimaki send --thread <thread_id> --prompt "/<agentname>-agent"
|
|
496
|
+
|
|
484
497
|
## scheduled sends and task management
|
|
485
498
|
|
|
486
499
|
Use \`--send-at\` to schedule a one-time or recurring task:
|