kimaki 0.22.0 → 0.23.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.
Files changed (47) hide show
  1. package/dist/anthropic-auth-plugin.js +78 -33
  2. package/dist/anthropic-auth-state.js +62 -27
  3. package/dist/anthropic-auth-state.test.js +55 -2
  4. package/dist/channel-management.js +29 -6
  5. package/dist/cli-commands/project.js +6 -1
  6. package/dist/commands/login.js +1 -1
  7. package/dist/commands/new-worktree.js +9 -3
  8. package/dist/commands/queue.js +12 -2
  9. package/dist/database.js +7 -8
  10. package/dist/db.js +1 -0
  11. package/dist/discord-bot.js +9 -0
  12. package/dist/hrana-server.js +19 -0
  13. package/dist/oauth-rotation-shared.js +21 -0
  14. package/dist/opencode.js +73 -4
  15. package/dist/schema.js +3 -0
  16. package/dist/session-handler/global-event-listener.js +38 -6
  17. package/dist/session-handler/thread-runtime-state.js +2 -0
  18. package/dist/session-handler/thread-session-runtime.js +19 -3
  19. package/dist/system-message.js +34 -20
  20. package/dist/system-message.test.js +34 -20
  21. package/dist/task-runner.js +8 -4
  22. package/dist/worktree-lifecycle.e2e.test.js +16 -2
  23. package/package.json +6 -6
  24. package/skills/holocron/SKILL.md +11 -8
  25. package/src/anthropic-auth-plugin.ts +82 -35
  26. package/src/anthropic-auth-state.test.ts +73 -1
  27. package/src/anthropic-auth-state.ts +85 -25
  28. package/src/channel-management.ts +35 -6
  29. package/src/cli-commands/project.ts +6 -1
  30. package/src/commands/login.ts +1 -1
  31. package/src/commands/new-worktree.ts +14 -3
  32. package/src/commands/queue.ts +13 -2
  33. package/src/database.ts +8 -9
  34. package/src/db.ts +1 -0
  35. package/src/discord-bot.ts +12 -0
  36. package/src/hrana-server.ts +19 -0
  37. package/src/oauth-rotation-shared.ts +22 -0
  38. package/src/opencode.ts +90 -6
  39. package/src/schema.sql +1 -0
  40. package/src/schema.ts +3 -0
  41. package/src/session-handler/global-event-listener.ts +38 -6
  42. package/src/session-handler/thread-runtime-state.ts +4 -1
  43. package/src/session-handler/thread-session-runtime.ts +25 -3
  44. package/src/system-message.test.ts +34 -20
  45. package/src/system-message.ts +34 -20
  46. package/src/task-runner.ts +8 -4
  47. package/src/worktree-lifecycle.e2e.test.ts +18 -2
@@ -309,7 +309,7 @@ ${escapePromptText(repliedMessage.text)}
309
309
  : []),
310
310
  ...(worktree && worktreeChanged
311
311
  ? [
312
- `<system-reminder>\nThis session is running inside a git worktree. The working directory (cwd / pwd) has changed. The user expects you to edit files in the new cwd. You MUST operate inside the new worktree from now on.\n- New worktree path (new cwd / pwd, edit files here): ${worktree.worktreeDirectory}\n- Branch: ${worktree.branch}\n- Main repo path (previous folder, DO NOT TOUCH): ${worktree.mainRepoDirectory}\n- To find the base branch (the branch this worktree was created from): \`git -C ${worktree.mainRepoDirectory} symbolic-ref --short HEAD\`\n- To find the base commit (the commit this worktree diverged from): \`git merge-base <base-branch> HEAD\`\nYou MUST read, write, and edit files only under the new worktree path ${worktree.worktreeDirectory}. You MUST NOT read, write, or edit any files under the main repo path ${worktree.mainRepoDirectory} — even though it is the same project, that folder is a separate checkout and the user or another agent may be actively working there, so writing to it would override their unrelated changes. Run all checks (tests, builds, lint) inside the new worktree. Do not create another worktree by default. Ask before merging changes back to the main branch.\n</system-reminder>`,
312
+ `<system-reminder>\nThis session is running inside a git worktree. The working directory (cwd / pwd) has changed. The user expects you to edit files in the new cwd. You MUST operate inside the new worktree from now on.\n- New worktree path (new cwd / pwd, edit files here): ${worktree.worktreeDirectory}\n- Branch: ${worktree.branch}\n- Main repo path (previous folder, DO NOT TOUCH): ${worktree.mainRepoDirectory}\n- To find the base branch (the branch this worktree was created from): \`git -C ${worktree.mainRepoDirectory} symbolic-ref --short HEAD\`\n- To find the base commit (the commit this worktree diverged from): \`git merge-base <base-branch> HEAD\`\nYou MUST read, write, and edit files only under the new worktree path ${worktree.worktreeDirectory}. You MUST NOT read, write, or edit any files under the main repo path ${worktree.mainRepoDirectory} — even though it is the same project, that folder is a separate checkout and the user or another agent may be actively working there, so writing to it would override their unrelated changes. Run all checks (tests, builds, lint) inside the new worktree. Do not create another worktree by default. To merge this worktree into the main branch, run \`kimaki merge-worktree\`. If it reports rebase conflicts, resolve them and rerun until it succeeds.\n</system-reminder>`,
313
313
  ]
314
314
  : []),
315
315
  ]
@@ -575,18 +575,30 @@ When the user specifies a time without a timezone, ask them to confirm their tim
575
575
 
576
576
  \`--wait\` is incompatible with \`--send-at\` because scheduled tasks run in the future.
577
577
 
578
- For scheduled tasks, use long and detailed prompts with goal, constraints, expected output format, and explicit completion criteria.
578
+ Keep scheduled task prompts **short**. The prompt text becomes the first message in the Discord thread, so long prompts clutter the channel. Instead of inlining the full task description in \`--prompt\`, write a markdown file in the project's \`tasks/\` folder and reference it:
579
579
 
580
- Notification prompts must be very detailed. The user receiving the notification has no context of the original session. Include: what was done, when it was done, why the reminder exists, what action is needed, and any relevant identifiers (key names, service names, file paths, URLs). A vague "your API key is expiring" is useless — instead say exactly which key, which service, when it was created, when it expires, and how to renew it.
580
+ \`\`\`bash
581
+ kimaki send --channel ${channelId} --prompt 'Read tasks/weekly-test-suite.md and follow instructions' --send-at '0 9 * * 1' --agent <current_agent>${parentSessionArg}${userArg}
582
+ \`\`\`
583
+
584
+ The task file should contain all the detail: goal, constraints, expected output, completion criteria. Use this frontmatter format:
585
+
586
+ \`\`\`yaml
587
+ ---
588
+ title: Weekly test suite
589
+ description: >
590
+ Managed by kimaki scheduled task. Do not move or delete this file
591
+ without also updating the kimaki task (kimaki task list / kimaki task edit).
592
+ ---
593
+ \`\`\`
594
+
595
+ For simple reminders and notifications (\`--notify-only\`), inline the prompt directly since there is no AI session to read files.
581
596
 
582
- Notification strategy for scheduled tasks:
597
+ Notification strategy:
583
598
  - NEVER use \`@username\` (e.g. \`@Tommy\`) directly in task prompts. The prompt text becomes the first message in the thread, so a raw \`@\` mention triggers an actual Discord ping every time the task fires. Instead, wrap it in inline code like \`\\\`@Tommy\\\`\`, or use Discord user ID mentions like \`<@USER_ID>\` only in the body of the prompt where the agent will process it, not in the opening line.
584
- - Prefer selective mentions in the prompt instead of relying on broad thread notifications.
585
- - If a task needs user attention, include this instruction in the prompt: "mention the user via Discord user ID when task requires user review or notification".
586
- - Without \`--user\`, there is no guaranteed direct user mention path; task output should mention users only when relevant.
587
- - With \`--user\`, the user is added to the thread and may receive more frequent thread-level notifications.
588
- - If a scheduled task completes with no actionable result and no user-visible change, prefer archiving the session after the final message so Discord does not keep a no-op thread highlighted.
589
- - Example no-op cleanup command: \`kimaki session archive ${archiveTarget}\`
599
+ - If a task needs user attention, add "mention the user via Discord user ID when task requires user review" in the task md file.
600
+ - With \`--user\`, the user is added to the thread and receives thread-level notifications.
601
+ - If a scheduled task completes with no actionable result, archive the session: \`kimaki session archive ${archiveTarget}\`
590
602
 
591
603
  Manage scheduled tasks with:
592
604
 
@@ -596,20 +608,22 @@ kimaki task delete <id>
596
608
 
597
609
  \`kimaki session list\` also shows if a session was started by a scheduled \`delay\` or \`cron\` task, including task ID when available.
598
610
 
611
+ **Never duplicate tasks to run more frequently.** If a task should run twice a day (morning and evening), edit the existing task's cron expression instead of creating a second task. Cron supports comma-separated hours:
612
+
613
+ \`\`\`bash
614
+ # runs at 9:00 UTC and 18:00 UTC every day
615
+ kimaki task edit <id> --send-at '0 9,18 * * *'
616
+ \`\`\`
617
+
599
618
  Use case patterns:
600
- - Reminder flows: create deadline reminders in this channel with one-time \`--send-at\`; mention only if action is required.
601
- - 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.
602
- - Weekly QA: schedule "run full test suite, inspect failures, post summary, and mention the user via Discord ID only when failures require review".
603
- - Weekly benchmark automation: schedule a benchmark prompt that runs model evals, writes JSON outputs in the repo, commits results, and mentions only for regressions.
604
- - 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.
605
- - Quiet no-op checks: if a recurring task checks something and finds nothing to report, let it post a brief final summary and then archive the session with \`kimaki session archive ${archiveTarget}\`. Example: a scheduled email triage run that finds no new emails should archive itself so it does not add noise to Discord.
606
- - 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:
619
+ - Reminder flows: create deadline reminders with one-time \`--send-at\` and \`--notify-only\`; mention only if action is required.
620
+ - Proactive reminders: when you encounter time-sensitive information (API key expiration, certificate renewal, trial ending), schedule a \`--notify-only\` reminder before the deadline. Always tell the user you scheduled the reminder so they know.
621
+ - Weekly QA / recurring maintenance: write the full task spec in \`tasks/\` and schedule a short prompt pointing to it.
622
+ - Thread reminders: when the user says "remind me about this in 2 hours", use \`--send-at\` with \`--thread\` to resurface the current thread:
607
623
 
608
624
  kimaki send ${sendToSelfTarget} --prompt 'Reminder: <@USER_ID> you asked to be reminded about this thread.' --send-at '<future_UTC_time>' --notify-only --agent <current_agent>
609
625
 
610
- 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.
611
-
612
- Scheduled tasks can maintain project memory by reading and updating an md file in the repository (for example \`docs/automation-notes.md\`) on each run.
626
+ Replace \`<future_UTC_time>\` with the computed UTC ISO timestamp.
613
627
 
614
628
  Worktrees are useful for handing off parallel tasks that need to be isolated from each other (each session works on its own branch).
615
629
 
@@ -10,7 +10,7 @@ import {
10
10
  markScheduledTaskCronRescheduled,
11
11
  markScheduledTaskCronRetry,
12
12
  markScheduledTaskFailed,
13
- markScheduledTaskOneShotCompleted,
13
+ deleteScheduledTask,
14
14
  recoverStaleRunningScheduledTasks,
15
15
  type ScheduledTask,
16
16
  } from './database.js'
@@ -59,7 +59,10 @@ async function executeThreadScheduledTask({
59
59
  const marker: ThreadStartMarker = {
60
60
  start: true,
61
61
  scheduledKind: task.schedule_kind,
62
- scheduledTaskId: task.id,
62
+ // Only include scheduledTaskId for cron tasks. One-shot tasks are deleted
63
+ // after execution, so the ID would be stale by the time the bot processes
64
+ // the Discord event and tries to insert a session_start_sources row.
65
+ ...(task.schedule_kind === 'cron' ? { scheduledTaskId: task.id } : {}),
63
66
  ...(payload.agent ? { agent: payload.agent } : {}),
64
67
  ...(payload.model ? { model: payload.model } : {}),
65
68
  ...(payload.username ? { username: payload.username } : {}),
@@ -105,7 +108,8 @@ async function executeChannelScheduledTask({
105
108
  : {
106
109
  start: true,
107
110
  scheduledKind: task.schedule_kind,
108
- scheduledTaskId: task.id,
111
+ // Only include scheduledTaskId for cron tasks (see thread variant comment)
112
+ ...(task.schedule_kind === 'cron' ? { scheduledTaskId: task.id } : {}),
109
113
  ...(payload.worktreeName ? { worktree: payload.worktreeName } : {}),
110
114
  ...(payload.cwd ? { cwd: payload.cwd } : {}),
111
115
  ...(payload.agent ? { agent: payload.agent } : {}),
@@ -222,7 +226,7 @@ async function finalizeSuccessfulTask({
222
226
  completedAt: Date
223
227
  }): Promise<void> {
224
228
  if (task.schedule_kind === 'at') {
225
- await markScheduledTaskOneShotCompleted({ taskId: task.id, completedAt })
229
+ await deleteScheduledTask(task.id)
226
230
  return
227
231
  }
228
232
 
@@ -61,6 +61,10 @@ function normalizeWorktreeLifecycleText(text: string): string {
61
61
  .replaceAll(CHANNEL_WORKTREE_NAME, 'CHANNEL_WORKTREE_NAME')
62
62
  .replaceAll(AUTO_WORKTREE_SUFFIX, 'AUTO_WORKTREE_NAME')
63
63
  .replaceAll(WORKTREE_NAME, 'WORKTREE_NAME')
64
+ .replace(
65
+ /opencode\/kimaki-rply-wth-exctly-snd-at-wt-[a-z0-9]+/g,
66
+ 'AUTO_WORKTREE_BRANCH',
67
+ )
64
68
  .replaceAll(WORKTREE_SUFFIX, 'SUFFIX')
65
69
  .replace(/ses_[a-zA-Z0-9]+/g, 'ses_TEST')
66
70
  .replace(/<#\d+>/g, '<#THREAD_ID>')
@@ -444,11 +448,23 @@ describe('worktree lifecycle', () => {
444
448
  expect(sourceSessionAfterFork).toBe(sessionBefore)
445
449
  const worktreeSession = await getThreadSession(worktreeThread.id)
446
450
  expect(worktreeSession).toBeTruthy()
451
+ if (!worktreeSession) throw new Error('Worktree session was not persisted')
447
452
  expect(worktreeSession).not.toBe(sessionBefore)
448
453
  await expect(getThreadWorktreeOrWorkspace(thread.id)).resolves.toBeUndefined()
449
454
  const worktreeInfo = await getThreadWorktreeOrWorkspace(worktreeThread.id)
450
455
  expect(worktreeInfo?.status).toBe('ready')
451
456
  expect(worktreeInfo?.workspace_directory).toContain(WORKTREE_NAME)
457
+ const worktreeDirectory = worktreeInfo?.workspace_directory
458
+ if (!worktreeDirectory) throw new Error('Worktree directory was not persisted')
459
+ const getWorktreeClient = await initializeOpencodeForDirectory(
460
+ worktreeDirectory,
461
+ )
462
+ if (getWorktreeClient instanceof Error) throw getWorktreeClient
463
+ const worktreeSessionResponse = await getWorktreeClient().session.get({
464
+ sessionID: worktreeSession,
465
+ directory: worktreeDirectory,
466
+ })
467
+ expect(worktreeSessionResponse.data?.directory).toBe(worktreeDirectory)
452
468
 
453
469
  const runtimeAfter = getRuntime(thread.id)
454
470
  expect(runtimeAfter).toBe(runtimeBefore)
@@ -841,9 +857,9 @@ describe('worktree lifecycle', () => {
841
857
  » **kimaki-cli:**
842
858
  Reply with exactly: send-auto-wt-SUFFIX
843
859
  [embed]
844
- 🌳 **Worktree: opencode/kimaki-rply-wth-exctly-snd-at-wt-l8kct**
860
+ 🌳 **Worktree: AUTO_WORKTREE_BRANCH**
845
861
  📁 \`/tmp/worktrees/WORKTREE_NAME\`
846
- 🌿 Branch: \`opencode/kimaki-rply-wth-exctly-snd-at-wt-l8kct\`
862
+ 🌿 Branch: \`AUTO_WORKTREE_BRANCH\`
847
863
  *using deterministic-provider/deterministic-v2*
848
864
  ⬥ ok"
849
865
  `)