@loicngr/kobo 2.1.15 → 2.1.18
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 +61 -0
- package/CHANGELOG.md +12 -0
- package/CONFIGURATION.md +74 -17
- package/README.md +7 -3
- 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 +8 -0
- package/dist/server/middleware/workspace-creation-maintenance.js +13 -0
- package/dist/server/routes/maintenance.js +32 -0
- package/dist/server/routes/mcp.js +69 -3
- package/dist/server/routes/pull-requests.js +2 -1
- package/dist/server/routes/workspace-messages.js +22 -0
- package/dist/server/routes/workspaces.js +60 -4
- package/dist/server/services/agent/orchestrator.js +21 -6
- package/dist/server/services/agent/session-controller.js +4 -1
- package/dist/server/services/cron-service.js +7 -0
- 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/quota-backoff-service.js +7 -0
- package/dist/server/services/update-maintenance-service.js +44 -0
- package/dist/server/services/wakeup-service.js +21 -3
- package/dist/server/services/workspace-creation-mcp-service.js +214 -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/docker-compose.vps.yml +60 -0
- package/docs/deployment-vps.md +506 -0
- package/docs/getting-started.md +9 -0
- package/package.json +8 -1
- package/scripts/kobo-gh +9 -0
- package/scripts/kobo-github-app-token +190 -0
- package/scripts/kobo-install +392 -0
- package/scripts/kobo-update +499 -0
- package/src/client/dist/pwa/assets/ActivityFeed-S3XGw3WW.js +2 -0
- package/src/client/dist/pwa/assets/{AutoLoopFinalReviewSettings-lKjfaC6V.js → AutoLoopFinalReviewSettings-CC8ICe_I.js} +1 -1
- package/src/client/dist/pwa/assets/{ChangelogPage-BplM7qEB.js → ChangelogPage-C1aiPBoi.js} +1 -1
- package/src/client/dist/pwa/assets/{ComparisonPanel-DxK5v9w_.js → ComparisonPanel-DElbn4ac.js} +1 -1
- package/src/client/dist/pwa/assets/CreatePage-Bx58t4YW.js +2 -0
- package/src/client/dist/pwa/assets/{DashboardPage-BsP-MATS.js → DashboardPage-D1S3QGp4.js} +1 -1
- package/src/client/dist/pwa/assets/DiffViewer-BH4X9ru1.js +8 -0
- package/src/client/dist/pwa/assets/{DocumentsPanel-CGrf8Q3H.js → DocumentsPanel-C4AvyZF3.js} +1 -1
- package/src/client/dist/pwa/assets/{DrawerToggleButton-BANeLSsR.js → DrawerToggleButton-LzIEeeJY.js} +1 -1
- package/src/client/dist/pwa/assets/GitPanel-BP3N_4nh.js +3 -0
- package/src/client/dist/pwa/assets/{HealthPage-BVTNx-92.js → HealthPage-Cm13vxOi.js} +1 -1
- package/src/client/dist/pwa/assets/MainLayout-DGmMkfuw.js +2 -0
- package/src/client/dist/pwa/assets/{MainLayout-C6nvIQjU.css → MainLayout-DoAEgANU.css} +1 -1
- package/src/client/dist/pwa/assets/{MemoryPanel-C71rTpv9.js → MemoryPanel-C6tzCv6D.js} +1 -1
- package/src/client/dist/pwa/assets/{MemoryProposalList-CoqhUGrv.js → MemoryProposalList-CzpzIirK.js} +1 -1
- package/src/client/dist/pwa/assets/{QBtnDropdown-U9Ey5W8h.js → QBtnDropdown-BnuKPevV.js} +1 -1
- package/src/client/dist/pwa/assets/{QExpansionItem-Be_A1MT1.js → QExpansionItem-DjaUhgjt.js} +1 -1
- package/src/client/dist/pwa/assets/{QScrollArea-D4lYzAPQ.js → QScrollArea-Dpksslru.js} +1 -1
- package/src/client/dist/pwa/assets/QSelect-Dh4CjliE.js +1 -0
- package/src/client/dist/pwa/assets/{QTooltip-kStOgY0X.js → QTooltip-CH45NuVs.js} +1 -1
- package/src/client/dist/pwa/assets/{SchedulePanel-C7nTGdWE.js → SchedulePanel-QAjXuTIN.js} +1 -1
- package/src/client/dist/pwa/assets/{SearchPage-C6J72pkQ.js → SearchPage-B1pbwDej.js} +1 -1
- package/src/client/dist/pwa/assets/SettingsPage-CUQefsDh.js +9 -0
- package/src/client/dist/pwa/assets/{SplitWorkspacePage-Bqn4gdVw.js → SplitWorkspacePage-CJ7e-KPh.js} +1 -1
- package/src/client/dist/pwa/assets/{TerminalPanel-zLeDCReI.js → TerminalPanel-DVomz3oo.js} +10 -10
- package/src/client/dist/pwa/assets/{ToolCallItem-B8b_Sagj.js → ToolCallItem-BntDHc6p.js} +3 -3
- package/src/client/dist/pwa/assets/{TouchPan-Bl8VzIDK.js → TouchPan-DwqfsAxH.js} +1 -1
- package/src/client/dist/pwa/assets/{TourReplayButton-pL0HCboU.js → TourReplayButton-OIavXdUg.js} +1 -1
- package/src/client/dist/pwa/assets/{WorkspaceAttentionLabels-9Eomx0rQ.js → WorkspaceAttentionLabels-CYwN1-Rf.js} +1 -1
- package/src/client/dist/pwa/assets/WorkspacePage-CAsY7Hl-.js +4 -0
- package/src/client/dist/pwa/assets/{browser-C-PJ4alw.js → browser-Cl0d1gbu.js} +1 -1
- package/src/client/dist/pwa/assets/{build-path-tree-SW9rj7B_.js → build-path-tree-J3YQeMuV.js} +1 -1
- package/src/client/dist/pwa/assets/{custom-sounds-toz-4drX.js → custom-sounds-D-T5rpgv.js} +1 -1
- package/src/client/dist/pwa/assets/de-C-RtXLqL.js +17 -0
- package/src/client/dist/pwa/assets/{editor.api-BJTPhnh8.js → editor.api-BoFiCjBZ.js} +1 -1
- package/src/client/dist/pwa/assets/{efforts-BO9xGn9q.js → efforts-Y6XotW97.js} +1 -1
- package/src/client/dist/pwa/assets/es-CBlW91ZK.js +17 -0
- package/src/client/dist/pwa/assets/{expand-template-BOZMtWau.js → expand-template-sfOHWudd.js} +1 -1
- package/src/client/dist/pwa/assets/{format-hEnpRyit.js → format-mWov137C.js} +1 -1
- package/src/client/dist/pwa/assets/{formatters-DD_eFc4z.js → formatters-B_XsveM0.js} +1 -1
- package/src/client/dist/pwa/assets/fr-Hfzp_sF5.js +17 -0
- package/src/client/dist/pwa/assets/{freemarker2-B6nUofvT.js → freemarker2-2pGNakhH.js} +1 -1
- package/src/client/dist/pwa/assets/{handlebars-4P3gtM8p.js → handlebars-Bn2V6Liw.js} +1 -1
- package/src/client/dist/pwa/assets/{html-C9ZzWo-y.js → html-BBhkJkf2.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-sBCWwf8Y.js +1 -0
- package/src/client/dist/pwa/assets/index-DCpe-Tfm.js +20 -0
- package/src/client/dist/pwa/assets/it-Bl-v27qg.js +17 -0
- package/src/client/dist/pwa/assets/{javascript-BGEC5ggd.js → javascript-Ba0G68U2.js} +1 -1
- package/src/client/dist/pwa/assets/{liquid-DNtF630O.js → liquid-C5VrzHr_.js} +1 -1
- package/src/client/dist/pwa/assets/{mdx-CICwyOd0.js → mdx-DrPRiuKb.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco-contributions-BkuwvgrT.js → monaco-contributions-qQUY6VS7.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco.contribution-GX8XA-PE.js → monaco.contribution-Dtq2RsyB.js} +2 -2
- package/src/client/dist/pwa/assets/network-auth-BPJpXK4L.js +1 -0
- package/src/client/dist/pwa/assets/notify-theme-Bl9iRsZB.js +1 -0
- package/src/client/dist/pwa/assets/{project-color-B1SMFjSe.js → project-color-Dzvvmaml.js} +1 -1
- package/src/client/dist/pwa/assets/{python-n5uPdTVS.js → python-Cuf_7umq.js} +1 -1
- package/src/client/dist/pwa/assets/{razor-AOj0nsUt.js → razor-DcVpxFeh.js} +1 -1
- package/src/client/dist/pwa/assets/{typescript-Uhkrqihh.js → typescript-Bjcc3kYC.js} +1 -1
- package/src/client/dist/pwa/assets/{use-search-index-status-CW0BdpBZ.js → use-search-index-status-Bwf40BEI.js} +1 -1
- package/src/client/dist/pwa/assets/use-tours-CWafZt-m.js +2 -0
- package/src/client/dist/pwa/assets/{use-worktree-restore-BZxibZS5.js → use-worktree-restore-DkcM2FAZ.js} +1 -1
- package/src/client/dist/pwa/assets/{workspace-templates-W557e1-m.js → workspace-templates-DL_Cs7mD.js} +1 -1
- package/src/client/dist/pwa/assets/{xml-B-hnMgEs.js → xml-BmJwqJOz.js} +1 -1
- package/src/client/dist/pwa/assets/{yaml-DqXkTvgm.js → yaml-DAssfLhS.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/de-Ddxe8cc6.js +0 -17
- 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/AGENTS.md
CHANGED
|
@@ -41,6 +41,7 @@ npm run typecheck:tests # type check backend test fixtures and tests
|
|
|
41
41
|
npm test # backend vitest suite
|
|
42
42
|
(cd src/client && npm test) # client stores, utilities, composables and components
|
|
43
43
|
npm run test:all # backend + client suites
|
|
44
|
+
npm run test:deployment # Python 3.10+ / OpenSSL tooling tests; no Docker/network/production data
|
|
44
45
|
make ci # install, audit, lint, type checks, build, both suites
|
|
45
46
|
npm run test:watch # vitest in watch mode
|
|
46
47
|
|
|
@@ -124,6 +125,7 @@ src/
|
|
|
124
125
|
| `session_event_metrics` / `usage_snapshots` | session event counters and provider usage snapshots |
|
|
125
126
|
| `workspace_activity` | significant lifecycle/PR events for the absence digest |
|
|
126
127
|
| `search_*` | derived logical-message search index and indexing progress; see `search-schema.ts` |
|
|
128
|
+
| `workspace_message_batches` | immutable recipient snapshots, request fingerprint and per-recipient delivery outcomes (v53); no stored message body |
|
|
127
129
|
| `mcp_message_requests` | durable external-message idempotency ledger; independent of event retention |
|
|
128
130
|
| `schema_migrations` | applied migration versions, names and timestamps |
|
|
129
131
|
| `workspace_chat_history` | chat-input history per workspace: message text + `created_at`, ordered by autoincrement id, capped at 200 entries by the service; CASCADE DELETE on workspace |
|
|
@@ -211,6 +213,8 @@ Claude SDK `task_*` messages cover agents **and** background jobs. Preserve `tas
|
|
|
211
213
|
|
|
212
214
|
### External MCP dialogue and update checks
|
|
213
215
|
|
|
216
|
+
Workspace-bound stdio, global stdio and HTTP expose shared `create_workspace` / `diagnose_workspace_pr` definitions in `src/shared/workspace-creation-tools.ts`. Stdio forwards them to the backend. `workspace-creation-mcp-service.ts` validates inputs and dispatches through the existing workspace and PR routes in process; preserve UI validation, checkout fingerprints, lifecycle guards and rollback. Inputs use snake_case except the existing camelCase PR checkout decisions. Attachments are inline base64, bounded to the shared 10-file/50 MiB decoded limit before checkout mutations. Keep SDK HTTP/stdio limits aligned with the shared creation envelope budget. Only creation envelopes get the larger base64 HTTP body budget; ordinary MCP requests remain 1 MiB. After a known PR creation failure, rollback only a checkout proven newly created, unchanged, clean (including ignored files) and unowned, under the Git repository lock; retain other checkouts and expose recovery details. Do not retry uncertain creation automatically or treat `creation_id` as an idempotency key. Restricted review/handoff launches cannot create workspaces or invoke lifecycle mutations. Shared lifecycle tools (`workspace-lifecycle-tools.ts` / `workspace-lifecycle-mcp-service.ts`) also expose archive, purge, delete, unarchive and restore through the same transports. Reject reserved/bulk/path-like IDs, require explicit purge/delete flags, and preserve warnings. Single-workspace DELETE accepts optional `confirmationBranch`, rechecked under its lifecycle guard before stopping processes; MCP always supplies it. Branch rename/resync share this guard so the confirmed branch and checkout path cannot change during teardown. Recovery uses `restore-worktree` before unarchive and never starts an agent.
|
|
217
|
+
|
|
214
218
|
`POST /api/mcp` exposes stateless Streamable HTTP discovery/dialogue tools, behind the existing Host/Origin and network-token gates. The global stdio server forwards dialogue calls through this backend so agent runtime ownership remains centralized. `workspace-message-service.ts` shares delivery with WebSocket chat.
|
|
215
219
|
|
|
216
220
|
Optional `send_workspace_message.idempotency_key` uses `mcp_message_requests` (migration v43). Reserve before dispatch, preserve fingerprints and terminal outcomes, and never automatically resend an unfinished request after restart. The user event and accepted receipt commit in one SQLite transaction; broadcast follows commit. Dispatch may precede persistence, so interrupted requests are explicitly `delivery_unknown`, not an exactly-once execution promise. The backend alone calls `reconcileMessageRequests` on startup. History retention does not delete the ledger; workspace deletion cascades it.
|
|
@@ -245,6 +249,57 @@ Two emit flavors in `websocket-service.ts`:
|
|
|
245
249
|
|
|
246
250
|
## External integrations
|
|
247
251
|
|
|
252
|
+
### VPS deployment tooling
|
|
253
|
+
|
|
254
|
+
`docker-compose.vps.yml` is the standalone fresh-instance example for an existing
|
|
255
|
+
Traefik network. It persists `/root`, mounts project paths identically on host and
|
|
256
|
+
container, and leaves the Docker socket opt-in. `docs/deployment-vps.md` owns the
|
|
257
|
+
operator walkthrough; `CONFIGURATION.md` compares all Compose topologies.
|
|
258
|
+
|
|
259
|
+
`scripts/kobo-install` guides a fresh installation; it does not adopt existing
|
|
260
|
+
unmanaged stacks. Preserve its read-only `--check`, explicit Docker-socket opt-in,
|
|
261
|
+
managed-file fingerprints, shared updater lock, and durable start phases. Completed
|
|
262
|
+
reruns must never restart containers or replace credentials. Only the updater's
|
|
263
|
+
image pin may differ from the managed Compose fingerprint. Interrupted starts
|
|
264
|
+
require diagnosis instead of automatic recreation. This tool reuses the updater's
|
|
265
|
+
release resolution/build helpers; no separate public image registry is assumed.
|
|
266
|
+
|
|
267
|
+
Optional GitHub App authentication uses `scripts/kobo-gh` and
|
|
268
|
+
`scripts/kobo-github-app-token`, mounted read-only with the user's RSA key. The
|
|
269
|
+
wrapper honors explicit `GH_TOKEN` / `GITHUB_TOKEN`, otherwise obtains a renewable
|
|
270
|
+
installation token from the private, locked cache. Keep JWTs, PEM contents and
|
|
271
|
+
tokens out of logs/errors. HTTPS Git uses the same wrapper via a github.com-scoped
|
|
272
|
+
credential helper; commit author identity is configured separately. Never bake
|
|
273
|
+
credentials into an image or add web settings for these host-managed secrets.
|
|
274
|
+
|
|
275
|
+
`scripts/kobo-update` is a Python 3.10+ operator tool for this layout, separate
|
|
276
|
+
from the backend's update notifications. Preserve explicit invocation, idle checks,
|
|
277
|
+
unique image tags, private stopped-volume backups, migration tests on a copy,
|
|
278
|
+
and durable failure records. The authenticated `/api/maintenance/update` reservation
|
|
279
|
+
checks runtime ownership and closes agent admission synchronously before shutdown.
|
|
280
|
+
Track in-flight workspace creations and PR checkouts through all asynchronous
|
|
281
|
+
steps and rollback. Maintenance and creation admission must exclude each other
|
|
282
|
+
synchronously; rejecting only the final agent start is too late. Direct HTTP and
|
|
283
|
+
MCP creation paths share this guard.
|
|
284
|
+
Scheduled cron/wakeup/quota deliveries defer without consuming persisted intent;
|
|
285
|
+
valid pending wakeups get `retry_at=target_at` before successful reservation (only
|
|
286
|
+
when `retry_at` was null), so long maintenance does not expire them at boot. Keep
|
|
287
|
+
the requested deadline/prompt and existing retry metadata; do not revive already
|
|
288
|
+
stale, never-attempted wakeups. New wakeups scheduled during maintenance get the
|
|
289
|
+
same durable deferral marker.
|
|
290
|
+
Replaced timers must invalidate deferred callbacks. Legacy runtimes without this
|
|
291
|
+
protocol refuse automatic deployment while `--prepare` remains available. Failed
|
|
292
|
+
pre-stop requests release their reservation; uncertain stops must be confirmed
|
|
293
|
+
before restarting. A crashed updater may require the documented manual recovery. Never automatically run an old image against a
|
|
294
|
+
potentially migrated database or restore snapshots after new work may have run.
|
|
295
|
+
No deployment-specific hostname, App credentials or private source paths belong
|
|
296
|
+
in this script. Its deterministic tests run through `npm run test:deployment`,
|
|
297
|
+
`make ci` and the PR workflow; live deployment is not a CI test.
|
|
298
|
+
Installer/updater Compose commands explicitly select the stack's `.env` and strip
|
|
299
|
+
the template's Kobo/Traefik interpolation variables plus `COMPOSE_*` from a copy
|
|
300
|
+
of the child environment. Apply the same policy to captured and logged commands;
|
|
301
|
+
never mutate the parent environment or lose Docker/forge authentication settings.
|
|
302
|
+
|
|
248
303
|
### Forge providers
|
|
249
304
|
|
|
250
305
|
`src/server/services/forge/` implements `ForgeProvider` for `github` (`gh`), `gitlab` (`glab`), `bitbucket-community` (`bkt`), and `none` (disables PR/MR features). `getForgeProvider(id)` resolves a provider; `resolveForge(projectPath)` first reads the explicit project setting, then classifies the `origin` URL by `github.com`, `gitlab`, or `bitbucket`, falling back to `none`. Successful remote reads are cached for five minutes. PR routes and the watcher use the resolved provider. **Kōbō ships no forge credentials**: users authenticate `gh`/`glab` themselves; Bitbucket uses the email and API token entered in Settings, passed to `bkt` and excluded from settings exports. Missing or unauthenticated CLIs disable PR/MR actions with an explanation.
|
|
@@ -527,6 +582,12 @@ unread events or deleting history; re-enabling resumes from the saved checkpoint
|
|
|
527
582
|
|
|
528
583
|
`src/client/src/utils/workspace-attention.ts` derives a small set of badges (CI failure, changes-requested) from the PR snapshot + git stats stored on each workspace. `WorkspaceAttentionLabels.vue` renders them inline on the workspace cards in the left drawer. The derivation is a pure function, easy to unit-test and free of IO. Drawer cards therefore stay reactive to whatever the pr-watcher / bulk-info refresh writes back into the store.
|
|
529
584
|
|
|
585
|
+
### Group workspace messages
|
|
586
|
+
|
|
587
|
+
`workspace-group-message-service.ts` owns durable group receipts and a server-wide queue capped at three simultaneous delivery attempts. `/api/workspace-messages` creates a batch (202); `/:id` reads its receipt. Requests contain explicit unique workspace IDs, one content body and a stable request ID. A repeated ID is observation only; different content/recipients/source conflicts. Tags/status filters are preview helpers (OR within each group, AND across groups); archived/purged workspaces cannot receive messages. The optional `devServerRunning` preview filter (`dev_server_running` in MCP) requires exact dev-server status `running` and intersects tags/statuses. The dialog prefers the live dev-server store status over the workspace snapshot. Both UI tag filters union the global settings catalogue with assigned tags via `collectTags`; unused catalogue tags have count zero. Wait for settings and archived workspaces to load before pruning a remembered sidebar selection.
|
|
588
|
+
|
|
589
|
+
Snapshot each target and its delivery mode before scheduling: auto-loop uses `next_iteration`, other workspaces normal chat. Preserve common lifecycle, human-input and compaction guards; a mode change rejects instead of silently changing behavior. For an existing controller, run `beforeDispatch` after its startup promise settles and immediately before engine delivery, so a mode change during startup is still rejected. Receipt completion means accepted/queued, not an agent reply. Persist chat event and accepted outcome transactionally; failed notification cannot undo acceptance. Startup marks pending targets `not_sent` and in-flight ones `unknown`, never replays them. Shutdown stops scheduling before draining active attempts and closing SQLite. Migration v53 and fresh schema share `initWorkspaceMessageBatchSchema`. The dialog stores its immutable request in per-tab sessionStorage for reload recovery and never POSTs automatically on reload. Keep all MCP transports on the same backend owner, launch read-only restrictions and own-workspace exclusion.
|
|
590
|
+
|
|
530
591
|
### Bulk workspace info refresh
|
|
531
592
|
|
|
532
593
|
`GET /api/workspaces/info` returns `{ workspaces, prSnapshots, gitStats }` in one shot. The client polls this endpoint every 15 s so every non-archived workspace stays ≤ 15 s fresh without a per-card stats request. The poll is skipped while the browser tab is hidden and fires once immediately when the tab becomes visible again. The server-side pr-watcher feeds the same caches (`lastKnownGitStats` map, PR snapshots) so the work is shared between the polling client and the watchdog loop.
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@ All notable changes to Kōbō are documented here. The format is based on
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/). Each release is an `## <version>`
|
|
5
5
|
section — the in-app "What's new" dialog reads this file.
|
|
6
6
|
|
|
7
|
+
## 2.1.18
|
|
8
|
+
|
|
9
|
+
- fix(tests): resolve deployment paths on macos
|
|
10
|
+
|
|
11
|
+
## 2.1.17
|
|
12
|
+
|
|
13
|
+
- feat(kobo): add VPS tooling and touch drawer resizing
|
|
14
|
+
|
|
15
|
+
## 2.1.16
|
|
16
|
+
|
|
17
|
+
- feat(workspaces): add group messaging and expand mcp lifecycle tools
|
|
18
|
+
|
|
7
19
|
## 2.1.15
|
|
8
20
|
|
|
9
21
|
- feat(auto-loop): add durable final reviews and correction cycles
|
package/CONFIGURATION.md
CHANGED
|
@@ -35,6 +35,8 @@ Reference for Kōbō configuration and external integrations. The tables cover t
|
|
|
35
35
|
- [Quick local test — docker-compose.local.yml](#quick-local-test--docker-composelocalyml)
|
|
36
36
|
- [Rehearsing the reverse-proxy stack — docker-compose.local-traefikyml](#rehearsing-the-reverse-proxy-stack--docker-composelocal-traefikyml)
|
|
37
37
|
- [Real deployment — docker-compose.exampleyml](#real-deployment--docker-composeexampleyml)
|
|
38
|
+
- [VPS with existing Traefik](#vps-with-existing-traefik)
|
|
39
|
+
- [Docker updates and backups](#docker-updates-and-backups)
|
|
38
40
|
- [Troubleshooting](#troubleshooting)
|
|
39
41
|
- [Agent runtimes](#agent-runtimes)
|
|
40
42
|
- [Claude Code](#claude-code)
|
|
@@ -255,6 +257,12 @@ When working from source, `npm run install-all` installs the root, client, and
|
|
|
255
257
|
build-only Workbox dependencies and is required by `npm run build`, but not by
|
|
256
258
|
the normal development client.
|
|
257
259
|
|
|
260
|
+
On touch screens, drag the inner edge of either side panel to resize it. The
|
|
261
|
+
horizontal divider inside the right panel can also be dragged. Widths and the
|
|
262
|
+
split position are remembered in this browser; on small screens, a strip of the
|
|
263
|
+
chat remains visible so you can tap outside a panel to close it. Sidebar actions
|
|
264
|
+
wrap onto additional lines when needed, keeping Settings and Create accessible.
|
|
265
|
+
|
|
258
266
|
## Custom change-source-branch script
|
|
259
267
|
|
|
260
268
|
The **Change source branch** action uses the effective `changeSourceBranchScript`:
|
|
@@ -502,6 +510,18 @@ Two consequences worth knowing:
|
|
|
502
510
|
- However far behind the loop falls (a laptop waking from sleep, say), it sends
|
|
503
511
|
**one** reminder per pass, not one per missed interval.
|
|
504
512
|
|
|
513
|
+
## Group workspace messages
|
|
514
|
+
|
|
515
|
+
Use **Group message** in the workspace drawer to send the same instruction to several workspaces. Combine tags and workspace statuses, review the named recipient list, and deselect individual workspaces before sending. Selected tags match any tag; selected statuses match any status; both filter groups must match. Enable **Only workspaces with a running development server** to further restrict the list to server status `running`; starting, stopping, stopped, unknown and failed servers are excluded. This filter updates with received server status changes. Empty filters include all non-archived, non-purged workspaces. Both the group-message tag selector and the sidebar tag filter include the saved global tag catalogue plus tags already assigned to workspaces. Unused catalogue tags remain visible (with a zero count in the sidebar); assign a tag through a workspace menu before using it to target that workspace.
|
|
516
|
+
|
|
517
|
+
Auto-loop recipients receive a durable instruction for their next iteration, without stopping the loop. Other recipients use normal chat delivery, which can resume an inactive agent. Normal chat restrictions still apply: pending human questions, compaction and lifecycle operations are not bypassed. The delivery mode is fixed when submitted; a subsequent auto-loop mode change rejects that recipient rather than silently switching modes.
|
|
518
|
+
|
|
519
|
+
Each batch accepts up to 200 recipients and 100,000 characters of plain text. Delivery runs on the server with at most three attempts in flight. Results distinguish sent, queued for auto-loop, rejected, not sent and uncertain delivery. **Queued** means saved for an upcoming iteration, not already processed by the agent. Failures do not stop delivery to the other recipients.
|
|
520
|
+
|
|
521
|
+
Closing the dialog or disconnecting the browser does not cancel an accepted batch. The browser tab retains the request ID and original payload across reloads to recover its receipt, independently of other tabs. **Check this delivery** reuses that request and cannot duplicate accepted deliveries. After a server restart, unstarted recipients become **Not sent**, and interrupted attempts become **Delivery uncertain**; they are never resent automatically. Inspect the affected workspace's chat before creating a new message for an uncertain result.
|
|
522
|
+
|
|
523
|
+
The same feature is available through internal/global stdio and external HTTP MCP; see the [MCP guide](src/mcp-server/README.md). Batch receipts retain recipient names, IDs, modes and outcomes independently of workspace deletion. The batch table stores a content fingerprint, not the message body; accepted messages retain normal workspace chat history.
|
|
524
|
+
|
|
505
525
|
## Attachments in the creation description
|
|
506
526
|
|
|
507
527
|
On the create page, paste or drop files into the mission description, or use
|
|
@@ -827,7 +847,7 @@ The setting lives in `settings.json` under `global` and has no UI control yet:
|
|
|
827
847
|
|
|
828
848
|
## External LLM access through MCP
|
|
829
849
|
|
|
830
|
-
An external MCP client can connect to `http://127.0.0.1:3000/api/mcp` using Streamable HTTP (use your backend's configured port). It can discover workspaces, read their conversations, send messages
|
|
850
|
+
An external MCP client can connect to `http://127.0.0.1:3000/api/mcp` using Streamable HTTP (use your backend's configured port). It can create workspaces with the same options as the form, diagnose and check out existing PRs, discover workspaces, read their conversations, send messages and answer pending questions. Workspace-bound stdio, global stdio and HTTP share the creation contract, including brainstorm settings, final auto-loop review, workflow policy, imports, existing worktrees, comparison grouping and inline attachments. All three transports also expose archive, disk-space purge, permanent deletion with explicit branch confirmation, unarchive and recovery of purged worktrees. See the [creation and PR guide](src/mcp-server/README.md#create_workspace) for fields, explicit checkout decisions and timeout/retry behavior.
|
|
831
851
|
|
|
832
852
|
Remote HTTP clients follow the [network access](#network-access) settings and supply the Kōbō token as `Authorization: Bearer <token>` or `X-Kobo-Token`. The endpoint uses the same Host/Origin and reverse-proxy policy as the rest of the backend. No network setting is changed automatically.
|
|
833
853
|
|
|
@@ -1096,27 +1116,31 @@ For the concrete Docker setup (which compose file, which env vars, which volumes
|
|
|
1096
1116
|
|
|
1097
1117
|
## Docker deployment
|
|
1098
1118
|
|
|
1099
|
-
Kōbō ships an official `Dockerfile` and
|
|
1100
|
-
|
|
1119
|
+
Kōbō ships an official `Dockerfile` and four Compose examples. Choose whether you
|
|
1120
|
+
want to reuse host credentials or start an independent instance, and whether
|
|
1121
|
+
Traefik already exists. Build the image from a source checkout before starting.
|
|
1101
1122
|
|
|
1102
1123
|
### Choosing a compose file
|
|
1103
1124
|
|
|
1104
1125
|
| File | Topology | Use it for |
|
|
1105
1126
|
|---|---|---|
|
|
1106
|
-
| [`docker-compose.local.yml`](./docker-compose.local.yml) | Kōbō only, port `3000` published directly. No Traefik,
|
|
1127
|
+
| [`docker-compose.local.yml`](./docker-compose.local.yml) | Kōbō only, port `3000` published directly. No Traefik, TLS, published SSH port or Docker socket. | Trying Kōbō out or poking at a change locally — the fastest path to a running instance. |
|
|
1107
1128
|
| [`docker-compose.local-traefik.yml`](./docker-compose.local-traefik.yml) | Traefik in front, Kōbō never on a published port, `kobo.localhost` (auto-resolves to `127.0.0.1`, no `/etc/hosts` editing). | Rehearsing the exact reverse-proxy topology/auth/mount setup locally, before touching a real VPS. |
|
|
1129
|
+
| [`docker-compose.vps.yml`](./docker-compose.vps.yml) | Kōbō only, existing external Traefik network, persistent `/root`, no published ports or host credential mounts. | A fresh, independent remote instance; follow the [VPS guide](./docs/deployment-vps.md). |
|
|
1108
1130
|
| [`docker-compose.example.yml`](./docker-compose.example.yml) | Traefik + Let's Encrypt (real domain, real TLS), SSH access, optional Docker-socket passthrough. | The reference for an actual production deployment (VPS behind a reverse proxy). |
|
|
1109
1131
|
|
|
1110
|
-
All
|
|
1111
|
-
|
|
1112
|
-
|
|
1132
|
+
All four use the same underlying image (`docker build -t kobo:local .` from the
|
|
1133
|
+
repo root). The original three mount provider/Git configuration from the host;
|
|
1134
|
+
the VPS example keeps independent credentials in a persistent container home.
|
|
1135
|
+
The VPS example overrides the entrypoint to start Node directly; the original
|
|
1136
|
+
files use the image entrypoint, which starts `sshd` even when port 22 is not published.
|
|
1113
1137
|
|
|
1114
1138
|
### The image
|
|
1115
1139
|
|
|
1116
1140
|
Multi-stage `Dockerfile`, `node:24.19.0-slim` base:
|
|
1117
1141
|
|
|
1118
1142
|
- **Build stage**: installs root, client, and PWA deps, then runs `npm run build` (client via Quasar, server via `tsc`).
|
|
1119
|
-
- **Runtime stage**: production deps only, plus OS tooling the agents and forge CLIs need — `git`, `openssh-client` + `openssh-server`, `ripgrep`, `fd-find`, `jq`, `curl`, Python + build tools, the Docker CLI (talks to a mounted host socket, see [Volumes](#volumes)), `gh`, `glab`, `tmux`, `vim`/`nano`, and more for interactive shell use.
|
|
1143
|
+
- **Runtime stage**: production deps only, plus OS tooling the agents and forge CLIs need — `git`, `openssh-client` + `openssh-server`, `ripgrep`, `fd-find`, `jq`, `curl`, Python + build tools, the Docker CLI and Compose plugin (`docker compose`, talks to a mounted host socket, see [Volumes](#volumes)), `gh`, `glab`, `tmux`, `vim`/`nano`, and more for interactive shell use.
|
|
1120
1144
|
- **Global CLIs**: `@anthropic-ai/claude-code` and `@openai/codex` are installed globally for interactive login (`claude /login`, `codex login` over SSH) — separate from Kōbō's own bundled SDK/subprocess usage, which needs no global install.
|
|
1121
1145
|
- **`git config --system --add safe.directory '*'`**: the container runs as root, but bind-mounted project repos keep the *host* user's UID on disk. Git's ownership check (CVE-2022-24765 mitigation) refuses to touch a repo it doesn't "own" unless told otherwise. This line pre-trusts every mounted path — fine for a single-user dev container, not something you'd do on a multi-tenant host.
|
|
1122
1146
|
- **`HEALTHCHECK`**: `curl -f http://localhost:$SERVER_PORT/api/health` every 30s. `/api/health` is exempt from the network-access token gate unconditionally (even with `networkAccessBehindProxy` on) — Docker's `HEALTHCHECK` process has no way to supply the runtime-generated token, and without the exemption a "behind a reverse proxy" container never reports healthy, which makes Traefik's Docker provider filter it out of its router table entirely (Traefik drops unhealthy/starting containers from routing).
|
|
@@ -1130,11 +1154,13 @@ Beyond the [general environment variables](#environment-variables), these matter
|
|
|
1130
1154
|
|
|
1131
1155
|
| Variable | Where it's set | Purpose |
|
|
1132
1156
|
|---|---|---|
|
|
1133
|
-
| `KOBO_HOME` | all
|
|
1134
|
-
| `KOBO_NETWORK_ACCESS_ENABLED` | all
|
|
1135
|
-
| `KOBO_NETWORK_ACCESS_BEHIND_PROXY` | `local-traefik.yml`, `example.yml` | Disables the loopback-trust bypass entirely — needed whenever a reverse proxy sits in front, since every request now looks like it comes from the proxy. Not set in `local.yml`, which publishes Kōbō's port directly with no proxy in between. |
|
|
1136
|
-
| `GH_TOKEN` / `GITLAB_TOKEN` | `example.yml` (bring your own via `.env`) |
|
|
1137
|
-
| `KOBO_PROJECTS_DIR` |
|
|
1157
|
+
| `KOBO_HOME` | all four Compose files | Kōbō state at `/root/.config/kobo`, inside a named volume; survives `docker compose down` without `-v`. The VPS example persists all of `/root`. |
|
|
1158
|
+
| `KOBO_NETWORK_ACCESS_ENABLED` | all four | Binds Kōbō to `0.0.0.0` inside the container at boot — required, since a Docker-forwarded or Traefik-proxied connection never looks like true loopback from Kōbō's point of view, even locally. Without it the container's `127.0.0.1`-only default blocks every external route. |
|
|
1159
|
+
| `KOBO_NETWORK_ACCESS_BEHIND_PROXY` | `local-traefik.yml`, `example.yml`, `vps.yml` | Disables the loopback-trust bypass entirely — needed whenever a reverse proxy sits in front, since every request now looks like it comes from the proxy. Not set in `local.yml`, which publishes Kōbō's port directly with no proxy in between. |
|
|
1160
|
+
| `GH_TOKEN` / `GITLAB_TOKEN` | `example.yml` (bring your own via `.env`) | Optional non-interactive auth for `gh`/`glab` inside the container. A CLI browser/device flow can also be completed from your PC. Expiring GitHub App tokens need a [renewable helper](./docs/deployment-vps.md#github-app-bot-authentication), not a static token in `.env`. |
|
|
1161
|
+
| `KOBO_PROJECTS_DIR` | Compose interpolation on the host | Original three: source for `/projects`, default `./kobo-projects`. VPS: required existing absolute directory, mounted at the **same path** in the container for host-Docker bind mounts. |
|
|
1162
|
+
| `KOBO_DOMAIN` | `vps.yml`, Compose interpolation | Public hostname used in the Traefik rule and `KOBO_NETWORK_ACCESS_PROXY_HOST`; required, without `https://`. |
|
|
1163
|
+
| `TRAEFIK_NETWORK` / `TRAEFIK_ENTRYPOINT` / `TRAEFIK_CERTRESOLVER` | `vps.yml`, Compose interpolation | Existing Traefik names; defaults `web` / `websecure` / `letsencrypt`. Adapt to your proxy. |
|
|
1138
1164
|
|
|
1139
1165
|
Both `KOBO_NETWORK_ACCESS_ENABLED` and `KOBO_NETWORK_ACCESS_BEHIND_PROXY` are read once at boot —
|
|
1140
1166
|
this is what lets the container come up fully configured with no manual Settings-UI step (there's
|
|
@@ -1145,14 +1171,15 @@ retrieved once — see [Token / login flow](#token--login-flow).
|
|
|
1145
1171
|
|
|
1146
1172
|
| Mount | RO/RW | Purpose |
|
|
1147
1173
|
|---|---|---|
|
|
1174
|
+
| `kobo-home:/root` (`vps.yml`, named volume) | RW | Independent Kōbō, Claude/Codex, Git and forge state. Authenticate inside the container; no PC credential files are required. |
|
|
1148
1175
|
| `<name>-data:/root/.config/kobo` (named volume) | RW | Kōbō's own state — SQLite DB, `settings.json`, etc. Named (not bind-mounted) so it survives `docker compose down` without `-v`, and so it can be deliberately **shared** across compose files by reusing the same volume name (see the two local files, both `kobo-test-data`). |
|
|
1149
|
-
| `${KOBO_PROJECTS_DIR:-./kobo-projects}:/projects` | RW |
|
|
1176
|
+
| `${KOBO_PROJECTS_DIR:-./kobo-projects}:/projects` | RW | Your project repositories. Ensure the configured worktree directory is also persistent. For project Docker bind mounts, use the VPS example’s identical host/container paths; see the [path setup](./docs/deployment-vps.md#optional-project-docker-environments). |
|
|
1150
1177
|
| `${HOME}/.ssh:/root/.ssh` | RO | Git SSH auth — needed to push/pull over SSH remotes. |
|
|
1151
1178
|
| `${HOME}/.gitconfig:/root/.gitconfig` | RO | Commit author identity. Without it, agent-authored commits fail with "please tell me who you are". |
|
|
1152
1179
|
| `${HOME}/.claude.json:/root/.claude.json` | RW | Claude Code auth + MCP server registrations. **Must already exist as a file on the host** (run `claude /login` locally first if it doesn't) — Docker silently creates an empty *directory* instead of erroring on a missing bind-mount source, which breaks auth in a confusing way. |
|
|
1153
1180
|
| `${HOME}/.claude:/root/.claude` | RW | Claude Code's full state directory (skills/plugins cache, session transcripts, memory) — Claude Code writes into this tree during normal operation. |
|
|
1154
1181
|
| `${HOME}/.codex:/root/.codex` | RW | Codex's full state directory (`auth.json`, `config.toml`). Harmless to mount even if you only use Claude Code. |
|
|
1155
|
-
| `/var/run/docker.sock:/var/run/docker.sock` | RW, `example.yml`
|
|
1182
|
+
| `/var/run/docker.sock:/var/run/docker.sock` | RW, enabled in `example.yml`, opt-in in `vps.yml` | Lets the dev-server panel start/stop/tail logs for your projects' own `docker compose` stacks. **DANGER**: grants the container root-equivalent control over the host — anyone who can run a command inside `kobo` can spawn a privileged container that mounts the host filesystem. Remove this line if you don't need the dev-server panel. |
|
|
1156
1183
|
|
|
1157
1184
|
### Ports
|
|
1158
1185
|
|
|
@@ -1163,12 +1190,12 @@ retrieved once — see [Token / login flow](#token--login-flow).
|
|
|
1163
1190
|
| `443` | `example.yml` | Traefik's TLS entrypoint (Let's Encrypt via the `le` cert resolver), routes your real domain. |
|
|
1164
1191
|
| `2222 → 22` (published) | `example.yml` only | SSH into the container (`ssh -p 2222 root@<host>`), key-based auth only via the same mounted `~/.ssh`, no password login. The one exception to "no published ports" — Traefik proxies HTTP/WS, not raw TCP, so SSH needs its own path. Rebuilding the image regenerates the SSH host keys, so expect a one-time "host key changed" warning from your SSH client after a rebuild. |
|
|
1165
1192
|
|
|
1166
|
-
Kōbō itself is **never** published directly in the
|
|
1193
|
+
Kōbō itself is **never** published directly in the three Traefik-fronted files — only Traefik is
|
|
1167
1194
|
reachable, matching the real production posture.
|
|
1168
1195
|
|
|
1169
1196
|
### Token / login flow
|
|
1170
1197
|
|
|
1171
|
-
All
|
|
1198
|
+
All four files pre-enable network access via env vars, so there's no interactive Settings-UI step.
|
|
1172
1199
|
Retrieve the auto-generated token from the container's boot log, then bookmark the token-bearing URL:
|
|
1173
1200
|
|
|
1174
1201
|
```bash
|
|
@@ -1252,6 +1279,36 @@ Open `https://kobo.example.com/?token=<token>`. SSH access: `ssh -p 2222 root@<h
|
|
|
1252
1279
|
only). See [Ports](#ports) and [Volumes](#volumes) above for the full rationale on the Docker socket
|
|
1253
1280
|
and SSH tradeoffs before deploying this to a real machine.
|
|
1254
1281
|
|
|
1282
|
+
### VPS with existing Traefik
|
|
1283
|
+
|
|
1284
|
+
Use [`docker-compose.vps.yml`](./docker-compose.vps.yml) and the
|
|
1285
|
+
[step-by-step VPS guide](./docs/deployment-vps.md). This variant runs only Kōbō,
|
|
1286
|
+
keeps agent credentials in its own persistent home and joins an existing proxy
|
|
1287
|
+
network. Your browser's project picker refers to paths on the VPS/container,
|
|
1288
|
+
not your PC. The guide covers provider login, GitHub App bots, Git HTTPS helpers
|
|
1289
|
+
and the host/container path requirements for project Docker environments.
|
|
1290
|
+
|
|
1291
|
+
For a fresh stack, [`scripts/kobo-install`](./scripts/kobo-install) guides domain,
|
|
1292
|
+
project path, existing Traefik settings and optional GitHub App credentials.
|
|
1293
|
+
`--check` performs read-only checks; `--prepare` builds without starting. Its
|
|
1294
|
+
private manifest protects managed files from accidental replacement. GitHub App
|
|
1295
|
+
configuration stays in private host files with read-only container mounts, outside
|
|
1296
|
+
web settings. See the [guided installation](./docs/deployment-vps.md#guided-installation)
|
|
1297
|
+
and [manual bot configuration](./docs/deployment-vps.md#github-app-bot-authentication).
|
|
1298
|
+
|
|
1299
|
+
### Docker updates and backups
|
|
1300
|
+
|
|
1301
|
+
For the independent VPS layout, [`scripts/kobo-update`](./scripts/kobo-update)
|
|
1302
|
+
accepts a stable version or defaults to npm `latest`. It has `--check`, `--prepare`
|
|
1303
|
+
and `--directory` options. See [updater usage and recovery](./docs/deployment-vps.md#update-with-kobo-update).
|
|
1304
|
+
This is an operator-invoked script; the in-app update banner does not install releases.
|
|
1305
|
+
|
|
1306
|
+
A source-built image needs rebuilding for a new release; `docker compose pull`
|
|
1307
|
+
alone cannot update an image that only exists locally. Recreate the service
|
|
1308
|
+
with the same persistent volumes and credential mounts after backing up its
|
|
1309
|
+
state. Preserve repository/worktree data and project databases separately.
|
|
1310
|
+
An old image alone is not a complete rollback after database migrations.
|
|
1311
|
+
|
|
1255
1312
|
### Troubleshooting
|
|
1256
1313
|
|
|
1257
1314
|
- **`fatal: detected dubious ownership in repository at '/projects/<name>'`** — only happens if you
|
package/README.md
CHANGED
|
@@ -22,6 +22,8 @@ Kōbō (工房, "workshop") turns *one agent, one terminal* into a real workflow
|
|
|
22
22
|
| **🔁 Auto-loop, cron, wakeups** | Turn an agent loose on the task list: it works through tasks, retries on rate limits, and stops itself when there's nothing left, progress stalls, or it needs you. An optional [independent final review](CONFIGURATION.md#final-review-for-auto-loop) returns findings to the original session for automatic correction and repeats until none remain, with durable recovery after restart. Cron schedules and one-shot wakeups keep workspaces moving on their own timeline. |
|
|
23
23
|
| **🔀 Create from a PR/MR, or from a ticket** | Pick an open pull/merge request from GitHub, GitLab, or Bitbucket and Kōbō resolves every local conflict for you before spinning up the workspace. Or start straight from a Notion page or a Sentry issue URL. |
|
|
24
24
|
|
|
25
|
+
Use [group messages](CONFIGURATION.md#group-workspace-messages) to send one instruction to selected workspaces, filtered by tags and status, from the interface or MCP. Auto-loops receive it on their next iteration; each recipient has a durable delivery result.
|
|
26
|
+
|
|
25
27
|
## Quick start
|
|
26
28
|
|
|
27
29
|
Requires Node.js ≥ 24.15, Git, Bash and your own Claude Code **or** Codex authentication. No skill plugin or ticket integration is required. Kōbō is a local, single-user tool; each user installs their own instance.
|
|
@@ -51,7 +53,7 @@ Kōbō's production build is an installable PWA — use your browser's **Install
|
|
|
51
53
|
- **Attachments from the first prompt**: paste, drop or attach screenshots, Markdown, text files or PDFs to the creation description or workspace chat. Kōbō copies them into the worktree for the agent. See [creation attachments](./CONFIGURATION.md#attachments-in-the-creation-description) and [chat attachments](./CONFIGURATION.md#attachments-in-workspace-chat).
|
|
52
54
|
|
|
53
55
|
- **Command palette & search**: `/` autocompletes skills and commands, `@` fuzzy-completes worktree file paths, `Ctrl+F` searches readable messages, `Ctrl+K` opens a command palette. The global search page deep-links to the same spot.
|
|
54
|
-
- **MCP toolset (`kobo-tasks`)**: task/acceptance-criteria CRUD, dev server control, a unified `get_ticket` (Notion or Sentry), cross-workspace conversation search, per-session usage, and a `.ai/thoughts` decision log — see the [MCP guide](./src/mcp-server/README.md#tools).
|
|
56
|
+
- **MCP toolset (`kobo-tasks`)**: task/acceptance-criteria CRUD, dev server control, a unified `get_ticket` (Notion or Sentry), cross-workspace conversation search, per-session usage, and a `.ai/thoughts` decision log — see the [MCP guide](./src/mcp-server/README.md#tools). Internal and external MCP clients can create fully configured workspaces, including final auto-loop review, attachments and checkout of an existing PR. HTTP and stdio clients can also archive, purge, delete or restore workspaces, read conversations, send messages, and answer questions.
|
|
55
57
|
|
|
56
58
|

|
|
57
59
|
- **Multi-forge Git panel**: GitHub (`gh`), GitLab (`glab`), or Bitbucket Community (`bkt`), auto-detected from the remote — `Open PR`, `Merge ready PR`, `Change PR base`, `Change source branch`, all from the UI.
|
|
@@ -114,7 +116,9 @@ for credential priority and native-configuration scope.
|
|
|
114
116
|
|
|
115
117
|
### Docker
|
|
116
118
|
|
|
117
|
-
An official `Dockerfile` and
|
|
119
|
+
An official `Dockerfile` and four Compose examples ship in this repository: a local test stack, a local Traefik rehearsal, a deployment including Traefik, and a fresh VPS instance behind an existing Traefik.
|
|
120
|
+
|
|
121
|
+
**Working remotely on your own VPS?** The [VPS deployment guide](./docs/deployment-vps.md) includes a guided `kobo-install` installer, optional renewable GitHub App authentication, persistent agent logins and Docker updates. Your browser runs on your PC; repositories and agents run on the VPS. See [`CONFIGURATION.md`](./CONFIGURATION.md#docker-deployment) for the Compose comparison and shared settings.
|
|
118
122
|
|
|
119
123
|
### Network access
|
|
120
124
|
|
|
@@ -162,4 +166,4 @@ GPL-3.0-or-later. See [`LICENSE`](./LICENSE).
|
|
|
162
166
|
|
|
163
167
|
Report reproducible bugs and feature requests in [GitHub Issues](https://github.com/loicngr/Kobo/issues). Read [Contributing](./CONTRIBUTING.md) and the [Code of conduct](./CODE_OF_CONDUCT.md) before participating. Vulnerabilities belong in the private channel described in [Security](./SECURITY.md), not public issues. Support is maintained on a best-effort basis; no response-time commitment is implied.
|
|
164
168
|
|
|
165
|
-
|
|
169
|
+
Linux is locally exercised; macOS CI, WSL and real-provider beta sessions require their own evidence. See [third-party notices](./THIRD_PARTY_NOTICES.md) for redistributed assets.
|
|
@@ -4,7 +4,10 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
4
4
|
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
5
5
|
import { getDb } from '../server/db/index.js';
|
|
6
6
|
import { EXTERNAL_MEMORY_TOOL_DEFINITIONS, isMemoryToolName, MEMORY_TOOL_DEFINITIONS } from '../shared/memory-tools.js';
|
|
7
|
+
import { isWorkspaceCreationTool, MAX_MCP_CREATION_REQUEST_BYTES, WORKSPACE_CREATION_TOOLS, } from '../shared/workspace-creation-tools.js';
|
|
7
8
|
import { WORKSPACE_DIALOGUE_TOOLS } from '../shared/workspace-dialogue-tools.js';
|
|
9
|
+
import { isWorkspaceGroupMessageTool, WORKSPACE_GROUP_MESSAGE_TOOLS } from '../shared/workspace-group-message-tools.js';
|
|
10
|
+
import { isWorkspaceLifecycleTool, WORKSPACE_LIFECYCLE_TOOLS } from '../shared/workspace-lifecycle-tools.js';
|
|
8
11
|
import { createTaskHandler, cronListHandler, deleteTaskHandler, getDevServerStatusHandler, getSessionUsageHandler, getSettingsHandler, getTicketSourcesHandler, getWorkspaceInfoHandler, listDocumentsHandler, listTasksHandler, listWorkspaceImagesHandler, listWorkspacesHandler, logThoughtHandler, markAutoLoopReadyHandler, markTaskDoneHandler, readDocumentHandler, readWorkspaceEventsCsvHandler, setWorkspaceAgentDescriptionHandler, updateTaskHandler, } from './kobo-tasks-handlers.js';
|
|
9
12
|
import { callMemoryTool, isMemoryOutputSuppressed } from './memory-client.js';
|
|
10
13
|
import { callWorkspaceDialogueTool } from './workspace-dialogue-client.js';
|
|
@@ -566,6 +569,9 @@ const WORKSPACE_SCOPED_TOOLS = [
|
|
|
566
569
|
];
|
|
567
570
|
const GLOBAL_TOOLS = [
|
|
568
571
|
...WORKSPACE_DIALOGUE_TOOLS,
|
|
572
|
+
...WORKSPACE_CREATION_TOOLS,
|
|
573
|
+
...WORKSPACE_LIFECYCLE_TOOLS,
|
|
574
|
+
...WORKSPACE_GROUP_MESSAGE_TOOLS,
|
|
569
575
|
...EXTERNAL_MEMORY_TOOL_DEFINITIONS,
|
|
570
576
|
{
|
|
571
577
|
name: 'list_workspaces',
|
|
@@ -587,63 +593,6 @@ const GLOBAL_TOOLS = [
|
|
|
587
593
|
},
|
|
588
594
|
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
589
595
|
},
|
|
590
|
-
{
|
|
591
|
-
name: 'create_workspace',
|
|
592
|
-
description: '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.',
|
|
593
|
-
inputSchema: {
|
|
594
|
-
type: 'object',
|
|
595
|
-
properties: {
|
|
596
|
-
name: { type: 'string', description: 'Workspace display name.' },
|
|
597
|
-
project_path: { type: 'string', description: 'Absolute path to the project on disk.' },
|
|
598
|
-
source_branch: { type: 'string', description: 'Branch to base the new worktree on (e.g. "develop").' },
|
|
599
|
-
working_branch: {
|
|
600
|
-
type: 'string',
|
|
601
|
-
description: 'New branch name for the worktree (e.g. "feature/my-thing").',
|
|
602
|
-
},
|
|
603
|
-
model: { type: 'string', description: 'Model id override (optional, defaults to project/global setting).' },
|
|
604
|
-
reasoning_effort: { type: 'string', description: 'Reasoning effort override (optional).' },
|
|
605
|
-
engine: { type: 'string', description: 'Agent engine id, e.g. "claude-code" or "codex" (optional).' },
|
|
606
|
-
description: { type: 'string', description: 'Task description / initial brainstorming prompt (optional).' },
|
|
607
|
-
tags: {
|
|
608
|
-
type: 'array',
|
|
609
|
-
items: { type: 'string' },
|
|
610
|
-
description: 'Workspace tags to assign at creation (optional).',
|
|
611
|
-
},
|
|
612
|
-
tasks: { type: 'array', items: { type: 'string' }, description: 'Initial manual task titles (optional).' },
|
|
613
|
-
acceptance_criteria: {
|
|
614
|
-
type: 'array',
|
|
615
|
-
items: { type: 'string' },
|
|
616
|
-
description: 'Initial manual acceptance criteria (optional).',
|
|
617
|
-
},
|
|
618
|
-
agent_permission_mode: {
|
|
619
|
-
type: 'string',
|
|
620
|
-
enum: ['plan', 'bypass', 'strict', 'interactive'],
|
|
621
|
-
description: 'Permission mode for the new session (optional).',
|
|
622
|
-
},
|
|
623
|
-
auto_loop: { type: 'boolean', description: 'Start the workspace in auto-loop mode (optional).' },
|
|
624
|
-
auto_loop_session_mode: {
|
|
625
|
-
type: 'string',
|
|
626
|
-
enum: ['per_task', 'continuous'],
|
|
627
|
-
description: "Auto-loop session mode when auto_loop is true (optional, default 'per_task').",
|
|
628
|
-
},
|
|
629
|
-
skip_setup_script: { type: 'boolean', description: "Skip the project's setup script (optional)." },
|
|
630
|
-
},
|
|
631
|
-
required: ['name', 'project_path', 'source_branch', 'working_branch'],
|
|
632
|
-
},
|
|
633
|
-
annotations: { destructiveHint: false, openWorldHint: false },
|
|
634
|
-
},
|
|
635
|
-
{
|
|
636
|
-
name: 'archive_workspace',
|
|
637
|
-
description: 'Archive a workspace by id, like the "Archiver" action in the workspace context menu. Requires the Kōbō backend server to be running.',
|
|
638
|
-
inputSchema: {
|
|
639
|
-
type: 'object',
|
|
640
|
-
properties: {
|
|
641
|
-
workspace_id: { type: 'string', description: 'Workspace id, from list_workspaces.' },
|
|
642
|
-
},
|
|
643
|
-
required: ['workspace_id'],
|
|
644
|
-
},
|
|
645
|
-
annotations: { destructiveHint: false, openWorldHint: false },
|
|
646
|
-
},
|
|
647
596
|
{
|
|
648
597
|
name: 'stop_workspace',
|
|
649
598
|
description: 'Force-stop the currently running agent session on a workspace, like the red "Arrêter" button in the chat header. Requires the Kōbō backend server to be running. Safe to call when nothing is running.',
|
|
@@ -751,8 +700,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
751
700
|
: {}),
|
|
752
701
|
};
|
|
753
702
|
}
|
|
754
|
-
if (
|
|
755
|
-
|
|
703
|
+
if (isWorkspaceCreationTool(name) ||
|
|
704
|
+
isWorkspaceLifecycleTool(name) ||
|
|
705
|
+
isWorkspaceGroupMessageTool(name) ||
|
|
706
|
+
WORKSPACE_DIALOGUE_TOOLS.some((tool) => tool.name === name)) {
|
|
707
|
+
if (workspaceId &&
|
|
708
|
+
((a.workspace_id === workspaceId && name === 'send_workspace_message') ||
|
|
709
|
+
(name === 'send_workspace_group_message' &&
|
|
710
|
+
Array.isArray(a.workspace_ids) &&
|
|
711
|
+
a.workspace_ids.includes(workspaceId))))
|
|
756
712
|
return fail('Use the normal conversation to communicate in your own workspace.');
|
|
757
713
|
return await callWorkspaceDialogueTool(backendUrl, name, a, process.env.KOBO_NETWORK_TOKEN, process.env.KOBO_MCP_CLIENT_NAME ?? server.getClientVersion()?.name);
|
|
758
714
|
}
|
|
@@ -1025,50 +981,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1025
981
|
const tag = typeof a.tag === 'string' ? a.tag : undefined;
|
|
1026
982
|
return ok(listWorkspacesHandler(db, { includeArchived, tag }));
|
|
1027
983
|
}
|
|
1028
|
-
if (name === 'create_workspace') {
|
|
1029
|
-
const workspaceName = a.name;
|
|
1030
|
-
const projectPath = a.project_path;
|
|
1031
|
-
const sourceBranch = a.source_branch;
|
|
1032
|
-
const workingBranch = a.working_branch;
|
|
1033
|
-
if (!workspaceName || !projectPath || !sourceBranch || !workingBranch) {
|
|
1034
|
-
return fail('name, project_path, source_branch, and working_branch parameters are required');
|
|
1035
|
-
}
|
|
1036
|
-
try {
|
|
1037
|
-
const created = await backendRequest('POST', '/api/workspaces', {
|
|
1038
|
-
name: workspaceName,
|
|
1039
|
-
projectPath,
|
|
1040
|
-
sourceBranch,
|
|
1041
|
-
workingBranch,
|
|
1042
|
-
model: a.model,
|
|
1043
|
-
reasoningEffort: a.reasoning_effort,
|
|
1044
|
-
engine: a.engine,
|
|
1045
|
-
description: a.description,
|
|
1046
|
-
tags: a.tags,
|
|
1047
|
-
tasks: a.tasks,
|
|
1048
|
-
acceptanceCriteria: a.acceptance_criteria,
|
|
1049
|
-
agentPermissionMode: a.agent_permission_mode,
|
|
1050
|
-
autoLoop: a.auto_loop,
|
|
1051
|
-
autoLoopSessionMode: a.auto_loop_session_mode,
|
|
1052
|
-
skipSetupScript: a.skip_setup_script,
|
|
1053
|
-
});
|
|
1054
|
-
return ok(created);
|
|
1055
|
-
}
|
|
1056
|
-
catch (err) {
|
|
1057
|
-
return fail(backendErrorMessage(err));
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
if (name === 'archive_workspace') {
|
|
1061
|
-
const targetId = a.workspace_id;
|
|
1062
|
-
if (!targetId)
|
|
1063
|
-
return fail('workspace_id parameter is required');
|
|
1064
|
-
try {
|
|
1065
|
-
const result = await backendRequest('POST', `/api/workspaces/${targetId}/archive`);
|
|
1066
|
-
return ok(result);
|
|
1067
|
-
}
|
|
1068
|
-
catch (err) {
|
|
1069
|
-
return fail(backendErrorMessage(err));
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
984
|
if (name === 'stop_workspace') {
|
|
1073
985
|
const targetId = a.workspace_id;
|
|
1074
986
|
if (!targetId)
|
|
@@ -1087,7 +999,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1087
999
|
return fail(err instanceof Error ? err.message : String(err));
|
|
1088
1000
|
}
|
|
1089
1001
|
});
|
|
1090
|
-
const transport = new StdioServerTransport(
|
|
1002
|
+
const transport = new StdioServerTransport(process.stdin, process.stdout, {
|
|
1003
|
+
// Include the JSON-lines delimiter in addition to the HTTP envelope budget.
|
|
1004
|
+
maxBufferSize: MAX_MCP_CREATION_REQUEST_BYTES + 1,
|
|
1005
|
+
});
|
|
1091
1006
|
server.connect(transport).catch((err) => {
|
|
1092
1007
|
console.error('[kobo-tasks-server] Fatal:', err);
|
|
1093
1008
|
process.exit(1);
|
|
@@ -1,15 +1,27 @@
|
|
|
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 { isWorkspaceGroupMessageTool, validateWorkspaceGroupMessageArguments, } from '../shared/workspace-group-message-tools.js';
|
|
7
|
+
import { isWorkspaceLifecycleTool, validateWorkspaceLifecycleArguments } from '../shared/workspace-lifecycle-tools.js';
|
|
5
8
|
import { normalizeClientName } from '../shared/workspace-message-types.js';
|
|
6
9
|
/** Stateless HTTP bridge: external stdio clients never own agent runtime state. */
|
|
7
10
|
export async function callWorkspaceDialogueTool(backendUrl, name, args, token, clientName) {
|
|
8
11
|
const arguments_ = isMemoryToolName(name)
|
|
9
12
|
? validateMemoryToolArguments(name, args, true)
|
|
10
|
-
:
|
|
13
|
+
: isWorkspaceCreationTool(name)
|
|
14
|
+
? validateWorkspaceCreationArguments(name, args)
|
|
15
|
+
: isWorkspaceLifecycleTool(name)
|
|
16
|
+
? validateWorkspaceLifecycleArguments(name, args)
|
|
17
|
+
: isWorkspaceGroupMessageTool(name)
|
|
18
|
+
? validateWorkspaceGroupMessageArguments(name, args)
|
|
19
|
+
: validateDialogueArguments(name, args);
|
|
11
20
|
const client = new Client({ name: 'kobo-stdio-bridge', version: '1.0.0' });
|
|
12
|
-
|
|
21
|
+
// Creation can include forge reads, worktree setup and user setup scripts.
|
|
22
|
+
// A timed-out creation is ambiguous and must never be retried automatically.
|
|
23
|
+
const timeout = isWorkspaceCreationTool(name) || isWorkspaceLifecycleTool(name) ? 15 * 60_000 : 30_000;
|
|
24
|
+
const signal = AbortSignal.timeout(timeout);
|
|
13
25
|
const transport = new StreamableHTTPClientTransport(new URL(`${backendUrl.replace(/\/$/, '')}/api/mcp`), {
|
|
14
26
|
requestInit: {
|
|
15
27
|
signal,
|
|
@@ -23,7 +35,7 @@ export async function callWorkspaceDialogueTool(backendUrl, name, args, token, c
|
|
|
23
35
|
});
|
|
24
36
|
try {
|
|
25
37
|
await client.connect(transport, { signal });
|
|
26
|
-
return await client.callTool({ name, arguments: arguments_ }, undefined, { signal });
|
|
38
|
+
return await client.callTool({ name, arguments: arguments_ }, undefined, { signal, timeout });
|
|
27
39
|
}
|
|
28
40
|
finally {
|
|
29
41
|
await client.close();
|
|
@@ -6,6 +6,7 @@ import { initReviewReturnSchema, migrateDurableReviewReturns } from './review-re
|
|
|
6
6
|
import { initSchema } from './schema.js';
|
|
7
7
|
import { initSearchSchema } from './search-schema.js';
|
|
8
8
|
import { initSessionHandoffSchema } from './session-handoff-schema.js';
|
|
9
|
+
import { initWorkspaceMessageBatchSchema } from './workspace-message-batch-schema.js';
|
|
9
10
|
/** Ordered registry of all schema migrations. Append new entries at the end. */
|
|
10
11
|
export const migrations = [
|
|
11
12
|
{
|
|
@@ -808,6 +809,7 @@ export const migrations = [
|
|
|
808
809
|
initAutoLoopFinalReviewSchema(db);
|
|
809
810
|
},
|
|
810
811
|
},
|
|
812
|
+
{ version: 53, name: 'workspace-group-message-receipts', migrate: initWorkspaceMessageBatchSchema },
|
|
811
813
|
];
|
|
812
814
|
/** Current schema version — always equals the highest migration version. */
|
|
813
815
|
export const SCHEMA_VERSION = migrations.length > 0 ? migrations[migrations.length - 1].version : 1;
|
package/dist/server/db/schema.js
CHANGED
|
@@ -5,6 +5,7 @@ import { initMemorySchema } from './memory-schema.js';
|
|
|
5
5
|
import { initReviewReturnSchema } from './review-return-schema.js';
|
|
6
6
|
import { initSearchSchema } from './search-schema.js';
|
|
7
7
|
import { initSessionHandoffSchema } from './session-handoff-schema.js';
|
|
8
|
+
import { initWorkspaceMessageBatchSchema } from './workspace-message-batch-schema.js';
|
|
8
9
|
/** Create all tables and indexes for a fresh install. Not used for upgrades -- see migrations.ts. */
|
|
9
10
|
export function initSchema(db) {
|
|
10
11
|
db.exec(`
|
|
@@ -240,4 +241,5 @@ export function initSchema(db) {
|
|
|
240
241
|
initAutoLoopFinalReviewSchema(db);
|
|
241
242
|
initSessionHandoffSchema(db);
|
|
242
243
|
initMemorySchema(db);
|
|
244
|
+
initWorkspaceMessageBatchSchema(db);
|
|
243
245
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Receipts survive workspace deletion; message bodies live only in normal chat/loop storage. */
|
|
2
|
+
export function initWorkspaceMessageBatchSchema(db) {
|
|
3
|
+
db.exec(`CREATE TABLE IF NOT EXISTS workspace_message_batches (
|
|
4
|
+
id TEXT PRIMARY KEY,
|
|
5
|
+
fingerprint TEXT NOT NULL,
|
|
6
|
+
receipt_json TEXT NOT NULL,
|
|
7
|
+
created_at TEXT NOT NULL
|
|
8
|
+
)`);
|
|
9
|
+
}
|