@otto-assistant/bridge 0.4.97 → 0.4.100

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.
@@ -363,7 +363,7 @@ export function getOpencodeSystemMessage({
363
363
  .join('\n')}`
364
364
  : ''
365
365
  return `
366
- The user is reading your messages from inside Discord, via kimaki.xyz
366
+ The user is reading your messages from inside Discord, via kimaki.dev
367
367
 
368
368
  ## bash tool
369
369
 
@@ -431,39 +431,40 @@ ${
431
431
 
432
432
  To start a new thread/session in this channel pro-grammatically, run:
433
433
 
434
- kimaki send --channel ${channelId} --prompt "your prompt here"${userArg}
434
+ kimaki send --channel ${channelId} --prompt "your prompt here" --agent <current_agent>${userArg}
435
435
 
436
436
  You can use this to "spawn" parallel helper sessions like teammates: start new threads with focused prompts, then come back and collect the results.
437
+ Prefer passing the current agent with \`--agent <current_agent>\` so spawned or scheduled sessions keep the same agent unless you are intentionally switching. Replace \`<current_agent>\` with the value from the per-turn \`Current agent\` reminder.
437
438
 
438
439
  IMPORTANT: NEVER use \`--worktree\` unless the user explicitly asks for a worktree. Default to creating normal threads without worktrees.
439
440
 
440
441
  To send a prompt to an existing thread instead of creating a new one:
441
442
 
442
- kimaki send --thread <thread_id> --prompt "follow-up prompt"
443
+ kimaki send --thread <thread_id> --prompt "follow-up prompt" --agent <current_agent>
443
444
 
444
445
  Use this when you already have the Discord thread ID.
445
446
 
446
447
  To send to the thread associated with a known session:
447
448
 
448
- kimaki send --session <session_id> --prompt "follow-up prompt"
449
+ kimaki send --session <session_id> --prompt "follow-up prompt" --agent <current_agent>
449
450
 
450
451
  Use this when you have the OpenCode session ID.
451
452
 
452
453
  Use --notify-only to create a notification thread without starting an AI session:
453
454
 
454
- kimaki send --channel ${channelId} --prompt "User cancelled subscription" --notify-only${userArg}
455
+ kimaki send --channel ${channelId} --prompt "User cancelled subscription" --notify-only --agent <current_agent>${userArg}
455
456
 
456
457
  Use --user to add a specific Discord user to the new thread:
457
458
 
458
- kimaki send --channel ${channelId} --prompt "Review the latest CI failure"${userArg}
459
+ kimaki send --channel ${channelId} --prompt "Review the latest CI failure" --agent <current_agent>${userArg}
459
460
 
460
461
  Use --worktree to create a git worktree for the session (ONLY when the user explicitly asks for a worktree):
461
462
 
462
- kimaki send --channel ${channelId} --prompt "Add dark mode support" --worktree dark-mode${userArg}
463
+ kimaki send --channel ${channelId} --prompt "Add dark mode support" --worktree dark-mode --agent <current_agent>${userArg}
463
464
 
464
465
  Use --cwd to start a session in an existing git worktree directory (must be a worktree of the project):
465
466
 
466
- kimaki send --channel ${channelId} --prompt "Continue work on feature" --cwd /path/to/existing-worktree${userArg}
467
+ kimaki send --channel ${channelId} --prompt "Continue work on feature" --cwd /path/to/existing-worktree --agent <current_agent>${userArg}
467
468
 
468
469
  Important:
469
470
  - NEVER use \`--worktree\` unless the user explicitly requests a worktree. Most tasks should use normal threads without worktrees.
@@ -481,8 +482,8 @@ ${availableAgentsContext}
481
482
 
482
483
  You can trigger registered opencode commands (slash commands, skills, MCP prompts) by starting the \`--prompt\` with \`/commandname\`:
483
484
 
484
- kimaki send --thread <thread_id> --prompt "/review fix the auth module"
485
- kimaki send --channel ${channelId} --prompt "/build-cmd update dependencies"${userArg}
485
+ kimaki send --thread <thread_id> --prompt "/review fix the auth module" --agent <current_agent>
486
+ kimaki send --channel ${channelId} --prompt "/build-cmd update dependencies" --agent <current_agent>${userArg}
486
487
 
487
488
  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
489
 
@@ -492,14 +493,14 @@ The user can switch the active agent mid-session using the Discord slash command
492
493
 
493
494
  You can also switch agents via \`kimaki send\`:
494
495
 
495
- kimaki send --thread <thread_id> --prompt "/<agentname>-agent"
496
+ kimaki send --thread <thread_id> --prompt "/<agentname>-agent" --agent <current_agent>
496
497
 
497
498
  ## scheduled sends and task management
498
499
 
499
500
  Use \`--send-at\` to schedule a one-time or recurring task:
500
501
 
501
- kimaki send --channel ${channelId} --prompt "Reminder: review open PRs" --send-at "2026-03-01T09:00:00Z"${userArg}
502
- kimaki send --channel ${channelId} --prompt "Run weekly test suite and summarize failures" --send-at "0 9 * * 1"${userArg}
502
+ kimaki send --channel ${channelId} --prompt "Reminder: review open PRs" --send-at "2026-03-01T09:00:00Z" --agent <current_agent>${userArg}
503
+ kimaki send --channel ${channelId} --prompt "Run weekly test suite and summarize failures" --send-at "0 9 * * 1" --agent <current_agent>${userArg}
503
504
 
504
505
  ALL scheduling is in UTC. Dates must be UTC ISO format ending with \`Z\`. Cron expressions also fire in UTC (e.g. \`0 9 * * 1\` means 9:00 UTC every Monday).
505
506
  When the user specifies a time without a timezone, ask them to confirm their timezone or the UTC equivalent. Never guess the user's timezone.
@@ -533,13 +534,13 @@ kimaki task delete <id>
533
534
 
534
535
  Use case patterns:
535
536
  - Reminder flows: create deadline reminders in this channel with one-time \`--send-at\`; mention only if action is required.
536
- - Proactive reminders: when you encounter time-sensitive information during your work (e.g. creating an API key that expires in 90 days, a certificate with an expiration date, a trial period ending, a deadline mentioned in code comments), proactively schedule a \`--notify-only\` reminder before the expiration so the user gets notified in time. For example, if you generate an API key expiring on 2026-06-01, schedule a reminder a few days before: \`kimaki send --channel ${channelId} --prompt "Reminder: <@USER_ID> the API key created on 2026-03-01 expires on 2026-06-01. Renew it before it breaks production." --send-at "2026-05-28T09:00:00Z" --notify-only\`. Always tell the user you scheduled the reminder so they know.
537
+ - Proactive reminders: when you encounter time-sensitive information during your work (e.g. creating an API key that expires in 90 days, a certificate with an expiration date, a trial period ending, a deadline mentioned in code comments), proactively schedule a \`--notify-only\` reminder before the expiration so the user gets notified in time. For example, if you generate an API key expiring on 2026-06-01, schedule a reminder a few days before: \`kimaki send --channel ${channelId} --prompt "Reminder: <@USER_ID> the API key created on 2026-03-01 expires on 2026-06-01. Renew it before it breaks production." --send-at "2026-05-28T09:00:00Z" --notify-only --agent <current_agent>\`. Always tell the user you scheduled the reminder so they know.
537
538
  - Weekly QA: schedule "run full test suite, inspect failures, post summary, and mention @username only when failures require review".
538
539
  - Weekly benchmark automation: schedule a benchmark prompt that runs model evals, writes JSON outputs in the repo, commits results, and mentions only for regressions.
539
540
  - Recurring maintenance: use cron \`--send-at\` for repetitive tasks like rotating secrets, checking dependency updates, running security audits, or cleaning up stale branches. Example: \`--send-at "0 9 1 * *"\` to run on the 1st of every month.
540
541
  - Thread reminders: when the user says "remind me about this in 2 hours" (or any duration), use \`--send-at\` with \`--thread\` to resurface the current thread. Compute the future UTC time and send a mention so Discord shows a notification:
541
542
 
542
- kimaki send --session ${sessionId} --prompt "Reminder: <@USER_ID> you asked to be reminded about this thread." --send-at "<future_UTC_time>" --notify-only
543
+ kimaki send --session ${sessionId} --prompt "Reminder: <@USER_ID> you asked to be reminded about this thread." --send-at "<future_UTC_time>" --notify-only --agent <current_agent>
543
544
 
544
545
  Replace \`<future_UTC_time>\` with the computed UTC ISO timestamp. The \`--notify-only\` flag creates just a notification message without starting a new AI session. The \`<@userId>\` mention ensures the user gets a Discord notification.
545
546
 
@@ -554,7 +555,7 @@ ONLY create worktrees when the user explicitly asks for one. Never proactively u
554
555
  When the user asks to "create a worktree" or "make a worktree", they mean you should use the kimaki CLI to create it. Do NOT use raw \`git worktree add\` commands. Instead use:
555
556
 
556
557
  \`\`\`bash
557
- kimaki send --channel ${channelId} --prompt "your task description" --worktree worktree-name${userArg}
558
+ kimaki send --channel ${channelId} --prompt "your task description" --worktree worktree-name --agent <current_agent>${userArg}
558
559
  \`\`\`
559
560
 
560
561
  This creates a new Discord thread with an isolated git worktree and starts a session in it. The worktree name should be kebab-case and descriptive of the task.
@@ -570,7 +571,7 @@ Critical recursion guard:
570
571
  Use \`--cwd\` to start a session in an existing git worktree directory instead of creating a new one:
571
572
 
572
573
  \`\`\`bash
573
- kimaki send --channel ${channelId} --prompt "Continue work on feature X" --cwd /path/to/existing-worktree${userArg}
574
+ kimaki send --channel ${channelId} --prompt "Continue work on feature X" --cwd /path/to/existing-worktree --agent <current_agent>${userArg}
574
575
  \`\`\`
575
576
 
576
577
  The path must be a git worktree of the project (validated via \`git worktree list\`). The session resolves to the correct project channel but uses the worktree as its working directory. Use \`--worktree\` to create a new worktree, \`--cwd\` to reuse an existing one.
@@ -584,7 +585,7 @@ This is useful for automation (cron jobs, GitHub webhooks, n8n, etc.)
584
585
  When you are approaching the **context window limit** or the user explicitly asks to **handoff to a new thread**, use the \`kimaki send\` command to start a fresh session with context:
585
586
 
586
587
  \`\`\`bash
587
- kimaki send --channel ${channelId} --prompt "Continuing from previous session: <summary of current task and state>"${userArg}
588
+ kimaki send --channel ${channelId} --prompt "Continuing from previous session: <summary of current task and state>" --agent <current_agent>${userArg}
588
589
  \`\`\`
589
590
 
590
591
  The command automatically handles long prompts (over 2000 chars) by sending them as file attachments.
@@ -642,10 +643,10 @@ To send a task to another project:
642
643
 
643
644
  \`\`\`bash
644
645
  # Send to a specific channel
645
- kimaki send --channel <channel_id> --prompt "Plan how to update the API client to v2"
646
+ kimaki send --channel <channel_id> --prompt "Plan how to update the API client to v2" --agent <current_agent>
646
647
 
647
648
  # Or use --project to resolve from directory
648
- kimaki send --project /path/to/other-repo --prompt "Plan how to bump version to 1.2.0"
649
+ kimaki send --project /path/to/other-repo --prompt "Plan how to bump version to 1.2.0" --agent <current_agent>
649
650
  \`\`\`
650
651
 
651
652
  When sending prompts to other projects, always ask the agent to plan first, never build upfront. The prompt should start with "Plan how to ..." so the user can review before greenlighting implementation.
@@ -668,10 +669,10 @@ If your Bash tool timeout triggers anyway, fall back to reading the session outp
668
669
 
669
670
  \`\`\`bash
670
671
  # Start a session and wait for it to finish
671
- kimaki send --channel <channel_id> --prompt "Fix the auth bug" --wait
672
+ kimaki send --channel <channel_id> --prompt "Fix the auth bug" --wait --agent <current_agent>
672
673
 
673
674
  # Send to an existing thread and wait
674
- kimaki send --thread <thread_id> --prompt "Run the tests" --wait
675
+ kimaki send --thread <thread_id> --prompt "Run the tests" --wait --agent <current_agent>
675
676
  \`\`\`
676
677
 
677
678
  The command exits with the session markdown on stdout once the model finishes responding.
@@ -33,6 +33,7 @@ type SessionState = {
33
33
  comparedTurn: number
34
34
  previousTurnContext: TurnContext | undefined
35
35
  currentTurnContext: TurnContext | undefined
36
+ pendingCompareTimeout: ReturnType<typeof setTimeout> | undefined
36
37
  }
37
38
 
38
39
  type SystemPromptDiff = {
@@ -146,7 +147,6 @@ function writeSystemPromptDiffFile({
146
147
  additions: number
147
148
  deletions: number
148
149
  filePath: string
149
- latestPromptPath: string
150
150
  } {
151
151
  const diff = buildPatch({
152
152
  beforeText: beforePrompt,
@@ -157,7 +157,7 @@ function writeSystemPromptDiffFile({
157
157
  const timestamp = new Date().toISOString().replaceAll(':', '-')
158
158
  const sessionDir = path.join(getSystemPromptDiffDir({ dataDir }), sessionId)
159
159
  const filePath = path.join(sessionDir, `${timestamp}.diff`)
160
- const latestPromptPath = path.join(sessionDir, `${timestamp}.md`)
160
+
161
161
  const fileContent = [
162
162
  `Session: ${sessionId}`,
163
163
  `Created: ${new Date().toISOString()}`,
@@ -176,7 +176,6 @@ function writeSystemPromptDiffFile({
176
176
  additions: diff.additions,
177
177
  deletions: diff.deletions,
178
178
  filePath,
179
- latestPromptPath,
180
179
  }
181
180
  },
182
181
  catch: (error) => {
@@ -204,6 +203,7 @@ function getOrCreateSessionState({
204
203
  comparedTurn: 0,
205
204
  previousTurnContext: undefined,
206
205
  currentTurnContext: undefined,
206
+ pendingCompareTimeout: undefined,
207
207
  }
208
208
  sessions.set(sessionId, state)
209
209
  return state
@@ -278,8 +278,7 @@ async function handleSystemTransform({
278
278
  sessionId,
279
279
  message:
280
280
  `system prompt changed since the previous message (+${diffFileResult.additions} / -${diffFileResult.deletions}). ` +
281
- `Diff: \`${abbreviatePath(diffFileResult.filePath)}\`. ` +
282
- `Latest prompt: \`${abbreviatePath(diffFileResult.latestPromptPath)}\``,
281
+ `Diff: \`${abbreviatePath(diffFileResult.filePath)}\`. `
283
282
  }),
284
283
  },
285
284
  })
@@ -315,13 +314,46 @@ const systemPromptDriftPlugin: Plugin = async ({ client, directory }) => {
315
314
  'experimental.chat.system.transform': async (input, output) => {
316
315
  const result = await errore.tryAsync({
317
316
  try: async () => {
318
- await handleSystemTransform({
319
- input,
320
- output,
321
- sessions,
322
- dataDir,
323
- client,
324
- })
317
+ const sessionId = input.sessionID
318
+ if (!sessionId) {
319
+ return
320
+ }
321
+ const state = getOrCreateSessionState({ sessions, sessionId })
322
+ if (state.pendingCompareTimeout) {
323
+ clearTimeout(state.pendingCompareTimeout)
324
+ }
325
+ // Delay one tick so other system-transform hooks can finish mutating
326
+ // output.system before we snapshot it for drift detection.
327
+ state.pendingCompareTimeout = setTimeout(() => {
328
+ state.pendingCompareTimeout = undefined
329
+ void errore.tryAsync({
330
+ try: async () => {
331
+ await handleSystemTransform({
332
+ input,
333
+ output,
334
+ sessions,
335
+ dataDir,
336
+ client,
337
+ })
338
+ },
339
+ catch: (error) => {
340
+ return new Error('system prompt drift transform hook failed', {
341
+ cause: error,
342
+ })
343
+ },
344
+ }).then((delayedResult) => {
345
+ if (!(delayedResult instanceof Error)) {
346
+ return
347
+ }
348
+ logger.warn(
349
+ `[system-prompt-drift-plugin] ${formatPluginErrorWithStack(delayedResult)}`,
350
+ )
351
+ void notifyError(
352
+ delayedResult,
353
+ 'system prompt drift plugin transform hook failed',
354
+ )
355
+ })
356
+ }, 0)
325
357
  },
326
358
  catch: (error) => {
327
359
  return new Error('system prompt drift transform hook failed', {
@@ -346,6 +378,10 @@ const systemPromptDriftPlugin: Plugin = async ({ client, directory }) => {
346
378
  if (!deletedSessionId) {
347
379
  return
348
380
  }
381
+ const state = sessions.get(deletedSessionId)
382
+ if (state?.pendingCompareTimeout) {
383
+ clearTimeout(state.pendingCompareTimeout)
384
+ }
349
385
  sessions.delete(deletedSessionId)
350
386
  },
351
387
  catch: (error) => {
package/src/utils.ts CHANGED
@@ -82,7 +82,7 @@ export function generateBotInstallUrl({
82
82
 
83
83
  export const KIMAKI_GATEWAY_APP_ID =
84
84
  process.env.KIMAKI_GATEWAY_APP_ID || '1477605701202481173'
85
- export const KIMAKI_WEBSITE_URL = process.env.KIMAKI_WEBSITE_URL || 'https://kimaki.xyz'
85
+ export const KIMAKI_WEBSITE_URL = process.env.KIMAKI_WEBSITE_URL || 'https://kimaki.dev'
86
86
 
87
87
  export function generateDiscordInstallUrlForBot({
88
88
  appId,