@loicngr/kobo 2.1.2 → 2.1.4

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 (132) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/server/routes/workspaces.js +39 -0
  3. package/dist/server/services/agent/engines/claude-code/engine.js +87 -20
  4. package/dist/server/services/agent/engines/claude-code/event-mapper.js +88 -22
  5. package/dist/server/services/agent/engines/claude-code/options-builder.js +1 -0
  6. package/dist/server/services/agent/engines/codex/engine.js +27 -5
  7. package/dist/server/services/agent/engines/codex/options-builder.js +1 -0
  8. package/dist/server/services/agent/orchestrator.js +35 -0
  9. package/dist/server/services/agent/session-controller.js +12 -0
  10. package/package.json +8 -8
  11. package/src/client/dist/pwa/assets/ActivityFeed-DdGARu8o.js +8 -0
  12. package/src/client/dist/pwa/assets/{ChangelogPage-DNRcPRtW.js → ChangelogPage-CGenjiI5.js} +1 -1
  13. package/src/client/dist/pwa/assets/{ClosePopup-BikuzYle.js → ClosePopup-C1a82pL9.js} +1 -1
  14. package/src/client/dist/pwa/assets/{ComparisonPanel-DZoDqgJI.js → ComparisonPanel-aXgjEulK.js} +2 -2
  15. package/src/client/dist/pwa/assets/{CreatePage-wNH35uTG.js → CreatePage-BaB_30xJ.js} +2 -2
  16. package/src/client/dist/pwa/assets/{DashboardPage-DUyy4SzS.js → DashboardPage-DsbwmO2W.js} +1 -1
  17. package/src/client/dist/pwa/assets/{DiffViewer-CZ5QwXqi.js → DiffViewer--dUMf7b3.js} +4 -4
  18. package/src/client/dist/pwa/assets/{DocumentsPanel-sBatgKA-.js → DocumentsPanel-DTjEUYUd.js} +1 -1
  19. package/src/client/dist/pwa/assets/{DrawerToggleButton-Dfvkaro0.js → DrawerToggleButton-Bh4FXdtJ.js} +1 -1
  20. package/src/client/dist/pwa/assets/GitPanel-PUPLj0M8.js +3 -0
  21. package/src/client/dist/pwa/assets/{HealthPage-BwXYpczh.js → HealthPage-BHgucifz.js} +1 -1
  22. package/src/client/dist/pwa/assets/{MainLayout-gHENJEFQ.css → MainLayout-CNmwCnDD.css} +1 -1
  23. package/src/client/dist/pwa/assets/MainLayout-CQVLnTX3.js +2 -0
  24. package/src/client/dist/pwa/assets/QBadge-rGILsUYl.js +1 -0
  25. package/src/client/dist/pwa/assets/QBanner-XE0EMe9L.js +1 -0
  26. package/src/client/dist/pwa/assets/QBtn-b_baQtAt.js +1 -0
  27. package/src/client/dist/pwa/assets/QBtnDropdown-DVbxGTNb.js +1 -0
  28. package/src/client/dist/pwa/assets/QBtnGroup-CUmAgBV8.js +1 -0
  29. package/src/client/dist/pwa/assets/QBtnToggle-COL46j6W.js +1 -0
  30. package/src/client/dist/pwa/assets/QChip-Bt_JvnHb.js +1 -0
  31. package/src/client/dist/pwa/assets/QExpansionItem-CxEBY3jY.js +1 -0
  32. package/src/client/dist/pwa/assets/QItemSection-Cf6OZEH4.js +1 -0
  33. package/src/client/dist/pwa/assets/QList-Z90BspT4.js +1 -0
  34. package/src/client/dist/pwa/assets/QMarkupTable-Bwjknbmw.js +1 -0
  35. package/src/client/dist/pwa/assets/QPage-BCDXjfVj.js +1 -0
  36. package/src/client/dist/pwa/assets/QSelect-CMzJDhi-.js +1 -0
  37. package/src/client/dist/pwa/assets/QSpinnerDots-BsKmccpX.js +1 -0
  38. package/src/client/dist/pwa/assets/QTooltip-BLhkNT4s.js +1 -0
  39. package/src/client/dist/pwa/assets/{SchedulePanel-D6LTiRFg.js → SchedulePanel-DTVwe7sX.js} +1 -1
  40. package/src/client/dist/pwa/assets/{SearchPage-Dj487BdP.js → SearchPage-CtrR7dNr.js} +1 -1
  41. package/src/client/dist/pwa/assets/SettingsPage-Bl_RNTGi.js +9 -0
  42. package/src/client/dist/pwa/assets/SplitWorkspacePage-Bac_Pzmm.js +1 -0
  43. package/src/client/dist/pwa/assets/{TerminalPanel-B9LDe7uG.js → TerminalPanel-D2j5AA8S.js} +1 -1
  44. package/src/client/dist/pwa/assets/TouchPan-C0EqFcrr.js +1 -0
  45. package/src/client/dist/pwa/assets/{TourReplayButton-Dtv_UDaY.js → TourReplayButton-DlMvPblb.js} +1 -1
  46. package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-BAk5l4Ik.js +1 -0
  47. package/src/client/dist/pwa/assets/WorkspacePage-FScR5YAD.js +4 -0
  48. package/src/client/dist/pwa/assets/{browser-B15GGwtW.js → browser-DNEbLF-4.js} +1 -1
  49. package/src/client/dist/pwa/assets/build-path-tree-C0b61nxI.js +1 -0
  50. package/src/client/dist/pwa/assets/custom-sounds-DE4JLdGG.js +1 -0
  51. package/src/client/dist/pwa/assets/{de-B_Y4EArT.js → de-DgZ9jKq_.js} +1 -1
  52. package/src/client/dist/pwa/assets/documents-CALwmao-.js +1 -0
  53. package/src/client/dist/pwa/assets/{editor.api-DhaLhNl-.js → editor.api-PENN0io6.js} +1 -1
  54. package/src/client/dist/pwa/assets/efforts-Detdm-FH.js +9 -0
  55. package/src/client/dist/pwa/assets/{es-DtsTTShP.js → es-zvlgzw9H.js} +1 -1
  56. package/src/client/dist/pwa/assets/expand-template-ZIVLPdW2.js +1 -0
  57. package/src/client/dist/pwa/assets/{format-2JisC3s1.js → format-CVI7mr7G.js} +1 -1
  58. package/src/client/dist/pwa/assets/{formatters-ZVNs2VOl.js → formatters-C_zcfJBt.js} +1 -1
  59. package/src/client/dist/pwa/assets/{fr-C82MIVrA.js → fr-BICzq59I.js} +1 -1
  60. package/src/client/dist/pwa/assets/{freemarker2-PYrDqU04.js → freemarker2-BxN_Aq6C.js} +1 -1
  61. package/src/client/dist/pwa/assets/{handlebars-BuiY0fFf.js → handlebars-C9UHXtO1.js} +1 -1
  62. package/src/client/dist/pwa/assets/{html-Du3cjdug.js → html-CTH9wSJr.js} +1 -1
  63. package/src/client/dist/pwa/assets/i18n-DG_vChZ3.js +1 -0
  64. package/src/client/dist/pwa/assets/index-C8zAZ-k-.css +1 -0
  65. package/src/client/dist/pwa/assets/index-fuLr-ZVa.js +20 -0
  66. package/src/client/dist/pwa/assets/{it-BJNuTcj2.js → it-BIGArQrS.js} +1 -1
  67. package/src/client/dist/pwa/assets/{javascript-Bb48hPli.js → javascript-WT4BS8p4.js} +1 -1
  68. package/src/client/dist/pwa/assets/{liquid-Be77iQe3.js → liquid-D6dfe-gU.js} +1 -1
  69. package/src/client/dist/pwa/assets/{mdx-CfohNJja.js → mdx-B8pNXNeC.js} +1 -1
  70. package/src/client/dist/pwa/assets/{monaco-contributions-CyM4wsIg.js → monaco-contributions-COLfslyi.js} +1 -1
  71. package/src/client/dist/pwa/assets/{monaco.contribution-BcIfN6Mi.js → monaco.contribution-CDhuQbzi.js} +2 -2
  72. package/src/client/dist/pwa/assets/{network-auth-B1xAaHmx.js → network-auth-QouPCx6A.js} +1 -1
  73. package/src/client/dist/pwa/assets/{notify-theme-DvvaSyg1.js → notify-theme-7MsG-MyN.js} +1 -1
  74. package/src/client/dist/pwa/assets/pending-diff-open-CX24u9gW.js +1 -0
  75. package/src/client/dist/pwa/assets/{pinia-CTJ3OfP3.js → pinia-BdFjQEqa.js} +1 -1
  76. package/src/client/dist/pwa/assets/{private.use-form-Bk8ti264.js → private.use-form-vxvbh_VN.js} +1 -1
  77. package/src/client/dist/pwa/assets/{project-color-Be0t12-n.js → project-color-Bo6iEcqN.js} +1 -1
  78. package/src/client/dist/pwa/assets/{python-BNemmtx8.js → python-D2gb6Jjz.js} +1 -1
  79. package/src/client/dist/pwa/assets/{razor-3NM8iFVf.js → razor-dksrCQF2.js} +1 -1
  80. package/src/client/dist/pwa/assets/{reactivity.esm-bundler-Bo9anJjg.js → reactivity.esm-bundler-tuEvQ47f.js} +1 -1
  81. package/src/client/dist/pwa/assets/{render-chat-markdown-BWJNIDv3.js → render-chat-markdown-px_6WDIX.js} +1 -1
  82. package/src/client/dist/pwa/assets/runtime-core.esm-bundler-DOiZcasU.js +1 -0
  83. package/src/client/dist/pwa/assets/{typescript-DwyZ4CF7.js → typescript-DTPyRTrW.js} +1 -1
  84. package/src/client/dist/pwa/assets/{use-checkbox-2xzX4yYj.js → use-checkbox-Bh9EQhkV.js} +1 -1
  85. package/src/client/dist/pwa/assets/use-id-b4A2I8LB.js +1 -0
  86. package/src/client/dist/pwa/assets/use-is-mobile-DLIZxEo9.js +1 -0
  87. package/src/client/dist/pwa/assets/{use-search-index-status-Bluf9L99.js → use-search-index-status-_nCyACc7.js} +1 -1
  88. package/src/client/dist/pwa/assets/{use-slide-transition-CiaDm-tR.js → use-slide-transition-C5eDGOmu.js} +1 -1
  89. package/src/client/dist/pwa/assets/{use-tours-CjASQcG5.js → use-tours-b00RruX_.js} +1 -1
  90. package/src/client/dist/pwa/assets/use-worktree-restore-BDOZc9fk.js +1 -0
  91. package/src/client/dist/pwa/assets/{workspace-templates-BxYxJt3S.js → workspace-templates-DV9Njz0T.js} +1 -1
  92. package/src/client/dist/pwa/assets/{xml-B1UujXdS.js → xml-mxUZCM4r.js} +1 -1
  93. package/src/client/dist/pwa/assets/{yaml-mHN8kNgU.js → yaml-BuN8sUWC.js} +1 -1
  94. package/src/client/dist/pwa/index.html +9 -9
  95. package/src/client/dist/pwa/sw.js +1 -1
  96. package/src/client/dist/pwa/assets/ActivityFeed-CO5rOaa5.js +0 -8
  97. package/src/client/dist/pwa/assets/GitPanel-B8e1olfm.js +0 -3
  98. package/src/client/dist/pwa/assets/MainLayout-DFXIS_wv.js +0 -2
  99. package/src/client/dist/pwa/assets/QBadge-CQF1smL-.js +0 -1
  100. package/src/client/dist/pwa/assets/QBanner-bdDA-A5g.js +0 -1
  101. package/src/client/dist/pwa/assets/QBtn-CcQQrYA_.js +0 -1
  102. package/src/client/dist/pwa/assets/QBtnDropdown-C-1EhO9J.js +0 -1
  103. package/src/client/dist/pwa/assets/QBtnGroup-wd5lKRXu.js +0 -1
  104. package/src/client/dist/pwa/assets/QBtnToggle-BnC8rTql.js +0 -1
  105. package/src/client/dist/pwa/assets/QChip-D75nKi-L.js +0 -1
  106. package/src/client/dist/pwa/assets/QExpansionItem-RpMPvfxX.js +0 -1
  107. package/src/client/dist/pwa/assets/QItemSection-Bt9H35JJ.js +0 -1
  108. package/src/client/dist/pwa/assets/QList-D5msNbSZ.js +0 -1
  109. package/src/client/dist/pwa/assets/QMarkupTable-D3REW6RC.js +0 -1
  110. package/src/client/dist/pwa/assets/QPage-HAaeQ-cv.js +0 -1
  111. package/src/client/dist/pwa/assets/QSelect-Dpru_T81.js +0 -1
  112. package/src/client/dist/pwa/assets/QSpinnerDots-DDsPDUH9.js +0 -1
  113. package/src/client/dist/pwa/assets/QTooltip-C31vXtse.js +0 -1
  114. package/src/client/dist/pwa/assets/SettingsPage-CJmgFiAa.js +0 -9
  115. package/src/client/dist/pwa/assets/SplitWorkspacePage-BGSDgGIO.js +0 -1
  116. package/src/client/dist/pwa/assets/TouchPan-DJqqxDBF.js +0 -1
  117. package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-VlJCIkwU.js +0 -1
  118. package/src/client/dist/pwa/assets/WorkspacePage-B_SlX8YB.js +0 -4
  119. package/src/client/dist/pwa/assets/build-path-tree-CU6uXunR.js +0 -1
  120. package/src/client/dist/pwa/assets/custom-sounds-CDJsgChY.js +0 -1
  121. package/src/client/dist/pwa/assets/documents-Cf6nmu8Z.js +0 -1
  122. package/src/client/dist/pwa/assets/efforts-DoYZi0AZ.js +0 -9
  123. package/src/client/dist/pwa/assets/engineFeatures-C9XiCfvv.js +0 -1
  124. package/src/client/dist/pwa/assets/expand-template-LDLM-6iN.js +0 -1
  125. package/src/client/dist/pwa/assets/i18n-BumeGGx_.js +0 -1
  126. package/src/client/dist/pwa/assets/index-DolbyIM4.js +0 -20
  127. package/src/client/dist/pwa/assets/index-VMFLfvp2.css +0 -1
  128. package/src/client/dist/pwa/assets/pending-diff-open-B6N0vI5n.js +0 -1
  129. package/src/client/dist/pwa/assets/runtime-core.esm-bundler-CNFKm4ij.js +0 -1
  130. package/src/client/dist/pwa/assets/use-id-vOr8PRTG.js +0 -1
  131. package/src/client/dist/pwa/assets/use-is-mobile-CJfSpTHx.js +0 -1
  132. /package/src/client/dist/pwa/assets/{engineFeatures-B-9jHocY.css → use-worktree-restore-B-9jHocY.css} +0 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@ All notable changes to Kōbō are documented here. The format is based on
4
4
  [Keep a Changelog](https://keepachangelog.com/). Each release is an `## <version>`
5
5
  section — the in-app "What's new" dialog reads this file.
6
6
 
7
+ ## 2.1.4
8
+
9
+ - chore(deps): update server client and pwa packages
10
+
11
+ ## 2.1.3
12
+
13
+ - fix(subagents): reliable lifecycle tracking and a stop button
14
+
7
15
  ## 2.1.2
8
16
 
9
17
  - fix: read-only returning reviews, resumed-turn stdin, feed and tag filter
@@ -4621,4 +4621,43 @@ app.post('/:id/interrupt', migrationGuard, async (c) => {
4621
4621
  return c.json({ error: message }, workspaceErrorStatus(err));
4622
4622
  }
4623
4623
  });
4624
+ // POST /api/workspaces/:id/subagents/stop - stop one running sub-agent (body
4625
+ // `{ id }`, its canonical card id) or all of them, without interrupting the turn
4626
+ app.post('/:id/subagents/stop', migrationGuard, async (c) => {
4627
+ try {
4628
+ const id = c.req.param('id');
4629
+ const workspace = workspaceService.getWorkspace(id);
4630
+ if (!workspace) {
4631
+ return c.json({ error: `Workspace '${id}' not found` }, 404);
4632
+ }
4633
+ const rawBody = await c.req.text();
4634
+ let body = {};
4635
+ if (rawBody.trim().length > 0) {
4636
+ let parsed;
4637
+ try {
4638
+ parsed = JSON.parse(rawBody);
4639
+ }
4640
+ catch {
4641
+ return c.json({ error: 'Request body must be valid JSON' }, 400);
4642
+ }
4643
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
4644
+ return c.json({ error: 'Request body must be a JSON object' }, 400);
4645
+ }
4646
+ body = parsed;
4647
+ }
4648
+ if (body.id !== undefined && (typeof body.id !== 'string' || body.id.trim().length === 0)) {
4649
+ return c.json({ error: 'id must be a non-empty string when provided' }, 400);
4650
+ }
4651
+ const stopped = agentManager.stopSubagents(id, body.id);
4652
+ return c.json({ stopped });
4653
+ }
4654
+ catch (err) {
4655
+ const message = err instanceof Error ? err.message : String(err);
4656
+ if (err instanceof agentManager.StopSubagentsError) {
4657
+ const status = err.code === 'subagent_not_running' ? 404 : 409;
4658
+ return c.json({ error: message, code: err.code }, status);
4659
+ }
4660
+ return c.json({ error: message }, workspaceErrorStatus(err));
4661
+ }
4662
+ });
4624
4663
  export default app;
@@ -124,9 +124,52 @@ export function createClaudeCodeEngine() {
124
124
  async start(options, onEvent) {
125
125
  const abortController = new AbortController();
126
126
  const mapperState = createMapperState();
127
- // UI/tool ids can differ from the SDK task id. Key by the stable UI id
128
- // so a terminal notification that omits task_id still clears the task.
129
- const activeSubagentTaskIds = new Map();
127
+ // Active sub-agents keyed by their canonical id (SDK task id when known,
128
+ // else the tool call id), which is also the id passed to `q.stopTask`.
129
+ // One task can surface under both ids (relaunch, kill/restart), so
130
+ // running and terminal events must resolve to the same entry.
131
+ const activeSubagentTaskIds = new Set();
132
+ // tool call id -> SDK task id, learned from events carrying both.
133
+ const subagentTaskIdByToolCallId = new Map();
134
+ // Ambient tasks (SDK `ambient`, e.g. Monitor watchers) are not activity:
135
+ // they never enter `activeSubagentTaskIds`, so they never block
136
+ // turn:completed, keep the input open or arm the stall watchdog. They are
137
+ // tracked apart only so a user interrupt still stops them.
138
+ const ambientSubagentTaskIds = new Set();
139
+ // Canonical ids whose last lifecycle event was terminal: a late
140
+ // `progress` for them is ignored, only a `started` relaunch revives them.
141
+ const terminalSubagentTaskIds = new Set();
142
+ const trackSubagentProgress = (ev) => {
143
+ if (ev.taskId) {
144
+ subagentTaskIdByToolCallId.set(ev.toolCallId, ev.taskId);
145
+ // Merge an entry first tracked under its tool call id alone.
146
+ if (ev.toolCallId !== ev.taskId) {
147
+ activeSubagentTaskIds.delete(ev.toolCallId);
148
+ ambientSubagentTaskIds.delete(ev.toolCallId);
149
+ if (terminalSubagentTaskIds.delete(ev.toolCallId))
150
+ terminalSubagentTaskIds.add(ev.taskId);
151
+ }
152
+ }
153
+ const taskId = ev.taskId ?? subagentTaskIdByToolCallId.get(ev.toolCallId);
154
+ const key = taskId ?? ev.toolCallId;
155
+ if (ev.status !== 'running') {
156
+ activeSubagentTaskIds.delete(key);
157
+ ambientSubagentTaskIds.delete(key);
158
+ terminalSubagentTaskIds.add(key);
159
+ return;
160
+ }
161
+ if (ev.phase === 'progress' && terminalSubagentTaskIds.has(key))
162
+ return;
163
+ terminalSubagentTaskIds.delete(key);
164
+ if (ev.ambient) {
165
+ activeSubagentTaskIds.delete(key);
166
+ ambientSubagentTaskIds.add(key);
167
+ }
168
+ else {
169
+ ambientSubagentTaskIds.delete(key);
170
+ activeSubagentTaskIds.add(key);
171
+ }
172
+ };
130
173
  // Foreground tool calls currently awaiting their tool_result. Cleared on
131
174
  // every `result` message: a turn cannot end with a tool still in flight.
132
175
  const pendingToolCallIds = new Set();
@@ -260,6 +303,17 @@ export function createClaudeCodeEngine() {
260
303
  sdkOptions.pathToClaudeCodeExecutable = explicitBinary;
261
304
  const inputStream = new ClaudeInputStream(effectivePrompt);
262
305
  const q = query({ prompt: inputStream, options: sdkOptions });
306
+ // Best-effort SDK stop of one background task; failures are ignored.
307
+ const requestStopTask = (taskId) => {
308
+ try {
309
+ void q.stopTask(taskId).catch(() => {
310
+ /* best-effort */
311
+ });
312
+ }
313
+ catch {
314
+ /* best-effort */
315
+ }
316
+ };
263
317
  let resolveReady;
264
318
  let rejectReady;
265
319
  const ready = new Promise((resolve, reject) => {
@@ -507,15 +561,14 @@ export function createClaudeCodeEngine() {
507
561
  clearResultDrainWatchdog();
508
562
  const events = mapSdkMessage(msg, mapperState);
509
563
  for (const ev of events) {
510
- if (ev.kind !== 'subagent:progress')
511
- continue;
512
- if (ev.status === 'running')
513
- activeSubagentTaskIds.set(ev.toolCallId, ev.taskId ?? ev.toolCallId);
514
- else
515
- activeSubagentTaskIds.delete(ev.toolCallId);
564
+ if (ev.kind === 'subagent:progress')
565
+ trackSubagentProgress(ev);
516
566
  }
517
567
  if (activeSubagentTaskIds.size > 0 &&
518
- (events.some((ev) => ev.kind === 'subagent:progress' ||
568
+ (events.some((ev) =>
569
+ // An ambient watcher's progress is not proof that a real
570
+ // sub-agent advanced: it must not reset the stall bound.
571
+ (ev.kind === 'subagent:progress' && !ev.ambient) ||
519
572
  ev.kind === 'message:text' ||
520
573
  ev.kind === 'message:thinking' ||
521
574
  ev.kind === 'tool:call' ||
@@ -689,6 +742,28 @@ export function createClaudeCodeEngine() {
689
742
  armSubagentStallWatchdog();
690
743
  return true;
691
744
  },
745
+ stopSubagents(ids) {
746
+ const isRunning = (taskId) => activeSubagentTaskIds.has(taskId) || ambientSubagentTaskIds.has(taskId);
747
+ const targets = new Set();
748
+ if (ids === undefined) {
749
+ for (const taskId of [...activeSubagentTaskIds, ...ambientSubagentTaskIds])
750
+ targets.add(taskId);
751
+ }
752
+ else {
753
+ for (const id of ids) {
754
+ // A card id is the SDK task id, or the tool call id when the
755
+ // task id was not known yet: resolve both to the tracked entry.
756
+ const taskId = isRunning(id) ? id : subagentTaskIdByToolCallId.get(id);
757
+ if (taskId && isRunning(taskId))
758
+ targets.add(taskId);
759
+ }
760
+ }
761
+ // The main turn keeps running: only the targeted tasks are stopped.
762
+ // Their terminal notification clears the tracking as usual.
763
+ for (const taskId of targets)
764
+ requestStopTask(taskId);
765
+ return targets.size;
766
+ },
692
767
  interrupt() {
693
768
  userInterrupted = true;
694
769
  // The SDK ends an interrupted run by emitting a `result` with
@@ -699,16 +774,8 @@ export function createClaudeCodeEngine() {
699
774
  // background subagent task keeps running unless told to stop via
700
775
  // its own API, which would otherwise leave the Stop button
701
776
  // appearing to do nothing for up to SUBAGENT_STALL_TIMEOUT_MS.
702
- for (const taskId of activeSubagentTaskIds.values()) {
703
- try {
704
- void q.stopTask(taskId).catch(() => {
705
- /* best-effort */
706
- });
707
- }
708
- catch {
709
- /* best-effort */
710
- }
711
- }
777
+ for (const taskId of [...activeSubagentTaskIds, ...ambientSubagentTaskIds])
778
+ requestStopTask(taskId);
712
779
  const qq = q;
713
780
  if (typeof qq.interrupt === 'function') {
714
781
  try {
@@ -77,6 +77,21 @@ export const QUOTA_PATTERN = /\b429\b|out of extra usage|rate[_ ]limit|usage lim
77
77
  * of a previous attempt, a sub-agent failure, or application HTTP handling.
78
78
  */
79
79
  const ASSISTANT_QUOTA_NOTICE = /^(?:you(?:'|’)re out of extra usage|you(?:'|’)ve hit your (?:session|weekly|usage) limit|claude ai usage limit reached|usage limit reached|quota exceeded|rate limit exceeded)(?:[.!]?(?:[ \t]*[·—-]?[ \t]*resets?\b[^\r\n]*)?)?[.!]?$/i;
80
+ /** SDKTaskNotificationMessage.status -> subagent:progress terminal status. */
81
+ const TASK_NOTIFICATION_STATUS = {
82
+ completed: 'done',
83
+ failed: 'failed',
84
+ stopped: 'stopped',
85
+ };
86
+ /**
87
+ * SDKTaskUpdatedMessage.patch.status values that end a task. `pending`,
88
+ * `running` and `paused` are not lifecycle edges and are ignored.
89
+ */
90
+ const TASK_UPDATED_TERMINAL_STATUS = {
91
+ completed: 'done',
92
+ failed: 'failed',
93
+ killed: 'stopped',
94
+ };
80
95
  export function createMapperState() {
81
96
  return {
82
97
  sessionStartedEmitted: false,
@@ -84,6 +99,8 @@ export function createMapperState() {
84
99
  sawErrorResult: false,
85
100
  quotaErrorEmitted: false,
86
101
  userInterrupted: false,
102
+ taskToolUseIds: new Map(),
103
+ taskFlags: new Map(),
87
104
  };
88
105
  }
89
106
  /** Known SDK `result` subtypes that indicate the run failed. */
@@ -191,30 +208,79 @@ export function mapSdkMessage(msg, state) {
191
208
  events.push({ kind: 'session:compacting', active: sdkStatus === 'compacting' });
192
209
  return events;
193
210
  }
194
- if (subtype === 'task_started' || subtype === 'task_progress' || subtype === 'task_notification') {
211
+ if (subtype === 'task_started' ||
212
+ subtype === 'task_progress' ||
213
+ subtype === 'task_notification' ||
214
+ subtype === 'task_updated') {
195
215
  const taskId = typeof parsed.task_id === 'string' ? parsed.task_id : undefined;
196
- const toolCallId = typeof parsed.tool_use_id === 'string' ? parsed.tool_use_id : taskId;
197
- if (toolCallId) {
198
- const usage = parsed.usage;
199
- // Unlike task_started/task_progress (in-flight signals), task_notification
200
- // is only ever emitted once a task settles — its `status` value is a
201
- // reason, not a liveness signal. Treat every task_notification as
202
- // terminal instead of matching against a status whitelist, so an SDK
203
- // status value added later doesn't leave the subagent stuck 'running'.
204
- const isDone = subtype === 'task_notification';
205
- events.push({
206
- kind: 'subagent:progress',
207
- toolCallId,
208
- ...(taskId ? { taskId } : {}),
209
- status: isDone ? 'done' : 'running',
210
- description: typeof parsed.description === 'string' ? parsed.description : undefined,
211
- taskType: typeof parsed.task_type === 'string' ? parsed.task_type : undefined,
212
- lastToolName: typeof parsed.last_tool_name === 'string' ? parsed.last_tool_name : undefined,
213
- totalTokens: typeof usage?.total_tokens === 'number' ? usage.total_tokens : undefined,
214
- toolUses: typeof usage?.tool_uses === 'number' ? usage.tool_uses : undefined,
215
- durationMs: typeof usage?.duration_ms === 'number' ? usage.duration_ms : undefined,
216
- });
216
+ const toolUseId = typeof parsed.tool_use_id === 'string' ? parsed.tool_use_id : undefined;
217
+ if (taskId && toolUseId)
218
+ state.taskToolUseIds.set(taskId, toolUseId);
219
+ const toolCallId = toolUseId ?? (taskId ? (state.taskToolUseIds.get(taskId) ?? taskId) : undefined);
220
+ if (!toolCallId)
221
+ return events;
222
+ let status;
223
+ let phase;
224
+ if (subtype === 'task_updated') {
225
+ // Only a terminal patch status is a lifecycle signal; other patches
226
+ // (pending/running/paused, description, is_backgrounded...) are ignored.
227
+ const patch = parsed.patch;
228
+ const patchStatus = TASK_UPDATED_TERMINAL_STATUS[String(patch?.status)];
229
+ if (!patchStatus)
230
+ return events;
231
+ status = patchStatus;
232
+ }
233
+ else if (subtype === 'task_notification') {
234
+ // task_notification is only ever emitted once a task settles: an
235
+ // unknown status value is still terminal (`done`), so a future SDK
236
+ // value never leaves the sub-agent stuck 'running'.
237
+ status = TASK_NOTIFICATION_STATUS[String(parsed.status)] ?? 'done';
238
+ }
239
+ else {
240
+ status = 'running';
241
+ phase = subtype === 'task_started' ? 'started' : 'progress';
242
+ }
243
+ if (taskId) {
244
+ const hasAmbient = typeof parsed.ambient === 'boolean';
245
+ const hasSkip = typeof parsed.skip_transcript === 'boolean';
246
+ if (subtype === 'task_started') {
247
+ // A (re)launch defines the task's classification afresh.
248
+ state.taskFlags.set(taskId, {
249
+ ...(hasAmbient ? { ambient: parsed.ambient } : {}),
250
+ ...(hasSkip ? { skipTranscript: parsed.skip_transcript } : {}),
251
+ });
252
+ }
253
+ else if (hasAmbient || hasSkip) {
254
+ state.taskFlags.set(taskId, {
255
+ ...state.taskFlags.get(taskId),
256
+ ...(hasAmbient ? { ambient: parsed.ambient } : {}),
257
+ ...(hasSkip ? { skipTranscript: parsed.skip_transcript } : {}),
258
+ });
259
+ }
217
260
  }
261
+ const flags = taskId ? state.taskFlags.get(taskId) : undefined;
262
+ const usage = parsed.usage;
263
+ const description = typeof parsed.description === 'string'
264
+ ? parsed.description
265
+ : subtype === 'task_updated' &&
266
+ typeof parsed.patch?.description === 'string'
267
+ ? parsed.patch.description
268
+ : undefined;
269
+ events.push({
270
+ kind: 'subagent:progress',
271
+ toolCallId,
272
+ ...(taskId ? { taskId } : {}),
273
+ status,
274
+ ...(phase ? { phase } : {}),
275
+ ...(flags?.ambient ? { ambient: true } : {}),
276
+ ...(flags?.skipTranscript ? { skipTranscript: true } : {}),
277
+ description,
278
+ taskType: typeof parsed.task_type === 'string' ? parsed.task_type : undefined,
279
+ lastToolName: typeof parsed.last_tool_name === 'string' ? parsed.last_tool_name : undefined,
280
+ totalTokens: typeof usage?.total_tokens === 'number' ? usage.total_tokens : undefined,
281
+ toolUses: typeof usage?.tool_uses === 'number' ? usage.tool_uses : undefined,
282
+ durationMs: typeof usage?.duration_ms === 'number' ? usage.duration_ms : undefined,
283
+ });
218
284
  return events;
219
285
  }
220
286
  if (subtype === 'init') {
@@ -14,6 +14,7 @@ const KOBO_MCP_BRIEF = [
14
14
  '• `kobo__set_workspace_status` when the mission is done or irrecoverably blocked. Never set `idle` while this session is active; use AskUserQuestion for clarification, or end the turn.',
15
15
  '• `kobo__schedule_wakeup` / `kobo__cancel_wakeup` to schedule (or cancel) a follow-up session — prefer these over the built-in `ScheduleWakeup` tool.',
16
16
  'Each tool carries its own "WHEN to use" guidance in its description — follow it.',
17
+ 'Only one agent edits the worktree at a time: do not run background subagents that modify files while you or another subagent modify files too. Parallel read-only subagents (search, analysis, review) are fine. Wait for a file-editing subagent to finish before editing or running tests yourself, and remember that changes appearing in the worktree may come from your own subagents.',
17
18
  ].join('\n');
18
19
  /**
19
20
  * Appended to the Claude Code preset system prompt so the rule is always in
@@ -340,13 +340,35 @@ export function createCodexEngine() {
340
340
  if (n.item.type === 'fileChange')
341
341
  fileChanges.set(itemKey(n.threadId, n.turnId, n.item.id), n.item);
342
342
  const events = mapTurnEvents(n, (state) => handleItemCompleted(n.item, state));
343
+ const collabItem = n.item.type === 'collabAgentToolCall' ? n.item : undefined;
344
+ const isActiveAgent = (status) => status === 'pendingInit' || status === 'running';
345
+ const progressIndex = events.findIndex((event) => event.kind === 'subagent:progress');
346
+ if (collabItem && progressIndex >= 0) {
347
+ // The card that first tracked a thread (normally its spawnAgent)
348
+ // owns it until it finishes. A later wait/sendInput/resumeAgent
349
+ // on an already-owned thread (e.g. a wait that timed out) must
350
+ // not steal it, or the owner card is orphaned in `running`; such
351
+ // a card is done as soon as its own call completes.
352
+ const progress = events[progressIndex];
353
+ const ownsActiveThread = Object.entries(collabItem.agentsStates).some(([threadId, agentState]) => {
354
+ if (!isActiveAgent(agentState.status))
355
+ return false;
356
+ const owner = activeSubagentThreads.get(threadId);
357
+ return !owner || owner.toolCallId === progress.toolCallId;
358
+ });
359
+ if (progress.status === 'running' && !ownsActiveThread) {
360
+ events[progressIndex] = { ...progress, status: 'done' };
361
+ }
362
+ }
343
363
  for (const ev of events)
344
364
  safeEmit(ev);
345
- if (n.item.type === 'collabAgentToolCall') {
346
- const progress = events.find((event) => event.kind === 'subagent:progress');
347
- for (const [threadId, agentState] of Object.entries(n.item.agentsStates)) {
348
- if (agentState.status === 'pendingInit' || agentState.status === 'running') {
349
- if (progress) {
365
+ if (collabItem) {
366
+ const progress = progressIndex >= 0
367
+ ? events[progressIndex]
368
+ : undefined;
369
+ for (const [threadId, agentState] of Object.entries(collabItem.agentsStates)) {
370
+ if (isActiveAgent(agentState.status)) {
371
+ if (progress && !activeSubagentThreads.has(threadId)) {
350
372
  activeSubagentThreads.set(threadId, {
351
373
  toolCallId: progress.toolCallId,
352
374
  description: progress.description,
@@ -11,6 +11,7 @@ const CODEX_KOBO_MCP_BRIEF = [
11
11
  '• `mcp__kobo-tasks__set_workspace_status` when the mission is done or irrecoverably blocked. Never set `idle` while this session is active; ask the user or end the turn instead.',
12
12
  '• `mcp__kobo-tasks__schedule_wakeup` / `mcp__kobo-tasks__cancel_wakeup` to schedule (or cancel) a follow-up session.',
13
13
  'Each tool carries its own "WHEN to use" guidance in its description — follow it.',
14
+ 'Only one agent edits the worktree at a time: do not run background subagents that modify files while you or another subagent modify files too. Parallel read-only subagents (search, analysis, review) are fine. Wait for a file-editing subagent to finish before editing or running tests yourself, and remember that changes appearing in the worktree may come from your own subagents.',
14
15
  ].join('\n');
15
16
  export function buildCodexOptions(input) {
16
17
  const isResume = input.resumeFromEngineSessionId !== undefined;
@@ -34,6 +34,14 @@ export class InterruptAgentError extends Error {
34
34
  this.name = 'InterruptAgentError';
35
35
  }
36
36
  }
37
+ export class StopSubagentsError extends Error {
38
+ code;
39
+ constructor(message, code) {
40
+ super(message);
41
+ this.code = code;
42
+ this.name = 'StopSubagentsError';
43
+ }
44
+ }
37
45
  // ── State ──────────────────────────────────────────────────────────────────────
38
46
  /** Actual bound port of the running backend — set at startup via setBackendPort() */
39
47
  let backendPort = process.env.PORT ? Number.parseInt(process.env.PORT, 10) : 3000;
@@ -1494,6 +1502,33 @@ export function interruptAgent(workspaceId, options = {}) {
1494
1502
  autoLoopService.disable(workspaceId, 'user-action');
1495
1503
  }
1496
1504
  }
1505
+ /**
1506
+ * Stop one running sub-agent (by its canonical id: task id or tool call id)
1507
+ * or, without id, every running sub-agent. The main turn keeps running.
1508
+ * Returns the number of sub-agents a stop was requested for.
1509
+ */
1510
+ export function stopSubagents(workspaceId, id) {
1511
+ const ctrl = controllers.get(workspaceId);
1512
+ if (!ctrl) {
1513
+ throw new StopSubagentsError(`No agent running for workspace '${workspaceId}'`, 'no_agent_running');
1514
+ }
1515
+ let stopped;
1516
+ try {
1517
+ stopped = ctrl.stopSubagents(id === undefined ? undefined : [id]);
1518
+ }
1519
+ catch (err) {
1520
+ // A controller whose engine has not started yet has nothing to stop.
1521
+ const message = err instanceof Error ? err.message : String(err);
1522
+ throw new StopSubagentsError(`No agent running for workspace '${workspaceId}': ${message}`, 'no_agent_running');
1523
+ }
1524
+ if (stopped === undefined) {
1525
+ throw new StopSubagentsError(`The agent engine of workspace '${workspaceId}' cannot stop a single sub-agent`, 'unsupported');
1526
+ }
1527
+ if (id !== undefined && stopped === 0) {
1528
+ throw new StopSubagentsError(`Sub-agent '${id}' is not running for workspace '${workspaceId}'`, 'subagent_not_running');
1529
+ }
1530
+ return stopped;
1531
+ }
1497
1532
  /** Bound the caller's wait, retaining ownership until actual engine shutdown. */
1498
1533
  export const STOP_AGENT_TIMEOUT_MS = 15_000;
1499
1534
  async function stopController(workspaceId, ctrl, cause = 'user') {
@@ -121,6 +121,18 @@ export class SessionController {
121
121
  throw new Error('SessionController not started');
122
122
  this._engineProcess.interrupt();
123
123
  }
124
+ /**
125
+ * Stop running sub-agents without interrupting the main turn. Returns the
126
+ * number of sub-agents a stop was requested for, or `undefined` when the
127
+ * engine cannot stop a single sub-agent.
128
+ */
129
+ stopSubagents(ids) {
130
+ if (!this._engineProcess)
131
+ throw new Error('SessionController not started');
132
+ if (typeof this._engineProcess.stopSubagents !== 'function')
133
+ return undefined;
134
+ return this._engineProcess.stopSubagents(ids);
135
+ }
124
136
  stop() {
125
137
  if (!this._stopPromise) {
126
138
  this._stopPromise = this.stopEngine().catch((err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loicngr/kobo",
3
- "version": "2.1.2",
3
+ "version": "2.1.4",
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
  }
@@ -0,0 +1,8 @@
1
+ import{M as e,S as t,dt as n,lt as r,p as i,st as a,v as o}from"./reactivity.esm-bundler-tuEvQ47f.js";import{C as s,F as c,J as l,L as u,N as d,O as f,P as p,Y as m,_ as h,d as g,f as _,g as v,j as y,k as b,l as x,p as S,r as C,u as w,y as T,z as ee}from"./runtime-core.esm-bundler-DOiZcasU.js";import{T as E,Y as D,b as te,l as O,lt as k,mt as A,nt as j,t as ne,x as re}from"./QBtn-b_baQtAt.js";import{Bt as ie,O as ae,Rt as M,T as oe,Xt as N,g as se,j as P,nt as ce,p as le,qt as ue,rt as de,ut as fe}from"./index-fuLr-ZVa.js";import{t as pe}from"./QSpinnerDots-BsKmccpX.js";import{t as me}from"./QList-Z90BspT4.js";import{t as F}from"./QMarkupTable-Bwjknbmw.js";import{c as I,l as L}from"./QSelect-CMzJDhi-.js";import{t as he}from"./QExpansionItem-CxEBY3jY.js";import{t as R}from"./documents-CALwmao-.js";import{r as ge,t as z}from"./pending-diff-open-CX24u9gW.js";import{a as B}from"./use-tours-b00RruX_.js";import{t as V}from"./QTooltip-BLhkNT4s.js";import{i as _e,n as ve,o as H,r as ye,t as be}from"./render-chat-markdown-px_6WDIX.js";import{a as xe,i as Se,n as Ce,o as we,r as Te,s as U}from"./WorkspacePage-FScR5YAD.js";function Ee(e,t){return s(`div`,e,[s(`table`,{class:`q-table`},t)])}var De={list:me,table:F},W=[`list`,`table`,`__qtable`],Oe=j({name:`QVirtualScroll`,props:{...L,type:{type:String,default:`list`,validator:e=>W.includes(e)},items:{type:Array,default:()=>[]},itemsFn:Function,itemsSize:Number,scrollTarget:N},setup(e,{slots:n,attrs:r}){let i,a=t(null),o=x(()=>e.itemsSize>=0&&e.itemsFn!==void 0?Number.parseInt(e.itemsSize,10):Array.isArray(e.items)?e.items.length:0),{virtualScrollSliceRange:c,localResetVirtualScroll:u,padVirtualScroll:f,onVirtualScrollEvt:m}=I({virtualScrollLength:o,getVirtualScrollTarget:S,getVirtualScrollEl:v}),h=x(()=>{if(o.value===0)return[];let t=(e,t)=>({index:c.value.from+t,item:e});return e.itemsFn===void 0?e.items.slice(c.value.from,c.value.to).map(t):e.itemsFn(c.value.from,c.value.to-c.value.from).map(t)}),g=x(()=>`q-virtual-scroll q-virtual-scroll`+(e.virtualScrollHorizontal?`--horizontal`:`--vertical`)+(e.scrollTarget===void 0?` scroll`:``)),_=x(()=>e.scrollTarget===void 0?{tabindex:0}:{});l(o,()=>{u()}),l(()=>e.scrollTarget,()=>{w(),C()});function v(){let e=a.value;return e===null?null:e.$el||e}function S(){return i}function C(){let t=v();t!==null&&(i=ue(t,e.scrollTarget),i.addEventListener(`scroll`,m,D.passive))}function w(){i!==void 0&&(i.removeEventListener(`scroll`,m,D.passive),i=void 0)}function T(){let t=f(e.type===`list`?`div`:`tbody`,h.value.map(n.default));return n.before!==void 0&&(t=n.before().concat(t)),re(n.after,t)}return u(),p(()=>{C()}),b(()=>{C()}),d(()=>{w()}),y(()=>{w()}),()=>{if(n.default===void 0){console.error(`QVirtualScroll: default scoped slot is required for rendering`);return}return e.type===`__qtable`?Ee({ref:a,class:`q-table__middle `+g.value,tabindex:0},T()):s(De[e.type],{...r,ref:a,class:[r.class,g.value],..._.value},T)}}}),G=new Set([`setup`,`cleanup`,`archive`]);function ke(e){switch(e.type){case`user`:return e.sender===`system-prompt`?`system-prompt`:G.has(e.sender)||oe(e.sender)?`script`:`user`;case`session`:return`session`;default:return`agent`}}function Ae(e){let t=[],n=null,r=null;for(let i of e){let e=ke(i),a=e===`session`||e===`system-prompt`,o=e===`script`&&i.type===`user`?`script:${i.sender}`:e===`user`&&i.type===`user`&&i.source?JSON.stringify([`mcp`,i.source.transport,i.source.clientName]):e;!n||r!==o||a?(n={speaker:e,ts:i.ts,items:[i]},r=o,t.push(n),a&&(n=null)):n.items.push(i)}return t}function je(e){switch(e.type){case`text`:return`text:${e.messageId}`;case`thinking`:return`thinking:${e.messageId}:${e.eventIds?.[0]??e.ts??``}`;case`tool`:return`tool:${e.toolCallId}`;case`session`:return`session:${e.kind}:${e.eventIds?.[0]??e.ts??``}`;case`user`:return`user:${e.eventIds?.[0]??`${e.sender}:${e.ts??``}`}`;case`error`:return`error:${e.eventIds?.[0]??`${e.category}:${e.ts??``}`}`}}function Me(e){let t=e.items[0];return`${e.speaker}:${t?je(t):`empty`}`}function Ne(e,t){let n=Math.min(t,e.length)-1;for(let t=n;t>=0;t--)if(e[t]?.speaker===`user`)return t;return-1}function Pe(e,t,n){return new Promise(r=>{let i=!1,a=null,o=null,s=e=>{i||(i=!0,a!==null&&clearTimeout(a),a=null,o?.(),r(e))};if(o=l(e,e=>{t(e)&&s(!0)},{immediate:!0}),i){o();return}a=setTimeout(()=>s(!1),n)})}var Fe={class:`agent-error-item`},Ie={class:`agent-error-item__body`},Le={class:`agent-error-item__title`},Re={class:`agent-error-item__message`},ze=B(T({__name:`AgentErrorItem`,props:{item:{}},setup(e){let t=e,{te:r}=M(),i=x(()=>{let e=`agent.error.${t.item.category}`;return r(e)?e:`agent.error.other`});return(t,r)=>(u(),S(`div`,Fe,[h(O,{name:`error_outline`,size:`16px`,class:`agent-error-item__icon`}),w(`div`,Ie,[w(`div`,Le,n(t.$t(i.value)),1),w(`div`,Re,n(e.item.message),1)])]))}}),[[`__scopeId`,`data-v-e98c3c02`]]),K={class:`text-caption text-kobo-3`},Be=T({__name:`SessionEventItem`,props:{item:{}},setup(e){let t=e,r=x(()=>{switch(t.item.kind){case`started`:return`session.started`;case`ended`:return U(t.item.detail);case`compacted`:return`session.compacted`;default:return`session.started`}});return(e,t)=>(u(),S(`span`,K,n(e.$t(r.value)),1))}});function q(e,t){if(t.length===0||e.length===0)return e;let n=[...t].sort((e,t)=>t.length-e.length),r=new DOMParser().parseFromString(`<div>${e}</div>`,`text/html`),i=r.body.firstChild;if(!i)return e;function a(e){if(e.nodeType===Node.TEXT_NODE){Ve(e,n,r);return}if(e.nodeName===`A`)return;let t=Array.from(e.childNodes);for(let e of t)a(e)}return a(i),i.innerHTML}function Ve(e,t,n){let r=e.textContent??``;if(!t.some(e=>r.includes(e)))return;let i=n.createDocumentFragment(),a=0;for(;a<r.length;){let e=J(r,a,t);if(!e){i.appendChild(n.createTextNode(r.slice(a)));break}e.index>a&&i.appendChild(n.createTextNode(r.slice(a,e.index)));let o=n.createElement(`a`);o.className=`document-link`,o.setAttribute(`data-document-path`,e.path),o.setAttribute(`href`,`#`),o.textContent=e.path,i.appendChild(o),a=e.index+e.path.length}e.parentNode?.replaceChild(i,e)}function J(e,t,n){let r=null;for(let i of n){let n=e.indexOf(i,t);n<0||(!r||n<r.index||n===r.index&&i.length>r.path.length)&&(r={index:n,path:i})}return r}var He=[`innerHTML`],Y=B(T({__name:`TextMessageItem`,props:{item:{}},setup(e){let t=e,n=R(),r=ce(),i=te(),{t:a}=M(),o=x(()=>{let e=r.selectedWorkspaceId;return e?n.documentsFor(e).map(e=>e.path):[]}),s=x(()=>{let e=t.item;if(e.streaming)return be(e.text);let n=o.value;return ye(`${e.messageId}|${n.join(`,`)}`,e.text,e=>{let t=q(H.parse(e,{async:!1,breaks:!0,gfm:!0}),n);return _e(t,{addAttr:[`data-document-path`]})})});function c(e){let t=e.target?.closest(`.document-link`);if(!t)return;e.preventDefault();let o=t.getAttribute(`data-document-path`),s=r.selectedWorkspaceId;o&&s&&n.openDocumentByPath(s,o).catch(e=>{console.error(`[TextMessageItem] openDocumentByPath failed:`,e),i.notify({type:`negative`,message:a(`documents.loadFailed`),position:`top`})})}return(t,n)=>(u(),S(`div`,{class:`markdown-message`,onClick:c},[w(`div`,{innerHTML:s.value},null,8,He),e.item.streaming?(u(),g(E,{key:0,size:`xs`,class:`q-ml-xs`})):_(``,!0)]))}}),[[`__scopeId`,`data-v-99371035`]]);function Ue(e,t){let n=e.split(`
2
+ `),r=t.split(`
3
+ `),i=0;for(;i<n.length&&i<r.length&&n[i]===r[i];)i++;let a=n.length,o=r.length;for(;a>i&&o>i&&n[a-1]===r[o-1];)a--,o--;let s=n.slice(0,i).map(e=>({type:`context`,content:e})),c=n.slice(a).map(e=>({type:`context`,content:e})),l=n.slice(i,a),u=r.slice(i,o);return l.length>800||u.length>800?[...s,...l.map(e=>({type:`del`,content:e})),...u.map(e=>({type:`add`,content:e})),...c]:[...s,...X(l,u),...c]}function X(e,t){let n=e.length,r=t.length;if(n===0&&r===0)return[];if(n===0)return t.map(e=>({type:`add`,content:e}));if(r===0)return e.map(e=>({type:`del`,content:e}));let i=r+1,a=new Int32Array((n+1)*i);for(let o=n-1;o>=0;o--)for(let n=r-1;n>=0;n--)a[o*i+n]=e[o]===t[n]?a[(o+1)*i+(n+1)]+1:Math.max(a[(o+1)*i+n],a[o*i+(n+1)]);let o=[],s=0,c=0;for(;s<n&&c<r;)e[s]===t[c]?(o.push({type:`context`,content:e[s]}),s++,c++):a[(s+1)*i+c]>=a[s*i+(c+1)]?(o.push({type:`del`,content:e[s]}),s++):(o.push({type:`add`,content:t[c]}),c++);for(;s<n;)o.push({type:`del`,content:e[s++]});for(;c<r;)o.push({type:`add`,content:t[c++]});return o}function We(e){let t=e.split(`
4
+ `),n=[],r=!1;for(let e of t){if(e.startsWith(`@@`)){r=!0;continue}(r||!e.startsWith(`+++`)&&!e.startsWith(`---`))&&(e.startsWith(`+`)?n.push({type:`add`,content:e.slice(1)}):e.startsWith(`-`)?n.push({type:`del`,content:e.slice(1)}):e.startsWith(` `)?n.push({type:`context`,content:e.slice(1)}):e.length>0&&n.push({type:`context`,content:e}))}return n}function Ge(e,t){if(!t||typeof t!=`object`)return null;let n=t;if(e===`Edit`){let e=n.file_path;if(!e)return null;let t=n.old_string??``,r=n.new_string??``,i=typeof n.diff==`string`?n.diff:``;if(!t&&!r&&i.length>0){let t=We(i);return{toolName:`Edit`,filePath:e,additions:t.filter(e=>e.type===`add`).length,deletions:t.filter(e=>e.type===`del`).length,diffLines:t}}return{toolName:`Edit`,filePath:e,oldString:t,newString:r,replaceAll:n.replace_all??!1,additions:r?r.split(`
5
+ `).length:0,deletions:t?t.split(`
6
+ `).length:0}}if(e===`Write`){let e=n.file_path;if(!e)return null;let t=n.content??``;return{toolName:`Write`,filePath:e,content:t,additions:t?t.split(`
7
+ `).length:0,deletions:0}}if(e===`Bash`){let e=(n.command??``).match(/^\s*rm\s+(?:-[a-zA-Z]*\s+)*(.+)/);if(e)return{toolName:`Bash:rm`,filePath:e[1].trim().replace(/["']/g,``),additions:0,deletions:1}}return null}function Z(e,t){if(!e||!t?.projectPath)return e;let n=t.worktreePath;if(n){let t=Q(e,n);if(t!==e)return t}let r=Q(e,`${t.projectPath}/${P}/${t.workingBranch}`);return r===e?Q(e,t.projectPath):r}function Q(e,t){if(!t)return e;let n=Ke(t);return n?e.replace(RegExp(`${n}[\\\\/]+`,`g`),``).replace(RegExp(`${n}(?=\\s|$|["'\`])`,`g`),`.`):e}function Ke(e){let t=e.replace(/[\\/]+$/,``);return t?`${/^[\\/]+/.test(t)?`[\\\\/]+`:``}${t.split(/[\\/]+/).filter(Boolean).map(qe).join(`[\\\\/]+`)}`:``}function qe(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}var Je={class:`tool-name`},Ye=[`title`],Xe={key:0,class:`tool-stat-add`},Ze={key:1,class:`tool-stat-del`},Qe={class:`diff-sign`},$e={class:`tool-name`},et=[`title`],tt=B(T({__name:`ToolCallItem`,props:{item:{}},setup(t){let r=t,i=o(!1),s=ce(),{t:c}=M(),d=x(()=>Ge(r.item.name,r.item.input)),f=x(()=>d.value?Z(d.value.filePath,s.selectedWorkspace):``),p={Bash:`terminal`,Read:`description`,Edit:`edit`,Write:`edit_note`,MultiEdit:`edit`,Glob:`folder_open`,Grep:`manage_search`,LS:`list`,Skill:`auto_awesome`,Task:`hub`,Agent:`hub`,TodoWrite:`checklist`,TodoRead:`checklist`,TaskCreate:`add_task`,TaskUpdate:`checklist`,TaskGet:`checklist`,TaskList:`checklist`,ToolSearch:`search`,WebFetch:`public`,WebSearch:`travel_explore`,NotebookRead:`book`,NotebookEdit:`edit_note`,SendMessage:`send`,ExitPlanMode:`check_circle_outline`,KillShell:`stop_circle`,BashOutput:`terminal`},y=x(()=>p[r.item.name]??`build`),b=x(()=>{if(d.value)return``;let e=r.item.input,t=T(e);return t?Z(t,s.selectedWorkspace):``});function T(e){if(!e||typeof e!=`object`)return typeof e==`string`?e:``;let t=e;for(let e of[`file_path`,`path`,`command`,`pattern`,`query`,`url`,`skill`,`description`,`subject`,`prompt`]){let n=t[e];if(typeof n==`string`&&n.length>0)return n}for(let e of Object.values(t))if(typeof e==`string`&&e.length>0)return e;return``}let E=x(()=>{let e=d.value;return e?e.diffLines?e.diffLines:e.toolName===`Edit`&&e.oldString!==void 0&&e.newString!==void 0?Ue(e.oldString,e.newString):e.toolName===`Write`&&e.content!==void 0?e.content.split(`
8
+ `).map(e=>({type:`add`,content:e})):e.toolName===`Bash:rm`?[{type:`del`,content:`File deleted`}]:null:null}),D=x(()=>{let e=r.item.result;if(!e)return``;if(typeof e.output==`string`)return e.output;try{return JSON.stringify(e.output)}catch{return String(e.output)}}),te=new Set([`Read`]),k=x(()=>!!(r.item.result&&D.value)&&(!te.has(r.item.name)||r.item.result?.isError===!0));function j(){i.value=!i.value}function re(){let e=d.value?.filePath,t=s.selectedWorkspace,n=t?.id,r=e?Z(e,t):``;r&&!/^(?:[A-Za-z]:)?[\\/]/.test(r)&&n&&(z(n,r),window.dispatchEvent(new CustomEvent(`kobo:open-diff`,{detail:{workspaceId:n,path:r}})))}return l(()=>r.item.result?.isError===!0,e=>{e&&(i.value=!0)},{immediate:!0}),(r,o)=>d.value?(u(),S(`div`,{key:0,class:a([`tool-row`,{"tool-row-expanded":i.value}])},[w(`div`,{class:`tool-header`,onClick:j},[h(O,{name:y.value,size:`14px`,class:`tool-icon`},null,8,[`name`]),w(`span`,Je,n(d.value.toolName===`Bash:rm`?`Bash`:d.value.toolName),1),w(`span`,{class:`tool-path`,title:d.value.filePath},n(f.value),9,Ye),h(ne,{flat:``,dense:``,round:``,size:`xs`,icon:`open_in_new`,class:`tool-open-diff`,onClick:A(re,[`stop`])},{default:m(()=>[h(V,null,{default:m(()=>[v(n(e(c)(`chat.openInGitDiff`)),1)]),_:1})]),_:1}),d.value.additions>0?(u(),S(`span`,Xe,`+`+n(d.value.additions),1)):_(``,!0),d.value.deletions>0?(u(),S(`span`,Ze,`-`+n(d.value.deletions),1)):_(``,!0),t.item.result?.isError?(u(),g(O,{key:2,name:`error_outline`,color:`negative`,size:`xs`,class:`q-ml-xs`})):t.item.result?(u(),g(O,{key:3,name:`check`,color:`positive`,size:`xs`,class:`q-ml-xs`})):_(``,!0),h(O,{name:i.value?`expand_less`:`expand_more`,size:`xs`,class:`q-ml-auto text-kobo-3`},null,8,[`name`])]),i.value&&E.value?(u(),S(`div`,{key:0,class:`tool-diff`,onClick:o[0]||=A(()=>{},[`stop`])},[(u(!0),S(C,null,ee(E.value,(e,t)=>(u(),S(`div`,{key:t,class:a([`diff-line`,{"diff-del":e.type===`del`,"diff-add":e.type===`add`,"diff-context":e.type===`context`}])},[w(`span`,Qe,n(e.type===`del`?`-`:e.type===`add`?`+`:` `),1),v(n(e.content),1)],2))),128))])):_(``,!0)],2)):(u(),S(`div`,{key:1,class:a([`tool-row tool-row-generic`,{"tool-row-expanded":i.value,"tool-row--toggleable":k.value}])},[w(`div`,{class:`tool-header`,onClick:o[1]||=e=>k.value&&j()},[h(O,{name:y.value,size:`14px`,class:`tool-icon`},null,8,[`name`]),w(`span`,$e,n(t.item.name),1),b.value?(u(),S(`span`,{key:0,class:`tool-arg`,title:T(t.item.input)||b.value},n(b.value),9,et)):_(``,!0),t.item.result?.isError?(u(),g(O,{key:1,name:`error_outline`,color:`negative`,size:`xs`,class:`q-ml-auto`})):t.item.result?(u(),g(O,{key:2,name:`check`,color:`positive`,size:`xs`,class:`q-ml-auto`})):_(``,!0),k.value?(u(),g(O,{key:3,name:i.value?`expand_less`:`expand_more`,size:`xs`,class:`q-ml-xs text-kobo-3`},null,8,[`name`])):_(``,!0)]),i.value&&k.value?(u(),S(`div`,{key:0,class:`tool-output`,onClick:o[2]||=A(()=>{},[`stop`])},n(D.value),1)):_(``,!0)],2))}}),[[`__scopeId`,`data-v-2482adfe`]]),$=new Map;function nt(){for(let e of $.values())e.controller.abort(),e.blobUrl&&URL.revokeObjectURL(e.blobUrl);$.clear()}typeof window<`u`&&(window.addEventListener(`kobo:auth-token-changed`,nt),window.addEventListener(`storage`,e=>{(e.key===`kobo:network-token`||e.key===null)&&nt()}));function rt(e){if(!/^\/api\/workspaces\/[^/]+\/images\/file\?path=/.test(e))throw Error(`Invalid workspace image URL`);let t=$.get(e);if(!t){let n=new AbortController,r={references:0,controller:n,ready:Promise.resolve(``)},i=setTimeout(()=>n.abort(),3e4);r.ready=fetch(e,{signal:n.signal}).then(async e=>{if(!e.ok)throw Error(`HTTP ${e.status}`);let t=await e.blob();if(n.signal.aborted)throw new DOMException(`Aborted`,`AbortError`);return r.blobUrl=URL.createObjectURL(t),r.blobUrl}).finally(()=>clearTimeout(i)),t=r,$.set(e,t)}t.references++;let n=!1,r=t;return{ready:r.ready,release(){n||(n=!0,!(--r.references>0)&&(r.controller.abort(),r.blobUrl&&URL.revokeObjectURL(r.blobUrl),$.get(e)===r&&$.delete(e)))}}}function it(e,t){let n=o(``),r=o(0),a=()=>{r.value++},s=e=>{(e.key===`kobo:network-token`||e.key===null)&&a()};return window.addEventListener(`kobo:auth-token-changed`,a),window.addEventListener(`storage`,s),i(()=>{window.removeEventListener(`kobo:auth-token-changed`,a),window.removeEventListener(`storage`,s)}),l([e,r],([e],r,i)=>{let a=!0,o=[],s=document.createElement(`template`);s.innerHTML=e;for(let e of s.content.querySelectorAll(`img`)){let r=e.getAttribute(`src`)??``;if(e.removeAttribute(`data-kobo-image-url`),!/^\/api\/workspaces\/[^/]+\/images\/file\?path=/.test(r))continue;e.removeAttribute(`src`);let i=rt(r);o.push(i),i.ready.then(t=>{a&&(e.setAttribute(`src`,t),e.setAttribute(`data-kobo-image-url`,r),n.value=s.innerHTML)}).catch(()=>{a&&(e.setAttribute(`title`,t()),n.value=s.innerHTML)})}n.value=s.innerHTML,i(()=>{a=!1;for(let e of o)e.release()})},{immediate:!0}),n}function at(e,t){return t?e.replace(/\[image:\s+([^\]]+)\]/g,(e,n)=>{let r=String(n).trim();return/^(\.ai\/images\/|images\/)/.test(r)?`![${r}](${`/api/workspaces/${encodeURIComponent(t)}/images/file?path=${encodeURIComponent(r)}`})`:e}):e}var ot=[`innerHTML`],st=[`innerHTML`],ct=[`src`],lt=B(T({__name:`UserMessageItem`,props:{item:{},workspaceId:{}},setup(t){let n=t,{t:r}=M(),a=x(()=>n.item.sender===`system-prompt`),s=it(x(()=>{let e=at(n.item.content,n.workspaceId);return ve(e)}),()=>r(`network.login.unreachable`)),c=o(null),d=o(!1),f;function p(){d.value=!1,c.value=null,f?.release(),f=void 0}l(d,e=>{e||p()}),l(()=>n.workspaceId,p);let v=e=>{(e.key===`kobo:network-token`||e.key===null)&&p()};window.addEventListener(`storage`,v),window.addEventListener(`kobo:auth-token-changed`,p),i(()=>{p(),window.removeEventListener(`kobo:auth-token-changed`,p),window.removeEventListener(`storage`,v)});function y(e){let t=e.target;if(t?.tagName!==`IMG`)return;let n=t;n.src&&(p(),n.dataset.koboImageUrl&&(f=rt(n.dataset.koboImageUrl),f.ready.catch(p)),c.value=n.src,d.value=!0)}return(t,n)=>(u(),S(C,null,[a.value?(u(),g(he,{key:0,dense:``,"dense-toggle":``,label:t.$t(`chat.systemPrompt`),"header-class":`text-kobo-2 text-caption`},{default:m(()=>[w(`div`,{class:`q-py-xs markdown-user-prompt`,innerHTML:e(s)},null,8,ot)]),_:1},8,[`label`])):(u(),S(`div`,{key:1,class:`markdown-message`,onClick:y},[w(`div`,{innerHTML:e(s)},null,8,st)])),h(ie,{modelValue:d.value,"onUpdate:modelValue":n[1]||=e=>d.value=e},{default:m(()=>[c.value?(u(),S(`img`,{key:0,src:c.value,alt:``,class:`image-lightbox-img`,onClick:n[0]||=e=>d.value=!1},null,8,ct)):_(``,!0)]),_:1},8,[`modelValue`])],64))}}),[[`__scopeId`,`data-v-db2fc622`]]),ut=[`data-event-ids`],dt={class:`turn-header`},ft={key:0,class:`turn-time`},pt={class:`turn-time turn-time-updated`},mt={key:2,class:`turn-actions`},ht={class:`turn-body`},gt={key:0,class:`turn-scroll-top`},_t=B(T({__name:`TurnCard`,props:{turn:{},workspaceId:{},highlighted:{type:Boolean}},emits:[`scrollTo`],setup(t,{emit:i}){let s=t,c=i,{t:l}=M(),d=o(null);function f(){let e=d.value;if(!e)return;let t=e.closest(`.q-scrollarea`)?.querySelector(`.q-scrollarea__content`);if(!t){e.scrollIntoView({behavior:`smooth`,block:`start`});return}let n=e.getBoundingClientRect().top-t.getBoundingClientRect().top;c(`scrollTo`,Math.max(0,n-8))}let p=x(()=>{switch(s.turn.speaker){case`user`:{let e=s.turn.items[0],t=e?.type===`user`?e.source:void 0;return{label:t?l(`chat.externalLlm`,{name:t.clientName}):l(`chat.you`),accent:`var(--kobo-turn-user)`,badgeClass:`turn-badge-user`}}case`agent`:return{label:l(`chat.agent`),accent:`var(--kobo-turn-agent)`,badgeClass:`turn-badge-agent`};case`system-prompt`:return{label:l(`chat.systemPrompt`),accent:`var(--kobo-text-3)`,badgeClass:`turn-badge-system`};case`session`:return{label:l(`chat.session`),accent:`var(--kobo-text-3)`,badgeClass:`turn-badge-session`};case`script`:{let e=s.turn.items[0],t=e?.type===`user`?e.sender:``;return{label:oe(t)?l(`chat.hookScript`,{event:t.slice(5)}):l(t===`archive`?`chat.archiveScript`:t===`setup`?`chat.setupScript`:`chat.cleanupScript`),accent:`var(--kobo-success)`,badgeClass:`turn-badge-script`}}}});function y(e,t=!1){if(!e)return``;let n=new Date(e);return Number.isNaN(n.getTime())?``:n.toLocaleTimeString(void 0,t?{hour:`2-digit`,minute:`2-digit`,second:`2-digit`}:{hour:`2-digit`,minute:`2-digit`})}let b=x(()=>y(s.turn.ts)),T=x(()=>s.turn.items.flatMap(e=>e.eventIds??[]).join(` `)),E=x(()=>{let e=s.turn.items;if(e.length===0)return null;for(let t=e.length-1;t>=0;t--){let n=e[t].ts;if(n)return n}return null}),D=x(()=>{let e=s.turn.ts,t=E.value;if(!t||!e||t===e)return``;let n=new Date(e).getTime(),r=new Date(t).getTime();return Number.isNaN(n)||Number.isNaN(r)||r<=n?``:y(t,r-n<6e4)}),te=x(()=>D.value!==``),k=x(()=>s.turn.items.filter(e=>e.type===`tool`).length);return(i,o)=>(u(),S(`div`,{ref_key:`cardEl`,ref:d,class:a([`turn-card`,{"turn-card--user":t.turn.speaker===`user`,"turn-card--highlighted":t.highlighted}]),"data-event-ids":T.value,style:r({"--turn-accent":p.value.accent})},[w(`div`,dt,[w(`span`,{class:a([`turn-badge`,p.value.badgeClass])},n(p.value.label),3),b.value?(u(),S(`span`,ft,n(b.value),1)):_(``,!0),te.value?(u(),S(C,{key:1},[h(O,{name:`arrow_forward`,size:`10px`,color:`kobo-3`,class:`turn-time-arrow`}),w(`span`,pt,[v(n(D.value)+` `,1),h(V,null,{default:m(()=>[v(n(e(l)(`chat.lastUpdatedAt`,{time:D.value})),1)]),_:1})])],64)):_(``,!0),k.value>0?(u(),S(`span`,mt,` · `+n(e(l)(`chat.nActions`,{n:k.value})),1)):_(``,!0)]),w(`div`,ht,[(u(!0),S(C,null,ee(t.turn.items,n=>(u(),S(C,{key:e(je)(n)},[n.type===`text`?(u(),g(Y,{key:0,item:n},null,8,[`item`])):n.type===`thinking`?(u(),g(Ce,{key:1,item:n},null,8,[`item`])):n.type===`tool`?(u(),g(tt,{key:2,item:n},null,8,[`item`])):n.type===`user`?(u(),g(lt,{key:3,item:n,"workspace-id":t.workspaceId},null,8,[`item`,`workspace-id`])):n.type===`session`?(u(),g(Be,{key:4,item:n},null,8,[`item`])):n.type===`error`?(u(),g(ze,{key:5,item:n},null,8,[`item`])):_(``,!0)],64))),128))]),t.turn.items.length>4?(u(),S(`div`,gt,[h(ne,{flat:``,round:``,dense:``,size:`xs`,icon:`arrow_upward`,color:`kobo-3`,class:`turn-scroll-top-btn`,onClick:f},{default:m(()=>[h(V,null,{default:m(()=>[v(n(e(l)(`chat.scrollToTurnTop`)),1)]),_:1})]),_:1})])):_(``,!0)],14,ut))}}),[[`__scopeId`,`data-v-c0e24cbd`]]),vt={key:0,class:`activity-feed-switching`},yt={key:1,class:`activity-feed-wrap`,"data-tour":`ws-chat`},bt=[`aria-label`],xt={key:0,class:`activity-feed-compacting`},St={class:`text-caption text-kobo-2`},Ct={key:0,class:`text-center q-py-sm text-caption text-kobo-3`},wt={key:1,class:`activity-feed-empty q-pa-md`},Tt={class:`activity-feed-empty__title`},Et={class:`activity-feed-empty__hint`},Dt={key:2,class:`q-px-md q-pb-md`},Ot={class:`activity-feed-nav-cluster`},kt=300,At=60,jt=200,Mt=180,Nt=3,Pt=60,Ft=B(T({__name:`ActivityFeed`,props:{workspaceId:{}},setup(t){let{t:r}=M(),i=t,a=fe(),s=ae(),d=ce(),y=le(),b=x(()=>y.isCompacting(i.workspaceId)),T=x(()=>d.selectedSessionId),D=x(()=>d.sessions.find(e=>e.id===T.value)?.engineSessionId??null),te=x(()=>{let e=d.sessions;return e.length!==0&&T.value===e[e.length-1].id});function A(e){return T.value?e?e===T.value||e===D.value:te.value:!0}let j=x(()=>(d.activityFeeds[i.workspaceId]??[]).filter(e=>e.type===`text`&&typeof e.content==`string`&&A(e.sessionId)).map(e=>({content:e.content,sender:e.meta?.sender??`user`,source:se(e.meta?.source),ts:e.timestamp,sessionId:e.sessionId,eventIds:[e.id]}))),re=x(()=>{let e=d.workspaces.find(e=>e.id===i.workspaceId);return de(e?.status)}),ie=Te(),oe=``,N=x(()=>{let e=a.eventsFor(i.workspaceId),t=a.timestampsFor(i.workspaceId),n=a.sessionIdsFor(i.workspaceId),r=a.eventIdsFor(i.workspaceId),o=[],c=[],l=[];for(let i=0;i<e.length;i++)A(n[i])&&(o.push(e[i]),c.push(t[i]),l.push(r[i]??null));let u=`${i.workspaceId}::${T.value??`*`}`;u!==oe&&(ie=Te(),oe=u);let d=Se(ie,o,c,re.value,l);return Ae(we(d,j.value).filter(e=>e.type===`thinking`?!1:e.type===`session`?e.kind===`ended`?s.showVerboseSystemMessages||!xe(e.detail):s.showVerboseSystemMessages:!0))}),P=x(()=>s.showVerboseSystemMessages?a.eventsFor(i.workspaceId).filter(e=>e.kind===`message:raw`).map(e=>e.content):[]),ue=x(()=>{for(let e=N.value.length-1;e>=0;e--){let t=N.value[e]?.items??[];for(let e=t.length-1;e>=0;e--){let n=t[e];if(n){if(n.type===`text`&&!n.streaming)return{key:`text:${n.messageId}`,text:n.text};if(n.type===`tool`)return n.result?{key:`tool:${n.toolCallId}:end`,text:n.result.isError?r(`activity.a11y.toolFailed`,{name:n.name}):r(`activity.a11y.toolFinished`,{name:n.name})}:{key:`tool:${n.toolCallId}:start`,text:r(`activity.a11y.toolStarted`,{name:n.name})}}}}return null}),me=o(``);l(ue,(e,t)=>{if(!e||e.key===t?.key)return;let n=e.key.startsWith(`text:`)?r(`activity.a11y.agentSaid`,{text:e.text.slice(0,kt)}):e.text;me.value=n});let F=o(null),I=o(!0),L=o(!1),R=o(null),z=!1,B=o(new Set),V=x(()=>{if(N.value.length>0||P.value.length>0)return!1;let e=i.workspaceId,t=T.value;return!!d.loadingSessions[e]||y.isSyncPending(e)||t!==null&&B.value.has(H(e,t))}),_e=o(new Map);function ve(e){let t=e.verticalSize-e.verticalPosition-e.verticalContainerSize;I.value=t<=At,z&&e.verticalPosition<=jt&&!L.value&&ye()&&Ee()}function H(e,t){return`${e}:${t}`}function ye(){let e=T.value;return e?_e.value.get(H(i.workspaceId,e))??!0:a.hasMoreOlderFor(i.workspaceId)}function be(e,t,n){_e.value.set(H(e,t),n)}function Ce(e){if(!T.value)return a.oldestIdFor(e);let t=a.eventIdsFor(e),n=a.sessionIdsFor(e);for(let e=0;e<t.length;e++){if(!A(n[e]))continue;let r=t[e];if(r)return r}}let U=null;function Ee(){return U||(U=De().finally(()=>{U=null}),U)}async function De(){let e=i.workspaceId,t=T.value,n=Ce(e);if(n){L.value=!0;try{let r=F.value;await f();let i=r?.getScroll().verticalSize??0,o=r?.getScroll().verticalPosition??0,s=new URLSearchParams({before:n,limit:`200`});t&&s.set(`session`,t);let c=await fetch(`/api/workspaces/${e}/events?${s.toString()}`);if(!c.ok){t?be(e,t,!1):a.prepend(e,[],[],{oldestId:n,hasMoreOlder:!1});return}let l=await c.json(),u=l.events??[],p=u.filter(t=>t.type===`agent:event`&&t.workspaceId===e),m=u.filter(t=>t.type===`user:message`&&t.workspaceId===e),h=p.map(e=>e.payload),g=p.map(e=>e.createdAt),_=p.map(e=>e.sessionId??null),v=p.map(e=>e.id),y=u.length>0?u[0].id:n;t&&be(e,t,l.hasMore),a.prepend(e,h,g,{oldestId:y,hasMoreOlder:t?a.hasMoreOlderFor(e):l.hasMore,sessionIds:_,eventIds:v});for(let t of m){let n=t.payload;typeof n.content==`string`&&d.addActivityItem(e,{id:t.id,type:`text`,content:n.content,timestamp:t.createdAt,sessionId:t.sessionId??void 0,meta:{sender:n.sender??`user`,source:se(n.source)}})}if(await f(),r){let e=r.getScroll().verticalSize-i;if(e>0){let t=Math.max(o+e,250);r.setScrollPosition(`vertical`,t,0)}}}catch(e){console.error(`[ActivityFeed] failed to load older events:`,e)}finally{L.value=!1}}}async function W(e=0){await f();let t=F.value;if(!t)return;let n=t.getScroll();t.setScrollPosition(`vertical`,n.verticalSize,e)}let G=null,ke=0;function je(){if(G!=null)return;let e=performance.now()-ke<Mt;G=requestAnimationFrame(()=>{G=null,ke=performance.now(),W(e?0:180)})}function Fe(e){let t=F.value;t&&t.setScrollPosition(`vertical`,Math.max(0,e),250)}function Ie(){let e=window.location.hash,t=e.indexOf(`?`);if(t===-1||!new URLSearchParams(e.slice(t+1)).has(`eventId`))return;let n=e.slice(0,t);window.history.replaceState(window.history.state,``,`${window.location.pathname}${window.location.search}${n}`)}async function Le(e){let t=e.detail;if(t?.workspaceId===i.workspaceId&&t.eventId)try{let e=new URLSearchParams({around:t.eventId,limit:`200`});t.sessionId&&e.set(`session`,t.sessionId);let n=await fetch(`/api/workspaces/${i.workspaceId}/events?${e}`);if(!n.ok)return;let r=await n.json(),o=r.events??[],s=o.filter(e=>e.type===`agent:event`&&e.workspaceId===i.workspaceId),c=o.filter(e=>e.type===`user:message`&&e.workspaceId===i.workspaceId);a.reset(i.workspaceId,s.map(e=>e.payload),s.map(e=>e.createdAt),{oldestId:o[0]?.id,hasMoreOlder:r.hasMore??!1,sessionIds:s.map(e=>e.sessionId),eventIds:s.map(e=>e.id)});for(let e of c){let t=e.payload;typeof t.content==`string`&&d.addActivityItem(i.workspaceId,{id:e.id,type:`text`,content:t.content,timestamp:e.createdAt,sessionId:e.sessionId??void 0,meta:{sender:t.sender??`user`,source:se(t.source)}})}await Pe(V,e=>!e,5e3),await f(),await new Promise(e=>requestAnimationFrame(()=>requestAnimationFrame(()=>e())));let l=N.value.findIndex(e=>e.items.some(e=>e.eventIds?.includes(t.eventId)??!1));if(l<0)return;R.value=t.eventId,Re.value?.scrollTo(l,`center`),K.value=l,window.setTimeout(()=>{R.value===t.eventId&&(R.value=null)},1800)}catch(e){console.error(`[ActivityFeed] failed to focus history event:`,e)}}let Re=o(null),ze=o(null),K=o(0);function Be(e){K.value=e.index}let q=o(!1);async function Ve(){if(q.value)return;q.value=!0;let e=i.workspaceId;try{let t=Ne(N.value,K.value);if(t<0)for(let n=0;n<15;n++){if(e!==i.workspaceId)return;if(!ye())break;let n=N.value.length;if(await Ee(),await f(),e!==i.workspaceId)return;if(K.value+=N.value.length-n,t=Ne(N.value,K.value),t>=0)break}t>=0&&e===i.workspaceId&&(Re.value?.scrollTo(t,`start`),K.value=t)}finally{q.value=!1}}let J=0;function He(){return new Promise(e=>requestAnimationFrame(()=>e()))}async function Y(){z=!1;let e=++J;await f();let t=-1,n=0;for(let r=0;r<Pt&&n<Nt;r++){if(e!==J)return;await W(0),await He();let r=F.value?.getScroll().verticalSize??0;n=r===t?n+1:0,t=r}e===J&&(z=!0)}function Ue(){z||=(J++,!0)}let X=x(()=>{let e=a.sessionIdsFor(i.workspaceId);if(!T.value)return e.length;let t=0;for(let n of e)A(n)&&t++;return t});l(V,async e=>{!e&&X.value>0&&await Y(),e||f(()=>{ze.value=F.value?.getScrollTarget()??null})}),p(()=>{window.addEventListener(`kobo:focus-history-event`,Le),f(()=>{ze.value=F.value?.getScrollTarget()??null}),X.value>0&&Y(),T.value&&Ke();let e=new URLSearchParams(window.location.hash.split(`?`)[1]??``).get(`eventId`);e&&(Le(new CustomEvent(`kobo:focus-history-event`,{detail:{workspaceId:i.workspaceId,sessionId:T.value,eventId:e}})),Ie())});let We=X.value>0;l(X,async e=>{L.value||!We&&e>0&&(We=!0,await Y())});let Ge=x(()=>a.liveAppendCountFor(i.workspaceId,A)),Z=()=>`${i.workspaceId}\u0000${d.selectedSessionId??``}`;l([Ge,Z],([e,t],[n,r])=>{t===r&&e>n&&I.value&&!L.value&&je()}),c(()=>{window.removeEventListener(`kobo:focus-history-event`,Le),G!=null&&(cancelAnimationFrame(G),G=null)}),l(()=>i.workspaceId,()=>{I.value=!0,We=X.value>0,z=!1,Ke(),X.value>0&&Y()}),l(()=>d.selectedSessionId,async()=>{I.value=!0,z=!1,Ke(),await Y()});let Q=new Set;async function Ke(){let e=i.workspaceId,t=T.value;if(!t||X.value>0)return;let n=H(e,t);if(!Q.has(n)){Q.add(n),B.value.add(n);try{let r=await fetch(`/api/workspaces/${e}/events?session=${encodeURIComponent(t)}&limit=500`);if(!r.ok){Q.delete(n);return}let o=await r.json(),s=o.events??[];if(s.length===0)return;let c=s.filter(t=>t.type===`agent:event`&&t.workspaceId===e),l=s.filter(t=>t.type===`user:message`&&t.workspaceId===e),u=c.map(e=>e.payload),p=c.map(e=>e.createdAt),m=c.map(e=>e.sessionId??null),h=c.map(e=>e.id);be(e,t,o.hasMore),u.length>0&&a.prepend(e,u,p,{oldestId:s[0].id,hasMoreOlder:a.hasMoreOlderFor(e),sessionIds:m,eventIds:h});for(let t of l){let n=t.payload;typeof n.content==`string`&&d.addActivityItem(e,{id:t.id,type:`text`,content:n.content,timestamp:t.createdAt,sessionId:t.sessionId??void 0,meta:{sender:n.sender??`user`,source:se(n.source)}})}await f(),i.workspaceId===e&&T.value===t&&await W(0)}catch(e){console.error(`[ActivityFeed] fetchSessionIfMissing failed:`,e),Q.delete(n)}finally{B.value.delete(n)}}}let qe=x(()=>j.value.filter(e=>e.sender!==`system-prompt`).length);l([qe,Z],async([e,t],[n,r])=>{t===r&&e>n&&(I.value=!0,await W(180))});async function Je(){I.value=!0,await W(250)}return(t,r)=>V.value?(u(),S(`div`,vt,[h(pe,{size:`40px`,color:`primary`})])):(u(),S(`div`,yt,[w(`div`,{"data-testid":`activity-live-region`,class:`sr-only`,role:`log`,"aria-live":`polite`,"aria-atomic":`true`,"aria-label":t.$t(`activity.a11y.region`)},n(me.value),9,bt),h(k,{name:`fade`},{default:m(()=>[b.value?(u(),S(`div`,xt,[h(pe,{size:`18px`,color:`primary`}),w(`span`,St,n(t.$t(`activity.compacting`)),1)])):_(``,!0)]),_:1}),h(ge,{ref_key:`scrollRef`,ref:F,class:`activity-feed-scroll`,onScroll:ve,onWheelPassive:Ue,onTouchstartPassive:Ue,onKeydown:Ue},{default:m(()=>[L.value?(u(),S(`div`,Ct,[h(E,{size:`sm`}),v(` `+n(t.$t(`activity.loading_older`)),1)])):_(``,!0),h(Oe,{ref_key:`virtualScrollRef`,ref:Re,class:`q-pa-md`,items:N.value,"scroll-target":ze.value??void 0,"virtual-scroll-item-size":160,"virtual-scroll-slice-size":20,onVirtualScroll:Be},{default:m(({item:t,index:n})=>[(u(),g(_t,{key:e(Me)(t),turn:t,"workspace-id":i.workspaceId,"data-turn-index":n,highlighted:t.items.some(e=>e.eventIds?.includes(R.value??``)??!1),onScrollTo:Fe},null,8,[`turn`,`workspace-id`,`data-turn-index`,`highlighted`]))]),_:1},8,[`items`,`scroll-target`]),N.value.length===0&&P.value.length===0&&!L.value?(u(),S(`div`,wt,[h(O,{name:`forum`,size:`28px`}),w(`div`,Tt,n(t.$t(`activity.empty`)),1),w(`div`,Et,n(t.$t(`activity.emptyHint`)),1)])):_(``,!0),P.value.length?(u(),S(`div`,Dt,[h(he,{label:t.$t(`activity.raw_lines`,{n:P.value.length}),dense:``},{default:m(()=>[(u(!0),S(C,null,ee(P.value,(e,t)=>(u(),S(`div`,{key:t,class:`text-caption text-kobo-3 q-pa-xs`},n(e),1))),128))]),_:1},8,[`label`])])):_(``,!0)]),_:1},512),w(`div`,Ot,[I.value?_(``,!0):(u(),g(ne,{key:0,round:``,dense:``,unelevated:``,color:`kobo-surface-2`,"text-color":`kobo-1`,icon:`arrow_downward`,size:`sm`,class:`activity-feed-nav-btn`,title:t.$t(`activity.scroll_to_bottom`),"aria-label":t.$t(`activity.scroll_to_bottom`),onClick:Je},null,8,[`title`,`aria-label`])),h(ne,{round:``,dense:``,unelevated:``,color:`kobo-surface-2`,"text-color":`kobo-1`,icon:`arrow_upward`,size:`sm`,class:`activity-feed-nav-btn`,title:t.$t(`activity.prev_user_message`),"aria-label":t.$t(`activity.prev_user_message`),loading:q.value,disable:q.value,onClick:Ve},null,8,[`title`,`aria-label`,`loading`,`disable`])])]))}}),[[`__scopeId`,`data-v-028e8af5`]]);export{Ft as default};
@@ -1 +1 @@
1
- import{j as e,ut as t,v as n}from"./reactivity.esm-bundler-Bo9anJjg.js";import{L as r,P as i,Y as a,_ as o,d as s,f as c,p as l,r as u,u as d,y as f,z as p}from"./runtime-core.esm-bundler-CNFKm4ij.js";import{t as m}from"./QBtn-CcQQrYA_.js";import{i as h,rn as g,tn as _}from"./index-DolbyIM4.js";import{a as v,i as y,n as b}from"./use-tours-CjASQcG5.js";import{n as x}from"./render-chat-markdown-BWJNIDv3.js";import{t as S}from"./QChip-D75nKi-L.js";import{t as C}from"./QPage-HAaeQ-cv.js";import{t as w}from"./DrawerToggleButton-Dfvkaro0.js";import{t as T}from"./TourReplayButton-Dtv_UDaY.js";var E={class:`row items-center q-mb-md`},D={class:`text-h6 q-ml-sm`},O={key:0,class:`text-kobo-3 text-center q-pa-lg`},k={key:1,class:`text-negative text-center q-pa-lg`},A={key:2,class:`text-kobo-3 text-center q-pa-lg`},j={key:3,class:`column q-gutter-md`,"data-tour":`changelog-list`},M={key:0,class:`text-caption text-kobo-3`},N={class:`row items-center q-mb-sm`},P={class:`text-subtitle1 text-kobo-2`,style:{"font-family":`var(--kobo-font-mono, monospace)`}},F=[`innerHTML`],I=v(f({__name:`ChangelogPage`,setup(f){let v=h(),{scheduleAutoRun:I}=b(),L=n([]),R=n(``),z=n(!1),B=n(null);function V(e){return x(e)}async function H(){z.value=!0,B.value=null;try{let e=await fetch(`/api/changelog`);if(!e.ok)throw Error(`HTTP ${e.status}`);let t=await e.json();R.value=t.currentVersion??``,L.value=t.versions??[]}catch(e){B.value=e instanceof Error?e.message:String(e)}finally{z.value=!1}}return i(()=>{H(),I(`changelog`)}),(n,i)=>(r(),s(C,{class:`q-pa-md`,style:{"max-width":`900px`,margin:`0 auto`}},{default:a(()=>[d(`div`,E,[o(w,{class:`q-mr-xs`}),o(m,{flat:``,dense:``,round:``,icon:`arrow_back`,onClick:i[0]||=t=>e(v).back()}),d(`div`,D,t(n.$t(`changelog.title`)),1),o(y),o(m,{flat:``,dense:``,icon:`refresh`,loading:z.value,label:n.$t(`common.refresh`),onClick:H},null,8,[`loading`,`label`]),o(T,{"tour-id":`changelog`,class:`q-ml-xs`})]),z.value&&L.value.length===0?(r(),l(`div`,O,t(n.$t(`common.loading`)),1)):B.value?(r(),l(`div`,k,t(B.value),1)):L.value.length===0?(r(),l(`div`,A,t(n.$t(`changelog.empty`)),1)):(r(),l(`div`,j,[R.value?(r(),l(`div`,M,t(n.$t(`changelog.currentVersion`,{version:R.value})),1)):c(``,!0),(r(!0),l(u,null,p(L.value,e=>(r(),s(_,{key:e.version,dark:``,flat:``,bordered:``},{default:a(()=>[o(g,null,{default:a(()=>[d(`div`,N,[d(`div`,P,` v`+t(e.version),1),e.version===R.value?(r(),s(S,{key:0,dense:``,size:`sm`,color:`primary`,"text-color":`kobo-1`,label:n.$t(`changelog.current`),class:`q-ml-sm`},null,8,[`label`])):c(``,!0)]),d(`div`,{class:`changelog-notes`,innerHTML:V(e.notes)},null,8,F)]),_:2},1024)]),_:2},1024))),128))]))]),_:1}))}}),[[`__scopeId`,`data-v-6137cb7c`]]);export{I as default};
1
+ import{M as e,dt as t,v as n}from"./reactivity.esm-bundler-tuEvQ47f.js";import{L as r,P as i,Y as a,_ as o,d as s,f as c,p as l,r as u,u as d,y as f,z as p}from"./runtime-core.esm-bundler-DOiZcasU.js";import{t as m}from"./QBtn-b_baQtAt.js";import{i as h,rn as g,tn as _}from"./index-fuLr-ZVa.js";import{a as v,i as y,n as b}from"./use-tours-b00RruX_.js";import{n as x}from"./render-chat-markdown-px_6WDIX.js";import{t as S}from"./QChip-Bt_JvnHb.js";import{t as C}from"./QPage-BCDXjfVj.js";import{t as w}from"./DrawerToggleButton-Bh4FXdtJ.js";import{t as T}from"./TourReplayButton-DlMvPblb.js";var E={class:`row items-center q-mb-md`},D={class:`text-h6 q-ml-sm`},O={key:0,class:`text-kobo-3 text-center q-pa-lg`},k={key:1,class:`text-negative text-center q-pa-lg`},A={key:2,class:`text-kobo-3 text-center q-pa-lg`},j={key:3,class:`column q-gutter-md`,"data-tour":`changelog-list`},M={key:0,class:`text-caption text-kobo-3`},N={class:`row items-center q-mb-sm`},P={class:`text-subtitle1 text-kobo-2`,style:{"font-family":`var(--kobo-font-mono, monospace)`}},F=[`innerHTML`],I=v(f({__name:`ChangelogPage`,setup(f){let v=h(),{scheduleAutoRun:I}=b(),L=n([]),R=n(``),z=n(!1),B=n(null);function V(e){return x(e)}async function H(){z.value=!0,B.value=null;try{let e=await fetch(`/api/changelog`);if(!e.ok)throw Error(`HTTP ${e.status}`);let t=await e.json();R.value=t.currentVersion??``,L.value=t.versions??[]}catch(e){B.value=e instanceof Error?e.message:String(e)}finally{z.value=!1}}return i(()=>{H(),I(`changelog`)}),(n,i)=>(r(),s(C,{class:`q-pa-md`,style:{"max-width":`900px`,margin:`0 auto`}},{default:a(()=>[d(`div`,E,[o(w,{class:`q-mr-xs`}),o(m,{flat:``,dense:``,round:``,icon:`arrow_back`,onClick:i[0]||=t=>e(v).back()}),d(`div`,D,t(n.$t(`changelog.title`)),1),o(y),o(m,{flat:``,dense:``,icon:`refresh`,loading:z.value,label:n.$t(`common.refresh`),onClick:H},null,8,[`loading`,`label`]),o(T,{"tour-id":`changelog`,class:`q-ml-xs`})]),z.value&&L.value.length===0?(r(),l(`div`,O,t(n.$t(`common.loading`)),1)):B.value?(r(),l(`div`,k,t(B.value),1)):L.value.length===0?(r(),l(`div`,A,t(n.$t(`changelog.empty`)),1)):(r(),l(`div`,j,[R.value?(r(),l(`div`,M,t(n.$t(`changelog.currentVersion`,{version:R.value})),1)):c(``,!0),(r(!0),l(u,null,p(L.value,e=>(r(),s(_,{key:e.version,dark:``,flat:``,bordered:``},{default:a(()=>[o(g,null,{default:a(()=>[d(`div`,N,[d(`div`,P,` v`+t(e.version),1),e.version===R.value?(r(),s(S,{key:0,dense:``,size:`sm`,color:`primary`,"text-color":`kobo-1`,label:n.$t(`changelog.current`),class:`q-ml-sm`},null,8,[`label`])):c(``,!0)]),d(`div`,{class:`changelog-notes`,innerHTML:V(e.notes)},null,8,F)]),_:2},1024)]),_:2},1024))),128))]))]),_:1}))}}),[[`__scopeId`,`data-v-6137cb7c`]]);export{I as default};
@@ -1 +1 @@
1
- import{V as e,nt as t}from"./QBtn-CcQQrYA_.js";import{m as n,p as r}from"./use-id-vOr8PRTG.js";function i(e){return e===!1?0:e===!0||e===void 0?1:Number.parseInt(e,10)||0}function a(e){let t=e.currentTarget,i=t.__qclosepopup;i!==0&&setTimeout(()=>{let a=n(t);a!==void 0&&r(a,e,i)},0)}function o(t){e(t,13)&&a(t)}var s=t({name:`close-popup`,beforeMount(e,{value:t}){e.__qclosepopup=i(t),e.addEventListener(`click`,a),e.addEventListener(`keyup`,o)},updated(e,{value:t,oldValue:n}){t!==n&&e.__qclosepopup!==void 0&&(e.__qclosepopup=i(t))},beforeUnmount(e){e.__qclosepopup!==void 0&&(e.removeEventListener(`click`,a),e.removeEventListener(`keyup`,o),e.__qclosepopup=void 0)}});export{s as t};
1
+ import{H as e,rt as t}from"./QBtn-b_baQtAt.js";import{g as n,h as r}from"./use-id-b4A2I8LB.js";function i(e){return e===!1?0:e===!0||e===void 0?1:Number.parseInt(e,10)||0}function a(e){let t=e.currentTarget,i=t.__qclosepopup;i!==0&&setTimeout(()=>{let a=n(t);a!==void 0&&r(a,e,i)},0)}function o(t){e(t,13)&&a(t)}var s=t({name:`close-popup`,beforeMount(e,{value:t}){e.__qclosepopup=i(t),e.addEventListener(`click`,a),e.addEventListener(`keyup`,o)},updated(e,{value:t,oldValue:n}){t!==n&&e.__qclosepopup!==void 0&&(e.__qclosepopup=i(t))},beforeUnmount(e){e.__qclosepopup!==void 0&&(e.removeEventListener(`click`,a),e.removeEventListener(`keyup`,o),e.__qclosepopup=void 0)}});export{s as t};