@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.
- package/AGENTS.md +38 -6
- package/CHANGELOG.md +12 -0
- package/CONFIGURATION.md +41 -5
- package/README.md +4 -2
- package/dist/mcp-server/kobo-tasks-server.js +62 -105
- package/dist/mcp-server/workspace-dialogue-client.js +15 -3
- package/dist/server/db/auto-loop-final-review-schema.js +18 -0
- package/dist/server/db/migrations.js +12 -1
- package/dist/server/db/review-return-schema.js +18 -1
- package/dist/server/db/schema.js +4 -0
- package/dist/server/db/workspace-message-batch-schema.js +9 -0
- package/dist/server/index.js +8 -1
- package/dist/server/routes/mcp.js +69 -3
- package/dist/server/routes/workspace-messages.js +22 -0
- package/dist/server/routes/workspaces.js +119 -4
- package/dist/server/services/agent/engines/claude-code/engine.js +37 -1
- package/dist/server/services/agent/orchestrator.js +159 -40
- package/dist/server/services/agent/session-controller.js +4 -1
- package/dist/server/services/auto-loop-final-review-service.js +329 -0
- package/dist/server/services/auto-loop-service.js +53 -10
- 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/review-launch-runtime.js +17 -0
- package/dist/server/services/review-return-service.js +58 -10
- package/dist/server/services/review-service.js +59 -11
- 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/server/services/workspace-service.js +11 -0
- package/dist/server/services/workspace-template-service.js +10 -0
- package/dist/shared/auto-loop-review.js +1 -0
- package/dist/shared/pr-checkout-steps.js +86 -0
- package/dist/shared/review-return.js +1 -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-Bbrq02Xt.js +1 -0
- package/src/client/dist/pwa/assets/AutoLoopFinalReviewSettings-Q-OlSQ1c.css +1 -0
- package/src/client/dist/pwa/assets/{ChangelogPage-DD90Nl6c.js → ChangelogPage-BJfkAVYp.js} +1 -1
- package/src/client/dist/pwa/assets/{ComparisonPanel-D_BQFWiT.js → ComparisonPanel-BfP8APKY.js} +1 -1
- package/src/client/dist/pwa/assets/CreatePage-BbtNfo0x.js +2 -0
- package/src/client/dist/pwa/assets/CreatePage-DxOes_cF.css +1 -0
- package/src/client/dist/pwa/assets/{DashboardPage-DyvTdmBg.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-bm639sIu.js → DocumentsPanel-BfPlfiwU.js} +1 -1
- package/src/client/dist/pwa/assets/{DrawerToggleButton-KfwSzjeY.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-37IHOIW-.js → HealthPage-_VXG1Zeo.js} +1 -1
- package/src/client/dist/pwa/assets/MainLayout-BH6grTuW.js +2 -0
- package/src/client/dist/pwa/assets/MainLayout-DnmUxNk7.css +1 -0
- package/src/client/dist/pwa/assets/{MemoryPanel-CucZMhjy.js → MemoryPanel-CVlvGZXZ.js} +1 -1
- package/src/client/dist/pwa/assets/{MemoryProposalList-CUQybzVR.js → MemoryProposalList-SJsnzUAM.js} +1 -1
- package/src/client/dist/pwa/assets/{QBtnDropdown-x6qPyl_7.js → QBtnDropdown-DBgGsN-D.js} +1 -1
- package/src/client/dist/pwa/assets/{QExpansionItem-D33aoRoG.js → QExpansionItem-DKgaMJ_l.js} +1 -1
- package/src/client/dist/pwa/assets/{QScrollArea-Di-D6t2u.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-BhwG7lp2.js → QTooltip-X5ps8Opw.js} +1 -1
- package/src/client/dist/pwa/assets/{SchedulePanel-qFi9Wxzh.js → SchedulePanel-Ww_FVlW5.js} +1 -1
- package/src/client/dist/pwa/assets/{SearchPage-DA9nRmsA.js → SearchPage-B87WEHf3.js} +1 -1
- package/src/client/dist/pwa/assets/SettingsPage-BAZLGUyg.js +9 -0
- package/src/client/dist/pwa/assets/{SplitWorkspacePage-Brw2XsYJ.js → SplitWorkspacePage-B99AJUbG.js} +1 -1
- package/src/client/dist/pwa/assets/{TerminalPanel-DVP18-nk.js → TerminalPanel-DnA1MXwu.js} +9 -9
- package/src/client/dist/pwa/assets/{ToolCallItem-cSHh3p_F.js → ToolCallItem-2vFggaur.js} +3 -3
- package/src/client/dist/pwa/assets/{TouchPan-C6LZZ3rw.js → TouchPan-BSU3z0aO.js} +1 -1
- package/src/client/dist/pwa/assets/{TourReplayButton-Czlqo18H.js → TourReplayButton-DQwS2xdD.js} +1 -1
- package/src/client/dist/pwa/assets/{WorkspaceAttentionLabels-BDYGq0pC.js → WorkspaceAttentionLabels-CM1tGDje.js} +1 -1
- package/src/client/dist/pwa/assets/WorkspacePage-B248nKIU.css +1 -0
- package/src/client/dist/pwa/assets/WorkspacePage-CQN6EKIn.js +4 -0
- package/src/client/dist/pwa/assets/{browser-ifCwUGpE.js → browser-DPsupI9Q.js} +1 -1
- package/src/client/dist/pwa/assets/{build-path-tree-BBkGq3lN.js → build-path-tree-CbIdt-p9.js} +1 -1
- package/src/client/dist/pwa/assets/custom-sounds-DR68eyF8.js +1 -0
- package/src/client/dist/pwa/assets/de-D3Ycvf_F.js +17 -0
- package/src/client/dist/pwa/assets/{editor.api-CV_seGJR.js → editor.api-DvgAtnOj.js} +1 -1
- package/src/client/dist/pwa/assets/{efforts-ttw0bL63.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-C8biMRXo.js → expand-template-Brn5O4a1.js} +1 -1
- package/src/client/dist/pwa/assets/{format-Cu95dbxt.js → format-DfJ3Fv1i.js} +1 -1
- package/src/client/dist/pwa/assets/{formatters-C25D1RSd.js → formatters-B30bNFhq.js} +1 -1
- package/src/client/dist/pwa/assets/fr-CJafryNb.js +17 -0
- package/src/client/dist/pwa/assets/{freemarker2-DJEFr74q.js → freemarker2-DnZe74tc.js} +1 -1
- package/src/client/dist/pwa/assets/{handlebars-C_lzsMjX.js → handlebars-CmuMfxzO.js} +1 -1
- package/src/client/dist/pwa/assets/{html-CWmLFAmY.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-tsuiEUe-.js → javascript-Y-OrgcmW.js} +1 -1
- package/src/client/dist/pwa/assets/{liquid-BBr9BvUk.js → liquid-BqOkKePH.js} +1 -1
- package/src/client/dist/pwa/assets/{mdx-gjBH1cXG.js → mdx-D-58x2h0.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco-contributions-CvGcBIsb.js → monaco-contributions-BW4ZMKO6.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco.contribution-Uw-Lfb8Z.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-PMSxqP6t.js → notify-theme-DZDzfxph.js} +1 -1
- package/src/client/dist/pwa/assets/{project-color-BDQa2TgU.js → project-color-gB5yeZPN.js} +1 -1
- package/src/client/dist/pwa/assets/{python-DlIgD1n6.js → python-BkBj1wDC.js} +1 -1
- package/src/client/dist/pwa/assets/{razor-AraFPlpM.js → razor-DOOJ_0b8.js} +1 -1
- package/src/client/dist/pwa/assets/{typescript-CB8UpXHM.js → typescript-Bheo43G5.js} +1 -1
- package/src/client/dist/pwa/assets/{use-search-index-status-CSREHQM4.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-BM3Giig1.js → use-worktree-restore-CgABsWno.js} +1 -1
- package/src/client/dist/pwa/assets/{workspace-templates-BPMFvTnQ.js → workspace-templates-BnFkigYg.js} +1 -1
- package/src/client/dist/pwa/assets/{xml-uRjL1P43.js → xml-vHt2BD26.js} +1 -1
- package/src/client/dist/pwa/assets/{yaml-C9CFR4qV.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 +156 -12
- package/src/mcp-server/kobo-tasks-server.ts +73 -106
- package/src/mcp-server/workspace-dialogue-client.ts +18 -3
- package/src/client/dist/pwa/assets/ActivityFeed-BTRzc60o.js +0 -2
- package/src/client/dist/pwa/assets/CreatePage-BZAoCukb.js +0 -2
- package/src/client/dist/pwa/assets/CreatePage-CdCs8PRS.css +0 -1
- package/src/client/dist/pwa/assets/DiffViewer-D0K2PlZD.js +0 -8
- package/src/client/dist/pwa/assets/GitPanel-C-Ejlc9_.js +0 -3
- package/src/client/dist/pwa/assets/MainLayout-BQOOup9o.css +0 -1
- package/src/client/dist/pwa/assets/MainLayout-DXQ1ouYl.js +0 -2
- package/src/client/dist/pwa/assets/QSelect-MdPXvwm4.js +0 -1
- package/src/client/dist/pwa/assets/SettingsPage-DcV6apqE.js +0 -9
- package/src/client/dist/pwa/assets/WorkspacePage-BwUkijpS.js +0 -4
- package/src/client/dist/pwa/assets/WorkspacePage-Bwfv559o.css +0 -1
- package/src/client/dist/pwa/assets/custom-sounds-CeSgjIY7.js +0 -1
- package/src/client/dist/pwa/assets/de-CHY2BAfL.js +0 -17
- package/src/client/dist/pwa/assets/es-DbcSKp38.js +0 -17
- package/src/client/dist/pwa/assets/fr-CyyJ8S2c.js +0 -17
- package/src/client/dist/pwa/assets/i18n-CobVQiTM.js +0 -1
- package/src/client/dist/pwa/assets/index-ImUH7Bbt.js +0 -20
- package/src/client/dist/pwa/assets/it-XQJW2Xkr.js +0 -17
- package/src/client/dist/pwa/assets/network-auth-D0Vm6a09.js +0 -1
- package/src/client/dist/pwa/assets/use-tours-6yxNVmjH.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
|
|
|
@@ -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
|
|
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
|
|
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
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
- `
|
|
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
|
-
|
|
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.
|
|
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
|
-
:
|
|
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 (
|
|
804
|
-
|
|
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
|
-
:
|
|
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{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)?`}/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};
|