@loicngr/kobo 2.1.20 → 2.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/AGENTS.md +37 -4
  2. package/CHANGELOG.md +9 -0
  3. package/CONFIGURATION.md +42 -5
  4. package/dist/server/services/agent/engines/claude-code/engine.js +5 -2
  5. package/dist/server/services/agent/engines/codex/engine.js +3 -0
  6. package/dist/server/services/agent/orchestrator.js +7 -0
  7. package/dist/server/services/agent/turn-liveness.js +1 -1
  8. package/dist/server/services/settings-service.js +30 -0
  9. package/docker-compose.vps.yml +9 -3
  10. package/docs/deployment-vps.md +153 -34
  11. package/docs/getting-started.md +16 -0
  12. package/package.json +1 -1
  13. package/scripts/kobo-install +35 -8
  14. package/scripts/kobo-update +37 -15
  15. package/src/client/dist/pwa/assets/{ActivityFeed-DiUxnmGJ.js → ActivityFeed-2wu6qXx1.js} +1 -1
  16. package/src/client/dist/pwa/assets/{AutoLoopFinalReviewSettings-f4VDR5dd.js → AutoLoopFinalReviewSettings-CwnNUWCh.js} +1 -1
  17. package/src/client/dist/pwa/assets/{ChangelogPage-dEWpjTlp.js → ChangelogPage-D9ke0TYz.js} +1 -1
  18. package/src/client/dist/pwa/assets/{ComparisonPanel-Bwbqgvr4.js → ComparisonPanel-ytDu1L7a.js} +1 -1
  19. package/src/client/dist/pwa/assets/{CreatePage-CCCCAhrx.js → CreatePage-YQ_jGilf.js} +1 -1
  20. package/src/client/dist/pwa/assets/{DashboardPage-3byhBITf.js → DashboardPage-C-PyArla.js} +1 -1
  21. package/src/client/dist/pwa/assets/{DiffViewer-Bj_NTfCF.js → DiffViewer-CPobwSwm.js} +3 -3
  22. package/src/client/dist/pwa/assets/{DocumentsPanel-xyLDV3T1.js → DocumentsPanel-BjZGNI-p.js} +1 -1
  23. package/src/client/dist/pwa/assets/{DrawerToggleButton-pwBSstQP.js → DrawerToggleButton-Dtd6fhYu.js} +1 -1
  24. package/src/client/dist/pwa/assets/{GitPanel-DvIdrkVw.js → GitPanel-BHJ3lpJZ.js} +3 -3
  25. package/src/client/dist/pwa/assets/{HealthPage-BSwhZQRo.js → HealthPage-Lv_IdF1l.js} +1 -1
  26. package/src/client/dist/pwa/assets/{MainLayout-DZJ9BvZw.js → MainLayout-DYumlWcP.js} +2 -2
  27. package/src/client/dist/pwa/assets/{MemoryPanel-ChR032zP.js → MemoryPanel-DS2F3x-S.js} +1 -1
  28. package/src/client/dist/pwa/assets/{MemoryProposalList-BRKSoS4K.js → MemoryProposalList-D_KDS3uX.js} +1 -1
  29. package/src/client/dist/pwa/assets/{QBtnDropdown-o3QH0Bb8.js → QBtnDropdown-XkBxmJO9.js} +1 -1
  30. package/src/client/dist/pwa/assets/{QExpansionItem-BhFHiyq9.js → QExpansionItem-B5vbHxJ2.js} +1 -1
  31. package/src/client/dist/pwa/assets/{QScrollArea-BsNY6slB.js → QScrollArea-BE_fAg20.js} +1 -1
  32. package/src/client/dist/pwa/assets/{QSelect-bEq3g_io.js → QSelect-BjZk4-r8.js} +1 -1
  33. package/src/client/dist/pwa/assets/{QTooltip-xYZ6KNu2.js → QTooltip-Dhn6VXWR.js} +1 -1
  34. package/src/client/dist/pwa/assets/{SchedulePanel-DIFv_FTY.js → SchedulePanel-BDhIt9pX.js} +1 -1
  35. package/src/client/dist/pwa/assets/{SearchPage-BqTBIt5E.js → SearchPage-CJrtIenZ.js} +1 -1
  36. package/src/client/dist/pwa/assets/SettingsPage-BSwN45zt.js +9 -0
  37. package/src/client/dist/pwa/assets/{SettingsPage-BRvUAZ5r.css → SettingsPage-CNIpxG-U.css} +1 -1
  38. package/src/client/dist/pwa/assets/{SplitWorkspacePage-D6r2pl5g.js → SplitWorkspacePage-BV62otQw.js} +1 -1
  39. package/src/client/dist/pwa/assets/{TerminalPanel-CladK9Vv.js → TerminalPanel-TQUHtZ-q.js} +1 -1
  40. package/src/client/dist/pwa/assets/{ToolCallItem-CdzEME4w.js → ToolCallItem-DxdYefuz.js} +1 -1
  41. package/src/client/dist/pwa/assets/{TouchPan-B4gQPwGN.js → TouchPan-CtwfZgkX.js} +1 -1
  42. package/src/client/dist/pwa/assets/{TourReplayButton-B0j6AZwU.js → TourReplayButton-DEC_Yt6E.js} +1 -1
  43. package/src/client/dist/pwa/assets/{WorkspaceAttentionLabels-CeVhoidC.js → WorkspaceAttentionLabels-BehGsWhI.js} +1 -1
  44. package/src/client/dist/pwa/assets/{WorkspacePage-w2bF8wTd.css → WorkspacePage-Bk-WWOYM.css} +1 -1
  45. package/src/client/dist/pwa/assets/{WorkspacePage-CK5XCuG_.js → WorkspacePage-CQz6QL5L.js} +3 -3
  46. package/src/client/dist/pwa/assets/{browser-B8tNScg1.js → browser-B3bGJv7H.js} +1 -1
  47. package/src/client/dist/pwa/assets/{build-path-tree-kmVAcvs_.js → build-path-tree-Bwwe_8vU.js} +1 -1
  48. package/src/client/dist/pwa/assets/{custom-sounds-CJwd-8TL.js → custom-sounds-1Rb1a5LA.js} +1 -1
  49. package/src/client/dist/pwa/assets/{de-BaO6kbdO.js → de-bfOiFUu1.js} +1 -1
  50. package/src/client/dist/pwa/assets/{editor.api-CgbPVW9s.js → editor.api-D2PTZoSF.js} +1 -1
  51. package/src/client/dist/pwa/assets/{efforts-y7Wf_Rgd.js → efforts-JkWc_mU5.js} +1 -1
  52. package/src/client/dist/pwa/assets/{es-DTvSsbD0.js → es-C5Sk8pec.js} +1 -1
  53. package/src/client/dist/pwa/assets/{expand-template-BFbbjj7X.js → expand-template-C-aGZz7k.js} +1 -1
  54. package/src/client/dist/pwa/assets/{format-BETfc4z9.js → format-BbXB3nKS.js} +1 -1
  55. package/src/client/dist/pwa/assets/{formatters-C22K9vlC.js → formatters-Bu3lC0Jk.js} +1 -1
  56. package/src/client/dist/pwa/assets/{fr-BH7V66Yz.js → fr-BNVJp67K.js} +1 -1
  57. package/src/client/dist/pwa/assets/{freemarker2-DiRVvp9O.js → freemarker2-qq6O820q.js} +1 -1
  58. package/src/client/dist/pwa/assets/{handlebars-BLYRroSQ.js → handlebars-nXATCa7t.js} +1 -1
  59. package/src/client/dist/pwa/assets/{html-DAW2vteY.js → html-ZSNyTQeY.js} +1 -1
  60. package/src/client/dist/pwa/assets/i18n-B_b02gtS.js +1 -0
  61. package/src/client/dist/pwa/assets/{index-CbyHfF8I.js → index-BZKHE14f.js} +3 -3
  62. package/src/client/dist/pwa/assets/{it-DZGbkkoy.js → it-B9g24VRZ.js} +1 -1
  63. package/src/client/dist/pwa/assets/{javascript-Bb5J8n0k.js → javascript-B3Elzrc0.js} +1 -1
  64. package/src/client/dist/pwa/assets/{liquid-Dqx3adnJ.js → liquid-CrMGh9mA.js} +1 -1
  65. package/src/client/dist/pwa/assets/{mdx-D_7igLV3.js → mdx-C6nxSGQ5.js} +1 -1
  66. package/src/client/dist/pwa/assets/{monaco-contributions-C-0CbrAy.js → monaco-contributions-WtuTHJJ8.js} +1 -1
  67. package/src/client/dist/pwa/assets/{monaco.contribution-c6JPBEZI.js → monaco.contribution--KeSH1BU.js} +2 -2
  68. package/src/client/dist/pwa/assets/{network-auth-BE4qbvWm.js → network-auth-DknPaiQS.js} +1 -1
  69. package/src/client/dist/pwa/assets/{notify-theme-BVFBBGrJ.js → notify-theme-DTaB0RsK.js} +1 -1
  70. package/src/client/dist/pwa/assets/{project-color-CHOR2jB5.js → project-color-bm_jpHka.js} +1 -1
  71. package/src/client/dist/pwa/assets/{python-Dqubw1F5.js → python-CQI_FwfK.js} +1 -1
  72. package/src/client/dist/pwa/assets/{razor-BQphc9Kt.js → razor-BT2wh4_7.js} +1 -1
  73. package/src/client/dist/pwa/assets/{typescript-Dep00l0X.js → typescript-D3ibItMf.js} +1 -1
  74. package/src/client/dist/pwa/assets/{use-search-index-status-CQHHvHwM.js → use-search-index-status-BnFR-_2l.js} +1 -1
  75. package/src/client/dist/pwa/assets/{use-tours-IVbXTmz2.js → use-tours-CsPhvrxf.js} +2 -2
  76. package/src/client/dist/pwa/assets/{use-worktree-restore-B3s5xDGR.js → use-worktree-restore-B5s0BuHk.js} +1 -1
  77. package/src/client/dist/pwa/assets/{workspace-templates-CyuTpztP.js → workspace-templates-GLO1mBlj.js} +1 -1
  78. package/src/client/dist/pwa/assets/{xml-BMS2dvaF.js → xml-EIEQlO3Y.js} +1 -1
  79. package/src/client/dist/pwa/assets/{yaml-CYw92aic.js → yaml-CJdHLr6G.js} +1 -1
  80. package/src/client/dist/pwa/index.html +1 -1
  81. package/src/client/dist/pwa/sw.js +1 -1
  82. package/src/client/dist/pwa/assets/SettingsPage-BLD0xWFB.js +0 -9
  83. package/src/client/dist/pwa/assets/i18n-DnuTmw3O.js +0 -1
package/AGENTS.md CHANGED
@@ -201,6 +201,10 @@ Every feature that touches the schema:
201
201
 
202
202
  ## WebSocket protocol
203
203
 
204
+ Global `showProgressBars` defaults to true (settings migration v65). The panel
205
+ honors this preference without resetting its store or dismissing rows: MCP writes,
206
+ revisions and success expiry continue while hidden.
207
+
204
208
  Session progress bars use the separate `progress:changed` ephemeral event with a
205
209
  public record payload and workspace/session envelope. Persist the row before
206
210
  broadcasting; never append numeric updates to chat history or overload native
@@ -233,7 +237,7 @@ locally queued or unacknowledged messages still block `turn:completed` and
233
237
  input closure. UUID-less legacy results retain a conservative one-input
234
238
  fallback for already yielded inputs only; a result never credits a future send.
235
239
  Backpressure caps unacknowledged yielded inputs at 64, matching the SDK's result
236
- UUID list bound. Keep the inactivity watchdog for genuinely unanswered work.
240
+ UUID list bound. Keep the opt-in inactivity watchdog for genuinely unanswered work.
237
241
 
238
242
  Claude SDK `task_*` messages cover agents **and** background jobs. Preserve `task_type` by task/tool identity across progress and terminal messages, which often omit it. The existing `subagent:progress` envelope also carries this lifecycle metadata for compatibility; `local_bash`, `local_workflow` and `mcp_task` must be excluded from agent cards, busy-agent counts, chat agent-name resolution and the selective stop-subagents action. Keep tracking these jobs for engine liveness and whole-session interruption. Legacy/unknown classifications and Codex agents remain supported; never classify from a description or from the agent's last tool (`Bash` can be a real agent's current action).
239
243
 
@@ -282,6 +286,21 @@ Traefik network. It persists `/root`, mounts project paths identically on host a
282
286
  container, and leaves the Docker socket opt-in. `docs/deployment-vps.md` owns the
283
287
  operator walkthrough; `CONFIGURATION.md` compares all Compose topologies.
284
288
 
289
+ The VPS launcher `scripts/kobo-vps-entrypoint` bootstraps the dedicated home and
290
+ executes Kōbō as positive `KOBO_UID:KOBO_GID`, adding the socket's numeric group
291
+ when mounted. Keep `HOME=/root` and the existing volume for compatibility. Do not
292
+ chown existing project mounts, follow home symlinks, or resolve root bootstrap
293
+ commands through user-writable skill/tool paths. `--exec` runs operator commands
294
+ as the agent without rescanning ownership. Legacy root Compose entrypoints are
295
+ preserved by the updater and need the documented explicit migration. New installer
296
+ identity fields must not silently reconfigure old completed manifests.
297
+
298
+ The updater needs the launcher beside its script to build its compatibility layer
299
+ (including netcat/Buildx). `docs/vps-projects-and-skills.md` covers dependency/env
300
+ setup, private registry auth, SSH dev-server access, scoped cleanup, native
301
+ Claude/Codex skills and persistence. Project-specific OS packages and host inotify
302
+ changes are operator-managed; the updater does not preserve arbitrary image layers.
303
+
285
304
  `scripts/kobo-install` guides a fresh installation; it does not adopt existing
286
305
  unmanaged stacks. Preserve its read-only `--check`, explicit Docker-socket opt-in,
287
306
  managed-file fingerprints, shared updater lock, and durable start phases. Completed
@@ -300,13 +319,20 @@ credentials into an image or add web settings for these host-managed secrets.
300
319
 
301
320
  `scripts/kobo-update` is a Python 3.10+ operator tool for this layout, separate
302
321
  from the backend's update notifications. Preserve explicit invocation, idle checks,
303
- unique image tags, private stopped-volume backups, migration tests on a copy,
322
+ unique image tags, private backups of `/root/.config/kobo` while stopped, migration tests on a copy,
304
323
  and durable failure records. The authenticated `/api/maintenance/update` reservation
305
324
  checks runtime ownership and closes agent admission synchronously before shutdown.
306
325
  Track in-flight workspace creations and PR checkouts through all asynchronous
307
326
  steps and rollback. Maintenance and creation admission must exclude each other
308
327
  synchronously; rejecting only the final agent start is too late. Direct HTTP and
309
328
  MCP creation paths share this guard.
329
+ The updater's `kobo.tar.gz` contains only Kōbō data, with paths relative to `/root`;
330
+ never archive engine homes, skills, caches or projects as part of routine updates.
331
+ Record the archive name/scope in update state. Migration checks extract regular
332
+ SQLite files and sidecars only. Recovery must preserve the original volume and
333
+ clone it before replacing only `.config/kobo` in the clone; a partial data archive
334
+ must not be described as a complete home snapshot. Old `home.tar.gz` jobs retain
335
+ their original recovery instructions.
310
336
  Scheduled cron/wakeup/quota deliveries defer without consuming persisted intent;
311
337
  valid pending wakeups get `retry_at=target_at` before successful reservation (only
312
338
  when `retry_at` was null), so long maintenance does not expire them at boot. Keep
@@ -372,11 +398,18 @@ through `ExitPlanMode`; a permission change requires a new launch.
372
398
 
373
399
  Two engines live under `src/server/services/agent/engines/`, both implementing the `AgentEngine` contract in `types.ts`:
374
400
 
375
- **Claude Code** (`claude-code/`): consumes the `@anthropic-ai/claude-agent-sdk` async iterator. The SDK manages the Claude runtime; Kōbō does not launch a separate `claude` CLI command. Authentication reuses the user's Claude login or `ANTHROPIC_API_KEY`. A settled result gets a **3 s continuation grace**, then a **15 s result-drain watchdog**. Trailing metadata and child output must not cancel the parent's drain/grace. Silence is bounded at **2 minutes** without tools, **30 minutes** for foreground tools and background shell/workflow/MCP jobs, and **10 minutes** for agents. Compaction has an absolute **10-minute** ceiling; repeated status messages cannot extend it. Human questions/permissions pause inactivity; resolution AND SDK cancellation re-evaluate that pause. Watchdogs request abort plus SDK `query.close()`; neither is proof of closure: controller ownership is released only when `EngineProcess.closed` confirms it. Never fake closure or allow a second writer if the SDK ignores termination. End emission is idempotent and timers are cleared on termination and in `finally`.
401
+ Global `agentWatchdogEnabled` is opt-in (false for fresh and existing installs;
402
+ settings migration v64). The orchestrator snapshots it into `StartOptions.watchdogEnabled`
403
+ on every launch/resume for both engines. Gate stream/tool inactivity, background
404
+ stall, compaction and failed-result-drain watchdogs; retain normal completion,
405
+ manual stop, protocol/startup deadlines and confirmed-dead-process cleanup.
406
+ The toggle is first in Settings → Agents and does not reconfigure active launches.
407
+
408
+ **Claude Code** (`claude-code/`): consumes the `@anthropic-ai/claude-agent-sdk` async iterator. The SDK manages the Claude runtime; Kōbō does not launch a separate `claude` CLI command. Authentication reuses the user's Claude login or `ANTHROPIC_API_KEY`. A settled result gets a **3 s continuation grace**, then an opt-in **15 s result-drain watchdog**. Trailing metadata and child output must not cancel the parent's drain/grace. When watchdogs are enabled, silence is bounded at **2 minutes** without tools, **30 minutes** for foreground tools and background shell/workflow/MCP jobs, and **10 minutes** for agents. Compaction has an absolute **10-minute** ceiling; repeated status messages cannot extend it. Human questions/permissions pause inactivity; resolution AND SDK cancellation re-evaluate that pause. Watchdogs request abort plus SDK `query.close()`; neither is proof of closure: controller ownership is released only when `EngineProcess.closed` confirms it. Never fake closure or allow a second writer if the SDK ignores termination. End emission is idempotent and timers are cleared on termination and in `finally`.
376
409
 
377
410
  After the continuation grace, Claude explicitly calls `query.close()`; closing
378
411
  the input iterator alone can leave the SDK waiting for an idle notification after
379
- a successful result. The 15-second drain watchdog is a failed-close backstop,
412
+ a successful result. The opt-in 15-second drain watchdog is a failed-close backstop,
380
413
  not normal cleanup. The SDK still constructs its runtime command; Kōbō's
381
414
  `spawnClaudeCodeProcess` hook tracks actual child exit and forwards stderr.
382
415
  `EngineProcess.closed` waits for both that exit and iterator completion because
package/CHANGELOG.md CHANGED
@@ -4,6 +4,15 @@ All notable changes to Kōbō are documented here. The format is based on
4
4
  [Keep a Changelog](https://keepachangelog.com/). Each release is an `## <version>`
5
5
  section — the in-app "What's new" dialog reads this file.
6
6
 
7
+ ## 2.1.22
8
+
9
+ - feat(settings): make agent watchdogs opt-in and progress bars configurable
10
+ - feat(update): refine `kobo-update` to backup only `.config/kobo` and improve migration handling
11
+
12
+ ## 2.1.21
13
+
14
+ - feat(vps): introduce non-root runtime launcher and UID/GID support
15
+
7
16
  ## 2.1.20
8
17
 
9
18
  - fix(progress): clarify activity updates and completion
package/CONFIGURATION.md CHANGED
@@ -859,6 +859,12 @@ External senders can supply `idempotency_key` once per intended message and reus
859
859
 
860
860
  ## Session progress bars
861
861
 
862
+ **Settings → Agents → Show progress bars** (`showProgressBars`) is enabled by
863
+ default. Saving this global preference hides or restores the panel immediately in
864
+ the current client; other clients receive it on their next settings refresh.
865
+ Hiding the panel does not stop reporting, dismiss activities or reset their
866
+ success expiry timer. Individually dismissed bars stay dismissed when re-enabled.
867
+
862
868
  Agents can report long-running tests, builds or reviews through the
863
869
  `create_progress_bar`, `update_progress_bar`, `finish_progress_bar` and
864
870
  `list_progress_bars` MCP tools. These tools are available through the workspace
@@ -1183,17 +1189,20 @@ Traefik already exists. Build the image from a source checkout before starting.
1183
1189
  All four use the same underlying image (`docker build -t kobo:local .` from the
1184
1190
  repo root). The original three mount provider/Git configuration from the host;
1185
1191
  the VPS example keeps independent credentials in a persistent container home.
1186
- The VPS example overrides the entrypoint to start Node directly; the original
1192
+ The VPS example uses `kobo-vps-entrypoint`: it prepares the dedicated home volume
1193
+ then drops to `KOBO_UID:KOBO_GID` before starting Node, without `sshd`. The original
1187
1194
  files use the image entrypoint, which starts `sshd` even when port 22 is not published.
1195
+ See the [VPS runtime user and migration procedure](./docs/deployment-vps.md#runtime-user-and-existing-installations)
1196
+ and [project/skills operations guide](./docs/vps-projects-and-skills.md).
1188
1197
 
1189
1198
  ### The image
1190
1199
 
1191
1200
  Multi-stage `Dockerfile`, `node:24.19.0-slim` base:
1192
1201
 
1193
1202
  - **Build stage**: installs root, client, and PWA deps, then runs `npm run build` (client via Quasar, server via `tsc`).
1194
- - **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.
1203
+ - **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, Compose and Buildx plugins (talk to a mounted host socket, see [Volumes](#volumes)), netcat (`nc`), `gh`, `glab`, `tmux`, `vim`/`nano`, and more for interactive shell use.
1195
1204
  - **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.
1196
- - **`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.
1205
+ - **`git config --system --add safe.directory '*'`**: legacy Compose examples run as root while mounted repos retain the host UID; the VPS example instead uses a matching non-root UID. The image still pre-trusts mounted Git repositories. This broad trust is for a single-user environment, not a multi-tenant host.
1197
1206
  - **`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).
1198
1207
  - **`EXPOSE 3000`**, `ENV SERVER_PORT=3000`, entrypoint `docker-entrypoint.sh` → `npm start`.
1199
1208
 
@@ -1201,6 +1210,15 @@ Build once: `docker build -t kobo:local .`
1201
1210
 
1202
1211
  ### Environment variables (Docker)
1203
1212
 
1213
+ The VPS template accepts `KOBO_UID` and `KOBO_GID` (positive numeric IDs, default
1214
+ 1000); the installer defaults to the invoking host user's IDs. It retains
1215
+ `HOME=/root` for volume compatibility and adds the mounted Docker socket's group
1216
+ when applicable. Use `docker compose exec kobo kobo-vps-entrypoint --exec <command>`
1217
+ for logins, skills and project tooling as that user. It also sets
1218
+ `NODE_ENV=development` and `NPM_CONFIG_INCLUDE=dev` for project subprocesses;
1219
+ Kōbō continues serving its prebuilt UI/server. The base image retains its
1220
+ production default for other deployment layouts.
1221
+
1204
1222
  Beyond the [general environment variables](#environment-variables), these matter specifically for a container deployment:
1205
1223
 
1206
1224
  | Variable | Where it's set | Purpose |
@@ -1353,6 +1371,9 @@ For the independent VPS layout, [`scripts/kobo-update`](./scripts/kobo-update)
1353
1371
  accepts a stable version or defaults to npm `latest`. It has `--check`, `--prepare`
1354
1372
  and `--directory` options. See [updater usage and recovery](./docs/deployment-vps.md#update-with-kobo-update).
1355
1373
  This is an operator-invoked script; the in-app update banner does not install releases.
1374
+ Its `kobo.tar.gz` backup contains only `/root/.config/kobo` (database, settings and
1375
+ other Kōbō data). The rest of the persistent home, including engine credentials,
1376
+ skills and caches, is preserved in place but is not included in this backup.
1356
1377
 
1357
1378
  A source-built image needs rebuilding for a new release; `docker compose pull`
1358
1379
  alone cannot update an image that only exists locally. Recreate the service
@@ -1404,15 +1425,27 @@ The model selector is populated from [`src/shared/models.ts`](./src/shared/model
1404
1425
 
1405
1426
  #### Session watchdogs
1406
1427
 
1428
+ **Settings → Agents → Enable agent inactivity watchdogs**, at the top of the tab,
1429
+ controls the global `agentWatchdogEnabled` boolean. It is **off by default**, for
1430
+ both new and existing installations. Each Claude/Codex launch or resume captures
1431
+ the current value; saving the toggle does not interrupt or reconfigure an already
1432
+ running agent. Stop and resume that agent to apply the new value.
1433
+
1434
+ When off, Kōbō does not force-close a session because of stream/tool silence,
1435
+ background agents/jobs, stalled compaction, or a stalled result drain. Long silent
1436
+ CI monitoring can continue. Normal completion, manual Stop, provider failures,
1437
+ protocol request/startup deadlines and cleanup of confirmed dead processes still
1438
+ work; this switch only controls the engine watchdog deadlines described below.
1439
+
1407
1440
  Kōbō distinguishes a finished response from a fully closed SDK session. Once a
1408
1441
  response is settled, it allows 3 seconds for continuation, then explicitly closes
1409
1442
  the SDK query. Normal cleanup is reported as completion, not a watchdog failure.
1410
- A 15-second drain watchdog remains if that close fails to end the iterator.
1443
+ When watchdogs are enabled, a 15-second drain watchdog remains if that close fails to end the iterator.
1411
1444
  Metadata alone cannot extend this cleanup deadline. Writer ownership waits for
1412
1445
  both iterator completion and confirmed runtime exit, even if the SDK's own
1413
1446
  bounded cleanup returns earlier.
1414
1447
 
1415
- Silence limits are 2 minutes without a pending tool, 10 minutes for background
1448
+ With watchdogs enabled, silence limits are 2 minutes without a pending tool, 10 minutes for background
1416
1449
  agents, and 30 minutes for tools (including background Bash, workflow and MCP
1417
1450
  jobs). A compaction has a separate absolute 10-minute limit. Waiting for your
1418
1451
  answer or permission pauses the inactivity watchdog.
@@ -1482,6 +1515,10 @@ rules survive restarts and can be removed from the workspace's permission UI.
1482
1515
 
1483
1516
  Each workspace can run its own dev server. The **Tools** panel shows its status, a clickable URL, the live container count, and a logs button. That works only when your project follows a small contract. This section documents the contract so the panel lights up for *your* project, not just Docker-instance projects.
1484
1517
 
1518
+ For remote installations, `localhost` in that link is the browser's computer.
1519
+ See [VPS dev-server access](./docs/vps-projects-and-skills.md#reach-a-development-server-from-your-pc)
1520
+ for SSH forwarding, separate proxy routes and scoped stop/cleanup commands.
1521
+
1485
1522
  ### What the panel actually does
1486
1523
 
1487
1524
  Kōbō treats the dev server in two independent layers:
@@ -565,6 +565,7 @@ export function createClaudeCodeEngine() {
565
565
  // first SDK message, re-armed on meaningful progress, suspended while a human
566
566
  // decision or a background subagent is outstanding, stopped in `finally`.
567
567
  const turnLiveness = createTurnLiveness({
568
+ enabled: options.watchdogEnabled === true,
568
569
  timeoutMs: CLAUDE_STREAM_IDLE_TIMEOUT_MS,
569
570
  onTimeout() {
570
571
  // The armed deadline tracks whether a foreground tool call is in
@@ -618,7 +619,7 @@ export function createClaudeCodeEngine() {
618
619
  compactionStallTimer = undefined;
619
620
  };
620
621
  const armCompactionStallTimer = () => {
621
- if (compactionStallTimer)
622
+ if (!options.watchdogEnabled || compactionStallTimer)
622
623
  return;
623
624
  compactionStallTimer = setTimeout(() => {
624
625
  compactionStallTimer = undefined;
@@ -639,7 +640,7 @@ export function createClaudeCodeEngine() {
639
640
  compactionStallTimer.unref?.();
640
641
  };
641
642
  const armResultDrainWatchdog = () => {
642
- if (resultDrainTimer)
643
+ if (!options.watchdogEnabled || resultDrainTimer)
643
644
  return;
644
645
  resultDrainTimer = setTimeout(() => {
645
646
  console.warn(`[claude-engine] SDK generator still open ${RESULT_DRAIN_TIMEOUT_MS}ms after 'result' — forcing session:ended`);
@@ -725,6 +726,8 @@ export function createClaudeCodeEngine() {
725
726
  // Human waits suspend it independently; genuine activity restarts it.
726
727
  const armSubagentStallWatchdog = () => {
727
728
  clearSubagentStallWatchdog();
729
+ if (!options.watchdogEnabled)
730
+ return;
728
731
  if (pendingResolvers.size > 0 || activeSubagentTaskIds.size === 0)
729
732
  return;
730
733
  const hasAgent = [...activeSubagentTaskIds].some((id) => isSubagentTask({ taskType: mapperState.taskTypes.get(id) }));
@@ -205,6 +205,8 @@ export function createCodexEngine() {
205
205
  // deadline tracks the last activity, not the start of the wait.
206
206
  const armSubagentStallWatchdog = () => {
207
207
  clearSubagentStallWatchdog();
208
+ if (!options.watchdogEnabled)
209
+ return;
208
210
  if (!waitingForBackgroundSubagents || pendingByCallId.size > 0 || activeSubagentThreads.size === 0)
209
211
  return;
210
212
  subagentStallTimer = setTimeout(() => {
@@ -251,6 +253,7 @@ export function createCodexEngine() {
251
253
  }
252
254
  };
253
255
  const turnLiveness = createTurnLiveness({
256
+ enabled: options.watchdogEnabled === true,
254
257
  timeoutMs: CODEX_TURN_IDLE_TIMEOUT_MS,
255
258
  onTimeout() {
256
259
  watchdogEnded = true;
@@ -496,6 +496,7 @@ function readEffectiveSettingsSafe(projectPath) {
496
496
  function buildMcpServers(workspaceId, extraEnv, memoryCapability) {
497
497
  const mcpServerCompiled = getCompiledMcpServerPath();
498
498
  const mcpServerSource = getMcpServerSourcePath();
499
+ const global = getGlobalSettings();
499
500
  return [
500
501
  {
501
502
  name: 'kobo-tasks',
@@ -508,6 +509,11 @@ function buildMcpServers(workspaceId, extraEnv, memoryCapability) {
508
509
  KOBO_DB_PATH: getDbPath(),
509
510
  KOBO_SETTINGS_PATH: getSettingsPath(),
510
511
  KOBO_BACKEND_URL: `http://127.0.0.1:${backendPort}`,
512
+ // Behind a proxy, even internal loopback requests require network auth.
513
+ // Per-launch memory/progress capabilities remain independently required.
514
+ ...(global.networkAccessBehindProxy && global.networkAccessToken
515
+ ? { KOBO_NETWORK_TOKEN: global.networkAccessToken }
516
+ : {}),
511
517
  },
512
518
  },
513
519
  ];
@@ -1571,6 +1577,7 @@ export function startAgent(workspaceId, workingDir, prompt, model, resume = fals
1571
1577
  const integrationServers = buildIntegrationMcpServers(getGlobalSettings(), engine.id, (name) => unavailableIntegrations.push(name));
1572
1578
  const options = {
1573
1579
  workspaceId,
1580
+ watchdogEnabled: getGlobalSettings().agentWatchdogEnabled === true,
1574
1581
  workingDir,
1575
1582
  prompt: buildAgentPrompt(`${ws ? renderWorkflowPolicy(ws.workflowPolicy) : ''}\n${integrationMcpPrompt(integrationServers, unavailableIntegrations)}\n${prompt}`, ws?.projectPath),
1576
1583
  model,
@@ -13,7 +13,7 @@ export function createTurnLiveness(input) {
13
13
  let timedOut = false;
14
14
  let currentTimeoutMs = input.timeoutMs;
15
15
  const arm = () => {
16
- if (!active || paused || timedOut)
16
+ if (input.enabled === false || !active || paused || timedOut)
17
17
  return;
18
18
  if (timer)
19
19
  clearTimeout(timer);
@@ -980,6 +980,22 @@ const settingsMigrations = [
980
980
  global.memoryMode = 'hybrid';
981
981
  },
982
982
  },
983
+ {
984
+ version: 64,
985
+ name: 'opt-in-agent-watchdogs',
986
+ migrate: ({ global }) => {
987
+ if (typeof global.agentWatchdogEnabled !== 'boolean')
988
+ global.agentWatchdogEnabled = false;
989
+ },
990
+ },
991
+ {
992
+ version: 65,
993
+ name: 'session-progress-visibility',
994
+ migrate: ({ global }) => {
995
+ if (typeof global.showProgressBars !== 'boolean')
996
+ global.showProgressBars = true;
997
+ },
998
+ },
983
999
  ];
984
1000
  /** Current settings schema version — always equals the highest migration version. */
985
1001
  export const SETTINGS_SCHEMA_VERSION = settingsMigrations.length > 0 ? settingsMigrations[settingsMigrations.length - 1].version : 0;
@@ -1035,6 +1051,8 @@ function defaultSettings() {
1035
1051
  terminalCommand: '',
1036
1052
  autoPurgeOnPrMerged: false,
1037
1053
  autoLoopMaxRetries: 5,
1054
+ agentWatchdogEnabled: false,
1055
+ showProgressBars: true,
1038
1056
  awaitingUserReminderMinutes: 0,
1039
1057
  activityDigestEnabled: true,
1040
1058
  maxConcurrentAgents: 2,
@@ -1183,6 +1201,10 @@ export function runSettingsMigrations(raw) {
1183
1201
  current.global.workflowPolicy = resolveWorkflowPolicy(current.global.workflowPolicy);
1184
1202
  if (!isMemoryMode(current.global.memoryMode))
1185
1203
  current.global.memoryMode = 'hybrid';
1204
+ if (typeof current.global.showProgressBars !== 'boolean')
1205
+ current.global.showProgressBars = true;
1206
+ if (typeof current.global.agentWatchdogEnabled !== 'boolean')
1207
+ current.global.agentWatchdogEnabled = false;
1186
1208
  for (const project of current.projects) {
1187
1209
  if (!isWorkflowPolicy(project.workflowPolicy))
1188
1210
  project.workflowPolicy = {};
@@ -1535,6 +1557,12 @@ export function updateGlobalSettings(input) {
1535
1557
  delete data.autoLoopMaxRetries;
1536
1558
  }
1537
1559
  }
1560
+ if ('showProgressBars' in data && typeof data.showProgressBars !== 'boolean') {
1561
+ delete data.showProgressBars;
1562
+ }
1563
+ if ('agentWatchdogEnabled' in data && typeof data.agentWatchdogEnabled !== 'boolean') {
1564
+ delete data.agentWatchdogEnabled;
1565
+ }
1538
1566
  // 0 is a legitimate value here — it means "never remind me" — so the floor is
1539
1567
  // 0, not 1. The ceiling of a day keeps a typo from arming a reminder that
1540
1568
  // never fires and looks like a broken feature.
@@ -1598,6 +1626,8 @@ export function updateGlobalSettings(input) {
1598
1626
  'terminalCommand',
1599
1627
  'autoPurgeOnPrMerged',
1600
1628
  'autoLoopMaxRetries',
1629
+ 'agentWatchdogEnabled',
1630
+ 'showProgressBars',
1601
1631
  'awaitingUserReminderMinutes',
1602
1632
  'activityDigestEnabled',
1603
1633
  'maxConcurrentAgents',
@@ -10,10 +10,16 @@ services:
10
10
  init: true
11
11
  restart: unless-stopped
12
12
  # Use the host's SSH and `docker compose exec`; no container SSH daemon.
13
- entrypoint: ["node"]
14
- command: ["dist/server/index.js"]
13
+ # Bootstrap permissions as root, then execute Kōbō/agents as this host UID/GID.
14
+ entrypoint: ["kobo-vps-entrypoint"]
15
+ command: ["node", "dist/server/index.js"]
15
16
  environment:
16
- NODE_ENV: production
17
+ KOBO_UID: ${KOBO_UID:-1000}
18
+ KOBO_GID: ${KOBO_GID:-1000}
19
+ HOME: /root
20
+ # Kōbō serves its built assets; project commands need development dependencies.
21
+ NODE_ENV: development
22
+ NPM_CONFIG_INCLUDE: dev
17
23
  SERVER_PORT: "3000"
18
24
  KOBO_HOME: /root/.config/kobo
19
25
  KOBO_NETWORK_ACCESS_ENABLED: "true"