@loicngr/kobo 2.1.13 → 2.1.16

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/AGENTS.md +38 -6
  2. package/CHANGELOG.md +12 -0
  3. package/CONFIGURATION.md +41 -5
  4. package/README.md +4 -2
  5. package/dist/mcp-server/kobo-tasks-server.js +62 -105
  6. package/dist/mcp-server/workspace-dialogue-client.js +15 -3
  7. package/dist/server/db/auto-loop-final-review-schema.js +18 -0
  8. package/dist/server/db/migrations.js +12 -1
  9. package/dist/server/db/review-return-schema.js +18 -1
  10. package/dist/server/db/schema.js +4 -0
  11. package/dist/server/db/workspace-message-batch-schema.js +9 -0
  12. package/dist/server/index.js +8 -1
  13. package/dist/server/routes/mcp.js +69 -3
  14. package/dist/server/routes/workspace-messages.js +22 -0
  15. package/dist/server/routes/workspaces.js +119 -4
  16. package/dist/server/services/agent/engines/claude-code/engine.js +37 -1
  17. package/dist/server/services/agent/orchestrator.js +159 -40
  18. package/dist/server/services/agent/session-controller.js +4 -1
  19. package/dist/server/services/auto-loop-final-review-service.js +329 -0
  20. package/dist/server/services/auto-loop-service.js +53 -10
  21. package/dist/server/services/pr-checkout-rollback-service.js +51 -0
  22. package/dist/server/services/pr-checkout-service.js +21 -1
  23. package/dist/server/services/review-launch-runtime.js +17 -0
  24. package/dist/server/services/review-return-service.js +58 -10
  25. package/dist/server/services/review-service.js +59 -11
  26. package/dist/server/services/workspace-creation-mcp-service.js +198 -0
  27. package/dist/server/services/workspace-group-message-mcp-service.js +58 -0
  28. package/dist/server/services/workspace-group-message-service.js +182 -0
  29. package/dist/server/services/workspace-lifecycle-mcp-service.js +72 -0
  30. package/dist/server/services/workspace-message-service.js +5 -0
  31. package/dist/server/services/workspace-service.js +11 -0
  32. package/dist/server/services/workspace-template-service.js +10 -0
  33. package/dist/shared/auto-loop-review.js +1 -0
  34. package/dist/shared/pr-checkout-steps.js +86 -0
  35. package/dist/shared/review-return.js +1 -0
  36. package/dist/shared/workspace-creation-tools.js +182 -0
  37. package/dist/shared/workspace-group-message-tools.js +134 -0
  38. package/dist/shared/workspace-group-messages.js +27 -0
  39. package/dist/shared/workspace-lifecycle-tools.js +112 -0
  40. package/package.json +1 -1
  41. package/src/client/dist/pwa/assets/ActivityFeed-tJmJhmtl.js +2 -0
  42. package/src/client/dist/pwa/assets/AutoLoopFinalReviewSettings-Bbrq02Xt.js +1 -0
  43. package/src/client/dist/pwa/assets/AutoLoopFinalReviewSettings-Q-OlSQ1c.css +1 -0
  44. package/src/client/dist/pwa/assets/{ChangelogPage-DD90Nl6c.js → ChangelogPage-BJfkAVYp.js} +1 -1
  45. package/src/client/dist/pwa/assets/{ComparisonPanel-D_BQFWiT.js → ComparisonPanel-BfP8APKY.js} +1 -1
  46. package/src/client/dist/pwa/assets/CreatePage-BbtNfo0x.js +2 -0
  47. package/src/client/dist/pwa/assets/CreatePage-DxOes_cF.css +1 -0
  48. package/src/client/dist/pwa/assets/{DashboardPage-DyvTdmBg.js → DashboardPage-DZYsAh5y.js} +1 -1
  49. package/src/client/dist/pwa/assets/DiffViewer-4Q-GJgyA.js +8 -0
  50. package/src/client/dist/pwa/assets/{DocumentsPanel-bm639sIu.js → DocumentsPanel-BfPlfiwU.js} +1 -1
  51. package/src/client/dist/pwa/assets/{DrawerToggleButton-KfwSzjeY.js → DrawerToggleButton-DG1Qjqk1.js} +1 -1
  52. package/src/client/dist/pwa/assets/GitPanel-ECnbb_rx.js +3 -0
  53. package/src/client/dist/pwa/assets/{HealthPage-37IHOIW-.js → HealthPage-_VXG1Zeo.js} +1 -1
  54. package/src/client/dist/pwa/assets/MainLayout-BH6grTuW.js +2 -0
  55. package/src/client/dist/pwa/assets/MainLayout-DnmUxNk7.css +1 -0
  56. package/src/client/dist/pwa/assets/{MemoryPanel-CucZMhjy.js → MemoryPanel-CVlvGZXZ.js} +1 -1
  57. package/src/client/dist/pwa/assets/{MemoryProposalList-CUQybzVR.js → MemoryProposalList-SJsnzUAM.js} +1 -1
  58. package/src/client/dist/pwa/assets/{QBtnDropdown-x6qPyl_7.js → QBtnDropdown-DBgGsN-D.js} +1 -1
  59. package/src/client/dist/pwa/assets/{QExpansionItem-D33aoRoG.js → QExpansionItem-DKgaMJ_l.js} +1 -1
  60. package/src/client/dist/pwa/assets/{QScrollArea-Di-D6t2u.js → QScrollArea-D3OCsq3g.js} +1 -1
  61. package/src/client/dist/pwa/assets/QSelect-Cb_s_nox.js +1 -0
  62. package/src/client/dist/pwa/assets/{QTooltip-BhwG7lp2.js → QTooltip-X5ps8Opw.js} +1 -1
  63. package/src/client/dist/pwa/assets/{SchedulePanel-qFi9Wxzh.js → SchedulePanel-Ww_FVlW5.js} +1 -1
  64. package/src/client/dist/pwa/assets/{SearchPage-DA9nRmsA.js → SearchPage-B87WEHf3.js} +1 -1
  65. package/src/client/dist/pwa/assets/SettingsPage-BAZLGUyg.js +9 -0
  66. package/src/client/dist/pwa/assets/{SplitWorkspacePage-Brw2XsYJ.js → SplitWorkspacePage-B99AJUbG.js} +1 -1
  67. package/src/client/dist/pwa/assets/{TerminalPanel-DVP18-nk.js → TerminalPanel-DnA1MXwu.js} +9 -9
  68. package/src/client/dist/pwa/assets/{ToolCallItem-cSHh3p_F.js → ToolCallItem-2vFggaur.js} +3 -3
  69. package/src/client/dist/pwa/assets/{TouchPan-C6LZZ3rw.js → TouchPan-BSU3z0aO.js} +1 -1
  70. package/src/client/dist/pwa/assets/{TourReplayButton-Czlqo18H.js → TourReplayButton-DQwS2xdD.js} +1 -1
  71. package/src/client/dist/pwa/assets/{WorkspaceAttentionLabels-BDYGq0pC.js → WorkspaceAttentionLabels-CM1tGDje.js} +1 -1
  72. package/src/client/dist/pwa/assets/WorkspacePage-B248nKIU.css +1 -0
  73. package/src/client/dist/pwa/assets/WorkspacePage-CQN6EKIn.js +4 -0
  74. package/src/client/dist/pwa/assets/{browser-ifCwUGpE.js → browser-DPsupI9Q.js} +1 -1
  75. package/src/client/dist/pwa/assets/{build-path-tree-BBkGq3lN.js → build-path-tree-CbIdt-p9.js} +1 -1
  76. package/src/client/dist/pwa/assets/custom-sounds-DR68eyF8.js +1 -0
  77. package/src/client/dist/pwa/assets/de-D3Ycvf_F.js +17 -0
  78. package/src/client/dist/pwa/assets/{editor.api-CV_seGJR.js → editor.api-DvgAtnOj.js} +1 -1
  79. package/src/client/dist/pwa/assets/{efforts-ttw0bL63.js → efforts-nmxfTVQD.js} +1 -1
  80. package/src/client/dist/pwa/assets/es-TgeS5898.js +17 -0
  81. package/src/client/dist/pwa/assets/{expand-template-C8biMRXo.js → expand-template-Brn5O4a1.js} +1 -1
  82. package/src/client/dist/pwa/assets/{format-Cu95dbxt.js → format-DfJ3Fv1i.js} +1 -1
  83. package/src/client/dist/pwa/assets/{formatters-C25D1RSd.js → formatters-B30bNFhq.js} +1 -1
  84. package/src/client/dist/pwa/assets/fr-CJafryNb.js +17 -0
  85. package/src/client/dist/pwa/assets/{freemarker2-DJEFr74q.js → freemarker2-DnZe74tc.js} +1 -1
  86. package/src/client/dist/pwa/assets/{handlebars-C_lzsMjX.js → handlebars-CmuMfxzO.js} +1 -1
  87. package/src/client/dist/pwa/assets/{html-CWmLFAmY.js → html-BD7OJFGX.js} +1 -1
  88. package/src/client/dist/pwa/assets/i18n-BY4JeBjk.js +1 -0
  89. package/src/client/dist/pwa/assets/index-C5fAw9ll.js +20 -0
  90. package/src/client/dist/pwa/assets/it-C5mN8Y61.js +17 -0
  91. package/src/client/dist/pwa/assets/{javascript-tsuiEUe-.js → javascript-Y-OrgcmW.js} +1 -1
  92. package/src/client/dist/pwa/assets/{liquid-BBr9BvUk.js → liquid-BqOkKePH.js} +1 -1
  93. package/src/client/dist/pwa/assets/{mdx-gjBH1cXG.js → mdx-D-58x2h0.js} +1 -1
  94. package/src/client/dist/pwa/assets/{monaco-contributions-CvGcBIsb.js → monaco-contributions-BW4ZMKO6.js} +1 -1
  95. package/src/client/dist/pwa/assets/{monaco.contribution-Uw-Lfb8Z.js → monaco.contribution-DJWqzNG-.js} +2 -2
  96. package/src/client/dist/pwa/assets/network-auth-BGPRhUa6.js +1 -0
  97. package/src/client/dist/pwa/assets/{notify-theme-PMSxqP6t.js → notify-theme-DZDzfxph.js} +1 -1
  98. package/src/client/dist/pwa/assets/{project-color-BDQa2TgU.js → project-color-gB5yeZPN.js} +1 -1
  99. package/src/client/dist/pwa/assets/{python-DlIgD1n6.js → python-BkBj1wDC.js} +1 -1
  100. package/src/client/dist/pwa/assets/{razor-AraFPlpM.js → razor-DOOJ_0b8.js} +1 -1
  101. package/src/client/dist/pwa/assets/{typescript-CB8UpXHM.js → typescript-Bheo43G5.js} +1 -1
  102. package/src/client/dist/pwa/assets/{use-search-index-status-CSREHQM4.js → use-search-index-status-CBpnyV-R.js} +1 -1
  103. package/src/client/dist/pwa/assets/use-tours-BHNasCvg.js +2 -0
  104. package/src/client/dist/pwa/assets/{use-worktree-restore-BM3Giig1.js → use-worktree-restore-CgABsWno.js} +1 -1
  105. package/src/client/dist/pwa/assets/{workspace-templates-BPMFvTnQ.js → workspace-templates-BnFkigYg.js} +1 -1
  106. package/src/client/dist/pwa/assets/{xml-uRjL1P43.js → xml-vHt2BD26.js} +1 -1
  107. package/src/client/dist/pwa/assets/{yaml-C9CFR4qV.js → yaml-BvhLPzP9.js} +1 -1
  108. package/src/client/dist/pwa/index.html +1 -1
  109. package/src/client/dist/pwa/sw.js +1 -1
  110. package/src/mcp-server/README.md +156 -12
  111. package/src/mcp-server/kobo-tasks-server.ts +73 -106
  112. package/src/mcp-server/workspace-dialogue-client.ts +18 -3
  113. package/src/client/dist/pwa/assets/ActivityFeed-BTRzc60o.js +0 -2
  114. package/src/client/dist/pwa/assets/CreatePage-BZAoCukb.js +0 -2
  115. package/src/client/dist/pwa/assets/CreatePage-CdCs8PRS.css +0 -1
  116. package/src/client/dist/pwa/assets/DiffViewer-D0K2PlZD.js +0 -8
  117. package/src/client/dist/pwa/assets/GitPanel-C-Ejlc9_.js +0 -3
  118. package/src/client/dist/pwa/assets/MainLayout-BQOOup9o.css +0 -1
  119. package/src/client/dist/pwa/assets/MainLayout-DXQ1ouYl.js +0 -2
  120. package/src/client/dist/pwa/assets/QSelect-MdPXvwm4.js +0 -1
  121. package/src/client/dist/pwa/assets/SettingsPage-DcV6apqE.js +0 -9
  122. package/src/client/dist/pwa/assets/WorkspacePage-BwUkijpS.js +0 -4
  123. package/src/client/dist/pwa/assets/WorkspacePage-Bwfv559o.css +0 -1
  124. package/src/client/dist/pwa/assets/custom-sounds-CeSgjIY7.js +0 -1
  125. package/src/client/dist/pwa/assets/de-CHY2BAfL.js +0 -17
  126. package/src/client/dist/pwa/assets/es-DbcSKp38.js +0 -17
  127. package/src/client/dist/pwa/assets/fr-CyyJ8S2c.js +0 -17
  128. package/src/client/dist/pwa/assets/i18n-CobVQiTM.js +0 -1
  129. package/src/client/dist/pwa/assets/index-ImUH7Bbt.js +0 -20
  130. package/src/client/dist/pwa/assets/it-XQJW2Xkr.js +0 -17
  131. package/src/client/dist/pwa/assets/network-auth-D0Vm6a09.js +0 -1
  132. package/src/client/dist/pwa/assets/use-tours-6yxNVmjH.js +0 -2
@@ -2,7 +2,7 @@
2
2
 
3
3
  Standalone MCP (Model Context Protocol) server configured for each Claude Code or Codex agent running inside a workspace. Exposes workspace-scoped tools that the agent can invoke to interact with Kōbō state: tasks, settings, dev server, images, git, etc.
4
4
 
5
- Kōbō also exposes conversation tools to external MCP clients over HTTP and through the global stdio server.
5
+ Kōbō also exposes workspace creation, PR checkout diagnosis and conversation tools to external MCP clients over HTTP and through the global stdio server.
6
6
 
7
7
  ## Connect an external LLM
8
8
 
@@ -162,6 +162,32 @@ conversation tools are described separately. The registration schemas in
162
162
  | `cron_list` | None | List this workspace's schedules. |
163
163
  | `cron_delete` | `id` | Cancel one of this workspace's schedules. |
164
164
 
165
+ ### Final auto-loop review
166
+
167
+ `submit_final_review` (shown to agents as `kobo__submit_final_review`) is available
168
+ only in the separate final-review session launched by Kōbō. It is not an external
169
+ HTTP/global-stdio conversation tool or a general task-completion tool. Kōbō binds
170
+ its capability to the active reviewer; the agent cannot select another workspace
171
+ or session. Plan/read-only reviewers can submit this verdict without permission to
172
+ modify files or tasks.
173
+
174
+ Arguments: `summary` (non-empty, at most 12,000 characters) and `findings` (at most
175
+ 100 items). Each finding requires `severity` (`critical`, `important`, or `minor`),
176
+ `file` (at most 1,000 characters), `description` and `recommendation` (each at most
177
+ 4,000 characters), with an optional positive integer `line`. The complete
178
+ serialized verdict is capped at 60,000 characters. Include every actionable
179
+ finding; use `findings: []` only when the entire mission and its acceptance
180
+ criteria are clear. A prose-only report does not satisfy this completion gate.
181
+
182
+ Submit one final verdict, then end the turn. Repeating an identical verdict is
183
+ safe; replacing an already accepted verdict with different findings is refused.
184
+ Kōbō turns findings into tasks, returns them to the original working session for
185
+ automatic correction and final verification, and launches a fresh review. With no
186
+ findings, the original session receives the result before the loop completes.
187
+ This review/return state survives a client disconnect or server restart. An
188
+ uncertain return delivery requires explicit retry/cancel in Kōbō; the reviewer
189
+ must not try to bypass that decision. See [final review configuration](../../CONFIGURATION.md#final-review-for-auto-loop).
190
+
165
191
  ### Documents, context and history
166
192
 
167
193
  | Tool | Input | Purpose |
@@ -339,7 +365,7 @@ List all images uploaded to the current workspace via Kōbō's chat paste/upload
339
365
 
340
366
  ### Global tools (workspace-less mode)
341
367
 
342
- These 4 management tools and the 6 [conversation tools](#conversation-tools) are always registered, regardless of whether `KOBO_WORKSPACE_ID` is set — they let a session that Kōbō did NOT spawn (e.g. a standalone terminal session) discover, manage and talk to workspaces instead of being bound to one. The HTTP endpoint exposes discovery and conversation only.
368
+ These management tools and the 6 [conversation tools](#conversation-tools) are registered regardless of whether `KOBO_WORKSPACE_ID` is set. Workspace-bound stdio, global stdio and external HTTP share creation, PR diagnosis, archive, purge, deletion and restoration tools. The HTTP endpoint also exposes discovery, conversation and memory; `stop_workspace` remains a stdio tool. Restricted final-review and handoff sessions cannot call creation or lifecycle mutation tools.
343
369
 
344
370
  #### `list_workspaces`
345
371
  List Kōbō workspaces with id, title, status, and creation date. Reads the DB directly — works even when the Kōbō backend server isn't running.
@@ -352,28 +378,113 @@ List Kōbō workspaces with id, title, status, and creation date. Reads the DB d
352
378
  ---
353
379
 
354
380
  #### `create_workspace`
355
- Create a new workspace (git worktree + agent session), like the "Créer" button on the Create page. Requires the Kōbō backend server to be running and reachable at `KOBO_BACKEND_URL` — this API has no automatic branch-name derivation from the workspace name, unlike the UI.
381
+ Create a workspace through the same backend handlers as the Create page, including worktree setup, imports, attachments, auto-loop and final review. The backend must be running. Inputs use snake_case; returned `Workspace` fields use the HTTP API's camelCase.
356
382
 
357
- **Input:**
358
- - `name`, `project_path`, `source_branch`, `working_branch` (string, required)
359
- - `model`, `reasoning_effort`, `engine`, `description` (string, optional)
360
- - `tasks`, `acceptance_criteria` (string[], optional)
361
- - `agent_permission_mode` (`plan | bypass | strict | interactive`, optional)
362
- - `auto_loop` (boolean, optional), `auto_loop_session_mode` (`per_task | continuous`, optional)
363
- - `skip_setup_script` (boolean, optional)
383
+ | Inputs | Meaning |
384
+ |---|---|
385
+ | `name`, `project_path` | Required. Select a project configured in Kōbō. |
386
+ | `source_branch`, `working_branch` | Required for ordinary creation; supply branch names explicitly. With `worktree_path`, only `source_branch` is required. With `pr_url`, canonical PR base/head branches are derived. |
387
+ | `engine`, `model`, `reasoning_effort`, `agent_permission_mode` | Same engine and permissions as the form (`claude-code` / `codex`; `plan` / `bypass` / `strict` / `interactive`, subject to engine support). |
388
+ | `description`, `tags`, `tasks`, `acceptance_criteria` | Description and arrays of strings. |
389
+ | `auto_loop`, `auto_loop_session_mode` | Enable auto-loop; mode is `per_task` or `continuous`. |
390
+ | `brainstorm_model`, `brainstorm_reasoning_effort` | Separate initial brainstorm configuration, on the workspace's engine. |
391
+ | `auto_loop_final_review` | Reviewer `{engine, model, reasoning_effort, additional_instructions?}`; uses the same durable review/correction cycle as the form. |
392
+ | `workflow_policy` | Optional `commit`, `push`, `publish`, each `manual` or `automatic`. |
393
+ | `notion_url`, `notion_page_id`, `sentry_url` | `notion_url` or `sentry_url` imports ticket context using the enabled, configured integration. `notion_page_id` stores an optional identifier; it does not import content on its own. |
394
+ | `pr_url`, `pr_checkout` | Resume a PR/MR through diagnosis and checkout; see below. |
395
+ | `worktree_path`, `skip_setup_script` | Reuse an existing worktree or control the setup script. PR checkout skips setup by default; explicit `false` runs it. |
396
+ | `comparison_id`, `creation_id` | Group separate creations for comparison; correlate creation progress. These are not idempotency keys. |
397
+ | `attachments` | Up to 10 `{name, mime_type, data_base64}` files, 50 MiB decoded total. Same file types as the form; send inline base64, never a server file path. |
398
+
399
+ For an engine comparison, create two workspaces with distinct working branches and the same `comparison_id`, configuring each engine separately. Selecting a saved form preset in an MCP client means sending its effective field values.
400
+
401
+ Example with final review:
402
+
403
+ ```json
404
+ {
405
+ "name": "Implement feature",
406
+ "project_path": "/home/me/project",
407
+ "source_branch": "develop",
408
+ "working_branch": "feature/example",
409
+ "engine": "codex",
410
+ "auto_loop": true,
411
+ "auto_loop_final_review": {
412
+ "engine": "claude-code",
413
+ "model": "opus",
414
+ "reasoning_effort": "high",
415
+ "additional_instructions": "Check regressions and tests"
416
+ },
417
+ "workflow_policy": { "commit": "manual", "push": "manual", "publish": "manual" }
418
+ }
419
+ ```
420
+
421
+ **Result:** a created `Workspace`, or `{created: false, requiresAction: true, report, pr, fingerprint, reason?}` when PR checkout needs decisions. Opening/unarchiving an existing workspace returns `{created: false, workspaceId, workspace}`. Cancellation returns `{created: false, cancelled: true, ...}`. Backend errors are MCP errors with `{error, status, stage, details}` so stale checkout reports remain available.
422
+
423
+ If creation fails after PR checkout, Kōbō removes only the checkout created by that request when its directory, branch and HEAD are unchanged, it has no local or ignored files, and no workspace adopted it. The PR branch and earlier checkout decisions remain intact. Existing, modified or adopted checkouts are preserved. Errors include `details.checkoutRecovery` with the path, `removed` outcome and a reason when retained. An uncertain creation outcome preserves the checkout for inspection; do not retry automatically.
364
424
 
365
- **Output:** created `Workspace`
425
+ Creation requests have no automatic retry or exactly-once guarantee. After a disconnect/timeout, inspect `list_workspaces` before trying again: checkout or creation may have completed. The stdio bridge allows up to 15 minutes for creation; external clients should configure their own request timeout for slow setup scripts. Ordinary MCP requests retain the 1 MiB envelope limit; creation allows base64 expansion of the 50 MiB upload limit plus 1 MiB metadata, including at the SDK HTTP and stdio transport boundaries.
426
+
427
+ #### `diagnose_workspace_pr`
428
+
429
+ **Input:** `{project_path, pr_url}`. Returns `{report, pr, fingerprint}` for the configured forge and the PR's local Git state. Creates no workspace and applies no checkout changes.
430
+
431
+ 1. Call `diagnose_workspace_pr` to inspect the PR. A clean PR can also be passed directly to `create_workspace` using only `name`, `project_path`, `pr_url` and the desired agent options.
432
+ 2. When checkout needs a choice, call `create_workspace` with the same PR and `pr_checkout: {fingerprint, decisions}`. Use the returned fingerprint, not a guessed value. Missing choices return `requiresAction`; stale state returns an error with the current report.
433
+ 3. Inspect the result before continuing. Existing workspaces can be opened or unarchived; use `restore_workspace` first for a purged checkout. No duplicate workspace is created automatically.
434
+
435
+ `decisions` uses the existing checkout contract's camelCase keys: `existingWorkspace: "open"`, `archivedWorkspace: "unarchive"`, `orphanWorktree: "attach" | "create-elsewhere"`, `pathCollision: {worktreePath}`, `localChanges: "stash" | "commit" | "discard" | "keep"`, `ongoingOperation: "abort" | "cancel"`, `divergence: "fast-forward" | "rebase" | "reset-hard" | "keep"`. Destructive choices must be supplied explicitly. The backend rechecks Git state under its checkout lock before applying them. Forks, unavailable forge CLIs and other blockers are reported using the same rules as the form.
366
436
 
367
437
  ---
368
438
 
369
439
  #### `archive_workspace`
370
- Archive a workspace by id, like the "Archiver" action in the workspace context menu. Requires the backend to be running.
440
+ Archive a workspace by id, like the "Archiver" action in the workspace context menu. Stops its processes and auto-loop, preserves checkout and history, and may run the configured archive script. Available on internal stdio, global stdio and external HTTP; requires the backend to be running.
371
441
 
372
442
  **Input:**
373
443
  - `workspace_id` (string, required) — from `list_workspaces`
374
444
 
375
445
  ---
376
446
 
447
+ #### `purge_workspace_worktree`
448
+
449
+ Free disk space by removing a Kōbō-owned checkout and archiving the workspace. Conversation history and recovery metadata remain available; uncommitted files are not recoverable. External attached worktrees are protected.
450
+
451
+ **Input:** `{workspace_id, confirm_purge: true}`.
452
+
453
+ **Output:** `{workspace, warnings, outcome}` from the existing purge lifecycle. Inspect warnings and outcome: a failed removal does not mean disk space was freed.
454
+
455
+ #### `delete_workspace`
456
+
457
+ Permanently delete one workspace and its history, with the same branch options as the deletion dialog. Stops processes and removes its owned checkout; externally managed worktrees are preserved.
458
+
459
+ **Input:**
460
+
461
+ ```json
462
+ {
463
+ "workspace_id": "workspace-id",
464
+ "confirm_delete": true,
465
+ "confirmation_branch": "feature/example",
466
+ "delete_local_branch": true,
467
+ "delete_remote_branch": true
468
+ }
469
+ ```
470
+
471
+ Both branch flags default to `false`; remote deletion requires local deletion too, as in the form. `confirmation_branch` must match the current working branch for every deletion. The backend checks it again under the lifecycle lock before any teardown; branch rename and resynchronization are rejected while this operation is in progress. Bulk deletion through the reserved `archived` endpoint is not exposed.
472
+
473
+ **Output:** `{ok: true, workspaceId, warnings: []}` after a clean 204 response, or the backend's `{ok: true, warnings}` when cleanup was incomplete. Preserve these warnings: deletion of the workspace record can succeed while branch or disk cleanup fails.
474
+
475
+ #### `unarchive_workspace` and `restore_workspace`
476
+
477
+ Both accept `{workspace_id}` and are available on all three MCP transports.
478
+
479
+ - `unarchive_workspace` restores visibility when the checkout is still present, preserving the workspace's prior status. A purged checkout is refused; use `restore_workspace`.
480
+ - `restore_workspace` checks or recreates the Kōbō-owned worktree using the existing recovery service, then unarchives the workspace. It preserves chat history, returns the final workspace with restoration outcome/source, and starts no agent or setup script. An already active, non-purged workspace is returned unchanged.
481
+
482
+ Restoration reuses the saved Git recovery information and retains the existing ownership/path/conflict checks. A checkout deleted manually without purge metadata may return `not-purged`; a workspace whose database record and history were permanently deleted cannot be restored by these tools.
483
+
484
+ Lifecycle errors preserve `{error, status, stage, details}`. No automatic mutation retries occur. A connection can close while an operation finishes, especially when an agent archives or deletes its own workspace; inspect current state before repeating a request.
485
+
486
+ ---
487
+
377
488
  #### `stop_workspace`
378
489
  Stop the currently running agent session on a workspace, like the Stop action in the chat header. Also disables auto-loop and cancels a queued replacement. Requires the backend to be running. Safe to call when nothing is running; an unconfirmed shutdown is reported as an error.
379
490
 
@@ -414,3 +525,36 @@ Automatic mode means the agent deliberately calls `remember` when it has stable,
414
525
  - **Notifications**: `mark_task_done` hits `POST /tasks/:id/notify-done`, while `create_task` / `update_task` / `delete_task` hit `POST /tasks/notify-updated`. Both cause the backend to emit a `task:updated` WS event so the Vue UI refreshes.
415
526
  - **Workspace scoping**: every handler that touches tasks uses `WHERE workspace_id = ?` to prevent cross-workspace access, even if the LLM passes a task_id from another workspace.
416
527
  - **Error handling**: the MCP dispatcher wraps every tool call in a `try/catch` and returns `{ isError: true, content: [{ type: 'text', text: 'Error: ...' }] }` on failure. Handlers should throw with descriptive messages.
528
+
529
+ ### Group messages
530
+
531
+ The UI, workspace-bound stdio, global stdio and external HTTP MCP share the same
532
+ backend delivery service. The MCP exposes three tools:
533
+
534
+ 1. `preview_workspace_group_message`: select optional `tags`, `statuses`, and
535
+ `dev_server_running` (boolean, default false). Set it to true to keep only
536
+ workspaces with development server status `running`.
537
+ Tags match any selected tag, statuses match any selected status, and all enabled
538
+ filters intersect. Empty filters match all non-archived, non-purged workspaces.
539
+ Results include `workspaceId`, `name`, `tags`, `status`, and `delivery`, sorted
540
+ by workspace ID. Use `limit` (1–200, default 200) and `offset` (default 0),
541
+ checking `total` to paginate. Preview is live, not a reservation.
542
+ 2. `send_workspace_group_message`: supply a unique `request_id`, an explicit
543
+ `workspace_ids` list (1–200 distinct IDs), and `content` (up to 100,000
544
+ characters). Filters and wildcards cannot substitute for selected IDs.
545
+ Workspace-bound agents cannot include their own workspace. Manual targets
546
+ receive immediately; auto-loop targets queue for their next iteration.
547
+ 3. `get_workspace_group_message`: inspect the durable receipt using `request_id`.
548
+ Submission returns promptly; `complete` and each recipient's `state` track
549
+ delivery separately (`pending`, `sending`, `sent`, `queued`, `rejected`,
550
+ `unknown`, or `not_sent`). `queued` confirms enqueueing, not execution.
551
+
552
+ Repeating the same request ID with the same content, recipients and MCP source
553
+ returns its receipt without sending again. Reusing that ID with different input
554
+ fails. Ineligible recipients fail individually without discarding other results.
555
+ After a server interruption, undelivered recipients become `not_sent` and
556
+ ambiguous dispatches become `unknown`; neither is automatically resent. Inspect
557
+ workspace history before issuing another request for an uncertain recipient.
558
+ MCP client name and HTTP/stdio provenance remain attached to delivered messages.
559
+ Read-only final-review and handoff launches expose preview and receipt lookup,
560
+ but cannot submit a group message.
@@ -5,7 +5,14 @@ import { CallToolRequestSchema, ListToolsRequestSchema, type Tool } from '@model
5
5
  import type Database from 'better-sqlite3'
6
6
  import { getDb } from '../server/db/index.js'
7
7
  import { EXTERNAL_MEMORY_TOOL_DEFINITIONS, isMemoryToolName, MEMORY_TOOL_DEFINITIONS } from '../shared/memory-tools.js'
8
+ import {
9
+ isWorkspaceCreationTool,
10
+ MAX_MCP_CREATION_REQUEST_BYTES,
11
+ WORKSPACE_CREATION_TOOLS,
12
+ } from '../shared/workspace-creation-tools.js'
8
13
  import { WORKSPACE_DIALOGUE_TOOLS } from '../shared/workspace-dialogue-tools.js'
14
+ import { isWorkspaceGroupMessageTool, WORKSPACE_GROUP_MESSAGE_TOOLS } from '../shared/workspace-group-message-tools.js'
15
+ import { isWorkspaceLifecycleTool, WORKSPACE_LIFECYCLE_TOOLS } from '../shared/workspace-lifecycle-tools.js'
9
16
  import {
10
17
  createTaskHandler,
11
18
  cronListHandler,
@@ -30,6 +37,8 @@ import {
30
37
  import { callMemoryTool, isMemoryOutputSuppressed } from './memory-client.js'
31
38
  import { callWorkspaceDialogueTool } from './workspace-dialogue-client.js'
32
39
 
40
+ const finalReviewToken = process.env.KOBO_FINAL_REVIEW_TOKEN
41
+ const finalReviewSessionId = process.env.KOBO_FINAL_REVIEW_SESSION_ID
33
42
  const handoffId = process.env.KOBO_HANDOFF_ID
34
43
  const handoffToken = process.env.KOBO_HANDOFF_TOKEN
35
44
  const workspaceId = process.env.KOBO_WORKSPACE_ID
@@ -184,6 +193,34 @@ const TASK_ORDER_PROPERTIES = {
184
193
  }
185
194
 
186
195
  const WORKSPACE_SCOPED_TOOLS: Tool[] = [
196
+ {
197
+ name: 'submit_final_review',
198
+ description:
199
+ 'Submit the final structured verdict for this backend-owned auto-loop review, then end your turn. Include all actionable findings; use an empty findings array only when the entire mission is clear.',
200
+ inputSchema: {
201
+ type: 'object',
202
+ properties: {
203
+ summary: { type: 'string', minLength: 1, maxLength: 12000 },
204
+ findings: {
205
+ type: 'array',
206
+ maxItems: 100,
207
+ items: {
208
+ type: 'object',
209
+ properties: {
210
+ severity: { type: 'string', enum: ['critical', 'important', 'minor'] },
211
+ file: { type: 'string', minLength: 1, maxLength: 1000 },
212
+ line: { type: 'integer', minimum: 1 },
213
+ description: { type: 'string', minLength: 1, maxLength: 4000 },
214
+ recommendation: { type: 'string', minLength: 1, maxLength: 4000 },
215
+ },
216
+ required: ['severity', 'file', 'description', 'recommendation'],
217
+ },
218
+ },
219
+ },
220
+ required: ['summary', 'findings'],
221
+ },
222
+ annotations: { destructiveHint: false, openWorldHint: false },
223
+ },
187
224
  {
188
225
  name: 'submit_session_handoff',
189
226
  description:
@@ -605,6 +642,9 @@ const WORKSPACE_SCOPED_TOOLS: Tool[] = [
605
642
 
606
643
  const GLOBAL_TOOLS: typeof WORKSPACE_SCOPED_TOOLS = [
607
644
  ...WORKSPACE_DIALOGUE_TOOLS,
645
+ ...WORKSPACE_CREATION_TOOLS,
646
+ ...WORKSPACE_LIFECYCLE_TOOLS,
647
+ ...WORKSPACE_GROUP_MESSAGE_TOOLS,
608
648
  ...EXTERNAL_MEMORY_TOOL_DEFINITIONS,
609
649
  {
610
650
  name: 'list_workspaces',
@@ -627,65 +667,6 @@ const GLOBAL_TOOLS: typeof WORKSPACE_SCOPED_TOOLS = [
627
667
  },
628
668
  annotations: { readOnlyHint: true, openWorldHint: false },
629
669
  },
630
- {
631
- name: 'create_workspace',
632
- description:
633
- 'Create a new Kōbō workspace (git worktree + agent session), like the "Créer" button on the Create page. Requires the Kōbō backend server to be running and reachable at KOBO_BACKEND_URL. name/project_path/source_branch/working_branch are mandatory — unlike the UI, this API does not auto-derive a branch name from the workspace name.',
634
- inputSchema: {
635
- type: 'object',
636
- properties: {
637
- name: { type: 'string', description: 'Workspace display name.' },
638
- project_path: { type: 'string', description: 'Absolute path to the project on disk.' },
639
- source_branch: { type: 'string', description: 'Branch to base the new worktree on (e.g. "develop").' },
640
- working_branch: {
641
- type: 'string',
642
- description: 'New branch name for the worktree (e.g. "feature/my-thing").',
643
- },
644
- model: { type: 'string', description: 'Model id override (optional, defaults to project/global setting).' },
645
- reasoning_effort: { type: 'string', description: 'Reasoning effort override (optional).' },
646
- engine: { type: 'string', description: 'Agent engine id, e.g. "claude-code" or "codex" (optional).' },
647
- description: { type: 'string', description: 'Task description / initial brainstorming prompt (optional).' },
648
- tags: {
649
- type: 'array',
650
- items: { type: 'string' },
651
- description: 'Workspace tags to assign at creation (optional).',
652
- },
653
- tasks: { type: 'array', items: { type: 'string' }, description: 'Initial manual task titles (optional).' },
654
- acceptance_criteria: {
655
- type: 'array',
656
- items: { type: 'string' },
657
- description: 'Initial manual acceptance criteria (optional).',
658
- },
659
- agent_permission_mode: {
660
- type: 'string',
661
- enum: ['plan', 'bypass', 'strict', 'interactive'],
662
- description: 'Permission mode for the new session (optional).',
663
- },
664
- auto_loop: { type: 'boolean', description: 'Start the workspace in auto-loop mode (optional).' },
665
- auto_loop_session_mode: {
666
- type: 'string',
667
- enum: ['per_task', 'continuous'],
668
- description: "Auto-loop session mode when auto_loop is true (optional, default 'per_task').",
669
- },
670
- skip_setup_script: { type: 'boolean', description: "Skip the project's setup script (optional)." },
671
- },
672
- required: ['name', 'project_path', 'source_branch', 'working_branch'],
673
- },
674
- annotations: { destructiveHint: false, openWorldHint: false },
675
- },
676
- {
677
- name: 'archive_workspace',
678
- description:
679
- 'Archive a workspace by id, like the "Archiver" action in the workspace context menu. Requires the Kōbō backend server to be running.',
680
- inputSchema: {
681
- type: 'object',
682
- properties: {
683
- workspace_id: { type: 'string', description: 'Workspace id, from list_workspaces.' },
684
- },
685
- required: ['workspace_id'],
686
- },
687
- annotations: { destructiveHint: false, openWorldHint: false },
688
- },
689
670
  {
690
671
  name: 'stop_workspace',
691
672
  description:
@@ -718,7 +699,9 @@ function availableTools(): Tool[] {
718
699
  return tools.filter((tool) =>
719
700
  handoffId && handoffToken
720
701
  ? tool.name === 'submit_session_handoff' || tool.annotations?.readOnlyHint === true
721
- : tool.name !== 'submit_session_handoff',
702
+ : finalReviewToken && finalReviewSessionId
703
+ ? tool.name === 'submit_final_review' || tool.annotations?.readOnlyHint === true
704
+ : tool.name !== 'submit_session_handoff' && tool.name !== 'submit_final_review',
722
705
  )
723
706
  }
724
707
  server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: availableTools() }))
@@ -757,6 +740,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
757
740
  }
758
741
 
759
742
  try {
743
+ if (finalReviewToken && finalReviewSessionId && !availableTools().some((tool) => tool.name === name))
744
+ return fail('This is a read-only final review. Only read tools and submit_final_review are available.')
745
+ if (name === 'submit_final_review') {
746
+ if (!workspaceId || !finalReviewToken || !finalReviewSessionId)
747
+ return fail('No final review is active for this MCP invocation.')
748
+ return ok(
749
+ await backendRequest('POST', `/api/workspaces/${workspaceId}/auto-loop/final-review/report`, {
750
+ token: finalReviewToken,
751
+ sessionId: finalReviewSessionId,
752
+ report: a,
753
+ }),
754
+ )
755
+ }
760
756
  if (handoffId && handoffToken && !availableTools().some((tool) => tool.name === name))
761
757
  return fail(
762
758
  'This turn is reserved for a handoff report. Only read tools and submit_session_handoff are available.',
@@ -800,8 +796,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
800
796
  : {}),
801
797
  }
802
798
  }
803
- if (WORKSPACE_DIALOGUE_TOOLS.some((tool) => tool.name === name)) {
804
- if (workspaceId && a.workspace_id === workspaceId && name === 'send_workspace_message')
799
+ if (
800
+ isWorkspaceCreationTool(name) ||
801
+ isWorkspaceLifecycleTool(name) ||
802
+ isWorkspaceGroupMessageTool(name) ||
803
+ WORKSPACE_DIALOGUE_TOOLS.some((tool) => tool.name === name)
804
+ ) {
805
+ if (
806
+ workspaceId &&
807
+ ((a.workspace_id === workspaceId && name === 'send_workspace_message') ||
808
+ (name === 'send_workspace_group_message' &&
809
+ Array.isArray(a.workspace_ids) &&
810
+ a.workspace_ids.includes(workspaceId)))
811
+ )
805
812
  return fail('Use the normal conversation to communicate in your own workspace.')
806
813
  return await callWorkspaceDialogueTool(
807
814
  backendUrl,
@@ -1098,49 +1105,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1098
1105
  return ok(listWorkspacesHandler(db, { includeArchived, tag }))
1099
1106
  }
1100
1107
 
1101
- if (name === 'create_workspace') {
1102
- const workspaceName = a.name as string | undefined
1103
- const projectPath = a.project_path as string | undefined
1104
- const sourceBranch = a.source_branch as string | undefined
1105
- const workingBranch = a.working_branch as string | undefined
1106
- if (!workspaceName || !projectPath || !sourceBranch || !workingBranch) {
1107
- return fail('name, project_path, source_branch, and working_branch parameters are required')
1108
- }
1109
- try {
1110
- const created = await backendRequest('POST', '/api/workspaces', {
1111
- name: workspaceName,
1112
- projectPath,
1113
- sourceBranch,
1114
- workingBranch,
1115
- model: a.model as string | undefined,
1116
- reasoningEffort: a.reasoning_effort as string | undefined,
1117
- engine: a.engine as string | undefined,
1118
- description: a.description as string | undefined,
1119
- tags: a.tags as string[] | undefined,
1120
- tasks: a.tasks as string[] | undefined,
1121
- acceptanceCriteria: a.acceptance_criteria as string[] | undefined,
1122
- agentPermissionMode: a.agent_permission_mode as string | undefined,
1123
- autoLoop: a.auto_loop as boolean | undefined,
1124
- autoLoopSessionMode: a.auto_loop_session_mode as string | undefined,
1125
- skipSetupScript: a.skip_setup_script as boolean | undefined,
1126
- })
1127
- return ok(created)
1128
- } catch (err) {
1129
- return fail(backendErrorMessage(err))
1130
- }
1131
- }
1132
-
1133
- if (name === 'archive_workspace') {
1134
- const targetId = a.workspace_id as string | undefined
1135
- if (!targetId) return fail('workspace_id parameter is required')
1136
- try {
1137
- const result = await backendRequest('POST', `/api/workspaces/${targetId}/archive`)
1138
- return ok(result)
1139
- } catch (err) {
1140
- return fail(backendErrorMessage(err))
1141
- }
1142
- }
1143
-
1144
1108
  if (name === 'stop_workspace') {
1145
1109
  const targetId = a.workspace_id as string | undefined
1146
1110
  if (!targetId) return fail('workspace_id parameter is required')
@@ -1158,7 +1122,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1158
1122
  }
1159
1123
  })
1160
1124
 
1161
- const transport = new StdioServerTransport()
1125
+ const transport = new StdioServerTransport(process.stdin, process.stdout, {
1126
+ // Include the JSON-lines delimiter in addition to the HTTP envelope budget.
1127
+ maxBufferSize: MAX_MCP_CREATION_REQUEST_BYTES + 1,
1128
+ })
1162
1129
  server.connect(transport).catch((err) => {
1163
1130
  console.error('[kobo-tasks-server] Fatal:', err)
1164
1131
  process.exit(1)
@@ -1,7 +1,13 @@
1
1
  import { Client } from '@modelcontextprotocol/sdk/client/index.js'
2
2
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
3
3
  import { isMemoryToolName, validateMemoryToolArguments } from '../shared/memory-tools.js'
4
+ import { isWorkspaceCreationTool, validateWorkspaceCreationArguments } from '../shared/workspace-creation-tools.js'
4
5
  import { validateDialogueArguments } from '../shared/workspace-dialogue-tools.js'
6
+ import {
7
+ isWorkspaceGroupMessageTool,
8
+ validateWorkspaceGroupMessageArguments,
9
+ } from '../shared/workspace-group-message-tools.js'
10
+ import { isWorkspaceLifecycleTool, validateWorkspaceLifecycleArguments } from '../shared/workspace-lifecycle-tools.js'
5
11
  import { normalizeClientName } from '../shared/workspace-message-types.js'
6
12
 
7
13
  /** Stateless HTTP bridge: external stdio clients never own agent runtime state. */
@@ -14,9 +20,18 @@ export async function callWorkspaceDialogueTool(
14
20
  ) {
15
21
  const arguments_ = isMemoryToolName(name)
16
22
  ? validateMemoryToolArguments(name, args, true)
17
- : validateDialogueArguments(name, args)
23
+ : isWorkspaceCreationTool(name)
24
+ ? validateWorkspaceCreationArguments(name, args)
25
+ : isWorkspaceLifecycleTool(name)
26
+ ? validateWorkspaceLifecycleArguments(name, args)
27
+ : isWorkspaceGroupMessageTool(name)
28
+ ? validateWorkspaceGroupMessageArguments(name, args)
29
+ : validateDialogueArguments(name, args)
18
30
  const client = new Client({ name: 'kobo-stdio-bridge', version: '1.0.0' })
19
- const signal = AbortSignal.timeout(30_000)
31
+ // Creation can include forge reads, worktree setup and user setup scripts.
32
+ // A timed-out creation is ambiguous and must never be retried automatically.
33
+ const timeout = isWorkspaceCreationTool(name) || isWorkspaceLifecycleTool(name) ? 15 * 60_000 : 30_000
34
+ const signal = AbortSignal.timeout(timeout)
20
35
  const transport = new StreamableHTTPClientTransport(new URL(`${backendUrl.replace(/\/$/, '')}/api/mcp`), {
21
36
  requestInit: {
22
37
  signal,
@@ -30,7 +45,7 @@ export async function callWorkspaceDialogueTool(
30
45
  })
31
46
  try {
32
47
  await client.connect(transport, { signal })
33
- return await client.callTool({ name, arguments: arguments_ }, undefined, { signal })
48
+ return await client.callTool({ name, arguments: arguments_ }, undefined, { signal, timeout })
34
49
  } finally {
35
50
  await client.close()
36
51
  }
@@ -1,2 +0,0 @@
1
- import{M as e,S as t,dt as n,lt as r,p as i,pt as a,v as o}from"./reactivity.esm-bundler-DY1JkAEA.js";import{C as s,F as c,J as l,L as u,N as d,O as f,P as p,T as m,Y as h,_ as g,d as _,f as v,g as y,j as ee,k as b,l as x,p as S,r as C,u as w,y as T,z as te}from"./runtime-core.esm-bundler-Bqg71Vll.js";import{$ as ne,K as re,M as E,f as ie,z as ae}from"./dom-BtYTvLeN.js";import{l as oe,t as D,u as se}from"./QBtn-BUSdPaf4.js";import{Bt as ce,O as le,Qt as ue,Ut as de,Yt as fe,an as O,at as pe,cn as k,ft as me,j as he,m as ge,mt as _e,ot as ve,pt as ye,v as be}from"./index-ImUH7Bbt.js";import{t as xe}from"./QSpinnerDots-xdWyaE1i.js";import{t as Se}from"./QList-Cx1UfaUm.js";import{t as Ce}from"./QMarkupTable-UhP7fVUp.js";import{c as A,l as j,s as M}from"./QSelect-MdPXvwm4.js";import{t as N}from"./QItemSection-MdJ1_ZK7.js";import{t as we}from"./QExpansionItem-D33aoRoG.js";import{a as P,c as Te,r as F,t as I}from"./ToolCallItem-cSHh3p_F.js";import{t as Ee}from"./QScrollArea-Di-D6t2u.js";import{a as L}from"./use-tours-6yxNVmjH.js";import{f as De,g as R,m as Oe,p as ke,u as Ae}from"./use-worktree-restore-BM3Giig1.js";import{t as z}from"./QTooltip-BhwG7lp2.js";import{n as B}from"./render-chat-markdown-CafzCcmu.js";import{n as V}from"./WorkspacePage-BwUkijpS.js";function je(e,t){return s(`div`,e,[s(`table`,{class:`q-table`},t)])}var Me={list:Se,table:Ce},H=[`list`,`table`,`__qtable`],Ne=ae({name:`QVirtualScroll`,props:{...j,type:{type:String,default:`list`,validator:e=>H.includes(e)},items:{type:Array,default:()=>[]},itemsFn:Function,itemsSize:Number,scrollTarget:k},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}=A({virtualScrollLength:o,getVirtualScrollTarget:y,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,()=>{C(),S()});function v(){let e=a.value;return e===null?null:e.$el||e}function y(){return i}function S(){let t=v();t!==null&&(i=O(t,e.scrollTarget),i.addEventListener(`scroll`,m,E.passive))}function C(){i!==void 0&&(i.removeEventListener(`scroll`,m,E.passive),i=void 0)}function w(){let t=f(e.type===`list`?`div`:`tbody`,h.value.map(n.default));return n.before!==void 0&&(t=n.before().concat(t)),ie(n.after,t)}return u(),p(()=>{S()}),b(()=>{S()}),d(()=>{C()}),ee(()=>{C()}),()=>{if(n.default===void 0){console.error(`QVirtualScroll: default scoped slot is required for rendering`);return}return e.type===`__qtable`?je({ref:a,class:`q-table__middle `+g.value,tabindex:0},w()):s(Me[e.type],{...r,ref:a,class:[r.class,g.value],..._.value},w)}}}),Pe=new Set([`setup`,`cleanup`,`archive`]);function Fe(e){switch(e.type){case`user`:return e.sender===`system-prompt`?`system-prompt`:Pe.has(e.sender)||le(e.sender)?`script`:`user`;case`session`:return`session`;default:return`agent`}}function Ie(e){let t=[],n=null,r=null;for(let i of e){let e=Fe(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 U(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 Le(e){let t=e.items[0];return`${e.speaker}:${t?U(t):`empty`}`}function Re(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 ze(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 Be={class:`agent-error-item`},Ve={class:`agent-error-item__body`},He={class:`agent-error-item__title`},W={class:`agent-error-item__message`},Ue=L(T({__name:`AgentErrorItem`,props:{item:{}},setup(e){let t=e,{te:n}=fe(),r=x(()=>{let e=`agent.error.${t.item.category}`;return n(e)?e:`agent.error.other`});return(t,n)=>(u(),S(`div`,Be,[g(oe,{name:`error_outline`,size:`16px`,class:`agent-error-item__icon`}),w(`div`,Ve,[w(`div`,He,a(t.$t(r.value)),1),w(`div`,W,a(e.item.message),1)])]))}}),[[`__scopeId`,`data-v-e98c3c02`]]),We={class:`text-caption text-kobo-3`},Ge=T({__name:`SessionEventItem`,props:{item:{}},setup(e){let t=e,n=x(()=>{switch(t.item.kind){case`started`:return`session.started`;case`ended`:return R(t.item.detail);case`compacted`:return`session.compacted`;default:return`session.started`}});return(e,t)=>(u(),S(`span`,We,a(e.$t(n.value)),1))}}),G=new Map;function K(){for(let e of G.values())e.controller.abort(),e.blobUrl&&URL.revokeObjectURL(e.blobUrl);G.clear()}typeof window<`u`&&(window.addEventListener(`kobo:auth-token-changed`,K),window.addEventListener(`storage`,e=>{(e.key===`kobo:network-token`||e.key===null)&&K()}));function q(e){if(!/^\/api\/workspaces\/[^/]+\/images\/file\?path=/.test(e))throw Error(`Invalid workspace image URL`);let t=G.get(e);if(!t){let n=new AbortController,r={references:0,controller:n,ready:Promise.resolve(``)};r.ready=ce(e,{signal:n.signal}).then(async e=>{if(!e.ok)throw await de(e);let t=await e.blob();if(n.signal.aborted)throw new DOMException(`Aborted`,`AbortError`);return r.blobUrl=URL.createObjectURL(t),r.blobUrl}),t=r,G.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),G.get(e)===r&&G.delete(e)))}}}function Ke(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=q(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 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 Je=[`innerHTML`],Ye=[`innerHTML`],Xe=[`src`],J=L(T({__name:`UserMessageItem`,props:{item:{},workspaceId:{}},setup(t){let n=t,{t:r}=fe(),a=x(()=>n.item.sender===`system-prompt`),s=Ke(x(()=>{let e=qe(n.item.content,n.workspaceId);return B(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 m=e=>{(e.key===`kobo:network-token`||e.key===null)&&p()};window.addEventListener(`storage`,m),window.addEventListener(`kobo:auth-token-changed`,p),i(()=>{p(),window.removeEventListener(`kobo:auth-token-changed`,p),window.removeEventListener(`storage`,m)});function y(e){let t=e.target;if(t?.tagName!==`IMG`)return;let n=t;n.src&&(p(),n.dataset.koboImageUrl&&(f=q(n.dataset.koboImageUrl),f.ready.catch(p)),c.value=n.src,d.value=!0)}return(t,n)=>(u(),S(C,null,[a.value?(u(),_(we,{key:0,dense:``,"dense-toggle":``,label:t.$t(`chat.systemPrompt`),"header-class":`text-kobo-2 text-caption`},{default:h(()=>[w(`div`,{class:`q-py-xs markdown-user-prompt`,innerHTML:e(s)},null,8,Je)]),_:1},8,[`label`])):(u(),S(`div`,{key:1,class:`markdown-message`,onClick:y},[w(`div`,{innerHTML:e(s)},null,8,Ye)])),g(ue,{modelValue:d.value,"onUpdate:modelValue":n[1]||=e=>d.value=e},{default:h(()=>[c.value?(u(),S(`img`,{key:0,src:c.value,alt:``,class:`image-lightbox-img`,onClick:n[0]||=e=>d.value=!1},null,8,Xe)):v(``,!0)]),_:1},8,[`modelValue`])],64))}}),[[`__scopeId`,`data-v-db2fc622`]]),Ze=[`data-event-ids`],Y={class:`turn-header`},Qe={key:0,class:`turn-time`},$e={class:`turn-time turn-time-updated`},X={key:2,class:`turn-actions`},et={class:`turn-body`},tt={class:`ellipsis`},nt={class:`q-px-sm q-pb-sm`},rt={key:0,class:`turn-scroll-top`},it=L(T({__name:`TurnCard`,props:{turn:{},workspaceId:{},highlighted:{type:Boolean}},emits:[`scrollTo`],setup(t,{emit:i}){let s=t,c=i,{t:l}=fe(),d=pe(),f=m(P),p=o(null);function ee(){let e=p.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 b=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:le(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 T(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 re=x(()=>T(s.turn.ts)),E=x(()=>s.turn.items.flatMap(e=>e.eventIds??[]).join(` `)),ie=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}),ae=x(()=>{let e=s.turn.ts,t=ie.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?``:T(t,r-n<6e4)}),se=x(()=>ae.value!==``);function ce(e){return Te(e.origin,Object.values(d.subagents[s.workspaceId]??{}),e.type===`tool`?e.toolCallId:void 0)}function ue(e){return e||l(`chat.subagentActivity`)}function de(e){return(e.type===`text`||e.type===`thinking`||e.type===`tool`)&&e.origin?.kind===`subagent`}let O=x(()=>{let e=[],t=new Map;for(let n of s.turn.items){if(!de(n)){e.push({type:`item`,key:U(n),item:n});continue}let r=ce(n),i=r?.toolUseId??n.origin?.toolCallId??n.origin?.threadId??U(n),a=t.get(i);if(a){a.items.push(n);continue}let o={type:`subagent`,key:`subagent:${U(n)}`,items:[n],subagentKey:i,subagentName:r?.description||null};t.set(i,o),e.push(o)}return e});function k(e){e.origin&&f?.({origin:e.origin})}let me=x(()=>s.turn.items.filter(e=>e.type===`tool`).length);return(i,o)=>(u(),S(`div`,{ref_key:`cardEl`,ref:p,class:r([`turn-card`,{"turn-card--user":t.turn.speaker===`user`,"turn-card--highlighted":t.highlighted}]),"data-event-ids":E.value,style:n({"--turn-accent":b.value.accent})},[w(`div`,Y,[w(`span`,{class:r([`turn-badge`,b.value.badgeClass])},a(b.value.label),3),re.value?(u(),S(`span`,Qe,a(re.value),1)):v(``,!0),se.value?(u(),S(C,{key:1},[g(oe,{name:`arrow_forward`,size:`10px`,color:`kobo-3`,class:`turn-time-arrow`}),w(`span`,$e,[y(a(ae.value)+` `,1),g(z,null,{default:h(()=>[y(a(e(l)(`chat.lastUpdatedAt`,{time:ae.value})),1)]),_:1})])],64)):v(``,!0),me.value>0?(u(),S(`span`,X,` · `+a(e(l)(`chat.nActions`,{n:me.value})),1)):v(``,!0)]),w(`div`,et,[(u(!0),S(C,null,te(O.value,n=>(u(),S(C,{key:n.key},[n.type===`subagent`?(u(),_(we,{key:0,class:`turn-subagent-activity`,dense:``,label:ue(n.subagentName),caption:e(l)(`chat.nActions`,{n:n.items.length}),"default-opened":t.highlighted&&n.items.some(e=>e.eventIds?.some(e=>E.value.includes(e)))},{header:h(()=>[g(N,null,{default:h(()=>[g(M,{class:`row items-center no-wrap`,title:n.subagentName??void 0},{default:h(()=>[g(oe,{name:`hub`,size:`16px`,class:`q-mr-xs`},{default:h(()=>[g(z,null,{default:h(()=>[y(a(e(l)(`chat.subagentActivity`)),1)]),_:1})]),_:1}),w(`span`,tt,a(ue(n.subagentName)),1)]),_:2},1032,[`title`]),g(M,{caption:``},{default:h(()=>[y(a(e(l)(`chat.nActions`,{n:n.items.length})),1)]),_:2},1024)]),_:2},1024),g(N,{side:``},{default:h(()=>[g(D,{flat:``,round:``,dense:``,size:`sm`,icon:`open_in_new`,"aria-label":e(l)(`chat.openSubagentActivity`),onClick:ne(e=>k(n.items[0]),[`stop`])},{default:h(()=>[g(z,null,{default:h(()=>[y(a(e(l)(`chat.openSubagentActivity`)),1)]),_:1})]),_:1},8,[`aria-label`,`onClick`])]),_:2},1024)]),default:h(()=>[w(`div`,nt,[(u(!0),S(C,null,te(n.items,t=>(u(),S(C,{key:e(U)(t)},[t.type===`text`?(u(),_(F,{key:0,item:t},null,8,[`item`])):t.type===`thinking`?(u(),_(V,{key:1,item:t},null,8,[`item`])):t.type===`tool`?(u(),_(I,{key:2,item:t},null,8,[`item`])):v(``,!0)],64))),128))])]),_:2},1032,[`label`,`caption`,`default-opened`])):(u(),S(C,{key:1},[n.item.type===`text`?(u(),_(F,{key:0,item:n.item},null,8,[`item`])):n.item.type===`thinking`?(u(),_(V,{key:1,item:n.item},null,8,[`item`])):n.item.type===`tool`?(u(),_(I,{key:2,item:n.item},null,8,[`item`])):n.item.type===`user`?(u(),_(J,{key:3,item:n.item,"workspace-id":t.workspaceId},null,8,[`item`,`workspace-id`])):n.item.type===`session`?(u(),_(Ge,{key:4,item:n.item},null,8,[`item`])):n.item.type===`error`?(u(),_(Ue,{key:5,item:n.item},null,8,[`item`])):v(``,!0)],64))],64))),128))]),t.turn.items.length>4?(u(),S(`div`,rt,[g(D,{flat:``,round:``,dense:``,size:`xs`,icon:`arrow_upward`,color:`kobo-3`,class:`turn-scroll-top-btn`,onClick:ee},{default:h(()=>[g(z,null,{default:h(()=>[y(a(e(l)(`chat.scrollToTurnTop`)),1)]),_:1})]),_:1})])):v(``,!0)],14,Ze))}}),[[`__scopeId`,`data-v-13bebb3d`]]),at={key:0,class:`activity-feed-switching`},ot={key:1,class:`activity-feed-wrap`,"data-tour":`ws-chat`},st={key:0,role:`alert`,class:`q-pa-sm text-negative`},ct={key:1,"data-testid":`history-load-error`,role:`alert`,class:`q-pa-sm text-negative`},lt=[`aria-label`],ut={key:0,class:`activity-feed-compacting`},dt={class:`text-caption text-kobo-2`},ft={key:0,class:`text-center q-py-sm text-caption text-kobo-3`},pt={key:1,class:`activity-feed-empty q-pa-md`},mt={class:`activity-feed-empty__title`},ht={class:`activity-feed-empty__hint`},gt={key:2,class:`q-px-md q-pb-md`},_t=[`title`],vt={class:`ellipsis`},yt={class:`activity-feed-nav-cluster`},bt=300,xt=60,St=200,Ct=180,wt=255,Tt=3,Et=60,Dt=L(T({__name:`ActivityFeed`,props:{workspaceId:{}},setup(t){let{t:n}=fe(),r=t,i=_e(),s=he(),d=pe(),m=ge(),ee=x(()=>m.isCompacting(r.workspaceId)),b=x(()=>d.selectedSessionId),T=x(()=>d.sessions.find(e=>e.id===b.value)?.engineSessionId??null),ne=x(()=>{let e=d.sessions;return e.length!==0&&b.value===e[e.length-1].id});function E(e){return b.value?e?e===b.value||e===T.value:ne.value:!0}let ie=x(()=>(d.activityFeeds[r.workspaceId]??[]).filter(e=>e.type===`text`&&typeof e.content==`string`&&E(e.sessionId)).map(e=>({content:e.content,sender:e.meta?.sender??`user`,source:be(e.meta?.source),ts:e.timestamp,sessionId:e.sessionId,eventIds:[e.id]}))),ae=x(()=>{let e=d.workspaces.find(e=>e.id===r.workspaceId);return ve(e?.status)}),le=Ae(),ue=``,O=x(()=>{let e=i.eventsFor(r.workspaceId),t=i.timestampsFor(r.workspaceId),n=i.sessionIdsFor(r.workspaceId),a=i.eventIdsFor(r.workspaceId),o=[],c=[],l=[];for(let r=0;r<e.length;r++)E(n[r])&&(o.push(e[r]),c.push(t[r]),l.push(a[r]??null));let u=`${r.workspaceId}::${b.value??`*`}`;u!==ue&&(le=Ae(),ue=u);let d=De(le,o,c,ae.value,l);return Ie(Oe(d,ie.value).filter(e=>e.type===`thinking`?!1:e.type===`session`?e.kind===`ended`?s.showVerboseSystemMessages||!ke(e.detail):s.showVerboseSystemMessages:!0))}),k=x(()=>s.showVerboseSystemMessages?i.eventsFor(r.workspaceId).filter(e=>e.kind===`message:raw`).map(e=>e.content):[]),Se=x(()=>{for(let e=O.value.length-1;e>=0;e--){let t=O.value[e]?.items??[];for(let e=t.length-1;e>=0;e--){let r=t[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?n(`activity.a11y.toolFailed`,{name:r.name}):n(`activity.a11y.toolFinished`,{name:r.name})}:{key:`tool:${r.toolCallId}:start`,text:n(`activity.a11y.toolStarted`,{name:r.name})}}}}return null}),Ce=o(``);l(Se,(e,t)=>{if(!e||e.key===t?.key)return;let r=e.key.startsWith(`text:`)?n(`activity.a11y.agentSaid`,{text:e.text.slice(0,bt)}):e.text;Ce.value=r});let A=o(null),j=o(!0),M=o(!1),N=o(``),P=me(),Te=me(),F=me(),I=o(null),L=!1,R=!0,z=null,B=o(new Set),V=new Map,je=x(()=>{if(O.value.length>0||k.value.length>0)return!1;let e=r.workspaceId,t=b.value;return!!d.loadingSessions[e]||m.isSyncPending(e)||t!==null&&B.value.has(U(e,t))}),Me=o(new Map),H=x(()=>O.value.length>0||k.value.length>0),Pe=x(()=>!!d.loadingSessions[r.workspaceId]||m.isSyncPending(r.workspaceId)||b.value!==null&&B.value.has(U(r.workspaceId,b.value)));function Fe(e){tt();let t=z;if(z=e.verticalSize,t!==null&&e.verticalSize>t&&j.value&&!M.value){K(0);return}let n=e.verticalSize-e.verticalPosition-e.verticalContainerSize;j.value=n<=xt,L&&e.verticalPosition<=St&&!M.value&&!N.value&&Be()&&Ue()}function U(e,t){return`${e}:${t}`}function Be(){let e=b.value;return e?Me.value.get(U(r.workspaceId,e))??!0:i.hasMoreOlderFor(r.workspaceId)}function Ve(e,t,n){Me.value.set(U(e,t),n)}function He(e){if(!b.value)return i.oldestIdFor(e);let t=i.eventIdsFor(e),n=i.sessionIdsFor(e);for(let e=0;e<t.length;e++){if(!E(n[e]))continue;let r=t[e];if(r)return r}}let W=null;function Ue(){if(W)return W;let e=G().finally(()=>{W===e&&(W=null)});return W=e,W}function We(){P.abort(),Te.abort(),F.abort(),M.value=!1,W=null;for(let e of B.value)Ft.delete(e);V.clear(),B.value.clear(),N.value=``}function Ge(){N.value=``,Nt.value===0&&b.value?It():Ue()}l(()=>[r.workspaceId,b.value],We,{flush:`sync`});async function G(){let e=r.workspaceId,t=b.value,n=He(e);if(!n)return;let a=P.begin();N.value=``,M.value=!0;try{let r=A.value;if(await f(),!P.isCurrent(a))return;let o=r?.getScroll().verticalSize??0,s=r?.getScroll().verticalPosition??0,c=new URLSearchParams({before:n,limit:`200`});t&&c.set(`session`,t);let l=await ce(`/api/workspaces/${e}/events?${c.toString()}`,{signal:a});if(!l.ok)throw await de(l);let u=await l.json();if(!P.isCurrent(a))return;let p=u.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),v=m.map(e=>e.sessionId??null),y=m.map(e=>e.id),ee=p.length>0?p[0].id:n;t&&Ve(e,t,u.hasMore),i.prepend(e,g,_,{oldestId:ee,hasMoreOlder:t?i.hasMoreOlderFor(e):u.hasMore,sessionIds:v,eventIds:y});for(let t of h){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:be(n.source)}})}if(await f(),r&&P.isCurrent(a)){let e=r.getScroll().verticalSize-o;if(e>0){let t=Math.max(s+e,250);r.setScrollPosition(`vertical`,t,0)}}}catch(e){if(ye(e)||!P.isCurrent(a))return;N.value=e instanceof Error?e.message:String(e),console.error(`[ActivityFeed] failed to load older events:`,e)}finally{P.isCurrent(a)&&(M.value=!1)}}async function K(e=0){let t=$();if(await f(),t!==$())return;let n=A.value;if(!n)return;let r=n.getScroll();n.setScrollPosition(`vertical`,r.verticalSize,e)}let q=null,Ke=0;function qe(){if(q!=null)return;let e=performance.now()-Ke<Ct,t=$();q=requestAnimationFrame(()=>{q=null,t===$()&&(Ke=performance.now(),K(e?0:180))})}function Je(e){Q(),j.value=!1;let t=A.value;t&&t.setScrollPosition(`vertical`,Math.max(0,e),250)}function Ye(){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 Xe(e){let t=e.detail;if(t?.workspaceId!==r.workspaceId||!t.eventId)return;Q(),j.value=!1;let n=F.begin(),a=r.workspaceId;try{let e=new URLSearchParams({around:t.eventId,limit:`200`});t.sessionId&&e.set(`session`,t.sessionId);let o=await ce(`/api/workspaces/${a}/events?${e}`,{signal:n});if(!o.ok)throw await de(o);let s=await o.json();if(!F.isCurrent(n))return;let c=s.events??[],l=c.filter(e=>e.type===`agent:event`&&e.workspaceId===r.workspaceId),u=c.filter(e=>e.type===`user:message`&&e.workspaceId===r.workspaceId);i.reset(r.workspaceId,l.map(e=>e.payload),l.map(e=>e.createdAt),{oldestId:c[0]?.id,hasMoreOlder:s.hasMore??!1,sessionIds:l.map(e=>e.sessionId),eventIds:l.map(e=>e.id)});for(let e of u){let t=e.payload;typeof t.content==`string`&&d.addActivityItem(r.workspaceId,{id:e.id,type:`text`,content:t.content,timestamp:e.createdAt,sessionId:e.sessionId??void 0,meta:{sender:t.sender??`user`,source:be(t.source)}})}if(await ze(je,e=>!e,5e3),await f(),await new Promise(e=>requestAnimationFrame(()=>requestAnimationFrame(()=>e()))),!F.isCurrent(n))return;let p=O.value.findIndex(e=>e.items.some(e=>e.eventIds?.includes(t.eventId)??!1));if(p<0)return;I.value=t.eventId,J.value?.scrollTo(p,`center`),Y.value=p,window.setTimeout(()=>{I.value===t.eventId&&(I.value=null)},1800)}catch(e){if(ye(e)||!F.isCurrent(n))return;N.value=e instanceof Error?e.message:String(e),console.error(`[ActivityFeed] failed to focus history event:`,e)}}let J=o(null),Ze=o(null),Y=o(0),Qe=o(!1),$e=x(()=>{for(let e=O.value.length-1;e>=0;e--)if(O.value[e]?.speaker===`user`)return e;return-1}),X=null;function et(){let e=$e.value,t=A.value?.getScrollTarget(),n=t?.querySelector(`[data-turn-index="${e}"]`);if(!n||!t){Qe.value=!1;return}let r=t.getBoundingClientRect(),i=n.getBoundingClientRect();Qe.value=i.bottom>r.top&&i.top<r.bottom}function tt(){X===null&&(X=requestAnimationFrame(()=>{X=null,et()}))}function nt(e){return e.length<=wt?e:`${e.slice(0,252)}...`}let rt=x(()=>{let e=$e.value,t=O.value[e];if(!t||Y.value<=e||Qe.value)return null;let n=t.items.filter(e=>e.type===`user`).map(e=>e.content).join(`
2
- `).trim();return n?{index:e,content:nt(n)}:null});function Dt(e){Y.value=e.index,tt()}function Ot(){let e=rt.value;e&&(Q(),j.value=!1,J.value?.scrollTo(e.index,`start`),Y.value=e.index)}let kt=o(!1);async function At(){if(kt.value)return;Q(),j.value=!1,kt.value=!0;let e=r.workspaceId;try{let t=Re(O.value,Y.value);if(t<0)for(let n=0;n<15;n++){if(e!==r.workspaceId)return;if(!Be())break;let n=O.value.length;if(await Ue(),await f(),e!==r.workspaceId)return;if(Y.value+=O.value.length-n,t=Re(O.value,Y.value),t>=0)break}t>=0&&e===r.workspaceId&&(J.value?.scrollTo(t,`start`),Y.value=t)}finally{kt.value=!1}}let Z=0;function jt(){return new Promise(e=>requestAnimationFrame(()=>e()))}async function Mt(){if(!R||!H.value||je.value)return;L=!1;let e=++Z;if(await f(),e!==Z||!R||(Ze.value=A.value?.getScrollTarget()??null,await f(),e!==Z||!R))return;O.value.length>0&&J.value?.scrollTo(O.value.length-1,`end-force`),await jt();let t=-1,n=0;for(let r=0;r<Et&&n<Tt;r++){if(e!==Z||!R)return;await K(0),await jt();let r=A.value?.getScroll().verticalSize??0;z=r,n=r===t?n+1:0,t=r}e===Z&&!Pe.value&&(L=!0,R=!1)}function Q(){R=!1,!L&&(Z++,L=!0)}let Nt=x(()=>{let e=i.sessionIdsFor(r.workspaceId);if(!b.value)return e.length;let t=0;for(let n of e)E(n)&&t++;return t});l(je,async e=>{!e&&H.value&&await Mt(),e||f(()=>{Ze.value=A.value?.getScrollTarget()??null,tt()})}),l(Pe,(e,t)=>{!e&&t&&R&&H.value&&Mt()}),p(()=>{window.addEventListener(`kobo:focus-history-event`,Xe),f(()=>{Ze.value=A.value?.getScrollTarget()??null,tt()}),H.value&&Mt(),b.value&&It();let e=new URLSearchParams(window.location.hash.split(`?`)[1]??``).get(`eventId`);e&&(Xe(new CustomEvent(`kobo:focus-history-event`,{detail:{workspaceId:r.workspaceId,sessionId:b.value,eventId:e}})),Ye())}),l([Nt,()=>ie.value.length,()=>k.value.length],async([e,t,n])=>{M.value||R&&e+t+n>0&&await Mt()}),l([$e,()=>O.value.length],()=>{f(tt)});let Pt=x(()=>i.liveAppendCountFor(r.workspaceId,E)),$=()=>`${r.workspaceId}\u0000${d.selectedSessionId??``}`;l([Pt,$],([e,t],[n,r])=>{t===r&&e>n&&j.value&&!M.value&&qe()}),c(()=>{Z++,R=!1,We(),window.removeEventListener(`kobo:focus-history-event`,Xe),q!=null&&(cancelAnimationFrame(q),q=null),X!==null&&cancelAnimationFrame(X)}),l([()=>r.workspaceId,()=>d.selectedSessionId],()=>{Z++,R=!0,z=null,j.value=!0,L=!1,It(),H.value&&Mt()});let Ft=new Set;async function It(){let e=r.workspaceId,t=b.value;if(!t||Nt.value>0)return;let n=U(e,t);if(Ft.has(n))return;Ft.add(n),B.value.add(n);let a=Te.begin();V.set(n,a),N.value=``;try{let n=await ce(`/api/workspaces/${e}/events?session=${encodeURIComponent(t)}&limit=500`,{signal:a});if(!n.ok)throw await de(n);let r=await n.json();if(!Te.isCurrent(a))return;let o=r.events??[];if(o.length===0)return;let s=o.filter(t=>t.type===`agent:event`&&t.workspaceId===e),c=o.filter(t=>t.type===`user:message`&&t.workspaceId===e),l=s.map(e=>e.payload),u=s.map(e=>e.createdAt),f=s.map(e=>e.sessionId??null),p=s.map(e=>e.id);Ve(e,t,r.hasMore),l.length>0&&i.prepend(e,l,u,{oldestId:o[0].id,hasMoreOlder:i.hasMoreOlderFor(e),sessionIds:f,eventIds:p});for(let t of c){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:be(n.source)}})}}catch(e){if(ye(e)||!Te.isCurrent(a)){(!V.has(n)||V.get(n)===a)&&Ft.delete(n);return}N.value=e instanceof Error?e.message:String(e),console.error(`[ActivityFeed] fetchSessionIfMissing failed:`,e),Ft.delete(n)}finally{V.get(n)===a&&(V.delete(n),B.value.delete(n))}}let Lt=x(()=>ie.value.filter(e=>e.sender===`user`).length),Rt=x(()=>(d.activityFeeds[r.workspaceId]??[]).filter(e=>e.meta?.sender===`user`&&e.meta.pending&&E(e.sessionId)).length);l([Lt,Rt,$],async([e,t,n],[r,i,a])=>{let o=t>i;n===a&&(o||e>r&&j.value&&!M.value&&!Pe.value)&&(j.value=!0,await K(o?180:0))});async function zt(){j.value=!0,await K(250)}return(t,n)=>je.value?(u(),S(`div`,at,[g(xe,{size:`40px`,color:`primary`})])):(u(),S(`div`,ot,[e(d).sessionsErrors[r.workspaceId]?(u(),S(`div`,st,[y(a(t.$t(`activity.sessionsError`,{message:e(d).sessionsErrors[r.workspaceId]}))+` `,1),g(D,{flat:``,dense:``,label:t.$t(`common.retry`),onClick:n[0]||=t=>e(d).fetchSessions(r.workspaceId)},null,8,[`label`])])):v(``,!0),N.value?(u(),S(`div`,ct,[y(a(t.$t(`activity.historyError`,{message:N.value}))+` `,1),g(D,{flat:``,dense:``,label:t.$t(`common.retry`),onClick:Ge},null,8,[`label`])])):v(``,!0),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`)},a(Ce.value),9,lt),g(re,{name:`fade`},{default:h(()=>[ee.value?(u(),S(`div`,ut,[g(xe,{size:`18px`,color:`primary`}),w(`span`,dt,a(t.$t(`activity.compacting`)),1)])):v(``,!0)]),_:1}),g(Ee,{ref_key:`scrollRef`,ref:A,class:`activity-feed-scroll`,onScroll:Fe,onWheelPassive:Q,onTouchstartPassive:Q,onKeydown:Q},{default:h(()=>[M.value?(u(),S(`div`,ft,[g(se,{size:`sm`}),y(` `+a(t.$t(`activity.loading_older`)),1)])):v(``,!0),g(Ne,{ref_key:`virtualScrollRef`,ref:J,class:`q-pa-md`,items:O.value,"scroll-target":Ze.value??void 0,"virtual-scroll-item-size":160,"virtual-scroll-slice-size":20,onVirtualScroll:Dt},{default:h(({item:t,index:n})=>[(u(),_(it,{key:e(Le)(t),turn:t,"workspace-id":r.workspaceId,"data-turn-index":n,highlighted:t.items.some(e=>e.eventIds?.includes(I.value??``)??!1),onScrollTo:Je},null,8,[`turn`,`workspace-id`,`data-turn-index`,`highlighted`]))]),_:1},8,[`items`,`scroll-target`]),O.value.length===0&&k.value.length===0&&!M.value?(u(),S(`div`,pt,[g(oe,{name:`forum`,size:`28px`}),w(`div`,mt,a(t.$t(`activity.empty`)),1),w(`div`,ht,a(t.$t(`activity.emptyHint`)),1)])):v(``,!0),k.value.length?(u(),S(`div`,gt,[g(we,{label:t.$t(`activity.raw_lines`,{n:k.value.length}),dense:``},{default:h(()=>[(u(!0),S(C,null,te(k.value,(e,t)=>(u(),S(`div`,{key:t,class:`text-caption text-kobo-3 q-pa-xs`},a(e),1))),128))]),_:1},8,[`label`])])):v(``,!0)]),_:1},512),rt.value?(u(),S(`button`,{key:2,type:`button`,class:`activity-feed-last-user`,"data-testid":`latest-user-message`,title:rt.value.content,onClick:Ot},[g(oe,{name:`person`,size:`16px`}),w(`span`,vt,a(rt.value.content),1)],8,_t)):v(``,!0),w(`div`,yt,[j.value?v(``,!0):(u(),_(D,{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:zt},null,8,[`title`,`aria-label`])),g(D,{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:kt.value,disable:kt.value,onClick:At},null,8,[`title`,`aria-label`,`loading`,`disable`])])]))}}),[[`__scopeId`,`data-v-58324159`]]);export{Dt as default};