@loicngr/kobo 1.11.34 → 1.11.36

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 (140) hide show
  1. package/AGENTS.md +8 -0
  2. package/CHANGELOG.md +9 -0
  3. package/dist/server/db/migrations.js +14 -0
  4. package/dist/server/db/schema.js +9 -0
  5. package/dist/server/index.js +2 -0
  6. package/dist/server/routes/activity.js +17 -0
  7. package/dist/server/services/activity-service.js +62 -0
  8. package/dist/server/services/agent/engines/claude-code/engine.js +26 -0
  9. package/dist/server/services/agent/engines/claude-code/event-mapper.js +10 -10
  10. package/dist/server/services/agent/orchestrator.js +29 -0
  11. package/dist/server/services/auto-loop-service.js +14 -3
  12. package/dist/server/services/wakeup-service.js +14 -0
  13. package/dist/server/services/websocket-service.js +3 -0
  14. package/package.json +1 -1
  15. package/src/client/dist/pwa/assets/ActivityFeed-BPx-xhFY.js +8 -0
  16. package/src/client/dist/pwa/assets/ChangelogPage-BFGYQ6eY.js +1 -0
  17. package/src/client/dist/pwa/assets/ClosePopup-BqW5dy-O.js +1 -0
  18. package/src/client/dist/pwa/assets/ComparisonPanel-C6DfjpZZ.js +2 -0
  19. package/src/client/dist/pwa/assets/CreatePage-ypEzkAKv.js +2 -0
  20. package/src/client/dist/pwa/assets/DashboardPage-DsVRjzzy.js +1 -0
  21. package/src/client/dist/pwa/assets/DiffViewer-DSlSAIiW.js +8 -0
  22. package/src/client/dist/pwa/assets/DocumentsPanel-C1eXTw_Q.js +1 -0
  23. package/src/client/dist/pwa/assets/DrawerToggleButton-BxYpUb4y.js +1 -0
  24. package/src/client/dist/pwa/assets/{GitPanel-De5_E4NA.css → GitPanel-BFCnyhE2.css} +1 -1
  25. package/src/client/dist/pwa/assets/GitPanel-DnBWSdVp.js +3 -0
  26. package/src/client/dist/pwa/assets/HealthPage-rX1TiaP9.js +1 -0
  27. package/src/client/dist/pwa/assets/MainLayout-Ceuh5uJj.js +2 -0
  28. package/src/client/dist/pwa/assets/MainLayout-DxMVVA6f.css +1 -0
  29. package/src/client/dist/pwa/assets/{QBadge-CWEaKfKi.js → QBadge-DbkxGtht.js} +1 -1
  30. package/src/client/dist/pwa/assets/{QBanner-DRiFK0GP.js → QBanner-DhqH95GW.js} +1 -1
  31. package/src/client/dist/pwa/assets/QBtn-CZaBclNw.js +1 -0
  32. package/src/client/dist/pwa/assets/QBtnDropdown-K7syiyyH.js +1 -0
  33. package/src/client/dist/pwa/assets/{QBtnGroup-BY6XqeTJ.js → QBtnGroup-DELKYo3u.js} +1 -1
  34. package/src/client/dist/pwa/assets/{QBtnToggle-CCgwqSgg.js → QBtnToggle-BI3k3Ch2.js} +1 -1
  35. package/src/client/dist/pwa/assets/{QChip-CJ6WmvGE.js → QChip-owciRuZu.js} +1 -1
  36. package/src/client/dist/pwa/assets/QDrawer-DapzE8DH.js +1 -0
  37. package/src/client/dist/pwa/assets/QItemSection-D_5_0UrX.js +1 -0
  38. package/src/client/dist/pwa/assets/QList-qupzuEqO.js +1 -0
  39. package/src/client/dist/pwa/assets/{QMarkupTable-MxPL53nz.js → QMarkupTable-B9QuKtK_.js} +1 -1
  40. package/src/client/dist/pwa/assets/QPage-DaX9sMQC.js +1 -0
  41. package/src/client/dist/pwa/assets/QSelect-Bodml89k.js +1 -0
  42. package/src/client/dist/pwa/assets/{QSpinnerDots-6v3Yl0T6.js → QSpinnerDots-DnCV3VzI.js} +1 -1
  43. package/src/client/dist/pwa/assets/QTooltip-K-9OdDfq.js +1 -0
  44. package/src/client/dist/pwa/assets/SchedulePanel-CfumTzAa.js +1 -0
  45. package/src/client/dist/pwa/assets/SearchPage-AhZYS-dS.js +1 -0
  46. package/src/client/dist/pwa/assets/SettingsPage-BoHXWvKn.js +9 -0
  47. package/src/client/dist/pwa/assets/{SettingsPage-CDA54yPx.css → SettingsPage-Do9d0Pov.css} +1 -1
  48. package/src/client/dist/pwa/assets/SplitWorkspacePage-3wK11mcu.js +1 -0
  49. package/src/client/dist/pwa/assets/SplitWorkspacePage-lWQO8xro.css +1 -0
  50. package/src/client/dist/pwa/assets/{TerminalPanel-BzUFE4eS.js → TerminalPanel-CKg0BeNK.js} +9 -9
  51. package/src/client/dist/pwa/assets/TouchPan-D6ctyPYf.js +1 -0
  52. package/src/client/dist/pwa/assets/TourReplayButton-Dn4Dy_kH.js +1 -0
  53. package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-DnoabsfK.js +1 -0
  54. package/src/client/dist/pwa/assets/WorkspacePage-BI7CW2yT.js +4 -0
  55. package/src/client/dist/pwa/assets/{WorkspacePage-ygEb8olx.css → WorkspacePage-DYQrfeYw.css} +1 -1
  56. package/src/client/dist/pwa/assets/{browser-pf20JpKz.js → browser-DHpSUK8-.js} +1 -1
  57. package/src/client/dist/pwa/assets/build-path-tree-DtEjsZlh.js +1 -0
  58. package/src/client/dist/pwa/assets/de-Dt_m-H83.js +17 -0
  59. package/src/client/dist/pwa/assets/{documents-C1u794ea.js → documents-B2dg1wRf.js} +1 -1
  60. package/src/client/dist/pwa/assets/{editor.api-BaRghNyI.js → editor.api-Cd1D35oj.js} +1 -1
  61. package/src/client/dist/pwa/assets/{efforts-EHbgZlde.js → efforts-IB1vPjJQ.js} +2 -2
  62. package/src/client/dist/pwa/assets/engineFeatures-DYSyDEJe.js +1 -0
  63. package/src/client/dist/pwa/assets/es-DFg25DCS.js +17 -0
  64. package/src/client/dist/pwa/assets/{expand-template-D7OUa8G7.js → expand-template-CNAHiesL.js} +1 -1
  65. package/src/client/dist/pwa/assets/{format-D3dPFA-T.js → format-DidwgbbX.js} +7 -7
  66. package/src/client/dist/pwa/assets/{formatters-DBdoGhB7.js → formatters-BjzyUHlu.js} +1 -1
  67. package/src/client/dist/pwa/assets/fr-CQ1slpol.js +17 -0
  68. package/src/client/dist/pwa/assets/{freemarker2-DI7u6MdV.js → freemarker2-jNGWawVw.js} +1 -1
  69. package/src/client/dist/pwa/assets/{handlebars-BgkIQIEY.js → handlebars-DfSqClrW.js} +1 -1
  70. package/src/client/dist/pwa/assets/{html-BOPtZli3.js → html-ByLX-n8T.js} +1 -1
  71. package/src/client/dist/pwa/assets/i18n-CnjXQjxy.js +1 -0
  72. package/src/client/dist/pwa/assets/index-DcwcFDrZ.js +20 -0
  73. package/src/client/dist/pwa/assets/it-w09rfaP7.js +17 -0
  74. package/src/client/dist/pwa/assets/{javascript-Cf6-AeMO.js → javascript-Dja1fHfv.js} +1 -1
  75. package/src/client/dist/pwa/assets/{liquid-Bd9X87K-.js → liquid-D315hW6C.js} +1 -1
  76. package/src/client/dist/pwa/assets/{mdx-C_ObhhDT.js → mdx-B5yUwpzv.js} +1 -1
  77. package/src/client/dist/pwa/assets/{monaco-contributions-rHxRMwAP.js → monaco-contributions-NfxrU0r3.js} +1 -1
  78. package/src/client/dist/pwa/assets/monaco.contribution-D1hXPkDZ.js +2 -0
  79. package/src/client/dist/pwa/assets/network-auth-CARVtbNe.js +1 -0
  80. package/src/client/dist/pwa/assets/notify-theme-CKC3-EAZ.js +1 -0
  81. package/src/client/dist/pwa/assets/pending-diff-open-BlLrQaW7.js +1 -0
  82. package/src/client/dist/pwa/assets/permissionModes-DMzFEQhr.js +36 -0
  83. package/src/client/dist/pwa/assets/{pinia-jcytgarH.js → pinia-BKqmoVru.js} +1 -1
  84. package/src/client/dist/pwa/assets/{private.use-form-DVqJLg-9.js → private.use-form-C7umbGO-.js} +1 -1
  85. package/src/client/dist/pwa/assets/{project-color-CoEJs17L.js → project-color-CFjdIj6W.js} +1 -1
  86. package/src/client/dist/pwa/assets/{python-BIG7FEi1.js → python-Dc6mnEip.js} +1 -1
  87. package/src/client/dist/pwa/assets/{razor-CNJSV12m.js → razor-DArVFBGO.js} +1 -1
  88. package/src/client/dist/pwa/assets/{render-chat-markdown-Ddq-hxhM.js → render-chat-markdown-BTPAjU2g.js} +1 -1
  89. package/src/client/dist/pwa/assets/runtime-core.esm-bundler-CDnVUfeZ.js +1 -0
  90. package/src/client/dist/pwa/assets/{typescript-Blrg4f-0.js → typescript-COE1vIyB.js} +1 -1
  91. package/src/client/dist/pwa/assets/{use-checkbox-DDRAnhSJ.js → use-checkbox-FnauAgTp.js} +1 -1
  92. package/src/client/dist/pwa/assets/use-id-BIuaWKkB.js +1 -0
  93. package/src/client/dist/pwa/assets/use-is-mobile-CWjqe8gC.js +1 -0
  94. package/src/client/dist/pwa/assets/use-tours-C8x2V1w5.js +2 -0
  95. package/src/client/dist/pwa/assets/workspace-page-layout-Dw9NhZsL.js +1 -0
  96. package/src/client/dist/pwa/assets/{workspace-templates-BS34DvEp.js → workspace-templates-DHyA_GEW.js} +1 -1
  97. package/src/client/dist/pwa/assets/{xml-Bv6_W6i5.js → xml-eYjr4cGR.js} +1 -1
  98. package/src/client/dist/pwa/assets/{yaml-DH7PMAq0.js → yaml-UooOcssF.js} +1 -1
  99. package/src/client/dist/pwa/index.html +7 -6
  100. package/src/client/dist/pwa/sw.js +1 -1
  101. package/src/client/dist/pwa/assets/ActivityFeed-CGb-YHB0.js +0 -8
  102. package/src/client/dist/pwa/assets/ChangelogPage-BDVpqtWz.js +0 -1
  103. package/src/client/dist/pwa/assets/ClosePopup-ClH5KG_w.js +0 -1
  104. package/src/client/dist/pwa/assets/ComparisonPanel-D6kz5Q-o.js +0 -2
  105. package/src/client/dist/pwa/assets/CreatePage-f-C_R4o9.js +0 -2
  106. package/src/client/dist/pwa/assets/DashboardPage-C_9_IXdR.js +0 -1
  107. package/src/client/dist/pwa/assets/DiffViewer-CMjfVpAG.js +0 -8
  108. package/src/client/dist/pwa/assets/DocumentsPanel-Yim06L6k.js +0 -1
  109. package/src/client/dist/pwa/assets/DrawerToggleButton-aD_LMlna.js +0 -1
  110. package/src/client/dist/pwa/assets/GitPanel-x-whHRdM.js +0 -3
  111. package/src/client/dist/pwa/assets/HealthPage-BNZP_1l-.js +0 -1
  112. package/src/client/dist/pwa/assets/MainLayout-BMxAagOe.js +0 -2
  113. package/src/client/dist/pwa/assets/MainLayout-DfddEd0b.css +0 -1
  114. package/src/client/dist/pwa/assets/QBtn-DqZryKOh.js +0 -1
  115. package/src/client/dist/pwa/assets/QBtnDropdown-BLNdi1w5.js +0 -1
  116. package/src/client/dist/pwa/assets/QDrawer-C6TAn5dv.js +0 -1
  117. package/src/client/dist/pwa/assets/QItemSection-CjrpcdN_.js +0 -1
  118. package/src/client/dist/pwa/assets/SchedulePanel-D2HyVnU8.js +0 -1
  119. package/src/client/dist/pwa/assets/SearchPage-BG_zXo6V.js +0 -1
  120. package/src/client/dist/pwa/assets/SettingsPage-BNBeaT9t.js +0 -9
  121. package/src/client/dist/pwa/assets/TouchPan-CK2rmJkA.js +0 -1
  122. package/src/client/dist/pwa/assets/TourReplayButton-DHFo5_Kk.js +0 -1
  123. package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-BbtPN7pb.js +0 -1
  124. package/src/client/dist/pwa/assets/WorkspacePage-Aay3kkPT.js +0 -4
  125. package/src/client/dist/pwa/assets/build-path-tree-BRLZv-pU.js +0 -1
  126. package/src/client/dist/pwa/assets/de-CG7RYRLI.js +0 -17
  127. package/src/client/dist/pwa/assets/engineFeatures-DyKNpIKw.js +0 -1
  128. package/src/client/dist/pwa/assets/es-DfpfEjEu.js +0 -17
  129. package/src/client/dist/pwa/assets/fr-DgzZRmjM.js +0 -17
  130. package/src/client/dist/pwa/assets/i18n-Butermk0.js +0 -1
  131. package/src/client/dist/pwa/assets/index-BATViFoR.js +0 -20
  132. package/src/client/dist/pwa/assets/it-DKzqhjlT.js +0 -17
  133. package/src/client/dist/pwa/assets/monaco.contribution-DkNJdReE.js +0 -2
  134. package/src/client/dist/pwa/assets/network-auth-CyD51r9F.js +0 -1
  135. package/src/client/dist/pwa/assets/notify-theme-Bnw3Xbxg.js +0 -1
  136. package/src/client/dist/pwa/assets/pending-diff-open-DsW49A6Z.js +0 -1
  137. package/src/client/dist/pwa/assets/permissionModes-mF8Kofv1.js +0 -36
  138. package/src/client/dist/pwa/assets/runtime-core.esm-bundler-CS0xkoG_.js +0 -1
  139. package/src/client/dist/pwa/assets/use-is-mobile-ClPMtqZc.js +0 -1
  140. package/src/client/dist/pwa/assets/use-tours-Cz5Zz63W.js +0 -2
package/AGENTS.md CHANGED
@@ -265,6 +265,14 @@ Sequence: `captureRestoreData` (best-effort forge lookup for PR number / URL / m
265
265
 
266
266
  **Restore**: `POST /api/workspaces/:id/restore-worktree` calls `worktree-restore-service.ts` to recreate the exact checkout and only then clear purge/archive metadata via `restoreWorktreeFromDisk`. Source order: surviving local branch → optional `headCommitSha` in the existing restore JSON → exact branch fetched from `origin`. No SQL migration is needed for the optional JSON key; older records remain supported. No reset, overwrite, agent/dev-server start or setup script. `workspace-lifecycle-guard.ts` excludes overlapping restore/purge/delete operations, while the existing common-Git-directory lock serializes Git mutations. The watcher shares `isMatchingWorkspaceWorktree` validation and invalidates in-flight PR checks on restoration; delayed auto-purges carry their expected archive timestamp to avoid purging a workspace restored in the meantime. The context menu and purged banner expose the action; HTTP and the existing `workspace:worktree-restored` event reconcile client lists. Discarded uncommitted/ignored files and dependencies are not recoverable.
267
267
 
268
+ ### Workspace navigation and activity digest
269
+
270
+ `utils/workspace-sort.ts` applies the browser-persisted sort within each drawer group; fuzzy search relevance remains primary. `ActionAvailability.vue` pairs disabled controls with visible, focusable explanations derived from `utils/action-blocker.ts`.
271
+
272
+ `SplitWorkspacePage.vue` hosts two same-origin embedded clients (`?pane=1`), isolating routers, stores and terminal registries. `utils/split-workspace.ts` and the router bridge synchronize the host URL without treating that bookkeeping as a departure. Actual departures consult both panes for dirty edits, unsent drafts and all in-memory queued messages. Embedded clients suppress automatic tours and browser/audio notifications; the host owns these.
273
+
274
+ `activity-service.ts` records significant metadata from `emit` and `emitEphemeral` in `workspace_activity` (migration v41, 30-day retention). It excludes streaming data and superseded session endings. `/api/activity` exposes a paginated monotonic cursor; `stores/activity.ts` keeps browser-local visit checkpoints and protects against late responses after visibility changes. `ActivityDigest.vue` links events to workspace/session or Git context. Never advance an unread checkpoint to the head of unloaded pages.
275
+
268
276
  ### Workspace attention indicators
269
277
 
270
278
  `src/client/src/utils/workspace-attention.ts` derives a small set of badges (CI failure, changes-requested) from the PR snapshot + git stats stored on each workspace. `WorkspaceAttentionLabels.vue` renders them inline on the workspace cards in the left drawer. The derivation is a pure function, easy to unit-test and free of IO. Drawer cards therefore stay reactive to whatever the pr-watcher / bulk-info refresh writes back into the store.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,15 @@ 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
+ ## 1.11.36
8
+
9
+ - fix(agent): resume quota waits and scheduled background checks
10
+ - ✨ feat(quota): Améliorer la détection et récupération des erreurs de limite de quota
11
+
12
+ ## 1.11.35
13
+
14
+ - feat(workspaces): add split view, activity digest and compact toolbar
15
+
7
16
  ## 1.11.34
8
17
 
9
18
  - feat(workspaces): restore purged workspaces and improve startup display
@@ -726,6 +726,20 @@ export const migrations = [
726
726
  }
727
727
  },
728
728
  },
729
+ {
730
+ version: 41,
731
+ name: 'add-workspace-activity',
732
+ migrate: (db) => {
733
+ db.exec(`CREATE TABLE IF NOT EXISTS workspace_activity (
734
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
735
+ workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
736
+ kind TEXT NOT NULL,
737
+ session_id TEXT,
738
+ created_at TEXT NOT NULL
739
+ );
740
+ CREATE INDEX IF NOT EXISTS idx_workspace_activity_created ON workspace_activity(created_at);`);
741
+ },
742
+ },
729
743
  ];
730
744
  /** Current schema version — always equals the highest migration version. */
731
745
  export const SCHEMA_VERSION = migrations.length > 0 ? migrations[migrations.length - 1].version : 1;
@@ -192,6 +192,15 @@ export function initSchema(db) {
192
192
  created_at TEXT NOT NULL DEFAULT (datetime('now'))
193
193
  );
194
194
 
195
+ CREATE TABLE IF NOT EXISTS workspace_activity (
196
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
197
+ workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
198
+ kind TEXT NOT NULL,
199
+ session_id TEXT,
200
+ created_at TEXT NOT NULL
201
+ );
202
+ CREATE INDEX IF NOT EXISTS idx_workspace_activity_created ON workspace_activity(created_at);
203
+
195
204
  CREATE INDEX IF NOT EXISTS idx_tasks_workspace_id ON tasks(workspace_id);
196
205
  CREATE INDEX IF NOT EXISTS idx_agent_sessions_workspace_id ON agent_sessions(workspace_id);
197
206
  CREATE INDEX IF NOT EXISTS idx_ws_events_workspace_id ON ws_events(workspace_id);
@@ -8,6 +8,7 @@ import { closeDb, getDb } from './db/index.js';
8
8
  import { getPendingMigrations, runMigrations } from './db/migrations.js';
9
9
  import { hostCheckMiddleware } from './middleware/host-check-middleware.js';
10
10
  import { networkAuthMiddleware } from './middleware/network-auth-middleware.js';
11
+ import activityRouter from './routes/activity.js';
11
12
  import changelogRouter from './routes/changelog.js';
12
13
  import devServerRouter from './routes/dev-server.js';
13
14
  import documentsRouter from './routes/documents.js';
@@ -163,6 +164,7 @@ app.route('/api/sentry', sentryRouter);
163
164
  app.route('/api/git', gitRouter);
164
165
  app.route('/api/fs', fsRouter);
165
166
  app.route('/api/changelog', changelogRouter);
167
+ app.route('/api/activity', activityRouter);
166
168
  app.route('/api/settings', settingsRouter);
167
169
  app.route('/api/dev-server', devServerRouter);
168
170
  app.route('/api/templates', templatesRouter);
@@ -0,0 +1,17 @@
1
+ import { Hono } from 'hono';
2
+ import { activityCursor, listActivity } from '../services/activity-service.js';
3
+ const app = new Hono();
4
+ app.get('/', (c) => {
5
+ try {
6
+ if (c.req.query('head') === '1')
7
+ return c.json({ cursor: activityCursor() });
8
+ const raw = c.req.query('after') ?? '0';
9
+ if (!/^\d+$/.test(raw) || !Number.isSafeInteger(Number(raw)))
10
+ return c.json({ error: 'after must be a non-negative integer' }, 400);
11
+ return c.json(listActivity(Number(raw)));
12
+ }
13
+ catch (error) {
14
+ return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
15
+ }
16
+ });
17
+ export default app;
@@ -0,0 +1,62 @@
1
+ import { getDb } from '../db/index.js';
2
+ export function classifyActivity(type, payload) {
3
+ const event = payload && typeof payload === 'object' ? payload : {};
4
+ if (type === 'agent:event') {
5
+ if (event.kind === 'session:ended' && event.superseded)
6
+ return null;
7
+ if (event.kind === 'session:user-input-requested')
8
+ return event.requestKind === 'permission' ? 'permission' : 'question';
9
+ if (event.kind === 'error')
10
+ return 'error';
11
+ if (event.kind === 'session:ended' && event.reason === 'completed')
12
+ return 'completed';
13
+ if (event.kind === 'session:ended' && (event.reason === 'error' || event.reason === 'watchdog'))
14
+ return 'error';
15
+ }
16
+ const kinds = {
17
+ 'pr:merged': 'pr-merged',
18
+ 'pr:approved': 'pr-approved',
19
+ 'pr:changes-requested': 'pr-changes-requested',
20
+ 'pr:ci-failed': 'pr-ci-failed',
21
+ 'pr:ci-recovered': 'pr-ci-recovered',
22
+ 'pr:merge-conflict': 'pr-merge-conflict',
23
+ 'pr:ready-to-merge': 'pr-ready-to-merge',
24
+ 'workspace:archived': 'archived',
25
+ 'workspace:worktree-purged': 'purged',
26
+ 'workspace:worktree-restored': 'restored',
27
+ };
28
+ return kinds[type] ?? null;
29
+ }
30
+ /** Compact metadata only; failure must never interrupt agent/event delivery. */
31
+ export function recordActivity(workspaceId, type, payload, sessionId, db) {
32
+ const kind = classifyActivity(type, payload);
33
+ if (!kind)
34
+ return;
35
+ try {
36
+ const database = db ?? getDb();
37
+ database
38
+ .prepare('INSERT INTO workspace_activity (workspace_id, kind, session_id, created_at) VALUES (?, ?, ?, ?)')
39
+ .run(workspaceId, kind, sessionId ?? null, new Date().toISOString());
40
+ // Bounded even when no browser is connected. Only significant events reach here.
41
+ database
42
+ .prepare('DELETE FROM workspace_activity WHERE created_at < ?')
43
+ .run(new Date(Date.now() - 30 * 86400_000).toISOString());
44
+ }
45
+ catch (error) {
46
+ console.error('[activity] Could not record workspace activity:', error);
47
+ }
48
+ }
49
+ export function activityCursor(db = getDb()) {
50
+ return (db.prepare("SELECT seq FROM sqlite_sequence WHERE name = 'workspace_activity'").get()?.seq ?? 0);
51
+ }
52
+ export function listActivity(after, limit = 200, db = getDb()) {
53
+ limit = Math.min(200, Math.max(1, limit));
54
+ const rows = db
55
+ .prepare(`SELECT a.id, a.workspace_id AS workspaceId, w.name AS workspaceName,
56
+ a.kind, a.session_id AS sessionId, a.created_at AS createdAt
57
+ FROM workspace_activity a JOIN workspaces w ON w.id = a.workspace_id
58
+ WHERE a.id > ? AND a.created_at >= ? ORDER BY a.id ASC LIMIT ?`)
59
+ .all(after, new Date(Date.now() - 30 * 86400_000).toISOString(), limit + 1);
60
+ const items = rows.slice(0, limit);
61
+ return { items, nextCursor: items.at(-1)?.id ?? after, cursor: activityCursor(db), hasMore: rows.length > limit };
62
+ }
@@ -255,6 +255,7 @@ export function createClaudeCodeEngine() {
255
255
  let iteratorRunning = false;
256
256
  let userInterrupted = false;
257
257
  let completedResponses = 0;
258
+ let waitingForBackground = false;
258
259
  let turnCompletedEmittedForResponse = 0;
259
260
  // `result` is the Claude Agent SDK's per-turn completion signal. Keep
260
261
  // it distinct from iterator completion: the SDK may still drain
@@ -433,6 +434,11 @@ export function createClaudeCodeEngine() {
433
434
  turnLiveness.start();
434
435
  try {
435
436
  for await (const msg of q) {
437
+ // A parent continuation owns the foreground again. Background
438
+ // progress notifications alone do not end the between-turn wait.
439
+ if (msg.type === 'assistant' || msg.type === 'user' || msg.type === 'stream_event') {
440
+ waitingForBackground = false;
441
+ }
436
442
  // This SDK message proves that any drain condition armed by a
437
443
  // *previous* message is no longer current. A drain armed while
438
444
  // processing this message remains active if the generator then
@@ -495,6 +501,7 @@ export function createClaudeCodeEngine() {
495
501
  turnLiveness.setTimeoutMs(pendingToolCallIds.size > 0 ? CLAUDE_TOOL_IDLE_TIMEOUT_MS : CLAUDE_STREAM_IDLE_TIMEOUT_MS);
496
502
  reevaluateLivenessPause();
497
503
  if (msg.type === 'result') {
504
+ waitingForBackground = false;
498
505
  pendingToolCallIds.clear();
499
506
  completedResponses++;
500
507
  emitTurnCompletedIfSettled();
@@ -506,6 +513,7 @@ export function createClaudeCodeEngine() {
506
513
  armResultDrainWatchdog();
507
514
  }
508
515
  else {
516
+ waitingForBackground = true;
509
517
  armSubagentStallWatchdog();
510
518
  }
511
519
  }
@@ -583,6 +591,24 @@ export function createClaudeCodeEngine() {
583
591
  throw new Error(`Claude ${AGENT_NO_LONGER_RUNNING_TEXT}`);
584
592
  inputStream.send(text);
585
593
  },
594
+ sendWakeupIfWaiting(text) {
595
+ if (!iteratorRunning ||
596
+ abortController.signal.aborted ||
597
+ mapperState.sawErrorResult ||
598
+ !waitingForBackground ||
599
+ activeSubagentTaskIds.size === 0 ||
600
+ pendingToolCallIds.size > 0 ||
601
+ pendingResolvers.size > 0 ||
602
+ isCompacting ||
603
+ inputStream.hasUnansweredInput(completedResponses))
604
+ return false;
605
+ inputStream.send(text);
606
+ waitingForBackground = false;
607
+ // The wakeup starts another turn on this stream; its result will
608
+ // re-arm the stall deadline if background work is still outstanding.
609
+ clearSubagentStallWatchdog();
610
+ return true;
611
+ },
586
612
  interrupt() {
587
613
  userInterrupted = true;
588
614
  // The SDK ends an interrupted run by emitting a `result` with
@@ -63,20 +63,20 @@ function normalizeRateLimitInfo(info) {
63
63
  }
64
64
  // ── Public API ────────────────────────────────────────────────────────────────
65
65
  /**
66
- * Canonical "out of quota" surfaces from the Claude SDK and CLI. Centralised
67
- * so the three call-sites stay in sync:
68
- * - `result` events with an error subtype (`parsed.error` / `parsed.result`)
69
- * - assistant `message:text` blocks (the SDK occasionally streams the user-
70
- * visible quota notice as plain assistant text instead of a structured
71
- * error result; see workspace `-GyiAYM7X4xTWyZbcHGiR` session #25 for the
72
- * repro that motivated this path)
73
- * - CLI stderr in `engine.ts`
66
+ * Quota keywords for error results and CLI stderr. Assistant prose uses
67
+ * the narrower ASSISTANT_QUOTA_NOTICE below, because summaries may mention
68
+ * a previous quota failure while the current run is working normally.
74
69
  *
75
70
  * Patterns are kept loose on purpose to absorb minor wording drift between
76
71
  * Anthropic's surfaces (`rate_limit_exceeded`, `Claude AI usage limit
77
72
  * reached`, `You're out of extra usage`, `quota exceeded`).
78
73
  */
79
74
  export const QUOTA_PATTERN = /\b429\b|out of extra usage|rate[_ ]limit|usage limit|quota exceeded/i;
75
+ /** Only canonical, standalone notices qualify on the assistant prose surface.
76
+ * Broad quota keywords remain appropriate for structured errors, not summaries
77
+ * of a previous attempt, a sub-agent failure, or application HTTP handling.
78
+ */
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
80
  export function createMapperState() {
81
81
  return {
82
82
  sessionStartedEmitted: false,
@@ -274,8 +274,8 @@ export function mapSdkMessage(msg, state) {
274
274
  // Last-resort fallback: some SDK runs surface the quota notice as
275
275
  // plain assistant text without setting `assistant.error` or a
276
276
  // `result.error`. The structured signals above cover modern SDK
277
- // versions; this regex absorbs older or drifted wordings.
278
- if (QUOTA_PATTERN.test(text)) {
277
+ // versions; only a standalone notice qualifies on this prose surface.
278
+ if (ASSISTANT_QUOTA_NOTICE.test(text.trim())) {
279
279
  tryEmitQuotaError(state, events, text);
280
280
  }
281
281
  }
@@ -695,6 +695,15 @@ function handleEvent(workspaceId, agentSessionId, sourceController, ev) {
695
695
  const sourceControllerIsStopping = sourceController?.status === 'stopping';
696
696
  const routedEvent = ev.kind === 'session:ended' && (hasReplacement || sourceIsSuperseded) ? { ...ev, superseded: true } : ev;
697
697
  routeEvent(workspaceId, agentSessionId, routedEvent);
698
+ // A fresh tool invocation proves this controller resumed working (for
699
+ // example after an account change). Tool results can still drain after a
700
+ // rejection, so they must never count as recovery.
701
+ if (ev.kind === 'tool:call' && !sourceControllerIsStopping && getWs(workspaceId)?.status === 'quota') {
702
+ updateWorkspaceStatus(workspaceId, 'executing');
703
+ quotaBackoffService.cancel(workspaceId, 'completed');
704
+ retryCounts.delete(workspaceId);
705
+ emitEphemeral(workspaceId, 'agent:quota-recovered', {});
706
+ }
698
707
  if (ev.kind === 'rate_limit') {
699
708
  latestRateLimitInfo.set(workspaceId, ev.info);
700
709
  }
@@ -1345,6 +1354,23 @@ export async function stopAllAgents(timeoutMs = 3_000) {
1345
1354
  // dead code pretending to guard against a failure mode that can't occur.
1346
1355
  await Promise.all([...controllers.keys()].map((workspaceId) => stopAgentAndWait(workspaceId, timeoutMs)));
1347
1356
  }
1357
+ /** Deliver a scheduled check without stopping background work or cancelling the wakeup as user input. */
1358
+ export function sendWakeupIfWaiting(workspaceId, content, expectedSessionId) {
1359
+ const ctrl = controllers.get(workspaceId);
1360
+ if (ctrl?.status !== 'running')
1361
+ return false;
1362
+ if (expectedSessionId && ctrl.agentSessionId !== expectedSessionId)
1363
+ return false;
1364
+ const workspace = getWs(workspaceId);
1365
+ if (!workspace || workspace.archivedAt || workspace.worktreePurgedAt)
1366
+ return false;
1367
+ if (workspace.status === 'awaiting-user' || workspace.status === 'quota')
1368
+ return false;
1369
+ if (!ctrl.engineProcess?.sendWakeupIfWaiting?.(content))
1370
+ return false;
1371
+ emit(workspaceId, 'user:message', { content, sender: 'system-prompt' }, ctrl.agentSessionId);
1372
+ return true;
1373
+ }
1348
1374
  /** Write a user message to the running agent. */
1349
1375
  export async function sendMessage(workspaceId, content, expectedSessionId) {
1350
1376
  const ctrl = controllers.get(workspaceId);
@@ -1864,6 +1890,9 @@ async function handleQuota(workspaceId, _agentSessionId) {
1864
1890
  return;
1865
1891
  }
1866
1892
  const { delayMs, resetsAt, source } = await computeQuotaBackoffMs(workspaceId, retryCount);
1893
+ // Usage lookup can finish after the agent has already resumed.
1894
+ if (getWs(workspaceId)?.status !== 'quota')
1895
+ return;
1867
1896
  retryCounts.set(workspaceId, retryCount + 1);
1868
1897
  // The quotaBackoffService owns the timer + the persistent row + the
1869
1898
  // 'agent:quota-backoff' WS emit. Hand off everything to it.
@@ -9,7 +9,7 @@ import * as lifecycleHookService from './lifecycle-hook-service.js';
9
9
  import * as settingsService from './settings-service.js';
10
10
  import { getSuitePrompts } from './skill-suite-prompts.js';
11
11
  import { emit, emitEphemeral } from './websocket-service.js';
12
- import { listTasks } from './workspace-service.js';
12
+ import { listTasks, updateWorkspaceStatus } from './workspace-service.js';
13
13
  const NO_PROGRESS_STALL_THRESHOLD = 3;
14
14
  function getRow(workspaceId) {
15
15
  const db = getDb();
@@ -186,6 +186,10 @@ export function rehydrate() {
186
186
  const row = getRow(id);
187
187
  if (row?.auto_loop_ready !== 1)
188
188
  continue;
189
+ // Persisted quota timers own the restart of these workspaces. Boot
190
+ // restores them after this pass; starting here bypasses their delay.
191
+ if (row.status === 'quota')
192
+ continue;
189
193
  if (countPendingTasks(id) === 0) {
190
194
  disable(id, 'completed');
191
195
  continue;
@@ -427,9 +431,9 @@ function spawnNextIteration(workspaceId, opts = {}) {
427
431
  * Spawns the next auto-loop iteration if the workspace is still in quota status
428
432
  * with auto_loop active; no-ops otherwise (race-safe).
429
433
  *
430
- * No-op cases — these all leave the workspace in `quota` status awaiting
431
- * manual user action:
434
+ * No-op cases — leave the workspace unchanged:
432
435
  * - workspace was deleted between arm and fire
436
+ * - workspace was archived during the backoff window
433
437
  * - `auto_loop !== 1` (workspace was never an auto-loop target, OR the user
434
438
  * toggled the loop off during the backoff window)
435
439
  * - `status !== 'quota'` (user already manually resumed, or another path
@@ -439,10 +443,17 @@ export function onQuotaBackoffExpired(workspaceId) {
439
443
  const row = getRow(workspaceId);
440
444
  if (!row)
441
445
  return;
446
+ if (row.archived_at !== null)
447
+ return;
442
448
  if (row.auto_loop !== 1)
443
449
  return;
444
450
  if (row.status !== 'quota')
445
451
  return;
452
+ // The timer has consumed its persisted row. Release quota ownership before
453
+ // checking capacity so resumeWaitingWorkspaces can pick this workspace up
454
+ // when a slot frees, even if no agent can start at this instant.
455
+ updateWorkspaceStatus(workspaceId, 'idle');
456
+ emitEphemeral(workspaceId, 'agent:quota-backoff-cancelled', { reason: 'completed' });
446
457
  spawnNextIteration(workspaceId);
447
458
  }
448
459
  /**
@@ -146,6 +146,20 @@ function fire(workspaceId) {
146
146
  if (!row)
147
147
  return;
148
148
  if (orchestrator.hasController(workspaceId)) {
149
+ // Claude can keep the stream open after a result while a background
150
+ // task runs. Waiting for that controller to disappear deadlocks a
151
+ // scheduled check on the very work it is meant to inspect.
152
+ try {
153
+ if (orchestrator.sendWakeupIfWaiting(workspaceId, row.prompt, row.agent_session_id ?? undefined)) {
154
+ failedRetries.delete(workspaceId);
155
+ db.prepare('DELETE FROM pending_wakeups WHERE workspace_id = ?').run(workspaceId);
156
+ emitEphemeral(workspaceId, 'wakeup:fired', {});
157
+ return;
158
+ }
159
+ }
160
+ catch (err) {
161
+ console.error(`[wakeup-service] delivery to active session failed for '${workspaceId}':`, err);
162
+ }
149
163
  defer(workspaceId, row);
150
164
  return;
151
165
  }
@@ -1,5 +1,6 @@
1
1
  import { nanoid } from 'nanoid';
2
2
  import { getDb } from '../db/index.js';
3
+ import { recordActivity } from './activity-service.js';
3
4
  import { getAllPersistedSnapshots } from './usage/db.js';
4
5
  // ── State ──────────────────────────────────────────────────────────────────────
5
6
  /** Maps each WS client to the set of workspaceIds they are subscribed to */
@@ -154,6 +155,7 @@ function insertEventStatement() {
154
155
  return cachedInsert.statement;
155
156
  }
156
157
  export function emit(workspaceId, type, payload, sessionId) {
158
+ recordActivity(workspaceId, type, payload, sessionId);
157
159
  const id = nanoid();
158
160
  const createdAt = new Date().toISOString();
159
161
  let replayable = false;
@@ -198,6 +200,7 @@ export function emit(workspaceId, type, payload, sessionId) {
198
200
  * Used for ephemeral status updates (e.g., dev-server status) that don't need replay.
199
201
  */
200
202
  export function emitEphemeral(workspaceId, type, payload) {
203
+ recordActivity(workspaceId, type, payload);
201
204
  const id = nanoid();
202
205
  const createdAt = new Date().toISOString();
203
206
  const event = { id, workspaceId, type, payload, createdAt, replayable: false };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loicngr/kobo",
3
- "version": "1.11.34",
3
+ "version": "1.11.36",
4
4
  "description": "Kōbō — multi-workspace agent manager for Claude Code. Orchestrates isolated git worktrees with dev servers, Notion integration, and MCP tools.",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0-or-later",
@@ -0,0 +1,8 @@
1
+ import{S as e,ct as t,j as n,ot as r,ut as i,v as a}from"./reactivity.esm-bundler-DEJj9cFv.js";import{C as o,F as s,J as c,L as l,N as u,O as d,P as f,Y as p,_ as m,d as h,f as g,g as _,j as v,k as y,l as b,p as x,r as S,u as C,y as w,z as T}from"./runtime-core.esm-bundler-CDnVUfeZ.js";import{J as E,b as D,ct as O,l as k,pt as A,t as ee,tt as j,w as M,y as te}from"./QBtn-CZaBclNw.js";import{A as N,At as ne,Ct as P,Nt as F,O as re,k as ie,m as ae,p as I,q as oe,xt as L}from"./index-DcwcFDrZ.js";import{t as se}from"./QSpinnerDots-DnCV3VzI.js";import{t as ce}from"./QList-qupzuEqO.js";import{t as R}from"./QMarkupTable-B9QuKtK_.js";import{c as z,l as B}from"./QSelect-Bodml89k.js";import{d as le}from"./permissionModes-DMzFEQhr.js";import{r as ue,t as V}from"./pending-diff-open-BlLrQaW7.js";import{t as H}from"./documents-B2dg1wRf.js";import{a as U}from"./use-tours-C8x2V1w5.js";import{t as W}from"./QTooltip-K-9OdDfq.js";import{i as de,n as G,o as fe,r as K,t as pe}from"./render-chat-markdown-BTPAjU2g.js";import{a as me,i as he,n as q,o as ge,r as _e,s as ve}from"./WorkspacePage-BI7CW2yT.js";function ye(e,t){return o(`div`,e,[o(`table`,{class:`q-table`},t)])}var J={list:ce,table:R},Y=[`list`,`table`,`__qtable`],be=j({name:`QVirtualScroll`,props:{...B,type:{type:String,default:`list`,validator:e=>Y.includes(e)},items:{type:Array,default:()=>[]},itemsFn:Function,itemsSize:Number,scrollTarget:F},setup(t,{slots:n,attrs:r}){let i,a=e(null),s=b(()=>t.itemsSize>=0&&t.itemsFn!==void 0?Number.parseInt(t.itemsSize,10):Array.isArray(t.items)?t.items.length:0),{virtualScrollSliceRange:l,localResetVirtualScroll:d,padVirtualScroll:p,onVirtualScrollEvt:m}=z({virtualScrollLength:s,getVirtualScrollTarget:S,getVirtualScrollEl:x}),h=b(()=>{if(s.value===0)return[];let e=(e,t)=>({index:l.value.from+t,item:e});return t.itemsFn===void 0?t.items.slice(l.value.from,l.value.to).map(e):t.itemsFn(l.value.from,l.value.to-l.value.from).map(e)}),g=b(()=>`q-virtual-scroll q-virtual-scroll`+(t.virtualScrollHorizontal?`--horizontal`:`--vertical`)+(t.scrollTarget===void 0?` scroll`:``)),_=b(()=>t.scrollTarget===void 0?{tabindex:0}:{});c(s,()=>{d()}),c(()=>t.scrollTarget,()=>{w(),C()});function x(){let e=a.value;return e===null?null:e.$el||e}function S(){return i}function C(){let e=x();e!==null&&(i=ne(e,t.scrollTarget),i.addEventListener(`scroll`,m,E.passive))}function w(){i!==void 0&&(i.removeEventListener(`scroll`,m,E.passive),i=void 0)}function T(){let e=p(t.type===`list`?`div`:`tbody`,h.value.map(n.default));return n.before!==void 0&&(e=n.before().concat(e)),D(n.after,e)}return d(),f(()=>{C()}),y(()=>{C()}),u(()=>{w()}),v(()=>{w()}),()=>{if(n.default===void 0){console.error(`QVirtualScroll: default scoped slot is required for rendering`);return}return t.type===`__qtable`?ye({ref:a,class:`q-table__middle `+g.value,tabindex:0},T()):o(J[t.type],{...r,ref:a,class:[r.class,g.value],..._.value},T)}}}),xe=new Set([`setup`,`cleanup`,`archive`]);function Se(e){switch(e.type){case`user`:return e.sender===`system-prompt`?`system-prompt`:xe.has(e.sender)||re(e.sender)?`script`:`user`;case`session`:return`session`;default:return`agent`}}function Ce(e){let t=[],n=null,r=null;for(let i of e){let e=Se(i),a=e===`session`||e===`system-prompt`,o=e===`script`&&i.type===`user`?`script:${i.sender}`: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 we(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 Te(e){let t=e.items[0];return`${e.speaker}:${t?we(t):`empty`}`}function Ee(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 De(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=c(e,e=>{t(e)&&s(!0)},{immediate:!0}),i){o();return}a=setTimeout(()=>s(!1),n)})}var Oe={class:`agent-error-item`},ke={class:`agent-error-item__body`},Ae={class:`agent-error-item__title`},je={class:`agent-error-item__message`},X=U(w({__name:`AgentErrorItem`,props:{item:{}},setup(e){let t=e,{te:n}=L(),r=b(()=>{let e=`agent.error.${t.item.category}`;return n(e)?e:`agent.error.other`});return(t,n)=>(l(),x(`div`,Oe,[m(k,{name:`error_outline`,size:`16px`,class:`agent-error-item__icon`}),C(`div`,ke,[C(`div`,Ae,i(t.$t(r.value)),1),C(`div`,je,i(e.item.message),1)])]))}}),[[`__scopeId`,`data-v-e98c3c02`]]),Me={class:`text-caption text-kobo-3`},Z=w({__name:`SessionEventItem`,props:{item:{}},setup(e){let t=e,n=b(()=>{switch(t.item.kind){case`started`:return`session.started`;case`ended`:return ve(t.item.detail);case`compacted`:return`session.compacted`;default:return`session.started`}});return(e,t)=>(l(),x(`span`,Me,i(e.$t(n.value)),1))}});function Ne(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){Q(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 Q(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=$(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 $(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 Pe=[`innerHTML`],Fe=U(w({__name:`TextMessageItem`,props:{item:{}},setup(e){let t=e,n=H(),r=I(),i=te(),{t:a}=L(),o=b(()=>{let e=r.selectedWorkspaceId;return e?n.documentsFor(e).map(e=>e.path):[]}),s=b(()=>{let e=t.item;if(e.streaming)return pe(e.text);let n=o.value;return K(`${e.messageId}|${n.join(`,`)}`,e.text,e=>{let t=Ne(fe.parse(e,{async:!1,breaks:!0,gfm:!0}),n);return de(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)=>(l(),x(`div`,{class:`markdown-message`,onClick:c},[C(`div`,{innerHTML:s.value},null,8,Pe),e.item.streaming?(l(),h(M,{key:0,size:`xs`,class:`q-ml-xs`})):g(``,!0)]))}}),[[`__scopeId`,`data-v-99371035`]]);function Ie(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,...Le(l,u),...c]}function Le(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 Re(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 ze(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=Re(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 Be(e,t){if(!e||!t?.projectPath)return e;let n=t.worktreePath;if(n){let t=Ve(e,n);if(t!==e)return t}let r=Ve(e,`${t.projectPath}/${N}/${t.workingBranch}`);return r===e?Ve(e,t.projectPath):r}function Ve(e,t){if(!t)return e;let n=He(t);return n?e.replace(RegExp(`${n}[\\\\/]+`,`g`),``).replace(RegExp(`${n}(?=\\s|$|["'\`])`,`g`),`.`):e}function He(e){let t=e.replace(/[\\/]+$/,``);return t?`${/^[\\/]+/.test(t)?`[\\\\/]+`:``}${t.split(/[\\/]+/).filter(Boolean).map(Ue).join(`[\\\\/]+`)}`:``}function Ue(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}var We={class:`tool-name`},Ge=[`title`],Ke={key:0,class:`tool-stat-add`},qe={key:1,class:`tool-stat-del`},Je={class:`diff-sign`},Ye={class:`tool-name`},Xe=[`title`],Ze=U(w({__name:`ToolCallItem`,props:{item:{}},setup(e){let t=e,o=a(!1),s=I(),{t:u}=L(),d=b(()=>ze(t.item.name,t.item.input)),f=b(()=>d.value?Be(d.value.filePath,s.selectedWorkspace):``),v={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=b(()=>v[t.item.name]??`build`),w=b(()=>{if(d.value)return``;let e=t.item.input,n=E(e);return n?Be(n,s.selectedWorkspace):``});function E(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 D=b(()=>{let e=d.value;return e?e.diffLines?e.diffLines:e.toolName===`Edit`&&e.oldString!==void 0&&e.newString!==void 0?Ie(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}),O=b(()=>{let e=t.item.result;if(!e)return``;if(typeof e.output==`string`)return e.output;try{return JSON.stringify(e.output)}catch{return String(e.output)}}),j=new Set([`Read`]),M=b(()=>!!(t.item.result&&O.value)&&(!j.has(t.item.name)||t.item.result?.isError===!0));function te(){o.value=!o.value}function N(){let e=d.value?.filePath,t=s.selectedWorkspace,n=t?.id,r=e?Be(e,t):``;r&&!/^(?:[A-Za-z]:)?[\\/]/.test(r)&&n&&(V(n,r),window.dispatchEvent(new CustomEvent(`kobo:open-diff`,{detail:{workspaceId:n,path:r}})))}return c(()=>t.item.result?.isError===!0,e=>{e&&(o.value=!0)},{immediate:!0}),(t,a)=>d.value?(l(),x(`div`,{key:0,class:r([`tool-row`,{"tool-row-expanded":o.value}])},[C(`div`,{class:`tool-header`,onClick:te},[m(k,{name:y.value,size:`14px`,class:`tool-icon`},null,8,[`name`]),C(`span`,We,i(d.value.toolName===`Bash:rm`?`Bash`:d.value.toolName),1),C(`span`,{class:`tool-path`,title:d.value.filePath},i(f.value),9,Ge),m(ee,{flat:``,dense:``,round:``,size:`xs`,icon:`open_in_new`,class:`tool-open-diff`,onClick:A(N,[`stop`])},{default:p(()=>[m(W,null,{default:p(()=>[_(i(n(u)(`chat.openInGitDiff`)),1)]),_:1})]),_:1}),d.value.additions>0?(l(),x(`span`,Ke,`+`+i(d.value.additions),1)):g(``,!0),d.value.deletions>0?(l(),x(`span`,qe,`-`+i(d.value.deletions),1)):g(``,!0),e.item.result?.isError?(l(),h(k,{key:2,name:`error_outline`,color:`negative`,size:`xs`,class:`q-ml-xs`})):e.item.result?(l(),h(k,{key:3,name:`check`,color:`positive`,size:`xs`,class:`q-ml-xs`})):g(``,!0),m(k,{name:o.value?`expand_less`:`expand_more`,size:`xs`,class:`q-ml-auto text-kobo-3`},null,8,[`name`])]),o.value&&D.value?(l(),x(`div`,{key:0,class:`tool-diff`,onClick:a[0]||=A(()=>{},[`stop`])},[(l(!0),x(S,null,T(D.value,(e,t)=>(l(),x(`div`,{key:t,class:r([`diff-line`,{"diff-del":e.type===`del`,"diff-add":e.type===`add`,"diff-context":e.type===`context`}])},[C(`span`,Je,i(e.type===`del`?`-`:e.type===`add`?`+`:` `),1),_(i(e.content),1)],2))),128))])):g(``,!0)],2)):(l(),x(`div`,{key:1,class:r([`tool-row tool-row-generic`,{"tool-row-expanded":o.value,"tool-row--toggleable":M.value}])},[C(`div`,{class:`tool-header`,onClick:a[1]||=e=>M.value&&te()},[m(k,{name:y.value,size:`14px`,class:`tool-icon`},null,8,[`name`]),C(`span`,Ye,i(e.item.name),1),w.value?(l(),x(`span`,{key:0,class:`tool-arg`,title:E(e.item.input)||w.value},i(w.value),9,Xe)):g(``,!0),e.item.result?.isError?(l(),h(k,{key:1,name:`error_outline`,color:`negative`,size:`xs`,class:`q-ml-auto`})):e.item.result?(l(),h(k,{key:2,name:`check`,color:`positive`,size:`xs`,class:`q-ml-auto`})):g(``,!0),M.value?(l(),h(k,{key:3,name:o.value?`expand_less`:`expand_more`,size:`xs`,class:`q-ml-xs text-kobo-3`},null,8,[`name`])):g(``,!0)]),o.value&&M.value?(l(),x(`div`,{key:0,class:`tool-output`,onClick:a[2]||=A(()=>{},[`stop`])},i(O.value),1)):g(``,!0)],2))}}),[[`__scopeId`,`data-v-2482adfe`]]);function Qe(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 $e=[`innerHTML`],et=[`innerHTML`],tt=[`src`],nt=U(w({__name:`UserMessageItem`,props:{item:{}},setup(e){let t=e,n=I(),r=b(()=>t.item.sender===`system-prompt`),i=b(()=>{let e=Qe(t.item.content,n.selectedWorkspaceId??``);return G(e)}),o=a(null),s=a(!1);function c(e){let t=e.target;if(t?.tagName!==`IMG`)return;let n=t;n.src&&(o.value=n.src,s.value=!0)}return(e,t)=>(l(),x(S,null,[r.value?(l(),h(le,{key:0,dense:``,"dense-toggle":``,label:e.$t(`chat.systemPrompt`),"header-class":`text-kobo-2 text-caption`},{default:p(()=>[C(`div`,{class:`q-py-xs markdown-user-prompt`,innerHTML:i.value},null,8,$e)]),_:1},8,[`label`])):(l(),x(`div`,{key:1,class:`markdown-message`,onClick:c},[C(`div`,{innerHTML:i.value},null,8,et)])),m(P,{modelValue:s.value,"onUpdate:modelValue":t[1]||=e=>s.value=e},{default:p(()=>[o.value?(l(),x(`img`,{key:0,src:o.value,alt:``,class:`image-lightbox-img`,onClick:t[0]||=e=>s.value=!1},null,8,tt)):g(``,!0)]),_:1},8,[`modelValue`])],64))}}),[[`__scopeId`,`data-v-7687eb7e`]]),rt=[`data-event-ids`],it={class:`turn-header`},at={key:0,class:`turn-time`},ot={class:`turn-time turn-time-updated`},st={key:2,class:`turn-actions`},ct={class:`turn-body`},lt={key:0,class:`turn-scroll-top`},ut=U(w({__name:`TurnCard`,props:{turn:{},highlighted:{type:Boolean}},emits:[`scrollTo`],setup(e,{emit:o}){let s=e,c=o,{t:u}=L(),d=a(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 v=b(()=>{switch(s.turn.speaker){case`user`:return{label:u(`chat.you`),accent:`var(--kobo-turn-user)`,badgeClass:`turn-badge-user`};case`agent`:return{label:u(`chat.agent`),accent:`var(--kobo-turn-agent)`,badgeClass:`turn-badge-agent`};case`system-prompt`:return{label:u(`chat.systemPrompt`),accent:`var(--kobo-text-3)`,badgeClass:`turn-badge-system`};case`session`:return{label:u(`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:re(t)?u(`chat.hookScript`,{event:t.slice(5)}):u(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 w=b(()=>y(s.turn.ts)),E=b(()=>s.turn.items.flatMap(e=>e.eventIds??[]).join(` `)),D=b(()=>{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}),O=b(()=>{let e=s.turn.ts,t=D.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)}),A=b(()=>O.value!==``),j=b(()=>s.turn.items.filter(e=>e.type===`tool`).length);return(a,o)=>(l(),x(`div`,{ref_key:`cardEl`,ref:d,class:r([`turn-card`,{"turn-card--user":e.turn.speaker===`user`,"turn-card--highlighted":e.highlighted}]),"data-event-ids":E.value,style:t({"--turn-accent":v.value.accent})},[C(`div`,it,[C(`span`,{class:r([`turn-badge`,v.value.badgeClass])},i(v.value.label),3),w.value?(l(),x(`span`,at,i(w.value),1)):g(``,!0),A.value?(l(),x(S,{key:1},[m(k,{name:`arrow_forward`,size:`10px`,color:`kobo-3`,class:`turn-time-arrow`}),C(`span`,ot,[_(i(O.value)+` `,1),m(W,null,{default:p(()=>[_(i(n(u)(`chat.lastUpdatedAt`,{time:O.value})),1)]),_:1})])],64)):g(``,!0),j.value>0?(l(),x(`span`,st,` · `+i(n(u)(`chat.nActions`,{n:j.value})),1)):g(``,!0)]),C(`div`,ct,[(l(!0),x(S,null,T(e.turn.items,e=>(l(),x(S,{key:n(we)(e)},[e.type===`text`?(l(),h(Fe,{key:0,item:e},null,8,[`item`])):e.type===`thinking`?(l(),h(q,{key:1,item:e},null,8,[`item`])):e.type===`tool`?(l(),h(Ze,{key:2,item:e},null,8,[`item`])):e.type===`user`?(l(),h(nt,{key:3,item:e},null,8,[`item`])):e.type===`session`?(l(),h(Z,{key:4,item:e},null,8,[`item`])):e.type===`error`?(l(),h(X,{key:5,item:e},null,8,[`item`])):g(``,!0)],64))),128))]),e.turn.items.length>4?(l(),x(`div`,lt,[m(ee,{flat:``,round:``,dense:``,size:`xs`,icon:`arrow_upward`,color:`kobo-3`,class:`turn-scroll-top-btn`,onClick:f},{default:p(()=>[m(W,null,{default:p(()=>[_(i(n(u)(`chat.scrollToTurnTop`)),1)]),_:1})]),_:1})])):g(``,!0)],14,rt))}}),[[`__scopeId`,`data-v-6e61835e`]]),dt={key:0,class:`activity-feed-switching`},ft={key:1,class:`activity-feed-wrap`,"data-tour":`ws-chat`},pt=[`aria-label`],mt={key:0,class:`activity-feed-compacting`},ht={class:`text-caption text-kobo-2`},gt={key:0,class:`text-center q-py-sm text-caption text-kobo-3`},_t={key:1,class:`activity-feed-empty q-pa-md`},vt={class:`activity-feed-empty__title`},yt={class:`activity-feed-empty__hint`},bt={key:2,class:`q-px-md q-pb-md`},xt={class:`activity-feed-nav-cluster`},St=300,Ct=60,wt=200,Tt=200,Et=400,Dt=200,Ot=180,kt=1200,At=U(w({__name:`ActivityFeed`,props:{workspaceId:{}},setup(e){let{t}=L(),r=e,o=oe(),u=ie(),v=I(),y=b(()=>o.isCompacting(r.workspaceId)),w=b(()=>v.selectedSessionId),E=b(()=>v.sessions.find(e=>e.id===w.value)?.engineSessionId??null),D=b(()=>{let e=v.sessions;return e.length!==0&&w.value===e[e.length-1].id});function A(e){return w.value?e?e===w.value||e===E.value:D.value:!0}let j=b(()=>(v.activityFeeds[r.workspaceId]??[]).filter(e=>e.type===`text`&&typeof e.content==`string`&&A(e.sessionId)).map(e=>({content:e.content,sender:e.meta?.sender??`user`,ts:e.timestamp,sessionId:e.sessionId,eventIds:[e.id]}))),te=b(()=>{let e=v.workspaces.find(e=>e.id===r.workspaceId);return ae(e?.status)}),N=_e(),ne=``,P=b(()=>{let e=o.eventsFor(r.workspaceId),t=o.timestampsFor(r.workspaceId),n=o.sessionIdsFor(r.workspaceId),i=o.eventIdsFor(r.workspaceId),a=[],s=[],c=[];for(let r=0;r<e.length;r++)A(n[r])&&(a.push(e[r]),s.push(t[r]),c.push(i[r]??null));let l=`${r.workspaceId}::${w.value??`*`}`;l!==ne&&(N=_e(),ne=l);let d=he(N,a,s,te.value,c);return Ce(ge(d,j.value).filter(e=>e.type===`thinking`?!1:e.type===`session`?e.kind===`ended`?u.showVerboseSystemMessages||!me(e.detail):u.showVerboseSystemMessages:!0))}),F=b(()=>u.showVerboseSystemMessages?o.eventsFor(r.workspaceId).filter(e=>e.kind===`message:raw`).map(e=>e.content):[]),re=b(()=>{for(let e=P.value.length-1;e>=0;e--){let n=P.value[e]?.items??[];for(let e=n.length-1;e>=0;e--){let r=n[e];if(r){if(r.type===`text`&&!r.streaming)return{key:`text:${r.messageId}`,text:r.text};if(r.type===`tool`)return r.result?{key:`tool:${r.toolCallId}:end`,text:r.result.isError?t(`activity.a11y.toolFailed`,{name:r.name}):t(`activity.a11y.toolFinished`,{name:r.name})}:{key:`tool:${r.toolCallId}:start`,text:t(`activity.a11y.toolStarted`,{name:r.name})}}}}return null}),ce=a(``);c(re,(e,n)=>{if(!e||e.key===n?.key)return;let r=e.key.startsWith(`text:`)?t(`activity.a11y.agentSaid`,{text:e.text.slice(0,St)}):e.text;ce.value=r});let R=a(null),z=a(!0),B=a(!1),V=a(null),H=!1,U=a(!0),W=a(new Map);function de(e){let t=e.verticalSize-e.verticalPosition-e.verticalContainerSize;z.value=t<=Ct,H&&e.verticalPosition<=wt&&!B.value&&fe()&&ve()}function G(e,t){return`${e}:${t}`}function fe(){let e=w.value;return e?W.value.get(G(r.workspaceId,e))??!0:o.hasMoreOlderFor(r.workspaceId)}function K(e,t,n){W.value.set(G(e,t),n)}function pe(e){if(!w.value)return o.oldestIdFor(e);let t=o.eventIdsFor(e),n=o.sessionIdsFor(e);for(let e=0;e<t.length;e++){if(!A(n[e]))continue;let r=t[e];if(r)return r}}let q=null;function ve(){return q||(q=ye().finally(()=>{q=null}),q)}async function ye(){let e=r.workspaceId,t=w.value,n=pe(e);if(!n)return;B.value=!0;let i=Date.now();try{let r=R.value;await d();let i=r?.getScroll().verticalSize??0,a=r?.getScroll().verticalPosition??0,s=new URLSearchParams({before:n,limit:`200`});t&&s.set(`session`,t);let c=fetch(`/api/workspaces/${e}/events?${s.toString()}`),l=new Promise(e=>setTimeout(e,Tt)),[u]=await Promise.all([c,l]);if(!u.ok){t?K(e,t,!1):o.prepend(e,[],[],{oldestId:n,hasMoreOlder:!1});return}let f=await u.json(),p=f.events??[],m=p.filter(t=>t.type===`agent:event`&&t.workspaceId===e),h=p.filter(t=>t.type===`user:message`&&t.workspaceId===e),g=m.map(e=>e.payload),_=m.map(e=>e.createdAt),y=m.map(e=>e.sessionId??null),b=m.map(e=>e.id),x=p.length>0?p[0].id:n;t&&K(e,t,f.hasMore),o.prepend(e,g,_,{oldestId:x,hasMoreOlder:t?o.hasMoreOlderFor(e):f.hasMore,sessionIds:y,eventIds:b});for(let t of h){let n=t.payload;typeof n.content==`string`&&v.addActivityItem(e,{id:t.id,type:`text`,content:n.content,timestamp:t.createdAt,sessionId:t.sessionId??void 0,meta:{sender:n.sender??`user`}})}if(await d(),r){let e=r.getScroll().verticalSize-i;if(e>0){let t=Math.max(a+e,250);r.setScrollPosition(`vertical`,t,0)}}}catch(e){console.error(`[ActivityFeed] failed to load older events:`,e)}finally{let e=Date.now()-i,t=Math.max(0,Tt-e);await new Promise(e=>setTimeout(e,t+Et)),B.value=!1}}async function J(e=0){await d();let t=R.value;if(!t)return;let n=t.getScroll();t.setScrollPosition(`vertical`,n.verticalSize,e)}let Y=null,xe=0;function Se(){if(Y!=null)return;let e=performance.now()-xe<Ot;Y=requestAnimationFrame(()=>{Y=null,xe=performance.now(),J(e?0:180)})}function we(e){let t=R.value;t&&t.setScrollPosition(`vertical`,Math.max(0,e),250)}function Oe(){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 ke(e){let t=e.detail;if(t?.workspaceId===r.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/${r.workspaceId}/events?${e}`);if(!n.ok)return;let i=await n.json(),a=i.events??[],s=a.filter(e=>e.type===`agent:event`&&e.workspaceId===r.workspaceId),c=a.filter(e=>e.type===`user:message`&&e.workspaceId===r.workspaceId);o.reset(r.workspaceId,s.map(e=>e.payload),s.map(e=>e.createdAt),{oldestId:a[0]?.id,hasMoreOlder:i.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`&&v.addActivityItem(r.workspaceId,{id:e.id,type:`text`,content:t.content,timestamp:e.createdAt,sessionId:e.sessionId??void 0,meta:{sender:t.sender??`user`}})}await De(U,e=>!e,5e3),await d(),await new Promise(e=>requestAnimationFrame(()=>requestAnimationFrame(()=>e())));let l=P.value.findIndex(e=>e.items.some(e=>e.eventIds?.includes(t.eventId)??!1));if(l<0)return;V.value=t.eventId,Ae.value?.scrollTo(l,`center`),X.value=l,window.setTimeout(()=>{V.value===t.eventId&&(V.value=null)},1800)}catch(e){console.error(`[ActivityFeed] failed to focus history event:`,e)}}let Ae=a(null),je=a(null),X=a(0);function Me(e){X.value=e.index}let Z=a(!1);async function Ne(){if(Z.value)return;Z.value=!0;let e=r.workspaceId;try{let t=Ee(P.value,X.value);if(t<0)for(let n=0;n<15;n++){if(e!==r.workspaceId)return;if(!fe())break;let n=P.value.length;if(await ve(),await d(),e!==r.workspaceId)return;if(X.value+=P.value.length-n,t=Ee(P.value,X.value),t>=0)break}t>=0&&e===r.workspaceId&&(Ae.value?.scrollTo(t,`start`),X.value=t)}finally{Z.value=!1}}async function Q(){H=!1,await d(),await J(0),await new Promise(e=>requestAnimationFrame(()=>requestAnimationFrame(()=>e()))),await J(0),H=!0}let $=b(()=>{let e=o.sessionIdsFor(r.workspaceId);if(!w.value)return e.length;let t=0;for(let n of e)A(n)&&t++;return t}),Pe=b(()=>o.eventsFor(r.workspaceId).length);async function Fe(){U.value=!0;let e=Date.now();await new Promise(e=>setTimeout(e,Dt));let t=Math.max(0,e+kt-Date.now());await De(Pe,e=>e>0,t),U.value=!1}c(U,async e=>{!e&&$.value>0&&await Q(),!e&&$.value===0&&w.value&&Re(),e||d(()=>{je.value=R.value?.getScrollTarget()??null})}),f(()=>{window.addEventListener(`kobo:focus-history-event`,ke),d(()=>{je.value=R.value?.getScrollTarget()??null}),Fe(),$.value>0&&Q(),w.value&&Re();let e=new URLSearchParams(window.location.hash.split(`?`)[1]??``).get(`eventId`);e&&(ke(new CustomEvent(`kobo:focus-history-event`,{detail:{workspaceId:r.workspaceId,sessionId:w.value,eventId:e}})),Oe())});let Ie=$.value>0;c($,async(e,t)=>{if(!B.value){if(!Ie&&e>0){Ie=!0,await Q();return}e>t&&z.value&&!B.value&&Se()}}),s(()=>{window.removeEventListener(`kobo:focus-history-event`,ke),Y!=null&&(cancelAnimationFrame(Y),Y=null)}),c(()=>r.workspaceId,()=>{z.value=!0,Ie=$.value>0,H=!1,Fe(),$.value>0&&Q()}),c(()=>v.selectedSessionId,async()=>{z.value=!0,H=!1,await Q(),Re()});let Le=new Set;async function Re(){let e=w.value;if(!e||$.value>0)return;let t=G(r.workspaceId,e);if(!Le.has(t)){Le.add(t);try{let t=await fetch(`/api/workspaces/${r.workspaceId}/events?session=${encodeURIComponent(e)}&limit=500`);if(!t.ok)return;let n=await t.json(),i=n.events??[];if(i.length===0)return;let a=i.filter(e=>e.type===`agent:event`&&e.workspaceId===r.workspaceId),s=i.filter(e=>e.type===`user:message`&&e.workspaceId===r.workspaceId),c=a.map(e=>e.payload),l=a.map(e=>e.createdAt),u=a.map(e=>e.sessionId??null),f=a.map(e=>e.id);K(r.workspaceId,e,n.hasMore),c.length>0&&o.prepend(r.workspaceId,c,l,{oldestId:i[0].id,hasMoreOlder:o.hasMoreOlderFor(r.workspaceId),sessionIds:u,eventIds:f});for(let e of s){let t=e.payload;typeof t.content==`string`&&v.addActivityItem(r.workspaceId,{id:e.id,type:`text`,content:t.content,timestamp:e.createdAt,sessionId:e.sessionId??void 0,meta:{sender:t.sender??`user`}})}await d(),await J(0)}catch(e){console.error(`[ActivityFeed] fetchSessionIfMissing failed:`,e),Le.delete(t)}}}let ze=b(()=>j.value.filter(e=>e.sender!==`system-prompt`).length);c(ze,async(e,t)=>{e>t&&(z.value=!0,await J(180))});async function Be(){z.value=!0,await J(250)}return(e,t)=>U.value?(l(),x(`div`,dt,[m(se,{size:`40px`,color:`primary`})])):(l(),x(`div`,ft,[C(`div`,{"data-testid":`activity-live-region`,class:`sr-only`,role:`log`,"aria-live":`polite`,"aria-atomic":`true`,"aria-label":e.$t(`activity.a11y.region`)},i(ce.value),9,pt),m(O,{name:`fade`},{default:p(()=>[y.value?(l(),x(`div`,mt,[m(se,{size:`18px`,color:`primary`}),C(`span`,ht,i(e.$t(`activity.compacting`)),1)])):g(``,!0)]),_:1}),m(ue,{ref_key:`scrollRef`,ref:R,class:`activity-feed-scroll`,onScroll:de},{default:p(()=>[B.value?(l(),x(`div`,gt,[m(M,{size:`sm`}),_(` `+i(e.$t(`activity.loading_older`)),1)])):g(``,!0),m(be,{ref_key:`virtualScrollRef`,ref:Ae,class:`q-pa-md`,items:P.value,"scroll-target":je.value??void 0,"virtual-scroll-item-size":160,"virtual-scroll-slice-size":20,onVirtualScroll:Me},{default:p(({item:e,index:t})=>[(l(),h(ut,{key:n(Te)(e),turn:e,"data-turn-index":t,highlighted:e.items.some(e=>e.eventIds?.includes(V.value??``)??!1),onScrollTo:we},null,8,[`turn`,`data-turn-index`,`highlighted`]))]),_:1},8,[`items`,`scroll-target`]),P.value.length===0&&F.value.length===0&&!B.value?(l(),x(`div`,_t,[m(k,{name:`forum`,size:`28px`}),C(`div`,vt,i(e.$t(`activity.empty`)),1),C(`div`,yt,i(e.$t(`activity.emptyHint`)),1)])):g(``,!0),F.value.length?(l(),x(`div`,bt,[m(le,{label:e.$t(`activity.raw_lines`,{n:F.value.length}),dense:``},{default:p(()=>[(l(!0),x(S,null,T(F.value,(e,t)=>(l(),x(`div`,{key:t,class:`text-caption text-kobo-3 q-pa-xs`},i(e),1))),128))]),_:1},8,[`label`])])):g(``,!0)]),_:1},512),C(`div`,xt,[z.value?g(``,!0):(l(),h(ee,{key:0,round:``,dense:``,unelevated:``,color:`kobo-surface-2`,"text-color":`kobo-1`,icon:`arrow_downward`,size:`sm`,class:`activity-feed-nav-btn`,title:e.$t(`activity.scroll_to_bottom`),"aria-label":e.$t(`activity.scroll_to_bottom`),onClick:Be},null,8,[`title`,`aria-label`])),m(ee,{round:``,dense:``,unelevated:``,color:`kobo-surface-2`,"text-color":`kobo-1`,icon:`arrow_upward`,size:`sm`,class:`activity-feed-nav-btn`,title:e.$t(`activity.prev_user_message`),"aria-label":e.$t(`activity.prev_user_message`),loading:Z.value,disable:Z.value,onClick:Ne},null,8,[`title`,`aria-label`,`loading`,`disable`])])]))}}),[[`__scopeId`,`data-v-01cd9f2a`]]);export{At as default};
@@ -0,0 +1 @@
1
+ import{j as e,ut as t,v as n}from"./reactivity.esm-bundler-DEJj9cFv.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-CDnVUfeZ.js";import{t as m}from"./QBtn-CZaBclNw.js";import{Lt as h,n as g,zt as _}from"./index-DcwcFDrZ.js";import{a as v,i as y,n as b}from"./use-tours-C8x2V1w5.js";import{n as x}from"./render-chat-markdown-BTPAjU2g.js";import{t as S}from"./QChip-owciRuZu.js";import{t as C}from"./QPage-DaX9sMQC.js";import{t as w}from"./DrawerToggleButton-BxYpUb4y.js";import{t as T}from"./TourReplayButton-Dn4Dy_kH.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=g(),{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(h,{key:e.version,dark:``,flat:``,bordered:``},{default:a(()=>[o(_,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};
@@ -0,0 +1 @@
1
+ import{V as e,nt as t}from"./QBtn-CZaBclNw.js";import{m as n,p as r}from"./use-id-BIuaWKkB.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};
@@ -0,0 +1,2 @@
1
+ import{ot as e,ut as t,v as n}from"./reactivity.esm-bundler-DEJj9cFv.js";import{J as r,L as i,P as a,Y as ee,_ as o,d as te,f as s,g as ne,l as c,p as l,r as u,u as d,y as f,z as re}from"./runtime-core.esm-bundler-CDnVUfeZ.js";import{t as p}from"./QBtn-CZaBclNw.js";import{n as ie,p as ae,xt as oe}from"./index-DcwcFDrZ.js";import{t as se}from"./QMarkupTable-B9QuKtK_.js";import{a as m,i as h}from"./use-tours-C8x2V1w5.js";var g=[`workspace`,`engine`,`model`,`status`,`branch`,`tasks_done`,`tasks_total`,`commits`,`files_changed`,`insertions`,`deletions`,`agent_messages`,`user_messages`,`injected_prompts`,`questions`,`tool_calls`,`errors`,`input_tokens`,`output_tokens`,`sessions`,`duration_seconds`];function _(e){if(e==null)return``;let t=String(e);return/[",\r\n]/.test(t)?`"${t.replace(/"/g,`""`)}"`:t}function v(e){let t=e.map(e=>{let t=e.gitStats,n=e.activity;return[e.workspace.name,e.workspace.engine,e.workspace.model,e.workspace.status,e.workspace.workingBranch,e.tasks.done,e.tasks.total,t?.commitCount??null,t?.filesChanged??null,t?.insertions??null,t?.deletions??null,n.agentMessages,n.userMessages,n.injectedPrompts,n.questions,n.toolCalls,n.errors,n.inputTokens,n.outputTokens,n.sessions,Math.round(n.durationMs/1e3)].map(_).join(`,`)});return`${[g.join(`,`),...t].join(`\r
2
+ `)}`}var y={key:0,class:`comparison-panel q-pa-md`},b={class:`row items-center q-mb-sm`},x={class:`text-subtitle2`},S={class:`text-caption text-kobo-3 q-mb-sm`},C={class:`text-left`},w={class:`text-left`},T={class:`text-right`},E={class:`text-right`},D={class:`text-right`},O={class:`text-right`},k={class:`text-right`},A={class:`text-right`},j={class:`text-right`},M={class:`text-right`},N={class:`text-right`},P={class:`text-right`},F={class:`text-right`},I={class:`text-caption text-kobo-3 comparison-mono`},L={key:0,class:`text-caption text-kobo-3`},R={class:`text-right comparison-mono`},z={class:`text-right`},B={class:`text-right`},V={class:`text-right`},ce={key:0,class:`comparison-mono`},H={class:`text-kobo-success`},U={class:`text-kobo-danger q-ml-xs`},W={key:1,class:`text-kobo-3`},G={class:`text-right comparison-mono`},le=[`title`],ue={class:`text-right`},de={class:`text-right`},fe={class:`text-right comparison-mono`},pe=[`title`],me={class:`text-right`},he={class:`text-right comparison-mono`},ge={class:`text-right`},_e={key:1,class:`text-caption text-kobo-3`},ve={key:0,class:`text-caption text-kobo-warning q-mt-sm`},K=m(f({__name:`ComparisonPanel`,props:{workspaceId:{}},setup(f){let m=f;function g(e){return e<1e3?String(e):e<1e6?`${(e/1e3).toFixed(+(e<1e4))}k`:`${(e/1e6).toFixed(1)}M`}function _(e){if(e<=0)return`-`;let t=Math.round(e/1e3);if(t<60)return q(`reliability.durationSeconds`,{s:t});let n=Math.floor(t/60);return n<60?q(`reliability.durationMinutes`,{m:n,s:t%60}):q(`reliability.durationHours`,{h:Math.floor(n/60),m:n%60})}let K=ie(),ye=ae(),{t:q}=oe(),J=n([]),Y=n(!1),be={created:`workspaceStatus.created`,extracting:`workspaceStatus.extracting`,brainstorming:`workspaceStatus.brainstorming`,executing:`workspaceStatus.executing`,"awaiting-user":`workspaceStatus.awaitingUser`,completed:`workspaceStatus.completed`,idle:`workspaceStatus.idle`,error:`workspaceStatus.error`,quota:`workspaceStatus.quota`,retrying:`workspaceStatus.retrying`},X=n({});function xe(e){return X.value[e]??e}async function Se(){try{let e=await fetch(`/api/engines`);if(!e.ok)return;let t=await e.json();X.value=Object.fromEntries(t.map(e=>[e.id,e.displayName]))}catch{}}function Z(e){let t=be[e];return t?q(t):e}let Ce=c(()=>m.workspaceId);function Q(e){return e.workspace.id===Ce.value}function we(e){ye.selectWorkspace(e),K.push({name:`workspace`,params:{id:e}})}function Te(){let e=v(J.value),t=new Blob([e],{type:`text/csv;charset=utf-8`}),n=URL.createObjectURL(t),r=document.createElement(`a`);r.href=n,r.download=`kobo-comparison-${J.value[0]?.workspace.comparisonId??`comparison`}.csv`,r.click(),URL.revokeObjectURL(n)}async function $(){Y.value=!0;try{let e=await fetch(`/api/workspaces/${m.workspaceId}/comparison`);if(!e.ok){J.value=[];return}let t=await e.json();J.value=t.members??[]}catch{J.value=[]}finally{Y.value=!1}}return a(()=>{Se(),$()}),r(()=>m.workspaceId,$),(n,r)=>J.value.length>0?(i(),l(`div`,y,[d(`div`,b,[d(`div`,x,t(n.$t(`comparison.title`)),1),o(h),o(p,{flat:``,dense:``,round:``,size:`sm`,icon:`download`,title:n.$t(`comparison.exportCsv`),onClick:Te},null,8,[`title`]),o(p,{flat:``,dense:``,round:``,size:`sm`,icon:`refresh`,loading:Y.value,onClick:$},null,8,[`loading`])]),d(`div`,S,t(n.$t(`comparison.hint`)),1),o(se,{flat:``,dense:``,dark:``,class:`comparison-table`},{default:ee(()=>[d(`thead`,null,[d(`tr`,null,[d(`th`,C,t(n.$t(`comparison.engine`)),1),d(`th`,w,t(n.$t(`comparison.status`)),1),d(`th`,T,t(n.$t(`comparison.tasks`)),1),d(`th`,E,t(n.$t(`comparison.commits`)),1),d(`th`,D,t(n.$t(`comparison.files`)),1),d(`th`,O,t(n.$t(`comparison.diff`)),1),d(`th`,k,t(n.$t(`comparison.messages`)),1),d(`th`,A,t(n.$t(`comparison.questions`)),1),d(`th`,j,t(n.$t(`comparison.tools`)),1),d(`th`,M,t(n.$t(`comparison.errors`)),1),d(`th`,N,t(n.$t(`comparison.tokens`)),1),d(`th`,P,t(n.$t(`comparison.sessions`)),1),d(`th`,F,t(n.$t(`comparison.duration`)),1),r[0]||=d(`th`,null,null,-1)])]),d(`tbody`,null,[(i(!0),l(u,null,re(J.value,r=>(i(),l(`tr`,{key:r.workspace.id,class:e({"comparison-current":Q(r)})},[d(`td`,null,[d(`div`,null,t(xe(r.workspace.engine)),1),d(`div`,I,t(r.workspace.model),1)]),d(`td`,null,[ne(t(Z(r.workspace.status))+` `,1),r.workspace.archivedAt?(i(),l(`span`,L,` · `+t(n.$t(`comparison.archived`)),1)):s(``,!0)]),d(`td`,R,t(r.tasks.done)+`/`+t(r.tasks.total),1),d(`td`,z,t(r.gitStats?.commitCount??`-`),1),d(`td`,B,t(r.gitStats?.filesChanged??`-`),1),d(`td`,V,[r.gitStats?(i(),l(`span`,ce,[d(`span`,H,`+`+t(r.gitStats.insertions),1),d(`span`,U,`-`+t(r.gitStats.deletions),1)])):(i(),l(`span`,W,`-`))]),d(`td`,G,[d(`span`,{title:n.$t(`comparison.messagesTitle`)},t(r.activity.agentMessages)+` / `+t(r.activity.userMessages),9,le)]),d(`td`,ue,t(r.activity.questions),1),d(`td`,de,t(r.activity.toolCalls),1),d(`td`,{class:e([`text-right`,r.activity.errors>0?`text-kobo-danger`:``])},t(r.activity.errors),3),d(`td`,fe,[d(`span`,{title:n.$t(`comparison.tokensTitle`)},t(g(r.activity.inputTokens))+` / `+t(g(r.activity.outputTokens)),9,pe)]),d(`td`,me,t(r.activity.sessions),1),d(`td`,he,t(_(r.activity.durationMs)),1),d(`td`,ge,[Q(r)?(i(),l(`span`,_e,t(n.$t(`comparison.current`)),1)):(i(),te(p,{key:0,flat:``,dense:``,"no-caps":``,size:`sm`,label:n.$t(`comparison.open`),onClick:e=>we(r.workspace.id)},null,8,[`label`,`onClick`]))])],2))),128))])]),_:1}),J.value.length<2?(i(),l(`div`,ve,t(n.$t(`comparison.incomplete`)),1)):s(``,!0)])):s(``,!0)}}),[[`__scopeId`,`data-v-95e8c6b5`]]);export{K as default};