@loicngr/kobo 2.1.15 → 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.
- package/AGENTS.md +9 -0
- package/CHANGELOG.md +4 -0
- package/CONFIGURATION.md +13 -1
- package/README.md +3 -1
- package/dist/mcp-server/kobo-tasks-server.js +19 -104
- package/dist/mcp-server/workspace-dialogue-client.js +15 -3
- package/dist/server/db/migrations.js +2 -0
- package/dist/server/db/schema.js +2 -0
- package/dist/server/db/workspace-message-batch-schema.js +9 -0
- package/dist/server/index.js +6 -0
- package/dist/server/routes/mcp.js +69 -3
- package/dist/server/routes/workspace-messages.js +22 -0
- package/dist/server/routes/workspaces.js +58 -3
- package/dist/server/services/agent/orchestrator.js +1 -2
- package/dist/server/services/agent/session-controller.js +4 -1
- package/dist/server/services/pr-checkout-rollback-service.js +51 -0
- package/dist/server/services/pr-checkout-service.js +21 -1
- package/dist/server/services/workspace-creation-mcp-service.js +198 -0
- package/dist/server/services/workspace-group-message-mcp-service.js +58 -0
- package/dist/server/services/workspace-group-message-service.js +182 -0
- package/dist/server/services/workspace-lifecycle-mcp-service.js +72 -0
- package/dist/server/services/workspace-message-service.js +5 -0
- package/dist/shared/pr-checkout-steps.js +86 -0
- package/dist/shared/workspace-creation-tools.js +182 -0
- package/dist/shared/workspace-group-message-tools.js +134 -0
- package/dist/shared/workspace-group-messages.js +27 -0
- package/dist/shared/workspace-lifecycle-tools.js +112 -0
- package/package.json +1 -1
- package/src/client/dist/pwa/assets/ActivityFeed-tJmJhmtl.js +2 -0
- package/src/client/dist/pwa/assets/{AutoLoopFinalReviewSettings-lKjfaC6V.js → AutoLoopFinalReviewSettings-Bbrq02Xt.js} +1 -1
- package/src/client/dist/pwa/assets/{ChangelogPage-BplM7qEB.js → ChangelogPage-BJfkAVYp.js} +1 -1
- package/src/client/dist/pwa/assets/{ComparisonPanel-DxK5v9w_.js → ComparisonPanel-BfP8APKY.js} +1 -1
- package/src/client/dist/pwa/assets/CreatePage-BbtNfo0x.js +2 -0
- package/src/client/dist/pwa/assets/{DashboardPage-BsP-MATS.js → DashboardPage-DZYsAh5y.js} +1 -1
- package/src/client/dist/pwa/assets/DiffViewer-4Q-GJgyA.js +8 -0
- package/src/client/dist/pwa/assets/{DocumentsPanel-CGrf8Q3H.js → DocumentsPanel-BfPlfiwU.js} +1 -1
- package/src/client/dist/pwa/assets/{DrawerToggleButton-BANeLSsR.js → DrawerToggleButton-DG1Qjqk1.js} +1 -1
- package/src/client/dist/pwa/assets/GitPanel-ECnbb_rx.js +3 -0
- package/src/client/dist/pwa/assets/{HealthPage-BVTNx-92.js → HealthPage-_VXG1Zeo.js} +1 -1
- package/src/client/dist/pwa/assets/MainLayout-BH6grTuW.js +2 -0
- package/src/client/dist/pwa/assets/{MainLayout-C6nvIQjU.css → MainLayout-DnmUxNk7.css} +1 -1
- package/src/client/dist/pwa/assets/{MemoryPanel-C71rTpv9.js → MemoryPanel-CVlvGZXZ.js} +1 -1
- package/src/client/dist/pwa/assets/{MemoryProposalList-CoqhUGrv.js → MemoryProposalList-SJsnzUAM.js} +1 -1
- package/src/client/dist/pwa/assets/{QBtnDropdown-U9Ey5W8h.js → QBtnDropdown-DBgGsN-D.js} +1 -1
- package/src/client/dist/pwa/assets/{QExpansionItem-Be_A1MT1.js → QExpansionItem-DKgaMJ_l.js} +1 -1
- package/src/client/dist/pwa/assets/{QScrollArea-D4lYzAPQ.js → QScrollArea-D3OCsq3g.js} +1 -1
- package/src/client/dist/pwa/assets/QSelect-Cb_s_nox.js +1 -0
- package/src/client/dist/pwa/assets/{QTooltip-kStOgY0X.js → QTooltip-X5ps8Opw.js} +1 -1
- package/src/client/dist/pwa/assets/{SchedulePanel-C7nTGdWE.js → SchedulePanel-Ww_FVlW5.js} +1 -1
- package/src/client/dist/pwa/assets/{SearchPage-C6J72pkQ.js → SearchPage-B87WEHf3.js} +1 -1
- package/src/client/dist/pwa/assets/SettingsPage-BAZLGUyg.js +9 -0
- package/src/client/dist/pwa/assets/{SplitWorkspacePage-Bqn4gdVw.js → SplitWorkspacePage-B99AJUbG.js} +1 -1
- package/src/client/dist/pwa/assets/{TerminalPanel-zLeDCReI.js → TerminalPanel-DnA1MXwu.js} +10 -10
- package/src/client/dist/pwa/assets/{ToolCallItem-B8b_Sagj.js → ToolCallItem-2vFggaur.js} +3 -3
- package/src/client/dist/pwa/assets/{TouchPan-Bl8VzIDK.js → TouchPan-BSU3z0aO.js} +1 -1
- package/src/client/dist/pwa/assets/{TourReplayButton-pL0HCboU.js → TourReplayButton-DQwS2xdD.js} +1 -1
- package/src/client/dist/pwa/assets/{WorkspaceAttentionLabels-9Eomx0rQ.js → WorkspaceAttentionLabels-CM1tGDje.js} +1 -1
- package/src/client/dist/pwa/assets/WorkspacePage-CQN6EKIn.js +4 -0
- package/src/client/dist/pwa/assets/{browser-C-PJ4alw.js → browser-DPsupI9Q.js} +1 -1
- package/src/client/dist/pwa/assets/{build-path-tree-SW9rj7B_.js → build-path-tree-CbIdt-p9.js} +1 -1
- package/src/client/dist/pwa/assets/{custom-sounds-toz-4drX.js → custom-sounds-DR68eyF8.js} +1 -1
- package/src/client/dist/pwa/assets/{de-Ddxe8cc6.js → de-D3Ycvf_F.js} +2 -2
- package/src/client/dist/pwa/assets/{editor.api-BJTPhnh8.js → editor.api-DvgAtnOj.js} +1 -1
- package/src/client/dist/pwa/assets/{efforts-BO9xGn9q.js → efforts-nmxfTVQD.js} +1 -1
- package/src/client/dist/pwa/assets/es-TgeS5898.js +17 -0
- package/src/client/dist/pwa/assets/{expand-template-BOZMtWau.js → expand-template-Brn5O4a1.js} +1 -1
- package/src/client/dist/pwa/assets/{format-hEnpRyit.js → format-DfJ3Fv1i.js} +1 -1
- package/src/client/dist/pwa/assets/{formatters-DD_eFc4z.js → formatters-B30bNFhq.js} +1 -1
- package/src/client/dist/pwa/assets/fr-CJafryNb.js +17 -0
- package/src/client/dist/pwa/assets/{freemarker2-B6nUofvT.js → freemarker2-DnZe74tc.js} +1 -1
- package/src/client/dist/pwa/assets/{handlebars-4P3gtM8p.js → handlebars-CmuMfxzO.js} +1 -1
- package/src/client/dist/pwa/assets/{html-C9ZzWo-y.js → html-BD7OJFGX.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-BY4JeBjk.js +1 -0
- package/src/client/dist/pwa/assets/index-C5fAw9ll.js +20 -0
- package/src/client/dist/pwa/assets/it-C5mN8Y61.js +17 -0
- package/src/client/dist/pwa/assets/{javascript-BGEC5ggd.js → javascript-Y-OrgcmW.js} +1 -1
- package/src/client/dist/pwa/assets/{liquid-DNtF630O.js → liquid-BqOkKePH.js} +1 -1
- package/src/client/dist/pwa/assets/{mdx-CICwyOd0.js → mdx-D-58x2h0.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco-contributions-BkuwvgrT.js → monaco-contributions-BW4ZMKO6.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco.contribution-GX8XA-PE.js → monaco.contribution-DJWqzNG-.js} +2 -2
- package/src/client/dist/pwa/assets/network-auth-BGPRhUa6.js +1 -0
- package/src/client/dist/pwa/assets/notify-theme-DZDzfxph.js +1 -0
- package/src/client/dist/pwa/assets/{project-color-B1SMFjSe.js → project-color-gB5yeZPN.js} +1 -1
- package/src/client/dist/pwa/assets/{python-n5uPdTVS.js → python-BkBj1wDC.js} +1 -1
- package/src/client/dist/pwa/assets/{razor-AOj0nsUt.js → razor-DOOJ_0b8.js} +1 -1
- package/src/client/dist/pwa/assets/{typescript-Uhkrqihh.js → typescript-Bheo43G5.js} +1 -1
- package/src/client/dist/pwa/assets/{use-search-index-status-CW0BdpBZ.js → use-search-index-status-CBpnyV-R.js} +1 -1
- package/src/client/dist/pwa/assets/use-tours-BHNasCvg.js +2 -0
- package/src/client/dist/pwa/assets/{use-worktree-restore-BZxibZS5.js → use-worktree-restore-CgABsWno.js} +1 -1
- package/src/client/dist/pwa/assets/{workspace-templates-W557e1-m.js → workspace-templates-BnFkigYg.js} +1 -1
- package/src/client/dist/pwa/assets/{xml-B-hnMgEs.js → xml-vHt2BD26.js} +1 -1
- package/src/client/dist/pwa/assets/{yaml-DqXkTvgm.js → yaml-BvhLPzP9.js} +1 -1
- package/src/client/dist/pwa/index.html +1 -1
- package/src/client/dist/pwa/sw.js +1 -1
- package/src/mcp-server/README.md +130 -12
- package/src/mcp-server/kobo-tasks-server.ts +27 -105
- package/src/mcp-server/workspace-dialogue-client.ts +18 -3
- package/src/client/dist/pwa/assets/ActivityFeed-D7jouiuP.js +0 -2
- package/src/client/dist/pwa/assets/CreatePage-D5ilP5kt.js +0 -2
- package/src/client/dist/pwa/assets/DiffViewer-Cd15vFWj.js +0 -8
- package/src/client/dist/pwa/assets/GitPanel-CEPV5Qw0.js +0 -3
- package/src/client/dist/pwa/assets/MainLayout-DBKtwilW.js +0 -2
- package/src/client/dist/pwa/assets/QSelect-fS0DLOd1.js +0 -1
- package/src/client/dist/pwa/assets/SettingsPage-SuH-aIzv.js +0 -9
- package/src/client/dist/pwa/assets/WorkspacePage-B6clgURl.js +0 -4
- package/src/client/dist/pwa/assets/es-DBJIQeWW.js +0 -17
- package/src/client/dist/pwa/assets/fr-yBzIIPaQ.js +0 -17
- package/src/client/dist/pwa/assets/i18n-2-ULabk2.js +0 -1
- package/src/client/dist/pwa/assets/index-Vk99jtxD.js +0 -20
- package/src/client/dist/pwa/assets/it-D44U-PT1.js +0 -17
- package/src/client/dist/pwa/assets/network-auth-KnWzjYIc.js +0 -1
- package/src/client/dist/pwa/assets/notify-theme-CWuZTz4Z.js +0 -1
- package/src/client/dist/pwa/assets/use-tours-CndFlyL-.js +0 -2
package/src/mcp-server/README.md
CHANGED
|
@@ -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
|
|
|
@@ -365,7 +365,7 @@ List all images uploaded to the current workspace via Kōbō's chat paste/upload
|
|
|
365
365
|
|
|
366
366
|
### Global tools (workspace-less mode)
|
|
367
367
|
|
|
368
|
-
These
|
|
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.
|
|
369
369
|
|
|
370
370
|
#### `list_workspaces`
|
|
371
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.
|
|
@@ -378,28 +378,113 @@ List Kōbō workspaces with id, title, status, and creation date. Reads the DB d
|
|
|
378
378
|
---
|
|
379
379
|
|
|
380
380
|
#### `create_workspace`
|
|
381
|
-
Create a
|
|
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.
|
|
382
382
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
- `
|
|
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.
|
|
390
424
|
|
|
391
|
-
|
|
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.
|
|
392
436
|
|
|
393
437
|
---
|
|
394
438
|
|
|
395
439
|
#### `archive_workspace`
|
|
396
|
-
Archive a workspace by id, like the "Archiver" action in the workspace context menu.
|
|
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.
|
|
397
441
|
|
|
398
442
|
**Input:**
|
|
399
443
|
- `workspace_id` (string, required) — from `list_workspaces`
|
|
400
444
|
|
|
401
445
|
---
|
|
402
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
|
+
|
|
403
488
|
#### `stop_workspace`
|
|
404
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.
|
|
405
490
|
|
|
@@ -440,3 +525,36 @@ Automatic mode means the agent deliberately calls `remember` when it has stable,
|
|
|
440
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.
|
|
441
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.
|
|
442
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,
|
|
@@ -635,6 +642,9 @@ const WORKSPACE_SCOPED_TOOLS: Tool[] = [
|
|
|
635
642
|
|
|
636
643
|
const GLOBAL_TOOLS: typeof WORKSPACE_SCOPED_TOOLS = [
|
|
637
644
|
...WORKSPACE_DIALOGUE_TOOLS,
|
|
645
|
+
...WORKSPACE_CREATION_TOOLS,
|
|
646
|
+
...WORKSPACE_LIFECYCLE_TOOLS,
|
|
647
|
+
...WORKSPACE_GROUP_MESSAGE_TOOLS,
|
|
638
648
|
...EXTERNAL_MEMORY_TOOL_DEFINITIONS,
|
|
639
649
|
{
|
|
640
650
|
name: 'list_workspaces',
|
|
@@ -657,65 +667,6 @@ const GLOBAL_TOOLS: typeof WORKSPACE_SCOPED_TOOLS = [
|
|
|
657
667
|
},
|
|
658
668
|
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
659
669
|
},
|
|
660
|
-
{
|
|
661
|
-
name: 'create_workspace',
|
|
662
|
-
description:
|
|
663
|
-
'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.',
|
|
664
|
-
inputSchema: {
|
|
665
|
-
type: 'object',
|
|
666
|
-
properties: {
|
|
667
|
-
name: { type: 'string', description: 'Workspace display name.' },
|
|
668
|
-
project_path: { type: 'string', description: 'Absolute path to the project on disk.' },
|
|
669
|
-
source_branch: { type: 'string', description: 'Branch to base the new worktree on (e.g. "develop").' },
|
|
670
|
-
working_branch: {
|
|
671
|
-
type: 'string',
|
|
672
|
-
description: 'New branch name for the worktree (e.g. "feature/my-thing").',
|
|
673
|
-
},
|
|
674
|
-
model: { type: 'string', description: 'Model id override (optional, defaults to project/global setting).' },
|
|
675
|
-
reasoning_effort: { type: 'string', description: 'Reasoning effort override (optional).' },
|
|
676
|
-
engine: { type: 'string', description: 'Agent engine id, e.g. "claude-code" or "codex" (optional).' },
|
|
677
|
-
description: { type: 'string', description: 'Task description / initial brainstorming prompt (optional).' },
|
|
678
|
-
tags: {
|
|
679
|
-
type: 'array',
|
|
680
|
-
items: { type: 'string' },
|
|
681
|
-
description: 'Workspace tags to assign at creation (optional).',
|
|
682
|
-
},
|
|
683
|
-
tasks: { type: 'array', items: { type: 'string' }, description: 'Initial manual task titles (optional).' },
|
|
684
|
-
acceptance_criteria: {
|
|
685
|
-
type: 'array',
|
|
686
|
-
items: { type: 'string' },
|
|
687
|
-
description: 'Initial manual acceptance criteria (optional).',
|
|
688
|
-
},
|
|
689
|
-
agent_permission_mode: {
|
|
690
|
-
type: 'string',
|
|
691
|
-
enum: ['plan', 'bypass', 'strict', 'interactive'],
|
|
692
|
-
description: 'Permission mode for the new session (optional).',
|
|
693
|
-
},
|
|
694
|
-
auto_loop: { type: 'boolean', description: 'Start the workspace in auto-loop mode (optional).' },
|
|
695
|
-
auto_loop_session_mode: {
|
|
696
|
-
type: 'string',
|
|
697
|
-
enum: ['per_task', 'continuous'],
|
|
698
|
-
description: "Auto-loop session mode when auto_loop is true (optional, default 'per_task').",
|
|
699
|
-
},
|
|
700
|
-
skip_setup_script: { type: 'boolean', description: "Skip the project's setup script (optional)." },
|
|
701
|
-
},
|
|
702
|
-
required: ['name', 'project_path', 'source_branch', 'working_branch'],
|
|
703
|
-
},
|
|
704
|
-
annotations: { destructiveHint: false, openWorldHint: false },
|
|
705
|
-
},
|
|
706
|
-
{
|
|
707
|
-
name: 'archive_workspace',
|
|
708
|
-
description:
|
|
709
|
-
'Archive a workspace by id, like the "Archiver" action in the workspace context menu. Requires the Kōbō backend server to be running.',
|
|
710
|
-
inputSchema: {
|
|
711
|
-
type: 'object',
|
|
712
|
-
properties: {
|
|
713
|
-
workspace_id: { type: 'string', description: 'Workspace id, from list_workspaces.' },
|
|
714
|
-
},
|
|
715
|
-
required: ['workspace_id'],
|
|
716
|
-
},
|
|
717
|
-
annotations: { destructiveHint: false, openWorldHint: false },
|
|
718
|
-
},
|
|
719
670
|
{
|
|
720
671
|
name: 'stop_workspace',
|
|
721
672
|
description:
|
|
@@ -845,8 +796,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
845
796
|
: {}),
|
|
846
797
|
}
|
|
847
798
|
}
|
|
848
|
-
if (
|
|
849
|
-
|
|
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
|
+
)
|
|
850
812
|
return fail('Use the normal conversation to communicate in your own workspace.')
|
|
851
813
|
return await callWorkspaceDialogueTool(
|
|
852
814
|
backendUrl,
|
|
@@ -1143,49 +1105,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1143
1105
|
return ok(listWorkspacesHandler(db, { includeArchived, tag }))
|
|
1144
1106
|
}
|
|
1145
1107
|
|
|
1146
|
-
if (name === 'create_workspace') {
|
|
1147
|
-
const workspaceName = a.name as string | undefined
|
|
1148
|
-
const projectPath = a.project_path as string | undefined
|
|
1149
|
-
const sourceBranch = a.source_branch as string | undefined
|
|
1150
|
-
const workingBranch = a.working_branch as string | undefined
|
|
1151
|
-
if (!workspaceName || !projectPath || !sourceBranch || !workingBranch) {
|
|
1152
|
-
return fail('name, project_path, source_branch, and working_branch parameters are required')
|
|
1153
|
-
}
|
|
1154
|
-
try {
|
|
1155
|
-
const created = await backendRequest('POST', '/api/workspaces', {
|
|
1156
|
-
name: workspaceName,
|
|
1157
|
-
projectPath,
|
|
1158
|
-
sourceBranch,
|
|
1159
|
-
workingBranch,
|
|
1160
|
-
model: a.model as string | undefined,
|
|
1161
|
-
reasoningEffort: a.reasoning_effort as string | undefined,
|
|
1162
|
-
engine: a.engine as string | undefined,
|
|
1163
|
-
description: a.description as string | undefined,
|
|
1164
|
-
tags: a.tags as string[] | undefined,
|
|
1165
|
-
tasks: a.tasks as string[] | undefined,
|
|
1166
|
-
acceptanceCriteria: a.acceptance_criteria as string[] | undefined,
|
|
1167
|
-
agentPermissionMode: a.agent_permission_mode as string | undefined,
|
|
1168
|
-
autoLoop: a.auto_loop as boolean | undefined,
|
|
1169
|
-
autoLoopSessionMode: a.auto_loop_session_mode as string | undefined,
|
|
1170
|
-
skipSetupScript: a.skip_setup_script as boolean | undefined,
|
|
1171
|
-
})
|
|
1172
|
-
return ok(created)
|
|
1173
|
-
} catch (err) {
|
|
1174
|
-
return fail(backendErrorMessage(err))
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
if (name === 'archive_workspace') {
|
|
1179
|
-
const targetId = a.workspace_id as string | undefined
|
|
1180
|
-
if (!targetId) return fail('workspace_id parameter is required')
|
|
1181
|
-
try {
|
|
1182
|
-
const result = await backendRequest('POST', `/api/workspaces/${targetId}/archive`)
|
|
1183
|
-
return ok(result)
|
|
1184
|
-
} catch (err) {
|
|
1185
|
-
return fail(backendErrorMessage(err))
|
|
1186
|
-
}
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
1108
|
if (name === 'stop_workspace') {
|
|
1190
1109
|
const targetId = a.workspace_id as string | undefined
|
|
1191
1110
|
if (!targetId) return fail('workspace_id parameter is required')
|
|
@@ -1203,7 +1122,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1203
1122
|
}
|
|
1204
1123
|
})
|
|
1205
1124
|
|
|
1206
|
-
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
|
+
})
|
|
1207
1129
|
server.connect(transport).catch((err) => {
|
|
1208
1130
|
console.error('[kobo-tasks-server] Fatal:', err)
|
|
1209
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
|
-
:
|
|
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
|
-
|
|
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{$t as ce,O as le,Vt as O,Wt as ue,Xt as de,at as fe,ft as pe,ht as me,j as he,ln as k,m as ge,on as A,ot as _e,pt as ve,v as ye}from"./index-Vk99jtxD.js";import{t as be}from"./QSpinnerDots-xdWyaE1i.js";import{t as xe}from"./QList-Cx1UfaUm.js";import{t as Se}from"./QMarkupTable-UhP7fVUp.js";import{c as j,l as M,s as N}from"./QSelect-fS0DLOd1.js";import{t as P}from"./QItemSection-MdJ1_ZK7.js";import{t as Ce}from"./QExpansionItem-Be_A1MT1.js";import{a as F,c as we,r as I,t as L}from"./ToolCallItem-B8b_Sagj.js";import{t as Te}from"./QScrollArea-D4lYzAPQ.js";import{a as R}from"./use-tours-CndFlyL-.js";import{f as Ee,g as z,m as De,p as Oe,u as ke}from"./use-worktree-restore-BZxibZS5.js";import{t as B}from"./QTooltip-kStOgY0X.js";import{n as V}from"./render-chat-markdown-CafzCcmu.js";import{n as H}from"./WorkspacePage-B6clgURl.js";function Ae(e,t){return s(`div`,e,[s(`table`,{class:`q-table`},t)])}var je={list:xe,table:Se},U=[`list`,`table`,`__qtable`],Me=ae({name:`QVirtualScroll`,props:{...M,type:{type:String,default:`list`,validator:e=>U.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}=j({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=A(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`?Ae({ref:a,class:`q-table__middle `+g.value,tabindex:0},w()):s(je[e.type],{...r,ref:a,class:[r.class,g.value],..._.value},w)}}}),Ne=new Set([`setup`,`cleanup`,`archive`]);function Pe(e){switch(e.type){case`user`:return e.sender===`system-prompt`?`system-prompt`:Ne.has(e.sender)||le(e.sender)?`script`:`user`;case`session`:return`session`;default:return`agent`}}function Fe(e){let t=[],n=null,r=null;for(let i of e){let e=Pe(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 W(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 Ie(e){let t=e.items[0];return`${e.speaker}:${t?W(t):`empty`}`}function Le(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 Re(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 ze={class:`agent-error-item`},Be={class:`agent-error-item__body`},Ve={class:`agent-error-item__title`},G={class:`agent-error-item__message`},He=R(T({__name:`AgentErrorItem`,props:{item:{}},setup(e){let t=e,{te:n}=de(),r=x(()=>{let e=`agent.error.${t.item.category}`;return n(e)?e:`agent.error.other`});return(t,n)=>(u(),S(`div`,ze,[g(oe,{name:`error_outline`,size:`16px`,class:`agent-error-item__icon`}),w(`div`,Be,[w(`div`,Ve,a(t.$t(r.value)),1),w(`div`,G,a(e.item.message),1)])]))}}),[[`__scopeId`,`data-v-e98c3c02`]]),Ue={class:`text-caption text-kobo-3`},We=T({__name:`SessionEventItem`,props:{item:{}},setup(e){let t=e,n=x(()=>{switch(t.item.kind){case`started`:return`session.started`;case`ended`:return z(t.item.detail);case`compacted`:return`session.compacted`;default:return`session.started`}});return(e,t)=>(u(),S(`span`,Ue,a(e.$t(n.value)),1))}}),K=new Map;function q(){for(let e of K.values())e.controller.abort(),e.blobUrl&&URL.revokeObjectURL(e.blobUrl);K.clear()}typeof window<`u`&&(window.addEventListener(`kobo:auth-token-changed`,q),window.addEventListener(`storage`,e=>{(e.key===`kobo:network-token`||e.key===null)&&q()}));function J(e){if(!/^\/api\/workspaces\/[^/]+\/images\/file\?path=/.test(e))throw Error(`Invalid workspace image URL`);let t=K.get(e);if(!t){let n=new AbortController,r={references:0,controller:n,ready:Promise.resolve(``)};r.ready=O(e,{signal:n.signal}).then(async e=>{if(!e.ok)throw await ue(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,K.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),K.get(e)===r&&K.delete(e)))}}}function Ge(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=J(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 Ke(e,t){return t?e.replace(/\[image:\s+([^\]]+)\]/g,(e,n)=>{let r=String(n).trim();return/^(\.ai\/images\/|images\/)/.test(r)?`}/images/file?path=${encodeURIComponent(r)}`})`:e}):e}var qe=[`innerHTML`],Je=[`innerHTML`],Ye=[`src`],Y=R(T({__name:`UserMessageItem`,props:{item:{},workspaceId:{}},setup(t){let n=t,{t:r}=de(),a=x(()=>n.item.sender===`system-prompt`),s=Ge(x(()=>{let e=Ke(n.item.content,n.workspaceId);return V(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=J(n.dataset.koboImageUrl),f.ready.catch(p)),c.value=n.src,d.value=!0)}return(t,n)=>(u(),S(C,null,[a.value?(u(),_(Ce,{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,qe)]),_:1},8,[`label`])):(u(),S(`div`,{key:1,class:`markdown-message`,onClick:y},[w(`div`,{innerHTML:e(s)},null,8,Je)])),g(ce,{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,Ye)):v(``,!0)]),_:1},8,[`modelValue`])],64))}}),[[`__scopeId`,`data-v-db2fc622`]]),Xe=[`data-event-ids`],X={class:`turn-header`},Ze={key:0,class:`turn-time`},Qe={class:`turn-time turn-time-updated`},$e={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=R(T({__name:`TurnCard`,props:{turn:{},workspaceId:{},highlighted:{type:Boolean}},emits:[`scrollTo`],setup(t,{emit:i}){let s=t,c=i,{t:l}=de(),d=fe(),f=m(F),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 we(e.origin,Object.values(d.subagents[s.workspaceId]??{}),e.type===`tool`?e.toolCallId:void 0)}function O(e){return e||l(`chat.subagentActivity`)}function ue(e){return(e.type===`text`||e.type===`thinking`||e.type===`tool`)&&e.origin?.kind===`subagent`}let pe=x(()=>{let e=[],t=new Map;for(let n of s.turn.items){if(!ue(n)){e.push({type:`item`,key:W(n),item:n});continue}let r=ce(n),i=r?.toolUseId??n.origin?.toolCallId??n.origin?.threadId??W(n),a=t.get(i);if(a){a.items.push(n);continue}let o={type:`subagent`,key:`subagent:${W(n)}`,items:[n],subagentKey:i,subagentName:r?.description||null};t.set(i,o),e.push(o)}return e});function me(e){e.origin&&f?.({origin:e.origin})}let he=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`,X,[w(`span`,{class:r([`turn-badge`,b.value.badgeClass])},a(b.value.label),3),re.value?(u(),S(`span`,Ze,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`,Qe,[y(a(ae.value)+` `,1),g(B,null,{default:h(()=>[y(a(e(l)(`chat.lastUpdatedAt`,{time:ae.value})),1)]),_:1})])],64)):v(``,!0),he.value>0?(u(),S(`span`,$e,` · `+a(e(l)(`chat.nActions`,{n:he.value})),1)):v(``,!0)]),w(`div`,et,[(u(!0),S(C,null,te(pe.value,n=>(u(),S(C,{key:n.key},[n.type===`subagent`?(u(),_(Ce,{key:0,class:`turn-subagent-activity`,dense:``,label:O(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(P,null,{default:h(()=>[g(N,{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(B,null,{default:h(()=>[y(a(e(l)(`chat.subagentActivity`)),1)]),_:1})]),_:1}),w(`span`,tt,a(O(n.subagentName)),1)]),_:2},1032,[`title`]),g(N,{caption:``},{default:h(()=>[y(a(e(l)(`chat.nActions`,{n:n.items.length})),1)]),_:2},1024)]),_:2},1024),g(P,{side:``},{default:h(()=>[g(D,{flat:``,round:``,dense:``,size:`sm`,icon:`open_in_new`,"aria-label":e(l)(`chat.openSubagentActivity`),onClick:ne(e=>me(n.items[0]),[`stop`])},{default:h(()=>[g(B,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(W)(t)},[t.type===`text`?(u(),_(I,{key:0,item:t},null,8,[`item`])):t.type===`thinking`?(u(),_(H,{key:1,item:t},null,8,[`item`])):t.type===`tool`?(u(),_(L,{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(),_(I,{key:0,item:n.item},null,8,[`item`])):n.item.type===`thinking`?(u(),_(H,{key:1,item:n.item},null,8,[`item`])):n.item.type===`tool`?(u(),_(L,{key:2,item:n.item},null,8,[`item`])):n.item.type===`user`?(u(),_(Y,{key:3,item:n.item,"workspace-id":t.workspaceId},null,8,[`item`,`workspace-id`])):n.item.type===`session`?(u(),_(We,{key:4,item:n.item},null,8,[`item`])):n.item.type===`error`?(u(),_(He,{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(B,null,{default:h(()=>[y(a(e(l)(`chat.scrollToTurnTop`)),1)]),_:1})]),_:1})])):v(``,!0)],14,Xe))}}),[[`__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=R(T({__name:`ActivityFeed`,props:{workspaceId:{}},setup(t){let{t:n}=de(),r=t,i=me(),s=he(),d=fe(),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:ye(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 _e(e?.status)}),ce=ke(),le=``,k=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!==le&&(ce=ke(),le=u);let d=Ee(ce,o,c,ae.value,l);return Fe(De(d,ie.value).filter(e=>e.type===`thinking`?!1:e.type===`session`?e.kind===`ended`?s.showVerboseSystemMessages||!Oe(e.detail):s.showVerboseSystemMessages:!0))}),A=x(()=>s.showVerboseSystemMessages?i.eventsFor(r.workspaceId).filter(e=>e.kind===`message:raw`).map(e=>e.content):[]),xe=x(()=>{for(let e=k.value.length-1;e>=0;e--){let t=k.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}),Se=o(``);l(xe,(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;Se.value=r});let j=o(null),M=o(!0),N=o(!1),P=o(``),F=pe(),we=pe(),I=pe(),L=o(null),R=!1,z=!0,B=null,V=o(new Set),H=new Map,Ae=x(()=>{if(k.value.length>0||A.value.length>0)return!1;let e=r.workspaceId,t=b.value;return!!d.loadingSessions[e]||m.isSyncPending(e)||t!==null&&V.value.has(W(e,t))}),je=o(new Map),U=x(()=>k.value.length>0||A.value.length>0),Ne=x(()=>!!d.loadingSessions[r.workspaceId]||m.isSyncPending(r.workspaceId)||b.value!==null&&V.value.has(W(r.workspaceId,b.value)));function Pe(e){tt();let t=B;if(B=e.verticalSize,t!==null&&e.verticalSize>t&&M.value&&!N.value){q(0);return}let n=e.verticalSize-e.verticalPosition-e.verticalContainerSize;M.value=n<=xt,R&&e.verticalPosition<=St&&!N.value&&!P.value&&ze()&&He()}function W(e,t){return`${e}:${t}`}function ze(){let e=b.value;return e?je.value.get(W(r.workspaceId,e))??!0:i.hasMoreOlderFor(r.workspaceId)}function Be(e,t,n){je.value.set(W(e,t),n)}function Ve(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 G=null;function He(){if(G)return G;let e=K().finally(()=>{G===e&&(G=null)});return G=e,G}function Ue(){F.abort(),we.abort(),I.abort(),N.value=!1,G=null;for(let e of V.value)Ft.delete(e);H.clear(),V.value.clear(),P.value=``}function We(){P.value=``,Nt.value===0&&b.value?It():He()}l(()=>[r.workspaceId,b.value],Ue,{flush:`sync`});async function K(){let e=r.workspaceId,t=b.value,n=Ve(e);if(!n)return;let a=F.begin();P.value=``,N.value=!0;try{let r=j.value;if(await f(),!F.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 O(`/api/workspaces/${e}/events?${c.toString()}`,{signal:a});if(!l.ok)throw await ue(l);let u=await l.json();if(!F.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&&Be(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:ye(n.source)}})}if(await f(),r&&F.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(ve(e)||!F.isCurrent(a))return;P.value=e instanceof Error?e.message:String(e),console.error(`[ActivityFeed] failed to load older events:`,e)}finally{F.isCurrent(a)&&(N.value=!1)}}async function q(e=0){let t=$();if(await f(),t!==$())return;let n=j.value;if(!n)return;let r=n.getScroll();n.setScrollPosition(`vertical`,r.verticalSize,e)}let J=null,Ge=0;function Ke(){if(J!=null)return;let e=performance.now()-Ge<Ct,t=$();J=requestAnimationFrame(()=>{J=null,t===$()&&(Ge=performance.now(),q(e?0:180))})}function qe(e){Q(),M.value=!1;let t=j.value;t&&t.setScrollPosition(`vertical`,Math.max(0,e),250)}function Je(){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 Ye(e){let t=e.detail;if(t?.workspaceId!==r.workspaceId||!t.eventId)return;Q(),M.value=!1;let n=I.begin(),a=r.workspaceId;try{let e=new URLSearchParams({around:t.eventId,limit:`200`});t.sessionId&&e.set(`session`,t.sessionId);let o=await O(`/api/workspaces/${a}/events?${e}`,{signal:n});if(!o.ok)throw await ue(o);let s=await o.json();if(!I.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:ye(t.source)}})}if(await Re(Ae,e=>!e,5e3),await f(),await new Promise(e=>requestAnimationFrame(()=>requestAnimationFrame(()=>e()))),!I.isCurrent(n))return;let p=k.value.findIndex(e=>e.items.some(e=>e.eventIds?.includes(t.eventId)??!1));if(p<0)return;L.value=t.eventId,Y.value?.scrollTo(p,`center`),X.value=p,window.setTimeout(()=>{L.value===t.eventId&&(L.value=null)},1800)}catch(e){if(ve(e)||!I.isCurrent(n))return;P.value=e instanceof Error?e.message:String(e),console.error(`[ActivityFeed] failed to focus history event:`,e)}}let Y=o(null),Xe=o(null),X=o(0),Ze=o(!1),Qe=x(()=>{for(let e=k.value.length-1;e>=0;e--)if(k.value[e]?.speaker===`user`)return e;return-1}),$e=null;function et(){let e=Qe.value,t=j.value?.getScrollTarget(),n=t?.querySelector(`[data-turn-index="${e}"]`);if(!n||!t){Ze.value=!1;return}let r=t.getBoundingClientRect(),i=n.getBoundingClientRect();Ze.value=i.bottom>r.top&&i.top<r.bottom}function tt(){$e===null&&($e=requestAnimationFrame(()=>{$e=null,et()}))}function nt(e){return e.length<=wt?e:`${e.slice(0,252)}...`}let rt=x(()=>{let e=Qe.value,t=k.value[e];if(!t||X.value<=e||Ze.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){X.value=e.index,tt()}function Ot(){let e=rt.value;e&&(Q(),M.value=!1,Y.value?.scrollTo(e.index,`start`),X.value=e.index)}let kt=o(!1);async function At(){if(kt.value)return;Q(),M.value=!1,kt.value=!0;let e=r.workspaceId;try{let t=Le(k.value,X.value);if(t<0)for(let n=0;n<15;n++){if(e!==r.workspaceId)return;if(!ze())break;let n=k.value.length;if(await He(),await f(),e!==r.workspaceId)return;if(X.value+=k.value.length-n,t=Le(k.value,X.value),t>=0)break}t>=0&&e===r.workspaceId&&(Y.value?.scrollTo(t,`start`),X.value=t)}finally{kt.value=!1}}let Z=0;function jt(){return new Promise(e=>requestAnimationFrame(()=>e()))}async function Mt(){if(!z||!U.value||Ae.value)return;R=!1;let e=++Z;if(await f(),e!==Z||!z||(Xe.value=j.value?.getScrollTarget()??null,await f(),e!==Z||!z))return;k.value.length>0&&Y.value?.scrollTo(k.value.length-1,`end-force`),await jt();let t=-1,n=0;for(let r=0;r<Et&&n<Tt;r++){if(e!==Z||!z)return;await q(0),await jt();let r=j.value?.getScroll().verticalSize??0;B=r,n=r===t?n+1:0,t=r}e===Z&&!Ne.value&&(R=!0,z=!1)}function Q(){z=!1,!R&&(Z++,R=!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(Ae,async e=>{!e&&U.value&&await Mt(),e||f(()=>{Xe.value=j.value?.getScrollTarget()??null,tt()})}),l(Ne,(e,t)=>{!e&&t&&z&&U.value&&Mt()}),p(()=>{window.addEventListener(`kobo:focus-history-event`,Ye),f(()=>{Xe.value=j.value?.getScrollTarget()??null,tt()}),U.value&&Mt(),b.value&&It();let e=new URLSearchParams(window.location.hash.split(`?`)[1]??``).get(`eventId`);e&&(Ye(new CustomEvent(`kobo:focus-history-event`,{detail:{workspaceId:r.workspaceId,sessionId:b.value,eventId:e}})),Je())}),l([Nt,()=>ie.value.length,()=>A.value.length],async([e,t,n])=>{N.value||z&&e+t+n>0&&await Mt()}),l([Qe,()=>k.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&&M.value&&!N.value&&Ke()}),c(()=>{Z++,z=!1,Ue(),window.removeEventListener(`kobo:focus-history-event`,Ye),J!=null&&(cancelAnimationFrame(J),J=null),$e!==null&&cancelAnimationFrame($e)}),l([()=>r.workspaceId,()=>d.selectedSessionId],()=>{Z++,z=!0,B=null,M.value=!0,R=!1,It(),U.value&&Mt()});let Ft=new Set;async function It(){let e=r.workspaceId,t=b.value;if(!t||Nt.value>0)return;let n=W(e,t);if(Ft.has(n))return;Ft.add(n),V.value.add(n);let a=we.begin();H.set(n,a),P.value=``;try{let n=await O(`/api/workspaces/${e}/events?session=${encodeURIComponent(t)}&limit=500`,{signal:a});if(!n.ok)throw await ue(n);let r=await n.json();if(!we.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);Be(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:ye(n.source)}})}}catch(e){if(ve(e)||!we.isCurrent(a)){(!H.has(n)||H.get(n)===a)&&Ft.delete(n);return}P.value=e instanceof Error?e.message:String(e),console.error(`[ActivityFeed] fetchSessionIfMissing failed:`,e),Ft.delete(n)}finally{H.get(n)===a&&(H.delete(n),V.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&&M.value&&!N.value&&!Ne.value)&&(M.value=!0,await q(o?180:0))});async function zt(){M.value=!0,await q(250)}return(t,n)=>Ae.value?(u(),S(`div`,at,[g(be,{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),P.value?(u(),S(`div`,ct,[y(a(t.$t(`activity.historyError`,{message:P.value}))+` `,1),g(D,{flat:``,dense:``,label:t.$t(`common.retry`),onClick:We},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(Se.value),9,lt),g(re,{name:`fade`},{default:h(()=>[ee.value?(u(),S(`div`,ut,[g(be,{size:`18px`,color:`primary`}),w(`span`,dt,a(t.$t(`activity.compacting`)),1)])):v(``,!0)]),_:1}),g(Te,{ref_key:`scrollRef`,ref:j,class:`activity-feed-scroll`,onScroll:Pe,onWheelPassive:Q,onTouchstartPassive:Q,onKeydown:Q},{default:h(()=>[N.value?(u(),S(`div`,ft,[g(se,{size:`sm`}),y(` `+a(t.$t(`activity.loading_older`)),1)])):v(``,!0),g(Me,{ref_key:`virtualScrollRef`,ref:Y,class:`q-pa-md`,items:k.value,"scroll-target":Xe.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(Ie)(t),turn:t,"workspace-id":r.workspaceId,"data-turn-index":n,highlighted:t.items.some(e=>e.eventIds?.includes(L.value??``)??!1),onScrollTo:qe},null,8,[`turn`,`workspace-id`,`data-turn-index`,`highlighted`]))]),_:1},8,[`items`,`scroll-target`]),k.value.length===0&&A.value.length===0&&!N.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),A.value.length?(u(),S(`div`,gt,[g(Ce,{label:t.$t(`activity.raw_lines`,{n:A.value.length}),dense:``},{default:h(()=>[(u(!0),S(C,null,te(A.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,[M.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};
|