@loicngr/kobo 2.1.2 → 2.1.5

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 (200) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/CONFIGURATION.md +11 -3
  3. package/dist/server/index.js +24 -91
  4. package/dist/server/routes/git.js +6 -7
  5. package/dist/server/routes/settings.js +21 -11
  6. package/dist/server/routes/workspaces.js +82 -41
  7. package/dist/server/services/agent/engines/claude-code/engine.js +87 -20
  8. package/dist/server/services/agent/engines/claude-code/event-mapper.js +88 -22
  9. package/dist/server/services/agent/engines/claude-code/options-builder.js +1 -0
  10. package/dist/server/services/agent/engines/codex/engine.js +27 -5
  11. package/dist/server/services/agent/engines/codex/options-builder.js +1 -0
  12. package/dist/server/services/agent/integration-mcp.js +4 -3
  13. package/dist/server/services/agent/orchestrator.js +36 -1
  14. package/dist/server/services/agent/session-controller.js +12 -0
  15. package/dist/server/services/engine-handoff-service.js +120 -24
  16. package/dist/server/services/pr-checkout-service.js +90 -53
  17. package/dist/server/services/pr-watcher-service.js +13 -7
  18. package/dist/server/services/retention/worker.js +40 -0
  19. package/dist/server/services/review-service.js +8 -4
  20. package/dist/server/services/session-event-metrics.js +43 -0
  21. package/dist/server/services/session-handoff-service.js +6 -4
  22. package/dist/server/services/templates-service.js +67 -49
  23. package/dist/server/services/terminal-service.js +45 -14
  24. package/dist/server/services/terminal-websocket-service.js +143 -0
  25. package/dist/server/services/wakeup-service.js +23 -21
  26. package/dist/server/services/workspace-service.js +3 -44
  27. package/dist/server/services/workspace-template-service.js +16 -18
  28. package/dist/server/services/worktree-purge-service.js +1 -4
  29. package/dist/server/services/ws-events-retention-service.js +197 -104
  30. package/dist/server/services/ws-events-retention-worker-service.js +112 -0
  31. package/dist/server/utils/atomic-json-file.js +23 -0
  32. package/dist/server/utils/git-ops.js +262 -60
  33. package/dist/shared/codex-models.js +20 -3
  34. package/dist/shared/models.js +12 -0
  35. package/package.json +8 -8
  36. package/src/client/dist/pwa/assets/{ActivityFeed-DCmiJP-z.css → ActivityFeed-B4lTKMVE.css} +1 -1
  37. package/src/client/dist/pwa/assets/ActivityFeed-DXWXioW9.js +8 -0
  38. package/src/client/dist/pwa/assets/ChangelogPage-BL5Jh8-v.js +1 -0
  39. package/src/client/dist/pwa/assets/ChangelogPage-DVyx9ZjV.css +1 -0
  40. package/src/client/dist/pwa/assets/{ClosePopup-BikuzYle.js → ClosePopup-C1a82pL9.js} +1 -1
  41. package/src/client/dist/pwa/assets/ComparisonPanel-Bv2SZrVZ.js +2 -0
  42. package/src/client/dist/pwa/assets/ComparisonPanel-ByVbqYCs.css +1 -0
  43. package/src/client/dist/pwa/assets/CreatePage-CMyl2j8Z.css +1 -0
  44. package/src/client/dist/pwa/assets/CreatePage-d43ITOC1.js +2 -0
  45. package/src/client/dist/pwa/assets/DashboardPage-BEXXRguE.js +1 -0
  46. package/src/client/dist/pwa/assets/DashboardPage-CtaHaVdf.css +1 -0
  47. package/src/client/dist/pwa/assets/DiffViewer-B92w_jd7.js +8 -0
  48. package/src/client/dist/pwa/assets/DocumentsPanel-C9JXyqHg.js +1 -0
  49. package/src/client/dist/pwa/assets/{DrawerToggleButton-Dfvkaro0.js → DrawerToggleButton-Btx8QwHo.js} +1 -1
  50. package/src/client/dist/pwa/assets/{GitPanel-C4wOcZV2.css → GitPanel-C3blk_Pd.css} +1 -1
  51. package/src/client/dist/pwa/assets/GitPanel-Cgz9crLN.js +3 -0
  52. package/src/client/dist/pwa/assets/HealthPage-CDhgJQkL.js +1 -0
  53. package/src/client/dist/pwa/assets/HealthPage-DT8FGrKA.css +1 -0
  54. package/src/client/dist/pwa/assets/MainLayout-DTTtj9sq.js +2 -0
  55. package/src/client/dist/pwa/assets/{MainLayout-gHENJEFQ.css → MainLayout-knNNtkre.css} +1 -1
  56. package/src/client/dist/pwa/assets/QBadge-rGILsUYl.js +1 -0
  57. package/src/client/dist/pwa/assets/QBanner-7EWRdc4U.js +1 -0
  58. package/src/client/dist/pwa/assets/QBtn-b_baQtAt.js +1 -0
  59. package/src/client/dist/pwa/assets/QBtnDropdown-vjat4GZl.js +1 -0
  60. package/src/client/dist/pwa/assets/QBtnGroup-CUmAgBV8.js +1 -0
  61. package/src/client/dist/pwa/assets/QBtnToggle-COL46j6W.js +1 -0
  62. package/src/client/dist/pwa/assets/QChip-CG1kWhFm.js +1 -0
  63. package/src/client/dist/pwa/assets/QExpansionItem-BQuy89vv.js +1 -0
  64. package/src/client/dist/pwa/assets/QItemSection-BrVklYfM.js +1 -0
  65. package/src/client/dist/pwa/assets/QList-BVX-b8nc.js +1 -0
  66. package/src/client/dist/pwa/assets/QMarkupTable-B8_FKLvo.js +1 -0
  67. package/src/client/dist/pwa/assets/QPage-BCDXjfVj.js +1 -0
  68. package/src/client/dist/pwa/assets/QSelect-DBW_TZri.js +1 -0
  69. package/src/client/dist/pwa/assets/QSpinnerDots-BsKmccpX.js +1 -0
  70. package/src/client/dist/pwa/assets/QTooltip-DzYbguTR.js +1 -0
  71. package/src/client/dist/pwa/assets/SchedulePanel-BX6iKh6S.js +1 -0
  72. package/src/client/dist/pwa/assets/SchedulePanel-CBM_GpS3.css +1 -0
  73. package/src/client/dist/pwa/assets/SearchPage-_In2gH2P.js +1 -0
  74. package/src/client/dist/pwa/assets/SettingsPage-BqkNYzC5.js +9 -0
  75. package/src/client/dist/pwa/assets/{SettingsPage-BYdLXJEJ.css → SettingsPage-CpykRKq2.css} +1 -1
  76. package/src/client/dist/pwa/assets/SplitWorkspacePage-DRoXbSRP.js +1 -0
  77. package/src/client/dist/pwa/assets/{TerminalPanel-B9LDe7uG.js → TerminalPanel-CfqiuSuk.js} +2 -2
  78. package/src/client/dist/pwa/assets/{TerminalPanel-DbVZk0vD.css → TerminalPanel-Dz9EE-Ph.css} +1 -1
  79. package/src/client/dist/pwa/assets/TouchPan-DcOCRf7x.js +1 -0
  80. package/src/client/dist/pwa/assets/{TourReplayButton-Dtv_UDaY.js → TourReplayButton-gp7m6tSV.js} +1 -1
  81. package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-DSw8Upcp.css +1 -0
  82. package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-IcoMGkJh.js +1 -0
  83. package/src/client/dist/pwa/assets/{WorkspacePage--5HsnMQ1.css → WorkspacePage-BnW-gp3u.css} +1 -1
  84. package/src/client/dist/pwa/assets/WorkspacePage-CR2xAzW_.js +4 -0
  85. package/src/client/dist/pwa/assets/{browser-B15GGwtW.js → browser-BveHkbe-.js} +1 -1
  86. package/src/client/dist/pwa/assets/build-path-tree-Dtp7rlxS.js +1 -0
  87. package/src/client/dist/pwa/assets/custom-sounds-CBoWzJ85.js +1 -0
  88. package/src/client/dist/pwa/assets/de-BJsWkZro.js +17 -0
  89. package/src/client/dist/pwa/assets/defineProperty-BbfpZ9Tg.js +1 -0
  90. package/src/client/dist/pwa/assets/documents-sb2sQ4JC.js +1 -0
  91. package/src/client/dist/pwa/assets/{editor.api-DhaLhNl-.js → editor.api-CbkPxoJf.js} +1 -1
  92. package/src/client/dist/pwa/assets/efforts-BCli1Unj.js +9 -0
  93. package/src/client/dist/pwa/assets/es-tNYw7itB.js +17 -0
  94. package/src/client/dist/pwa/assets/expand-template-CfaEhb2f.js +1 -0
  95. package/src/client/dist/pwa/assets/{format-2JisC3s1.js → format-BRl2DUlm.js} +7 -7
  96. package/src/client/dist/pwa/assets/formatters-C5TjPSR_.js +1 -0
  97. package/src/client/dist/pwa/assets/fr-B5sFski4.js +17 -0
  98. package/src/client/dist/pwa/assets/{freemarker2-PYrDqU04.js → freemarker2-CDmvOQyn.js} +1 -1
  99. package/src/client/dist/pwa/assets/{handlebars-BuiY0fFf.js → handlebars-Bx_w8mx1.js} +1 -1
  100. package/src/client/dist/pwa/assets/{html-Du3cjdug.js → html-DVRfiRkS.js} +1 -1
  101. package/src/client/dist/pwa/assets/i18n-Bm-L4nW3.js +1 -0
  102. package/src/client/dist/pwa/assets/i18n-D21JKsVt.js +19 -0
  103. package/src/client/dist/pwa/assets/index-C7vFf6p2.js +2 -0
  104. package/src/client/dist/pwa/assets/index-C8zAZ-k-.css +1 -0
  105. package/src/client/dist/pwa/assets/it-Bc2FncM9.js +17 -0
  106. package/src/client/dist/pwa/assets/{javascript-Bb48hPli.js → javascript-CPvttU5J.js} +1 -1
  107. package/src/client/dist/pwa/assets/latest-request-CN4-B22z.js +1 -0
  108. package/src/client/dist/pwa/assets/{liquid-Be77iQe3.js → liquid-CI0gQddb.js} +1 -1
  109. package/src/client/dist/pwa/assets/{mdx-CfohNJja.js → mdx-BveBwYJH.js} +1 -1
  110. package/src/client/dist/pwa/assets/{monaco-contributions-CyM4wsIg.js → monaco-contributions-CHXFh4P2.js} +1 -1
  111. package/src/client/dist/pwa/assets/monaco.contribution-DOK8t_9B.js +2 -0
  112. package/src/client/dist/pwa/assets/network-auth-CItnNZDh.js +1 -0
  113. package/src/client/dist/pwa/assets/{notify-theme-DvvaSyg1.js → notify-theme-BO6r1Rd7.js} +1 -1
  114. package/src/client/dist/pwa/assets/pending-diff-open-DpS4iyDM.js +1 -0
  115. package/src/client/dist/pwa/assets/{permissionModes-B-ciuUbz.js → permissionModes-Cc5VaOdC.js} +1 -1
  116. package/src/client/dist/pwa/assets/preload-helper-BZ1Pz5am.js +1 -0
  117. package/src/client/dist/pwa/assets/{private.use-form-Bk8ti264.js → private.use-form-vxvbh_VN.js} +1 -1
  118. package/src/client/dist/pwa/assets/{project-color-Be0t12-n.js → project-color-Bnv02P9D.js} +1 -1
  119. package/src/client/dist/pwa/assets/{python-BNemmtx8.js → python-BesLCxpi.js} +1 -1
  120. package/src/client/dist/pwa/assets/{razor-3NM8iFVf.js → razor-Dv1PMxuY.js} +1 -1
  121. package/src/client/dist/pwa/assets/{reactivity.esm-bundler-Bo9anJjg.js → reactivity.esm-bundler-tuEvQ47f.js} +1 -1
  122. package/src/client/dist/pwa/assets/{render-chat-markdown-BWJNIDv3.js → render-chat-markdown-CafzCcmu.js} +1 -1
  123. package/src/client/dist/pwa/assets/runtime-core.esm-bundler-DOiZcasU.js +1 -0
  124. package/src/client/dist/pwa/assets/{typescript-DwyZ4CF7.js → typescript-26L2Xsu9.js} +1 -1
  125. package/src/client/dist/pwa/assets/use-checkbox-BLeif5-t.js +1 -0
  126. package/src/client/dist/pwa/assets/use-id-b4A2I8LB.js +1 -0
  127. package/src/client/dist/pwa/assets/use-is-mobile-DLIZxEo9.js +1 -0
  128. package/src/client/dist/pwa/assets/use-search-index-status-Mv3pIO6p.js +1 -0
  129. package/src/client/dist/pwa/assets/{use-slide-transition-CiaDm-tR.js → use-slide-transition-C5eDGOmu.js} +1 -1
  130. package/src/client/dist/pwa/assets/{use-tours-CjASQcG5.js → use-tours-B5uIqxRS.js} +2 -2
  131. package/src/client/dist/pwa/assets/use-worktree-restore-BBChALUM.js +1 -0
  132. package/src/client/dist/pwa/assets/workspace-templates-CDY-MiH2.js +1 -0
  133. package/src/client/dist/pwa/assets/{xml-B1UujXdS.js → xml-CkRojTcc.js} +1 -1
  134. package/src/client/dist/pwa/assets/{yaml-mHN8kNgU.js → yaml-DKkQHwew.js} +1 -1
  135. package/src/client/dist/pwa/index.html +12 -9
  136. package/src/client/dist/pwa/sw.js +1 -1
  137. package/src/client/dist/pwa/assets/ActivityFeed-CO5rOaa5.js +0 -8
  138. package/src/client/dist/pwa/assets/ChangelogPage-DKJkFiU_.css +0 -1
  139. package/src/client/dist/pwa/assets/ChangelogPage-DNRcPRtW.js +0 -1
  140. package/src/client/dist/pwa/assets/ComparisonPanel-CRRaDs2D.css +0 -1
  141. package/src/client/dist/pwa/assets/ComparisonPanel-DZoDqgJI.js +0 -2
  142. package/src/client/dist/pwa/assets/CreatePage-B9C9J9Pw.css +0 -1
  143. package/src/client/dist/pwa/assets/CreatePage-wNH35uTG.js +0 -2
  144. package/src/client/dist/pwa/assets/DashboardPage-DUyy4SzS.js +0 -1
  145. package/src/client/dist/pwa/assets/DashboardPage-XiCj24i2.css +0 -1
  146. package/src/client/dist/pwa/assets/DiffViewer-CZ5QwXqi.js +0 -8
  147. package/src/client/dist/pwa/assets/DocumentsPanel-sBatgKA-.js +0 -1
  148. package/src/client/dist/pwa/assets/GitPanel-B8e1olfm.js +0 -3
  149. package/src/client/dist/pwa/assets/HealthPage-BwXYpczh.js +0 -1
  150. package/src/client/dist/pwa/assets/HealthPage-CFZ-tdKK.css +0 -1
  151. package/src/client/dist/pwa/assets/MainLayout-DFXIS_wv.js +0 -2
  152. package/src/client/dist/pwa/assets/QBadge-CQF1smL-.js +0 -1
  153. package/src/client/dist/pwa/assets/QBanner-bdDA-A5g.js +0 -1
  154. package/src/client/dist/pwa/assets/QBtn-CcQQrYA_.js +0 -1
  155. package/src/client/dist/pwa/assets/QBtnDropdown-C-1EhO9J.js +0 -1
  156. package/src/client/dist/pwa/assets/QBtnGroup-wd5lKRXu.js +0 -1
  157. package/src/client/dist/pwa/assets/QBtnToggle-BnC8rTql.js +0 -1
  158. package/src/client/dist/pwa/assets/QChip-D75nKi-L.js +0 -1
  159. package/src/client/dist/pwa/assets/QExpansionItem-RpMPvfxX.js +0 -1
  160. package/src/client/dist/pwa/assets/QItemSection-Bt9H35JJ.js +0 -1
  161. package/src/client/dist/pwa/assets/QList-D5msNbSZ.js +0 -1
  162. package/src/client/dist/pwa/assets/QMarkupTable-D3REW6RC.js +0 -1
  163. package/src/client/dist/pwa/assets/QPage-HAaeQ-cv.js +0 -1
  164. package/src/client/dist/pwa/assets/QSelect-Dpru_T81.js +0 -1
  165. package/src/client/dist/pwa/assets/QSpinnerDots-DDsPDUH9.js +0 -1
  166. package/src/client/dist/pwa/assets/QTooltip-C31vXtse.js +0 -1
  167. package/src/client/dist/pwa/assets/SchedulePanel-BYKSFoFj.css +0 -1
  168. package/src/client/dist/pwa/assets/SchedulePanel-D6LTiRFg.js +0 -1
  169. package/src/client/dist/pwa/assets/SearchPage-Dj487BdP.js +0 -1
  170. package/src/client/dist/pwa/assets/SettingsPage-CJmgFiAa.js +0 -9
  171. package/src/client/dist/pwa/assets/SplitWorkspacePage-BGSDgGIO.js +0 -1
  172. package/src/client/dist/pwa/assets/TouchPan-DJqqxDBF.js +0 -1
  173. package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-CrLP_I5S.css +0 -1
  174. package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-VlJCIkwU.js +0 -1
  175. package/src/client/dist/pwa/assets/WorkspacePage-B_SlX8YB.js +0 -4
  176. package/src/client/dist/pwa/assets/build-path-tree-CU6uXunR.js +0 -1
  177. package/src/client/dist/pwa/assets/custom-sounds-CDJsgChY.js +0 -1
  178. package/src/client/dist/pwa/assets/de-B_Y4EArT.js +0 -17
  179. package/src/client/dist/pwa/assets/documents-Cf6nmu8Z.js +0 -1
  180. package/src/client/dist/pwa/assets/efforts-DoYZi0AZ.js +0 -9
  181. package/src/client/dist/pwa/assets/engineFeatures-C9XiCfvv.js +0 -1
  182. package/src/client/dist/pwa/assets/es-DtsTTShP.js +0 -17
  183. package/src/client/dist/pwa/assets/expand-template-LDLM-6iN.js +0 -1
  184. package/src/client/dist/pwa/assets/formatters-ZVNs2VOl.js +0 -1
  185. package/src/client/dist/pwa/assets/fr-C82MIVrA.js +0 -17
  186. package/src/client/dist/pwa/assets/i18n-BumeGGx_.js +0 -1
  187. package/src/client/dist/pwa/assets/index-DolbyIM4.js +0 -20
  188. package/src/client/dist/pwa/assets/index-VMFLfvp2.css +0 -1
  189. package/src/client/dist/pwa/assets/it-BJNuTcj2.js +0 -17
  190. package/src/client/dist/pwa/assets/monaco.contribution-BcIfN6Mi.js +0 -2
  191. package/src/client/dist/pwa/assets/network-auth-B1xAaHmx.js +0 -1
  192. package/src/client/dist/pwa/assets/pending-diff-open-B6N0vI5n.js +0 -1
  193. package/src/client/dist/pwa/assets/pinia-CTJ3OfP3.js +0 -1
  194. package/src/client/dist/pwa/assets/runtime-core.esm-bundler-CNFKm4ij.js +0 -1
  195. package/src/client/dist/pwa/assets/use-checkbox-2xzX4yYj.js +0 -1
  196. package/src/client/dist/pwa/assets/use-id-vOr8PRTG.js +0 -1
  197. package/src/client/dist/pwa/assets/use-is-mobile-CJfSpTHx.js +0 -1
  198. package/src/client/dist/pwa/assets/use-search-index-status-Bluf9L99.js +0 -1
  199. package/src/client/dist/pwa/assets/workspace-templates-BxYxJt3S.js +0 -1
  200. /package/src/client/dist/pwa/assets/{engineFeatures-B-9jHocY.css → use-worktree-restore-B-9jHocY.css} +0 -0
@@ -67,7 +67,7 @@ async function gitAsync(repoPath, args, timeout = READ_ONLY_GIT_TIMEOUT_MS) {
67
67
  cwd: repoPath,
68
68
  encoding: 'utf-8',
69
69
  timeout,
70
- maxBuffer: 10 * 1024 * 1024,
70
+ maxBuffer: GIT_MAX_BUFFER_BYTES,
71
71
  });
72
72
  return stdout.trimEnd();
73
73
  }
@@ -231,6 +231,26 @@ export function deleteRemoteBranch(repoPath, branchName, remote = 'origin') {
231
231
  throw new Error(`Failed to delete remote branch '${remote}/${branchName}': ${message}`);
232
232
  }
233
233
  }
234
+ /** Non-blocking local deletion with the synchronous helper's error contract. */
235
+ export async function deleteLocalBranchAsync(repoPath, branchName) {
236
+ try {
237
+ await gitNetworkAsync(repoPath, ['branch', '-D', branchName]);
238
+ }
239
+ catch (error) {
240
+ const message = error instanceof Error ? error.message : String(error);
241
+ throw new Error(`Failed to delete local branch '${branchName}': ${message}`);
242
+ }
243
+ }
244
+ /** Non-blocking remote deletion; keeps credential prompts disabled and the network deadline. */
245
+ export async function deleteRemoteBranchAsync(repoPath, branchName, remote = 'origin') {
246
+ try {
247
+ await gitNetworkAsync(repoPath, ['push', remote, '--delete', branchName]);
248
+ }
249
+ catch (error) {
250
+ const message = error instanceof Error ? error.message : String(error);
251
+ throw new Error(`Failed to delete remote branch '${remote}/${branchName}': ${message}`);
252
+ }
253
+ }
234
254
  /**
235
255
  * Push a branch to the remote with upstream tracking (`git push -u`).
236
256
  * When `options.force` is true, adds `--force-with-lease` (safer than `--force`:
@@ -341,18 +361,21 @@ export function getConflictedFiles(repoPath) {
341
361
  return [];
342
362
  }
343
363
  }
364
+ function classifyOngoingGitOperation(dir) {
365
+ if (existsSync(join(dir, 'MERGE_HEAD')))
366
+ return 'merge';
367
+ if (existsSync(join(dir, 'rebase-merge')) || existsSync(join(dir, 'rebase-apply')))
368
+ return 'rebase';
369
+ if (existsSync(join(dir, 'CHERRY_PICK_HEAD')) || existsSync(join(dir, 'sequencer')))
370
+ return 'cherry-pick';
371
+ return null;
372
+ }
344
373
  /** Detect whether a merge, rebase or cherry-pick is currently in progress in the worktree. */
345
374
  export function getOngoingGitOperation(repoPath) {
346
375
  try {
347
376
  const gitDir = git(repoPath, ['rev-parse', '--git-dir']);
348
377
  const dir = gitDir.startsWith('/') ? gitDir : join(repoPath, gitDir);
349
- if (existsSync(join(dir, 'MERGE_HEAD')))
350
- return 'merge';
351
- if (existsSync(join(dir, 'rebase-merge')) || existsSync(join(dir, 'rebase-apply')))
352
- return 'rebase';
353
- if (existsSync(join(dir, 'CHERRY_PICK_HEAD')) || existsSync(join(dir, 'sequencer')))
354
- return 'cherry-pick';
355
- return null;
378
+ return classifyOngoingGitOperation(dir);
356
379
  }
357
380
  catch {
358
381
  return null;
@@ -781,6 +804,19 @@ export function getCommitsBetween(repoPath, base, head) {
781
804
  return '';
782
805
  }
783
806
  }
807
+ const COMMIT_LOG_FORMAT = '--pretty=format:%H%x00%h%x00%s%x00%an%x00%aI';
808
+ function parseCommitLog(raw) {
809
+ const commits = [];
810
+ for (const line of raw.split('\n')) {
811
+ if (!line)
812
+ continue;
813
+ const [sha, shortSha, subject, author, date] = line.split('\x00');
814
+ if (!sha)
815
+ continue;
816
+ commits.push({ sha, shortSha: shortSha ?? '', subject: subject ?? '', author: author ?? '', date: date ?? '' });
817
+ }
818
+ return commits;
819
+ }
784
820
  /**
785
821
  * List commits between the source branch and HEAD, each flagged with whether
786
822
  * it's already present on `origin/<workingBranch>`. Used by the Git panel
@@ -791,10 +827,9 @@ export function listBranchCommits(repoPath, sourceBranch, workingBranch, limit =
791
827
  const sourceRef = resolveBase(repoPath, sourceBranch);
792
828
  const remoteRef = `${remote}/${workingBranch}`;
793
829
  // NUL-delimited format: sha \0 shortSha \0 subject \0 author \0 iso date \n
794
- const FORMAT = '--pretty=format:%H%x00%h%x00%s%x00%an%x00%aI';
795
830
  let raw;
796
831
  try {
797
- raw = git(repoPath, ['log', `${sourceRef}..HEAD`, `--max-count=${limit}`, FORMAT]);
832
+ raw = git(repoPath, ['log', `${sourceRef}..HEAD`, `--max-count=${limit}`, COMMIT_LOG_FORMAT]);
798
833
  }
799
834
  catch {
800
835
  return [];
@@ -815,23 +850,7 @@ export function listBranchCommits(repoPath, sourceBranch, workingBranch, limit =
815
850
  catch {
816
851
  // remote ref unknown → leave pushedShas empty
817
852
  }
818
- const commits = [];
819
- for (const line of raw.split('\n')) {
820
- if (!line)
821
- continue;
822
- const [sha, shortSha, subject, author, date] = line.split('\x00');
823
- if (!sha)
824
- continue;
825
- commits.push({
826
- sha,
827
- shortSha: shortSha ?? '',
828
- subject: subject ?? '',
829
- author: author ?? '',
830
- date: date ?? '',
831
- isPushed: pushedShas.has(sha),
832
- });
833
- }
834
- return commits;
853
+ return parseCommitLog(raw).map((commit) => ({ ...commit, isPushed: pushedShas.has(commit.sha) }));
835
854
  }
836
855
  /**
837
856
  * List commits on `sourceBranch` that are NOT yet on `workingBranch` —
@@ -840,32 +859,16 @@ export function listBranchCommits(repoPath, sourceBranch, workingBranch, limit =
840
859
  */
841
860
  export function listCommitsBehind(repoPath, sourceBranch, workingBranch, limit = 50) {
842
861
  const sourceRef = resolveBase(repoPath, sourceBranch);
843
- const FORMAT = '--pretty=format:%H%x00%h%x00%s%x00%an%x00%aI';
844
862
  let raw;
845
863
  try {
846
- raw = git(repoPath, ['log', `${workingBranch}..${sourceRef}`, `--max-count=${limit}`, FORMAT]);
864
+ raw = git(repoPath, ['log', `${workingBranch}..${sourceRef}`, `--max-count=${limit}`, COMMIT_LOG_FORMAT]);
847
865
  }
848
866
  catch {
849
867
  return [];
850
868
  }
851
869
  if (!raw)
852
870
  return [];
853
- const commits = [];
854
- for (const line of raw.split('\n')) {
855
- if (!line)
856
- continue;
857
- const [sha, shortSha, subject, author, date] = line.split('\x00');
858
- if (!sha)
859
- continue;
860
- commits.push({
861
- sha,
862
- shortSha: shortSha ?? '',
863
- subject: subject ?? '',
864
- author: author ?? '',
865
- date: date ?? '',
866
- });
867
- }
868
- return commits;
871
+ return parseCommitLog(raw);
869
872
  }
870
873
  /**
871
874
  * Rename a branch in-place (`git branch -m <old> <new>`). Must be run inside
@@ -960,6 +963,12 @@ function parseNameStatus(output) {
960
963
  return files;
961
964
  }
962
965
  /** List files changed between base and HEAD (committed), plus working tree changes. */
966
+ function parseWorktreeFilenames(out, limit) {
967
+ if (!out)
968
+ return [];
969
+ const files = out.split('\0').filter((line) => line.length > 0);
970
+ return files.length > limit ? files.slice(0, limit) : files;
971
+ }
963
972
  /**
964
973
  * List the worktree's files — tracked plus untracked-but-not-git-ignored.
965
974
  * Excludes `.git`, `node_modules`, and anything covered by `.gitignore`.
@@ -969,10 +978,7 @@ function parseNameStatus(output) {
969
978
  export function listWorktreeFiles(worktreePath, limit = 5000) {
970
979
  try {
971
980
  const out = git(worktreePath, ['ls-files', '-z', '--cached', '--others', '--exclude-standard']);
972
- if (!out)
973
- return [];
974
- const files = out.split('\0').filter((line) => line.length > 0);
975
- return files.length > limit ? files.slice(0, limit) : files;
981
+ return parseWorktreeFilenames(out, limit);
976
982
  }
977
983
  catch {
978
984
  return [];
@@ -1196,6 +1202,19 @@ function parseWorkingTreeStatus(output) {
1196
1202
  }
1197
1203
  return { staged, modified, untracked };
1198
1204
  }
1205
+ function parseWorkingTreeFiles(output) {
1206
+ const files = [];
1207
+ for (const { path: filePath, x, y } of parsePorcelain(output)) {
1208
+ const untracked = x === '?' && y === '?';
1209
+ files.push({
1210
+ path: filePath,
1211
+ staged: !untracked && x !== ' ' && x !== '?',
1212
+ modified: !untracked && y !== ' ' && y !== '?',
1213
+ untracked,
1214
+ });
1215
+ }
1216
+ return files;
1217
+ }
1199
1218
  /**
1200
1219
  * List uncommitted working-tree files with their status, parsed from
1201
1220
  * `git status --porcelain`. Same classification rule as getWorkingTreeStatus.
@@ -1204,17 +1223,7 @@ function parseWorkingTreeStatus(output) {
1204
1223
  export function getWorkingTreeFiles(repoPath) {
1205
1224
  try {
1206
1225
  const output = git(repoPath, ['status', '--porcelain', '-z']);
1207
- const files = [];
1208
- for (const { path: filePath, x, y } of parsePorcelain(output)) {
1209
- const untracked = x === '?' && y === '?';
1210
- files.push({
1211
- path: filePath,
1212
- staged: !untracked && x !== ' ' && x !== '?',
1213
- modified: !untracked && y !== ' ' && y !== '?',
1214
- untracked,
1215
- });
1216
- }
1217
- return files;
1226
+ return parseWorkingTreeFiles(output);
1218
1227
  }
1219
1228
  catch {
1220
1229
  return [];
@@ -1348,6 +1357,121 @@ async function resolveBaseAsync(repoPath, base) {
1348
1357
  return base;
1349
1358
  }
1350
1359
  }
1360
+ /** Non-blocking branch and file discovery for forms, autocomplete and PR diagnosis. */
1361
+ export async function listBranchesAsync(repoPath) {
1362
+ return (await gitAsync(repoPath, ['branch', '--format=%(refname:short)']))
1363
+ .split('\n')
1364
+ .map((branch) => branch.trim())
1365
+ .filter(Boolean);
1366
+ }
1367
+ export async function listRemoteBranchesAsync(repoPath) {
1368
+ try {
1369
+ return (await gitAsync(repoPath, ['branch', '-r', '--format=%(refname:short)']))
1370
+ .split('\n')
1371
+ .map((branch) => branch.trim())
1372
+ .filter(Boolean);
1373
+ }
1374
+ catch {
1375
+ return [];
1376
+ }
1377
+ }
1378
+ export async function localBranchExistsAsync(repoPath, name) {
1379
+ try {
1380
+ await gitAsync(repoPath, ['rev-parse', '--verify', '--quiet', `refs/heads/${name}`]);
1381
+ return true;
1382
+ }
1383
+ catch {
1384
+ return false;
1385
+ }
1386
+ }
1387
+ export async function getIndexLockPathAsync(repoPath) {
1388
+ try {
1389
+ const directory = await gitAsync(repoPath, ['rev-parse', '--absolute-git-dir']);
1390
+ const lockPath = join(directory, 'index.lock');
1391
+ return existsSync(lockPath) ? lockPath : null;
1392
+ }
1393
+ catch {
1394
+ return null;
1395
+ }
1396
+ }
1397
+ export async function getOngoingGitOperationAsync(repoPath) {
1398
+ try {
1399
+ const gitDirectory = await gitAsync(repoPath, ['rev-parse', '--git-dir']);
1400
+ return classifyOngoingGitOperation(gitDirectory.startsWith('/') ? gitDirectory : join(repoPath, gitDirectory));
1401
+ }
1402
+ catch {
1403
+ return null;
1404
+ }
1405
+ }
1406
+ export async function listWorktreeFilesAsync(worktreePath, limit = 5_000) {
1407
+ try {
1408
+ return parseWorktreeFilenames(await gitAsync(worktreePath, ['ls-files', '-z', '--cached', '--others', '--exclude-standard']), limit);
1409
+ }
1410
+ catch {
1411
+ return [];
1412
+ }
1413
+ }
1414
+ /** Non-blocking review-template commit list, preferring the remote base. */
1415
+ export async function getCommitsBetweenAsync(repoPath, base, head) {
1416
+ try {
1417
+ const ref = await resolveBaseAsync(repoPath, base);
1418
+ return await gitAsync(repoPath, ['log', `${ref}..${head}`, '--pretty=format:- %s (%h)', '--no-merges']);
1419
+ }
1420
+ catch {
1421
+ return '';
1422
+ }
1423
+ }
1424
+ /** Keep the existing raw three-dot ref semantics of the shortstat helper. */
1425
+ export async function getDiffStatsBetweenAsync(repoPath, base, head) {
1426
+ try {
1427
+ return await gitAsync(repoPath, ['diff', '--shortstat', `${base}...${head}`]);
1428
+ }
1429
+ catch {
1430
+ return '';
1431
+ }
1432
+ }
1433
+ export async function listBranchCommitsAsync(repoPath, sourceBranch, workingBranch, limit = 50, remote = 'origin') {
1434
+ const sourceRef = await resolveBaseAsync(repoPath, sourceBranch);
1435
+ const remoteRef = `${remote}/${workingBranch}`;
1436
+ let raw;
1437
+ try {
1438
+ raw = await gitAsync(repoPath, ['log', `${sourceRef}..HEAD`, `--max-count=${limit}`, COMMIT_LOG_FORMAT]);
1439
+ }
1440
+ catch {
1441
+ return [];
1442
+ }
1443
+ if (!raw)
1444
+ return [];
1445
+ const pushedShas = new Set();
1446
+ try {
1447
+ await gitAsync(repoPath, ['rev-parse', '--verify', remoteRef]);
1448
+ const pushed = await gitAsync(repoPath, ['log', `${sourceRef}..${remoteRef}`, '--pretty=format:%H']);
1449
+ for (const sha of pushed.split('\n'))
1450
+ if (sha)
1451
+ pushedShas.add(sha.trim());
1452
+ }
1453
+ catch {
1454
+ // A branch that has never been pushed has no remote ref.
1455
+ }
1456
+ return parseCommitLog(raw).map((commit) => ({ ...commit, isPushed: pushedShas.has(commit.sha) }));
1457
+ }
1458
+ export async function listCommitsBehindAsync(repoPath, sourceBranch, workingBranch, limit = 50) {
1459
+ try {
1460
+ const sourceRef = await resolveBaseAsync(repoPath, sourceBranch);
1461
+ return parseCommitLog(await gitAsync(repoPath, ['log', `${workingBranch}..${sourceRef}`, `--max-count=${limit}`, COMMIT_LOG_FORMAT]));
1462
+ }
1463
+ catch {
1464
+ return [];
1465
+ }
1466
+ }
1467
+ export async function getWorkingTreeFilesAsync(repoPath) {
1468
+ try {
1469
+ return parseWorkingTreeFiles(await gitAsync(repoPath, ['status', '--porcelain', '-z']));
1470
+ }
1471
+ catch {
1472
+ return [];
1473
+ }
1474
+ }
1351
1475
  /** Non-blocking commit count for request and polling hot paths. */
1352
1476
  export async function getCommitCountAsync(repoPath, base, head) {
1353
1477
  try {
@@ -1390,6 +1514,84 @@ export async function getWorkingTreeStatusAsync(repoPath) {
1390
1514
  return { staged: 0, modified: 0, untracked: 0 };
1391
1515
  }
1392
1516
  }
1517
+ /** Raw handoff state. Failures must not be presented as a clean checkout. */
1518
+ export function getWorkingTreePorcelainAsync(repoPath) {
1519
+ return gitAsync(repoPath, ['status', '--short']);
1520
+ }
1521
+ export function getWorkingTreeDiffStatsAsync(repoPath) {
1522
+ return gitAsync(repoPath, ['diff', '--stat', 'HEAD']);
1523
+ }
1524
+ export async function commitExistsAsync(repoPath, ref) {
1525
+ try {
1526
+ await gitAsync(repoPath, ['rev-parse', '--verify', '--quiet', `${ref}^{commit}`]);
1527
+ return true;
1528
+ }
1529
+ catch {
1530
+ return false;
1531
+ }
1532
+ }
1533
+ export async function getFileAtRefAsync(repoPath, ref, filePath) {
1534
+ const resolvedRef = await resolveBaseAsync(repoPath, ref);
1535
+ try {
1536
+ // File contents must retain their leading/trailing whitespace verbatim.
1537
+ const { stdout } = await execFileAsync('git', ['show', `${resolvedRef}:${filePath}`], {
1538
+ cwd: repoPath,
1539
+ encoding: 'utf-8',
1540
+ timeout: READ_ONLY_GIT_TIMEOUT_MS,
1541
+ maxBuffer: GIT_MAX_BUFFER_BYTES,
1542
+ env: NON_INTERACTIVE_GIT_ENV,
1543
+ });
1544
+ return stdout;
1545
+ }
1546
+ catch {
1547
+ return null;
1548
+ }
1549
+ }
1550
+ export async function getChangedFilesBetweenAsync(repoPath, fromRef, toRef) {
1551
+ try {
1552
+ return parseNameStatus(await gitAsync(repoPath, ['diff', '--name-status', '-z', `${fromRef}..${toRef}`]));
1553
+ }
1554
+ catch {
1555
+ return [];
1556
+ }
1557
+ }
1558
+ export async function getUnpushedChangedFilesAsync(repoPath, branchName, remote = 'origin') {
1559
+ const ref = `${remote}/${branchName}`;
1560
+ try {
1561
+ await gitAsync(repoPath, ['rev-parse', '--verify', ref]);
1562
+ return parseNameStatus(await gitAsync(repoPath, ['diff', '--name-status', '-z', `${ref}..HEAD`]));
1563
+ }
1564
+ catch {
1565
+ return [];
1566
+ }
1567
+ }
1568
+ export async function getChangedFilesAsync(repoPath, base, includeUntracked = false) {
1569
+ const ref = await resolveBaseAsync(repoPath, base);
1570
+ const [committed, working] = await Promise.allSettled([
1571
+ gitAsync(repoPath, ['diff', '--name-status', '-z', `${ref}...HEAD`]),
1572
+ gitAsync(repoPath, ['status', '--porcelain', '-z', includeUntracked ? '-uall' : '-uno']),
1573
+ ]);
1574
+ const files = committed.status === 'fulfilled' ? parseNameStatus(committed.value) : [];
1575
+ const seen = new Set(files.map((file) => file.path));
1576
+ if (working.status === 'fulfilled') {
1577
+ for (const { path: filePath, x, y } of parsePorcelain(working.value)) {
1578
+ if (seen.has(filePath))
1579
+ continue;
1580
+ const status = x === '?' && y === '?'
1581
+ ? 'untracked'
1582
+ : x === 'R' || y === 'R'
1583
+ ? 'renamed'
1584
+ : x === 'A' || y === 'A'
1585
+ ? 'added'
1586
+ : x === 'D' || y === 'D'
1587
+ ? 'deleted'
1588
+ : 'modified';
1589
+ files.push({ path: filePath, status });
1590
+ seen.add(filePath);
1591
+ }
1592
+ }
1593
+ return files;
1594
+ }
1393
1595
  /**
1394
1596
  * Async version of `getUnpushedCount`. Same `origin/<workingBranch>` semantic:
1395
1597
  * returns `-1` when the remote ref does not exist (never pushed), `0` when
@@ -5,9 +5,8 @@
5
5
  * editing the workspace `model` field directly. This list reflects the
6
6
  * recommended set surfaced in the create-workspace selector.
7
7
  *
8
- * Auth caveat: `gpt-6-astra`, the `gpt-5.6-*` family and `gpt-5.5` are
9
- * currently only reachable when authenticated via ChatGPT
10
- * (Plus/Pro/Team/Enterprise). API-key auth is limited to `gpt-5.4` and below.
8
+ * Availability can vary by account and authentication method. The CLI owns
9
+ * the actual model resolution; the catalogue only offers named choices.
11
10
  */
12
11
  export const CODEX_MODELS = [
13
12
  {
@@ -16,12 +15,30 @@ export const CODEX_MODELS = [
16
15
  i18nLabelKey: 'model.auto',
17
16
  i18nDescriptionKey: 'model.autoDescription',
18
17
  },
18
+ {
19
+ id: 'gpt-6.1-sol',
20
+ label: 'GPT-6.1 Sol',
21
+ i18nLabelKey: 'model.gpt61sol',
22
+ i18nDescriptionKey: 'model.gpt61solDescription',
23
+ },
19
24
  {
20
25
  id: 'gpt-6-astra',
21
26
  label: 'GPT-6 Astra',
22
27
  i18nLabelKey: 'model.gpt6astra',
23
28
  i18nDescriptionKey: 'model.gpt6astraDescription',
24
29
  },
30
+ {
31
+ id: 'gpt-6-sol',
32
+ label: 'GPT-6 Sol',
33
+ i18nLabelKey: 'model.gpt6sol',
34
+ i18nDescriptionKey: 'model.gpt6solDescription',
35
+ },
36
+ {
37
+ id: 'gpt-6-luna',
38
+ label: 'GPT-6 Luna',
39
+ i18nLabelKey: 'model.gpt6luna',
40
+ i18nDescriptionKey: 'model.gpt6lunaDescription',
41
+ },
25
42
  {
26
43
  id: 'gpt-5.6-sol',
27
44
  label: 'GPT-5.6 Sol',
@@ -17,6 +17,12 @@ export const CLAUDE_MODELS = [
17
17
  i18nLabelKey: 'model.mythos5',
18
18
  i18nDescriptionKey: 'model.mythos5Description',
19
19
  },
20
+ {
21
+ id: 'claude-opus-5-5',
22
+ label: 'Opus 5.5',
23
+ i18nLabelKey: 'model.opus55',
24
+ i18nDescriptionKey: 'model.opus55Description',
25
+ },
20
26
  {
21
27
  id: 'claude-opus-5',
22
28
  label: 'Opus 5',
@@ -53,6 +59,12 @@ export const CLAUDE_MODELS = [
53
59
  i18nLabelKey: 'model.opus1m',
54
60
  i18nDescriptionKey: 'model.opus1mDescription',
55
61
  },
62
+ {
63
+ id: 'claude-sonnet-5-5',
64
+ label: 'Sonnet 5.5',
65
+ i18nLabelKey: 'model.sonnet55',
66
+ i18nDescriptionKey: 'model.sonnet55Description',
67
+ },
56
68
  {
57
69
  id: 'claude-sonnet-5',
58
70
  label: 'Sonnet 5',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loicngr/kobo",
3
- "version": "2.1.2",
3
+ "version": "2.1.5",
4
4
  "description": "Kōbō — local multi-workspace orchestrator for Claude Code and OpenAI Codex, with isolated Git worktrees, live review and optional automation.",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0-or-later",
@@ -81,15 +81,15 @@
81
81
  "security:scan": "node scripts/scan-secrets.mjs"
82
82
  },
83
83
  "dependencies": {
84
- "@anthropic-ai/claude-agent-sdk": "^0.3.283",
84
+ "@anthropic-ai/claude-agent-sdk": "^0.3.285",
85
85
  "@emnapi/core": "^1.11.3",
86
86
  "@emnapi/runtime": "^1.11.3",
87
- "@hono/node-server": "^2.1.1",
88
- "@modelcontextprotocol/sdk": "^1.30.1",
89
- "@openai/codex": "^0.158.0",
87
+ "@hono/node-server": "^2.1.3",
88
+ "@modelcontextprotocol/sdk": "^1.31.0",
89
+ "@openai/codex": "^0.159.2",
90
90
  "better-sqlite3": "^13.0.3",
91
91
  "cron-parser": "^5.10.1",
92
- "hono": "^4.13.10",
92
+ "hono": "^4.13.12",
93
93
  "nanoid": "^6.0.1",
94
94
  "node-pty": "^1.1.0",
95
95
  "ws": "^8.22.0"
@@ -98,7 +98,7 @@
98
98
  "@biomejs/biome": "2.5.14",
99
99
  "@types/better-sqlite3": "^9.6.0",
100
100
  "@types/node": "^26.6.3",
101
- "@types/ws": "^8.18.1",
101
+ "@types/ws": "^8.18.2",
102
102
  "@vitest/runner": "^4.1.11",
103
103
  "concurrently": "^10.0.5",
104
104
  "tsx": "^4.23.15",
@@ -106,7 +106,7 @@
106
106
  "vitest": "^5.0.2"
107
107
  },
108
108
  "overrides": {
109
- "@hono/node-server": "^2.1.1",
109
+ "@hono/node-server": "^2.1.3",
110
110
  "shell-quote": "^1.10.0"
111
111
  }
112
112
  }
@@ -1 +1 @@
1
- .agent-error-item[data-v-e98c3c02]{gap:var(--kobo-space-sm);padding:var(--kobo-space-sm) var(--kobo-space-md);border-left:2px solid var(--kobo-danger);background:var(--kobo-surface-2);border-radius:var(--kobo-radius-sm);align-items:flex-start;display:flex}.agent-error-item__icon[data-v-e98c3c02]{color:var(--kobo-danger);flex:none;margin-top:2px}.agent-error-item__body[data-v-e98c3c02]{min-width:0}.agent-error-item__title[data-v-e98c3c02]{color:var(--kobo-text);font-weight:600}.agent-error-item__message[data-v-e98c3c02]{color:var(--kobo-text-2);font-family:var(--kobo-font-mono);white-space:pre-wrap;word-break:break-word;font-size:12px}.markdown-message[data-v-99371035]{color:var(--kobo-text-2);word-break:break-word;overflow-wrap:anywhere;min-width:0;max-width:100%;font-size:13px;line-height:1.55}.markdown-message[data-v-99371035] *{max-width:100%}.markdown-message[data-v-99371035] p{margin:0 0 .5em}.markdown-message[data-v-99371035] p:last-child{margin-bottom:0}.markdown-message[data-v-99371035] pre{background:#00000059;border-radius:4px;margin:.5em 0;padding:.5em .75em;overflow-x:auto}.markdown-message[data-v-99371035] code{word-break:break-all;background:#0000004d;border-radius:3px;padding:.1em .3em;font-size:.9em}.markdown-message[data-v-99371035] pre code{background:0 0;padding:0}.markdown-message[data-v-99371035] ul,.markdown-message[data-v-99371035] ol{margin:.25em 0 .5em;padding-left:1.5em}.markdown-message[data-v-99371035] li{margin:.15em 0}.markdown-message[data-v-99371035] a{color:var(--kobo-text-2);text-decoration:underline}.markdown-message[data-v-99371035] .document-link{color:var(--kobo-text-2);cursor:pointer;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.markdown-message[data-v-99371035] .document-link:hover{color:var(--kobo-text);-webkit-text-decoration:underline;text-decoration:underline}.markdown-message[data-v-99371035] h1,.markdown-message[data-v-99371035] h2,.markdown-message[data-v-99371035] h3,.markdown-message[data-v-99371035] h4,.markdown-message[data-v-99371035] h5,.markdown-message[data-v-99371035] h6{margin:.5em 0 .3em;font-weight:600;line-height:1.3}.markdown-message[data-v-99371035] h1{font-size:1.25em}.markdown-message[data-v-99371035] h2{font-size:1.15em}.markdown-message[data-v-99371035] h3{font-size:1.08em}.markdown-message[data-v-99371035] h4,.markdown-message[data-v-99371035] h5,.markdown-message[data-v-99371035] h6{font-size:1em}.markdown-message[data-v-99371035] blockquote{color:#ffffffb3;border-left:3px solid #fff3;margin:.5em 0;padding-left:.75em}.markdown-message[data-v-99371035] table{border-collapse:collapse;margin:.5em 0}.markdown-message[data-v-99371035] th,.markdown-message[data-v-99371035] td{border:1px solid #ffffff26;padding:.25em .5em}.tool-row[data-v-2482adfe]{border-radius:4px;margin:0;font-size:12px}.tool-header[data-v-2482adfe]{color:var(--kobo-text-2);cursor:default;align-items:center;gap:10px;min-width:0;padding:5px 10px;display:flex}.tool-row:not(.tool-row-generic) .tool-header[data-v-2482adfe],.tool-row--toggleable .tool-header[data-v-2482adfe]{cursor:pointer}.tool-row:has(.tool-diff) .tool-header[data-v-2482adfe]{cursor:pointer}.tool-row:not(.tool-row-generic) .tool-header[data-v-2482adfe]:hover,.tool-row--toggleable .tool-header[data-v-2482adfe]:hover{background:#ffffff08}.tool-icon[data-v-2482adfe]{color:var(--kobo-text-2);flex-shrink:0}.tool-name[data-v-2482adfe]{color:var(--kobo-text-2);flex-shrink:0;font-weight:600}.tool-arg[data-v-2482adfe],.tool-path[data-v-2482adfe]{color:var(--kobo-text-3);text-overflow:ellipsis;white-space:nowrap;min-width:0;max-width:100%;font-family:var(--kobo-font-mono);font-size:11.5px;overflow:hidden}.tool-open-diff[data-v-2482adfe]{flex-shrink:0}.tool-path[data-v-2482adfe],.tool-arg[data-v-2482adfe]{flex:1}.tool-stat-add[data-v-2482adfe]{color:var(--kobo-success);flex-shrink:0;font-size:11px;font-weight:600}.tool-stat-del[data-v-2482adfe]{color:var(--kobo-danger);flex-shrink:0;font-size:11px;font-weight:600}.tool-diff[data-v-2482adfe]{max-height:400px;font-family:var(--kobo-font-mono);background:#0003;border-radius:4px;margin-top:4px;padding:8px 0;font-size:11px;line-height:1.5;overflow:auto}.diff-line[data-v-2482adfe]{white-space:pre;color:var(--kobo-text-2);padding:0 12px}.diff-sign[data-v-2482adfe]{width:14px;color:var(--kobo-text-disabled);-webkit-user-select:none;user-select:none;display:inline-block}.diff-add[data-v-2482adfe]{color:var(--kobo-success);background:#66bb6a1a}.diff-add .diff-sign[data-v-2482adfe]{color:var(--kobo-success)}.diff-del[data-v-2482adfe]{color:var(--kobo-danger);background:#ef53501a}.diff-del .diff-sign[data-v-2482adfe]{color:var(--kobo-danger)}.tool-output[data-v-2482adfe]{color:var(--kobo-text-2);font-family:var(--kobo-font-mono);white-space:pre-wrap;background:#00000026;border-radius:4px;max-height:8em;margin-top:4px;padding:6px 10px;font-size:11px;overflow:auto}.markdown-message[data-v-db2fc622]{color:var(--kobo-text-2);word-break:break-word;overflow-wrap:anywhere;min-width:0;max-width:100%;font-size:13px;line-height:1.55}.markdown-message[data-v-db2fc622] *{max-width:100%}.markdown-message[data-v-db2fc622] img{-o-object-fit:contain;object-fit:contain;cursor:zoom-in;background:#0003;border-radius:4px;max-width:180px;max-height:100px;margin:.3em 0;display:block}.markdown-message[data-v-db2fc622] code{word-break:break-all}.markdown-message[data-v-db2fc622] p{margin:0 0 .4em}.markdown-message[data-v-db2fc622] p:last-child{margin-bottom:0}.markdown-message[data-v-db2fc622] code{background:#00000040;border-radius:3px;padding:.1em .3em}.markdown-message[data-v-db2fc622] h1,.markdown-message[data-v-db2fc622] h2,.markdown-message[data-v-db2fc622] h3,.markdown-message[data-v-db2fc622] h4,.markdown-message[data-v-db2fc622] h5,.markdown-message[data-v-db2fc622] h6{margin:.4em 0 .25em;font-weight:600;line-height:1.3}.markdown-message[data-v-db2fc622] h1{font-size:1.25em}.markdown-message[data-v-db2fc622] h2{font-size:1.15em}.markdown-message[data-v-db2fc622] h3{font-size:1.08em}.markdown-message[data-v-db2fc622] h4,.markdown-message[data-v-db2fc622] h5,.markdown-message[data-v-db2fc622] h6{font-size:1em}.markdown-user-prompt[data-v-db2fc622]{color:var(--kobo-text-2);font-size:12px;font-style:italic}.markdown-user-prompt[data-v-db2fc622] p{margin:0 0 .4em}.markdown-user-prompt[data-v-db2fc622] code{background:#ffffff14;border-radius:3px;padding:.1em .3em;font-style:normal}.image-lightbox-img{-o-object-fit:contain;object-fit:contain;cursor:zoom-out;background:#0000004d;border-radius:4px;max-width:92vw;max-height:92vh;display:block}.turn-card[data-v-c0e24cbd]{border:1px solid #ffffff14;border-left:3px solid var(--turn-accent);background:#ffffff05;border-radius:6px;min-width:0;max-width:100%;margin:14px 0;overflow:hidden}.turn-card--highlighted[data-v-c0e24cbd]{animation:1.8s ease-out search-highlight-c0e24cbd}@keyframes search-highlight-c0e24cbd{0%,45%{box-shadow:0 0 0 2px #818cf8f2,0 0 22px #818cf873}to{box-shadow:none}}.turn-header[data-v-c0e24cbd]{color:var(--kobo-text-3);background:#ffffff08;border-bottom:1px solid #ffffff0d;align-items:center;gap:8px;padding:8px 14px;font-size:11px;display:flex}.turn-badge[data-v-c0e24cbd]{letter-spacing:.3px;border-radius:3px;padding:2px 8px;font-size:11px;font-weight:700}.turn-badge-user[data-v-c0e24cbd]{color:var(--kobo-turn-user);background:#ce93d826}.turn-badge-agent[data-v-c0e24cbd]{color:var(--kobo-turn-agent);background:#7986cb26}.turn-badge-system[data-v-c0e24cbd]{color:var(--kobo-text-2);background:#75757533;font-style:italic}.turn-badge-session[data-v-c0e24cbd]{color:var(--kobo-text-3);background:#61616133}.turn-badge-script[data-v-c0e24cbd]{color:var(--kobo-success);background:#4db6ac26}.turn-time[data-v-c0e24cbd]{color:var(--kobo-text-disabled);font-family:var(--kobo-font-mono);font-size:11px}.turn-time-arrow[data-v-c0e24cbd]{opacity:.7;margin:0 -2px}.turn-time-updated[data-v-c0e24cbd]{color:var(--kobo-text-3)}.turn-actions[data-v-c0e24cbd]{color:var(--kobo-text-3);font-size:11px}.turn-body[data-v-c0e24cbd]{flex-direction:column;gap:12px;min-width:0;padding:14px 18px;display:flex}.turn-body[data-v-c0e24cbd]>*{min-width:0;max-width:100%}.turn-body[data-v-c0e24cbd] .tool-row+.tool-row{margin-top:-8px}.turn-scroll-top[data-v-c0e24cbd]{justify-content:flex-start;padding:0 8px 6px;display:flex}.turn-scroll-top-btn[data-v-c0e24cbd]{opacity:.5;transition:opacity .15s}.turn-scroll-top-btn[data-v-c0e24cbd]:hover{opacity:1}.activity-feed-wrap[data-v-028e8af5]{width:100%;height:100%;position:relative}.activity-feed-scroll[data-v-028e8af5]{width:100%;height:100%}.activity-feed-nav-cluster[data-v-028e8af5]{z-index:2;align-items:center;gap:8px;display:flex;position:absolute;bottom:14px;right:14px}.activity-feed-compacting[data-v-028e8af5]{z-index:3;background:var(--kobo-surface);border:1px solid var(--kobo-border);border-radius:6px;align-items:center;gap:8px;padding:6px 14px;display:flex;position:absolute;top:12px;left:50%;transform:translate(-50%);box-shadow:0 4px 16px #00000059}.fade-enter-active[data-v-028e8af5],.fade-leave-active[data-v-028e8af5]{transition:opacity .16s}.fade-enter-from[data-v-028e8af5],.fade-leave-to[data-v-028e8af5]{opacity:0}.activity-feed-nav-btn[data-v-028e8af5]{opacity:.8;transition:opacity .12s}.activity-feed-nav-btn[data-v-028e8af5]:hover{opacity:1}.activity-feed-scroll[data-v-028e8af5] .q-scrollarea__content{max-width:100%;overflow-x:hidden}.activity-feed-switching[data-v-028e8af5]{justify-content:center;align-items:center;width:100%;height:100%;display:flex}.activity-feed-empty[data-v-028e8af5]{align-items:center;gap:var(--kobo-space-sm);padding:var(--kobo-space-4xl) var(--kobo-space-xl);color:var(--kobo-text-3);text-align:center;flex-direction:column;display:flex}.activity-feed-empty__title[data-v-028e8af5]{font-family:var(--kobo-font-sans);color:var(--kobo-text-2);font-size:14px;font-weight:500}.activity-feed-empty__hint[data-v-028e8af5]{font-family:var(--kobo-font-sans);color:var(--kobo-text-3);max-width:380px;font-size:13px}
1
+ .agent-error-item[data-v-e98c3c02]{gap:var(--kobo-space-sm);padding:var(--kobo-space-sm) var(--kobo-space-md);border-left:2px solid var(--kobo-danger);background:var(--kobo-surface-2);border-radius:var(--kobo-radius-sm);align-items:flex-start;display:flex}.agent-error-item__icon[data-v-e98c3c02]{color:var(--kobo-danger);flex:none;margin-top:2px}.agent-error-item__body[data-v-e98c3c02]{min-width:0}.agent-error-item__title[data-v-e98c3c02]{color:var(--kobo-text);font-weight:600}.agent-error-item__message[data-v-e98c3c02]{color:var(--kobo-text-2);font-family:var(--kobo-font-mono);white-space:pre-wrap;word-break:break-word;font-size:12px}.markdown-message[data-v-99371035]{color:var(--kobo-text-2);word-break:break-word;overflow-wrap:anywhere;min-width:0;max-width:100%;font-size:13px;line-height:1.55}.markdown-message[data-v-99371035] *{max-width:100%}.markdown-message[data-v-99371035] p{margin:0 0 .5em}.markdown-message[data-v-99371035] p:last-child{margin-bottom:0}.markdown-message[data-v-99371035] pre{background:#00000059;border-radius:4px;margin:.5em 0;padding:.5em .75em;overflow-x:auto}.markdown-message[data-v-99371035] code{word-break:break-all;background:#0000004d;border-radius:3px;padding:.1em .3em;font-size:.9em}.markdown-message[data-v-99371035] pre code{background:0 0;padding:0}.markdown-message[data-v-99371035] ul,.markdown-message[data-v-99371035] ol{margin:.25em 0 .5em;padding-left:1.5em}.markdown-message[data-v-99371035] li{margin:.15em 0}.markdown-message[data-v-99371035] a{color:var(--kobo-text-2);text-decoration:underline}.markdown-message[data-v-99371035] .document-link{color:var(--kobo-text-2);cursor:pointer;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.markdown-message[data-v-99371035] .document-link:hover{color:var(--kobo-text);-webkit-text-decoration:underline;text-decoration:underline}.markdown-message[data-v-99371035] h1,.markdown-message[data-v-99371035] h2,.markdown-message[data-v-99371035] h3,.markdown-message[data-v-99371035] h4,.markdown-message[data-v-99371035] h5,.markdown-message[data-v-99371035] h6{margin:.5em 0 .3em;font-weight:600;line-height:1.3}.markdown-message[data-v-99371035] h1{font-size:1.25em}.markdown-message[data-v-99371035] h2{font-size:1.15em}.markdown-message[data-v-99371035] h3{font-size:1.08em}.markdown-message[data-v-99371035] h4,.markdown-message[data-v-99371035] h5,.markdown-message[data-v-99371035] h6{font-size:1em}.markdown-message[data-v-99371035] blockquote{color:#ffffffb3;border-left:3px solid #fff3;margin:.5em 0;padding-left:.75em}.markdown-message[data-v-99371035] table{border-collapse:collapse;margin:.5em 0}.markdown-message[data-v-99371035] th,.markdown-message[data-v-99371035] td{border:1px solid #ffffff26;padding:.25em .5em}.tool-row[data-v-2482adfe]{border-radius:4px;margin:0;font-size:12px}.tool-header[data-v-2482adfe]{color:var(--kobo-text-2);cursor:default;align-items:center;gap:10px;min-width:0;padding:5px 10px;display:flex}.tool-row:not(.tool-row-generic) .tool-header[data-v-2482adfe],.tool-row--toggleable .tool-header[data-v-2482adfe]{cursor:pointer}.tool-row:has(.tool-diff) .tool-header[data-v-2482adfe]{cursor:pointer}.tool-row:not(.tool-row-generic) .tool-header[data-v-2482adfe]:hover,.tool-row--toggleable .tool-header[data-v-2482adfe]:hover{background:#ffffff08}.tool-icon[data-v-2482adfe]{color:var(--kobo-text-2);flex-shrink:0}.tool-name[data-v-2482adfe]{color:var(--kobo-text-2);flex-shrink:0;font-weight:600}.tool-arg[data-v-2482adfe],.tool-path[data-v-2482adfe]{color:var(--kobo-text-3);text-overflow:ellipsis;white-space:nowrap;min-width:0;max-width:100%;font-family:var(--kobo-font-mono);font-size:11.5px;overflow:hidden}.tool-open-diff[data-v-2482adfe]{flex-shrink:0}.tool-path[data-v-2482adfe],.tool-arg[data-v-2482adfe]{flex:1}.tool-stat-add[data-v-2482adfe]{color:var(--kobo-success);flex-shrink:0;font-size:11px;font-weight:600}.tool-stat-del[data-v-2482adfe]{color:var(--kobo-danger);flex-shrink:0;font-size:11px;font-weight:600}.tool-diff[data-v-2482adfe]{max-height:400px;font-family:var(--kobo-font-mono);background:#0003;border-radius:4px;margin-top:4px;padding:8px 0;font-size:11px;line-height:1.5;overflow:auto}.diff-line[data-v-2482adfe]{white-space:pre;color:var(--kobo-text-2);padding:0 12px}.diff-sign[data-v-2482adfe]{width:14px;color:var(--kobo-text-disabled);-webkit-user-select:none;user-select:none;display:inline-block}.diff-add[data-v-2482adfe]{color:var(--kobo-success);background:#66bb6a1a}.diff-add .diff-sign[data-v-2482adfe]{color:var(--kobo-success)}.diff-del[data-v-2482adfe]{color:var(--kobo-danger);background:#ef53501a}.diff-del .diff-sign[data-v-2482adfe]{color:var(--kobo-danger)}.tool-output[data-v-2482adfe]{color:var(--kobo-text-2);font-family:var(--kobo-font-mono);white-space:pre-wrap;background:#00000026;border-radius:4px;max-height:8em;margin-top:4px;padding:6px 10px;font-size:11px;overflow:auto}.markdown-message[data-v-db2fc622]{color:var(--kobo-text-2);word-break:break-word;overflow-wrap:anywhere;min-width:0;max-width:100%;font-size:13px;line-height:1.55}.markdown-message[data-v-db2fc622] *{max-width:100%}.markdown-message[data-v-db2fc622] img{-o-object-fit:contain;object-fit:contain;cursor:zoom-in;background:#0003;border-radius:4px;max-width:180px;max-height:100px;margin:.3em 0;display:block}.markdown-message[data-v-db2fc622] code{word-break:break-all}.markdown-message[data-v-db2fc622] p{margin:0 0 .4em}.markdown-message[data-v-db2fc622] p:last-child{margin-bottom:0}.markdown-message[data-v-db2fc622] code{background:#00000040;border-radius:3px;padding:.1em .3em}.markdown-message[data-v-db2fc622] h1,.markdown-message[data-v-db2fc622] h2,.markdown-message[data-v-db2fc622] h3,.markdown-message[data-v-db2fc622] h4,.markdown-message[data-v-db2fc622] h5,.markdown-message[data-v-db2fc622] h6{margin:.4em 0 .25em;font-weight:600;line-height:1.3}.markdown-message[data-v-db2fc622] h1{font-size:1.25em}.markdown-message[data-v-db2fc622] h2{font-size:1.15em}.markdown-message[data-v-db2fc622] h3{font-size:1.08em}.markdown-message[data-v-db2fc622] h4,.markdown-message[data-v-db2fc622] h5,.markdown-message[data-v-db2fc622] h6{font-size:1em}.markdown-user-prompt[data-v-db2fc622]{color:var(--kobo-text-2);font-size:12px;font-style:italic}.markdown-user-prompt[data-v-db2fc622] p{margin:0 0 .4em}.markdown-user-prompt[data-v-db2fc622] code{background:#ffffff14;border-radius:3px;padding:.1em .3em;font-style:normal}.image-lightbox-img{-o-object-fit:contain;object-fit:contain;cursor:zoom-out;background:#0000004d;border-radius:4px;max-width:92vw;max-height:92vh;display:block}.turn-card[data-v-c0e24cbd]{border:1px solid #ffffff14;border-left:3px solid var(--turn-accent);background:#ffffff05;border-radius:6px;min-width:0;max-width:100%;margin:14px 0;overflow:hidden}.turn-card--highlighted[data-v-c0e24cbd]{animation:1.8s ease-out search-highlight-c0e24cbd}@keyframes search-highlight-c0e24cbd{0%,45%{box-shadow:0 0 0 2px #818cf8f2,0 0 22px #818cf873}to{box-shadow:none}}.turn-header[data-v-c0e24cbd]{color:var(--kobo-text-3);background:#ffffff08;border-bottom:1px solid #ffffff0d;align-items:center;gap:8px;padding:8px 14px;font-size:11px;display:flex}.turn-badge[data-v-c0e24cbd]{letter-spacing:.3px;border-radius:3px;padding:2px 8px;font-size:11px;font-weight:700}.turn-badge-user[data-v-c0e24cbd]{color:var(--kobo-turn-user);background:#ce93d826}.turn-badge-agent[data-v-c0e24cbd]{color:var(--kobo-turn-agent);background:#7986cb26}.turn-badge-system[data-v-c0e24cbd]{color:var(--kobo-text-2);background:#75757533;font-style:italic}.turn-badge-session[data-v-c0e24cbd]{color:var(--kobo-text-3);background:#61616133}.turn-badge-script[data-v-c0e24cbd]{color:var(--kobo-success);background:#4db6ac26}.turn-time[data-v-c0e24cbd]{color:var(--kobo-text-disabled);font-family:var(--kobo-font-mono);font-size:11px}.turn-time-arrow[data-v-c0e24cbd]{opacity:.7;margin:0 -2px}.turn-time-updated[data-v-c0e24cbd]{color:var(--kobo-text-3)}.turn-actions[data-v-c0e24cbd]{color:var(--kobo-text-3);font-size:11px}.turn-body[data-v-c0e24cbd]{flex-direction:column;gap:12px;min-width:0;padding:14px 18px;display:flex}.turn-body[data-v-c0e24cbd]>*{min-width:0;max-width:100%}.turn-body[data-v-c0e24cbd] .tool-row+.tool-row{margin-top:-8px}.turn-scroll-top[data-v-c0e24cbd]{justify-content:flex-start;padding:0 8px 6px;display:flex}.turn-scroll-top-btn[data-v-c0e24cbd]{opacity:.5;transition:opacity .15s}.turn-scroll-top-btn[data-v-c0e24cbd]:hover{opacity:1}.activity-feed-wrap[data-v-f3878c46]{flex-direction:column;width:100%;height:100%;display:flex;position:relative}.activity-feed-scroll[data-v-f3878c46]{flex:1;width:100%;height:100%;min-height:0}.activity-feed-nav-cluster[data-v-f3878c46]{z-index:2;align-items:center;gap:8px;display:flex;position:absolute;bottom:14px;right:14px}.activity-feed-compacting[data-v-f3878c46]{z-index:3;background:var(--kobo-surface);border:1px solid var(--kobo-border);border-radius:6px;align-items:center;gap:8px;padding:6px 14px;display:flex;position:absolute;top:12px;left:50%;transform:translate(-50%);box-shadow:0 4px 16px #00000059}.fade-enter-active[data-v-f3878c46],.fade-leave-active[data-v-f3878c46]{transition:opacity .16s}.fade-enter-from[data-v-f3878c46],.fade-leave-to[data-v-f3878c46]{opacity:0}.activity-feed-nav-btn[data-v-f3878c46]{opacity:.8;transition:opacity .12s}.activity-feed-nav-btn[data-v-f3878c46]:hover{opacity:1}.activity-feed-scroll[data-v-f3878c46] .q-scrollarea__content{max-width:100%;overflow-x:hidden}.activity-feed-switching[data-v-f3878c46]{justify-content:center;align-items:center;width:100%;height:100%;display:flex}.activity-feed-empty[data-v-f3878c46]{align-items:center;gap:var(--kobo-space-sm);padding:var(--kobo-space-4xl) var(--kobo-space-xl);color:var(--kobo-text-3);text-align:center;flex-direction:column;display:flex}.activity-feed-empty__title[data-v-f3878c46]{font-family:var(--kobo-font-sans);color:var(--kobo-text-2);font-size:14px;font-weight:500}.activity-feed-empty__hint[data-v-f3878c46]{font-family:var(--kobo-font-sans);color:var(--kobo-text-3);max-width:380px;font-size:13px}