feat: keep language servers alive across browser reconnects (#3899)
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
# ADR-2026-09-23-task-owned-lsp-leases: Retain LSP processes across browser disconnects
|
||||
|
||||
**Status:** accepted
|
||||
**Date:** 2026-09-23
|
||||
**Area:** protocol
|
||||
|
||||
## Context
|
||||
|
||||
The browser WebSocket currently owns a task-host language-server process. Closing the tab or losing the network closes the stream and interrupts the process, even when the task workspace remains active. A later editor must start a new server and repeat project analysis. Concurrent browser windows currently have independent protocol sessions and may hold different unsaved document buffers.
|
||||
|
||||
## Decision
|
||||
|
||||
The task runtime will own bounded LSP leases. One lease owns one language-server process and one logical LSP protocol session. A browser connection attaches to a lease; closing that connection detaches it while Kandev continues to drain and service the language server. A later editor for the same task and language may reclaim a detached lease. Concurrently attached browser windows keep independent leases. An active lease prevents idle-session reclaim of its execution. An intentional two-minute browser editor-idle timeout releases its lease, while tab close or network loss retains it for up to one hour. Each detachment starts a new one-hour deadline; reattachment cancels that deadline, and an attached lease never expires. At the LSP limit, admission of a new server evicts the least recently detached lease; attached leases are never evicted. Explicit Stop, server exit, task-host shutdown, or backend shutdown also ends a lease. The existing LSP limit counts retained leases as well as attached ones.
|
||||
|
||||
The runtime broker retains only bounded protocol state needed to reattach: server capabilities and dynamic registrations, workspace identity, current configuration, active progress, and open-document revision counters. It discards diagnostics on detach and accepts fresh publications only after the new document is synchronized. It must not treat a transport disconnect as a server crash. A release toggle merges disabled in all shipped profiles; the disabled path preserves the current browser-owned lifecycle.
|
||||
|
||||
## Consequences
|
||||
|
||||
Project analysis can continue while no browser is open, and a returning editor can regain its state. Keeping the lease active also retains the whole Local PC or Docker task host, including agentctl, container and worktree runtime resources, after an agent turn completes. Intentional browser editor-idle release, one-hour detached expiry, and capacity eviction bound this cost. A user returning after expiry or eviction gets a fresh server and visible initialization. Servers that publish diagnostics without a document version cannot safely restore those markers on a resumed lease, so they stay hidden until a fresh server generation. The broker must handle server requests and drain output without a browser, distinguish explicit Stop from transport loss, and reject stale responses from previous browser attachments. Backend restart loses runtime-only leases and requires a fresh server start.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- Keep browser-owned processes and restart automatically on reopen: easy to implement, but discards in-flight analysis and does not satisfy continuous work while the browser is closed.
|
||||
- Retain a process for a short grace period: bounds resource use, but a user returning after the grace period still loses project analysis.
|
||||
- Retain detached leases until capacity pressure: preserves analysis indefinitely when capacity is available, but can also keep an abandoned task host running indefinitely.
|
||||
- Share one protocol session across all browser windows: reduces process count, but conflicting unsaved buffers and request IDs would change the current independent-window behavior.
|
||||
@@ -0,0 +1,132 @@
|
||||
---
|
||||
created: 2026-09-23
|
||||
status: implemented
|
||||
requirements:
|
||||
- REQ-PLATFORM-LSP-FILE-INTELLIGENCE-002
|
||||
system_design:
|
||||
- ../../specs/platform/system-design/lsp-file-intelligence-01.md
|
||||
- ../../specs/platform/system-design/lsp-file-intelligence-02.md
|
||||
legacy_specs: []
|
||||
---
|
||||
|
||||
# Implementation Plan: LSP browser continuity
|
||||
|
||||
## Overview
|
||||
|
||||
Keep a task-host language server working after its browser tab closes, then reconnect a later editor to the retained process and restore valid LSP information. Replace the browser-to-agentctl pass-through with a bounded runtime lease that remains the protocol peer while no browser is attached. Add browser reattachment and status recovery, then prove the user flow with desktop and tablet E2E tests. The design follows [ADR-2026-09-23](../../decisions/2026-09-23-task-owned-lsp-leases.md).
|
||||
|
||||
## Scope
|
||||
|
||||
### In scope
|
||||
|
||||
- Retain one task-host process per LSP lease until intentional editor-idle release, one-hour detached expiry, explicit Stop, capacity eviction, actual process exit, task-runtime shutdown, or backend shutdown.
|
||||
- Reattach to a detached lease for the same execution and language; preserve independent simultaneously open windows.
|
||||
- Resume providers and progress, then accept fresh current-document diagnostics without treating transport loss as a process crash.
|
||||
- Count detached leases against the existing LSP limit and preserve supported-executor and access checks.
|
||||
- Update user-facing status, six locale catalogs, and public LSP/configuration documentation.
|
||||
|
||||
### Out of scope
|
||||
|
||||
- Persisting a language-server process through task-host or backend restart.
|
||||
- Sharing one live protocol session between concurrently open browser windows.
|
||||
- Phone LSP, unsupported executors, new language features, or a global LSP dashboard.
|
||||
|
||||
## Technical approach
|
||||
|
||||
### Runtime lease and protocol continuity
|
||||
|
||||
Refactor `apps/backend/internal/gateway/websocket/lsp_handler.go` around a lease manager keyed by execution identity, language, and opaque lease ID. Keep the agentctl upstream WebSocket open after browser detachment, continuously drain it, and track initialized and dynamic capabilities, workspace metadata, document version counters, latest configuration, and active work progress. Clear diagnostics on detach and accept only fresh, synchronized publications. A returning authorized browser claims its detached lease or an eligible detached lease for the same execution/language. Each concurrently attached browser window keeps its own lease. `lsp_capacity.go` charges one slot per lease and evicts the oldest detached lease before admitting a new process at capacity; if all are attached it returns `4005`. Reattachment does not charge twice. Intentional two-minute browser editor-idle release frees a lease; tab close and network loss retain it for at most one hour. The existing `limits.lspMaxConnections` / `KANDEV_LSP_MAX_CONNECTIONS` identity remains compatible but counts server leases. Update its config-catalog description without changing the key or default; coordinate with the startup-configuration-parity work order that also mentions the key.
|
||||
|
||||
The broker maps JSON-RPC request IDs and `$/cancelRequest` to the active attachment generation, cancels in-flight requests on detach, answers dynamic capability and configuration requests while detached, and drops replies to a prior generation. A settings-save event updates detached leases. It handles acknowledged explicit Stop and editor-idle release separately from socket close; Stop rejects pending browser requests, sends LSP `shutdown` and `exit`, then closes upstream after bounded cleanup. Agentctl sends explicit `4006` only for confirmed process exit. Upstream read failure without that signal maps to `4009` transport/broker failure, never to server-exited; reserved close codes `1005` and `1006` are normalized. Agentctl remains the process owner and reaps descendants. A lost execution or backend shutdown closes the upstream and releases the lease exactly once.
|
||||
|
||||
The orchestrator's idle-session reclaim path (`apps/backend/internal/orchestrator/reconcile_liveness.go`) must treat an active lease as live work at all three callers: the periodic idle reaper, the agent event handler, and the streaming event handler. Wire a narrow lease-liveness interface through backend initialization, without importing the WebSocket gateway into the orchestrator. The lease admission path and reclaim cleanup must share a session lifecycle fence or use equivalent generation checks, so a new lease cannot race with cleanup of the execution it needs. Lease release/eviction removes the pin so normal reclaim can resume.
|
||||
|
||||
Add restart-required `features.lspBrowserContinuity` / `KANDEV_FEATURES_LSP_BROWSER_CONTINUITY` as a runtime release toggle, off in prod, dev, and e2e profiles. Gate backend lease construction and WebSocket admission as well as frontend resume behavior. The disabled path keeps the current browser-owned process and reclaim behavior. Explicitly enable the flag in focused tests and E2E fixtures; exercise both enabled and disabled paths.
|
||||
|
||||
### Editor reconnection
|
||||
|
||||
Update `apps/web/lib/lsp/lsp-client-manager.ts` and its protocol/state helpers to remember an opaque lease ID for tab restoration, reattach automatically after transient transport loss, and accept a resumed ready handshake with retained and dynamic capabilities. A resumed browser does not send a second LSP `initialize`; it rebuilds Monaco providers, sends current document text, waits for synchronization, and accepts only new qualifying diagnostics. It restores current reported work progress from the lease snapshot. Explicit Stop and the existing two-minute last-editor idle timer send acknowledged release controls. A hinted tab may resume its existing lease after auto-start is disabled, unless the user explicitly stopped it; an unhinted new tab follows current auto-start/manual-enable policy. Backend or task-host restart clears stale lease state and follows the same policy. Duplicated-tab hints never steal an attached lease.
|
||||
|
||||
The toolbar and fine-pointer status bar show a localized reconnecting state; the coarse-pointer tablet uses the existing drawer. A true server exit still shows Retry. Phone viewing neither attaches nor starts LSP.
|
||||
|
||||
### Public documentation
|
||||
|
||||
Update `docs/public/developer-tools.md` for continuity, Stop, and restart behavior; `docs/public/configuration.md` for the retained-lease meaning of the existing limit; and `docs/public/websocket-api.md` for the close-code distinction. Check `docs/public/feature-status.md` and update its summary if the new behavior needs mention. These pages change with implementation, after the behavior is verified.
|
||||
|
||||
## ASCII UI preview
|
||||
|
||||
`UI-01: Desktop active Monaco file after transient LSP disconnection` (AC .2, .3). The status control stays in its selected toolbar or status-bar location. The reconnecting state replaces the misleading server-exited message.
|
||||
|
||||
```text
|
||||
Before: Go [!] Error: language server exited [Retry]
|
||||
After: Go [~] Reconnecting to language server... [Stop]
|
||||
Go [o] Ready | Project work: <server report> [Stop]
|
||||
```
|
||||
|
||||
`UI-02: Coarse-pointer tablet LSP drawer` (AC .2, .7). The existing toolbar trigger opens the same drawer; no new navigation surface is introduced.
|
||||
|
||||
```text
|
||||
File editor [Go LSP: Reconnecting]
|
||||
+-------------------------------------------+
|
||||
| Language server |
|
||||
| Reconnecting to language server... |
|
||||
| Project progress resumes when connected. |
|
||||
| [Stop] |
|
||||
+-------------------------------------------+
|
||||
```
|
||||
|
||||
`UI-03: Phone file viewer` (AC .7). The phone viewer retains its existing file header and content; it has no LSP trigger or background attachment.
|
||||
|
||||
```text
|
||||
< Back main.go
|
||||
--------------------------------
|
||||
File content (one scroll region)
|
||||
```
|
||||
|
||||
The structure and lifecycle states are required; the shown text and spacing are illustrative and must use localized copy. Desktop and tablet reuse their existing status placement and surfaces. Tablet keeps the current inset drawer, safe-area handling, touch-sized action, and internal scroll owner. The phone boundary is a no-attachment assertion, not a new screen design.
|
||||
|
||||
## Tests
|
||||
|
||||
- AC .1, .4, .5, .8, .10: Go lease-manager tests prove detach survives a completed turn and reaper interval, Stop/idle release/task stop release capacity, independent attached windows, one-hour detached expiry and reattachment cancellation, LRU detached eviction, exact accounting, and cancellation of a detached install on teardown.
|
||||
- AC .2, .3, .6, .8: Go protocol tests prove capability registration replay, detached configuration updates, monotonic document versions, diagnostic invalidation, version matching and resumed unversioned suppression, request cancellation and generation isolation, process-exit versus transport codes, and reserved-code normalization.
|
||||
- AC .2, .3, .6, .7, .8, .9: web manager tests prove resumed provider setup without second initialize, document synchronization, reconnect/backoff, idle release, close-code translation, policy after auto-start changes, duplicate-hint isolation, localized states, stale-marker suppression, and phone boundary. Flag contract tests cover both modes.
|
||||
|
||||
## E2E tests
|
||||
|
||||
- AC .1, .2, .3: With the flag enabled, extend `apps/web/e2e/tests/lsp/lsp-file-intelligence.spec.ts` and the fake LSP server. Close the desktop page, complete an agent turn, pass a reaper interval, reopen the task, confirm the same fake server process/initialize count, and verify fresh current-file diagnostics and status return.
|
||||
- AC .4, .5, .6, .8, .9: Cover explicit Stop, browser editor-idle release, a second live window and duplicated tab, detached-lease eviction at capacity, all-attached `4005`, and task-host restart followed by fresh initialization.
|
||||
- AC .7: Extend `apps/web/e2e/tests/lsp/mobile-lsp-file-intelligence.spec.ts` for tablet reattachment through its drawer; preserve its existing phone no-socket assertion.
|
||||
|
||||
## Work orders
|
||||
|
||||
- [x] [Task 01: Runtime LSP leases](task-01-runtime-lsp-leases.md) — done.
|
||||
- [x] [Task 02: Editor reconnection and status](task-02-editor-reconnection.md) — done.
|
||||
- [x] [Task 03: Browser proof and public docs](task-03-browser-proof-and-docs.md) — done.
|
||||
|
||||
Execution is sequential because the broker handshake, browser state, and E2E fixture share one protocol contract.
|
||||
|
||||
## Verification results
|
||||
|
||||
Implemented all three work orders. Focused backend tests and `go vet` passed, including the LSP gateway, agentctl API, orchestrator, backend wiring, runtime flags, profiles, and configuration catalog. The focused web LSP/component/feature suite passed (58 tests), web typecheck and changed-file ESLint passed, and `pnpm run i18n:pseudo && pnpm run i18n:check` passed. Managed desktop LSP E2E scenarios passed for close/reopen retention, independent windows and duplicate tabs, detached capacity eviction, all-attached capacity, intentional idle release, disabled-flag behavior, and fresh initialization after task-host restart. Managed mobile E2E passed for tablet drawer reattachment and the phone no-socket boundary. Public-doc validators and spec catalog/lint checks passed. `git diff --check` passed. The repository-wide E2E sleep lint reports unrelated existing violations outside the changed LSP files; targeted lint for the changed E2E files passed.
|
||||
|
||||
### Code-review remediation results (2026-09-23)
|
||||
|
||||
- Normal `agent.completed` cleanup now defers teardown while the exact execution owns a live LSP lease. Regression coverage drives the actual completion event and a stale-row idle-reaper scan; the runtime row remains running.
|
||||
- Detach keeps the upstream write fence through old-generation cancellation and `didClose` writes. Controlled concurrency coverage proves cleanup precedes successor `didOpen`, and a stale handler generation cannot detach its successor.
|
||||
- The broker owns the single `initialize` request and response across browser detaches. Tests cover reconnect before the response and reconnect after the response but before `initialized`; the resumed client completes the missing notification without a second initialize.
|
||||
- Detached reuse now requires the same user ID. Broker configuration lookup restores full-language and language-prefixed section behavior. Tests cover both-user lease selection and nested/missing configuration sections.
|
||||
- The browser advertises only supported dynamic provider methods, maps semantic tokens, and rebuilds registrations across resume. Focused backend/frontend registration tests pass.
|
||||
- The full non-race orchestrator suite passed (265.6s), the complete gateway race suite passed, and focused race tests for the LSP gateway and orchestrator reaper regressions passed. The combined full race run ended with a nil-executor panic in an asynchronous queued-message test goroutine (`Executor.GetExecutionBySession` from `executeQueuedMessageWithReservation`); no LSP regression test failed. All 134 LSP Vitest tests passed; web typecheck, changed-file ESLint, `git diff --check`, and the managed desktop E2E that completes a real follow-up turn and waits 95 seconds through the reaper age threshold and scan passed.
|
||||
- The one-hour detached expiry follow-up adds timer and admission cleanup, protects reattached leases from stale deadlines, and documents fresh analysis after expiry. The gateway race suite, `go vet`, spec catalog/lint, and public-doc validators pass.
|
||||
|
||||
## Risks
|
||||
|
||||
- A retained server keeps the entire Local PC or Docker task host alive, including agentctl, container, and worktree runtime resources, after a turn completes. Intentional editor-idle release, one-hour detached expiry, and oldest-detached eviction bound the cost; users returning after expiry or eviction see fresh initialization.
|
||||
- All three idle-reclaim call sites can end an execution after a turn completes. None may reclaim one with a live LSP lease, and lease admission must not race with those decisions.
|
||||
- Some servers issue client requests or progress while detached. The broker must continue draining and responding without a browser.
|
||||
- Multiple browser windows may have divergent unsaved text. They must remain on independent leases.
|
||||
- Reattached diagnostics need a matching server-facing document version. Unversioned diagnostics stay hidden on resumed leases until a fresh server generation, which reduces diagnostic coverage for servers that omit versions.
|
||||
|
||||
## Open questions
|
||||
|
||||
None blocking the design. Retention ends with the active task runtime; backend or task-host restart uses a fresh server and visible initialization.
|
||||
@@ -0,0 +1,100 @@
|
||||
---
|
||||
id: "01-runtime-lsp-leases"
|
||||
title: "Runtime LSP leases"
|
||||
status: done
|
||||
wave: 1
|
||||
depends_on: []
|
||||
plan: "plan.md"
|
||||
requirements:
|
||||
- REQ-PLATFORM-LSP-FILE-INTELLIGENCE-002
|
||||
acceptance_criteria:
|
||||
- AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.1
|
||||
- AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.2
|
||||
- AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.3
|
||||
- AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.4
|
||||
- AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.5
|
||||
- AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.6
|
||||
- AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.8
|
||||
- AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.9
|
||||
- AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.10
|
||||
system_design:
|
||||
- ../../specs/platform/system-design/lsp-file-intelligence-01.md
|
||||
- ../../specs/platform/system-design/lsp-file-intelligence-02.md
|
||||
---
|
||||
|
||||
# Task 01: Runtime LSP leases
|
||||
|
||||
## Summary
|
||||
|
||||
Make the backend own a bounded LSP lease and keep its agentctl stream and language-server process alive when a browser detaches. Supply a safe reattachment handshake, explicit Stop, and separate process-exit and transport-failure signals.
|
||||
|
||||
## In scope
|
||||
|
||||
- Add a backend lease manager around the current LSP handler. It continuously drains the task-host stream, owns request-generation mapping and bounded resume snapshots, and reattaches only authorized browsers for the same execution and language.
|
||||
- Keep concurrently attached browser windows on independent leases, including duplicated-tab hints. Count detached leases against the existing limit; release capacity once on editor-idle release, Stop, one-hour detached expiry, oldest-detached capacity eviction, process exit, task-runtime stop, or backend shutdown.
|
||||
- Integrate lease liveness with all `reclaimIdleSession` callers: `idle_session_reaper.go`, `event_handlers_agent.go`, and `event_handlers_streaming.go`. Fence admission against concurrent reclaim and preserve explicit task-stop behavior.
|
||||
- Handle dynamic registrations and `workspace/configuration` while detached, subscribe to saved per-language settings changes, map `$/cancelRequest`, cancel pending requests on detach/Stop, reconcile reopened document versions, invalidate old diagnostics, and normalize reserved WebSocket close codes. Stop sends `shutdown`/`exit`; agentctl marks confirmed process exit distinctly from upstream read failure and owns process-tree teardown.
|
||||
- Add the all-off, restart-required `features.lspBrowserContinuity` release toggle through profiles, typed backend config, runtime flag registry/binding, and backend construction/WebSocket gates. Its disabled path preserves the browser-owned proxy and idle-reclaim behavior. Update the `limits.lspMaxConnections` catalog description without changing its identity or default; coordinate with startup-configuration-parity Task 04.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Browser status rendering, locale copy, and Playwright flows.
|
||||
- Durable process persistence across backend or task-host restart.
|
||||
|
||||
## Acceptance
|
||||
|
||||
1. Closing a browser socket leaves the same upstream/process alive and draining; an authorized later attachment resumes its capabilities and progress without another task-host process start.
|
||||
2. Explicit Stop, editor-idle release, one-hour detached expiry, capacity eviction, and every runtime terminal path stop the intended lease and release capacity once; a separate live window is unchanged, a detached lease remains admissible at capacity, and all idle-reclaim callers skip an execution with an active lease even after the agent turn completes. Reattachment cancels expiry, and expiry allows subsequent idle reclaim.
|
||||
3. The broker isolates old request generations, translates cancellations, retains effective dynamic registrations and latest settings, uses increasing server-facing document versions, clears diagnostics on detach, accepts only qualifying fresh publications, and distinguishes confirmed server exit from transport failure.
|
||||
4. The release toggle is off in all shipped profiles; direct backend access uses the legacy behavior when disabled, and enabled-only work is unreachable without the flag.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
(cd apps/backend && go test ./internal/gateway/websocket ./internal/agentctl/server/api ./internal/orchestrator ./internal/backendapp ./internal/runtimeflags ./internal/common/config ./internal/profiles)
|
||||
(cd apps/backend && go vet ./internal/gateway/websocket ./internal/agentctl/server/api ./internal/orchestrator ./internal/backendapp)
|
||||
```
|
||||
|
||||
## Files likely touched
|
||||
|
||||
- `apps/backend/internal/gateway/websocket/lsp_handler.go`
|
||||
- `apps/backend/internal/gateway/websocket/lsp_capacity.go`
|
||||
- `apps/backend/internal/gateway/websocket/lsp_lease.go` and focused tests
|
||||
- `apps/backend/internal/agentctl/server/api/lsp.go` and focused tests for the upstream process-exit signal
|
||||
- `apps/backend/internal/backendapp/gateway.go` for lifecycle shutdown wiring
|
||||
- `apps/backend/internal/backendapp/services.go` or the nearest initialization seam for the lease-liveness callback
|
||||
- `apps/backend/internal/orchestrator/reconcile_liveness.go`, `idle_session_reaper.go`, `event_handlers_agent.go`, `event_handlers_streaming.go`, and focused reclaim tests
|
||||
- `apps/backend/internal/user/service/service.go` or the existing settings-save event seam for detached configuration updates
|
||||
- `profiles.yaml`, `apps/backend/internal/common/config/config.go`, `apps/backend/internal/runtimeflags/{registry,config}.go` and their contract tests
|
||||
- `apps/backend/internal/common/config/catalog.go` and its catalog tests
|
||||
- `AGENTS.md` Observability section and `apps/backend/AGENTS.md` LSP ownership notes
|
||||
|
||||
## Dependencies
|
||||
|
||||
None. Task 02 consumes the lease handshake and close-code contract.
|
||||
|
||||
## Risks
|
||||
|
||||
- A detached server can emit output indefinitely; reading, caching, and client request handling must stay bounded.
|
||||
- Capacity or process teardown races can leak a lease or interrupt the wrong execution after replacement.
|
||||
- Idle reclaim and lease admission can race unless both use the same session lifecycle fence or an equivalent generation check.
|
||||
- A new HTTP or control surface must enforce session authorization before lease lookup.
|
||||
- Holding the lease pins the whole task host; idle release, one-hour detached expiry, and capacity eviction must allow normal reclaim again.
|
||||
|
||||
## Parallelism
|
||||
|
||||
`sequential`
|
||||
|
||||
## Inputs
|
||||
|
||||
- [Plan and preview](plan.md)
|
||||
- `REQ-PLATFORM-LSP-FILE-INTELLIGENCE-002` and both linked system designs.
|
||||
- [ADR-2026-09-23](../../decisions/2026-09-23-task-owned-lsp-leases.md)
|
||||
|
||||
## Results
|
||||
|
||||
Implemented runtime-owned bounded leases, detach/reattach protocol brokering, capacity/idle release, task-runtime liveness fencing, process-exit signaling, and the all-off release toggle. Focused Go tests passed for the gateway, agentctl API, orchestrator, backend wiring, runtime flags, profiles, and the audited configuration catalog. Focused `go vet` passed for the changed runtime packages.
|
||||
|
||||
Code-review remediation preserves the task-host execution after normal `agent.completed` while that exact execution has an active lease. Detach cleanup remains ordered before successor document writes; the attach call returns its own generation; initialize/initialized handshake state survives either detach boundary; reuse is scoped to the settings owner; dynamic registrations are brokered while attached and replayed after resume; language-prefixed `workspace/configuration` sections retain their prior behavior; and pending browser requests have count, ID-size, aggregate-state, and timeout bounds. Focused race tests passed for both changed packages, the complete `websocket` package race suite passed, full non-race and race-enabled orchestrator suites passed, and backend lint and build passed. An earlier full race run hit a nil-executor panic in an asynchronous queued-message test goroutine (`Executor.GetExecutionBySession` from `executeQueuedMessageWithReservation`); the final full race rerun passed without reproducing it.
|
||||
|
||||
The follow-up detached deadline releases an idle lease one hour after browser detachment, cancels on reattachment, and permits normal task-host reclaim. Expired leases are also removed before admission if a timer callback was delayed. Focused deadline tests and the full gateway race suite pass.
|
||||
@@ -0,0 +1,107 @@
|
||||
---
|
||||
id: "02-editor-reconnection"
|
||||
title: "Editor reconnection and status"
|
||||
status: done
|
||||
wave: 2
|
||||
depends_on:
|
||||
- "01-runtime-lsp-leases"
|
||||
plan: "plan.md"
|
||||
requirements:
|
||||
- REQ-PLATFORM-LSP-FILE-INTELLIGENCE-002
|
||||
acceptance_criteria:
|
||||
- AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.2
|
||||
- AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.3
|
||||
- AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.4
|
||||
- AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.6
|
||||
- AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.7
|
||||
- AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.8
|
||||
- AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.9
|
||||
system_design:
|
||||
- ../../specs/platform/system-design/lsp-file-intelligence-01.md
|
||||
- ../../specs/platform/system-design/lsp-file-intelligence-02.md
|
||||
---
|
||||
|
||||
# Task 02: Editor reconnection and status
|
||||
|
||||
## Summary
|
||||
|
||||
Let a returning Monaco editor reattach to a retained LSP lease, rebuild its providers, and show current status and analysis. Recover brief transport loss automatically while keeping actual server exits actionable.
|
||||
|
||||
## In scope
|
||||
|
||||
- Store an opaque lease hint for tab restoration, consume the resumed handshake with effective dynamic registrations and without a second `initialize`, register providers, and synchronize current documents before accepting fresh diagnostics.
|
||||
- Add bounded reconnect attempts and a localized reconnecting status. Keep Stop as an acknowledged explicit action, send acknowledged release at the existing two-minute last-editor idle timeout, and preserve browser-local manual enablement. A hinted existing lease may resume after auto-start is disabled, unless explicitly stopped; an unhinted new tab follows current policy. Duplicated tabs never steal an attached lease.
|
||||
- Add the frontend feature key and enabled/disabled client behavior. When the flag is off, preserve the existing browser-owned connection and close-code handling. Translate `4006` only as confirmed process exit and `4009` or abnormal close as transport failure when on.
|
||||
- Reuse the existing desktop toolbar/status-bar and coarse-pointer tablet drawer; keep phone viewing LSP-free. Add focused unit/component coverage and all required locales.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- A new LSP dashboard, phone controls, or layout redesign.
|
||||
- Browser E2E and public documentation, covered by Task 03.
|
||||
|
||||
## Acceptance
|
||||
|
||||
1. A resumed connection rebuilds Monaco providers from retained and dynamic capabilities, synchronizes open documents, shows current project progress without a second LSP initialize request, and accepts only fresh qualifying diagnostics.
|
||||
2. Unexpected transport loss (`4009` or abnormal close) shows reconnecting and retries the retained lease; confirmed process exit (`4006`) shows server-exited Retry; stale responses from a replaced browser attachment cannot change the new status.
|
||||
3. Explicit Stop ends only this window's lease, and tablet shows the same state/action in its existing drawer while phone mounts no LSP connection.
|
||||
4. The last-editor idle timeout releases its lease, a duplicated tab cannot steal an attached lease, and a hinted tab resumes its lease after an auto-start preference change unless explicitly stopped.
|
||||
|
||||
## ASCII UI preview
|
||||
|
||||
`UI-01: Desktop active Monaco file after transient disconnection` and `UI-02: Coarse-pointer tablet LSP drawer` follow the [combined preview](plan.md#ascii-ui-preview) (AC .2, .3, .7).
|
||||
|
||||
```text
|
||||
Desktop: Go [~] Reconnecting to language server... [Stop]
|
||||
Go [o] Ready | Project work: <server report> [Stop]
|
||||
Tablet: [Go LSP: Reconnecting] -> drawer -> status + [Stop]
|
||||
Phone: file header + content; no LSP control or attachment
|
||||
```
|
||||
|
||||
The reconnecting state and action are required. Copy and spacing are illustrative and localized. Existing desktop placement, tablet drawer geometry, and phone viewer composition remain the structural baseline.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
(cd apps/web && pnpm exec vitest run lib/lsp/lsp-client-manager.test.ts lib/lsp/lsp-client-manager.progress.test.ts lib/lsp/lsp-client-manager.document-sync.test.ts hooks/use-lsp.test.tsx)
|
||||
(cd apps && pnpm --filter @kandev/web test -- lib/state/slices/features/features-contract.test.ts)
|
||||
(cd apps/web && pnpm run typecheck)
|
||||
(cd apps/web && pnpm run i18n:check)
|
||||
(cd apps/web && pnpm exec eslint lib/lsp hooks/use-lsp.ts components/editors/lsp-status-button.tsx)
|
||||
```
|
||||
|
||||
## Files likely touched
|
||||
|
||||
- `apps/web/lib/lsp/lsp-client-manager.ts` and its focused tests
|
||||
- `apps/web/lib/lsp/lsp-json-rpc.ts`, progress and document-sync helpers
|
||||
- `apps/web/hooks/use-lsp.ts` and tests
|
||||
- `apps/web/lib/state/slices/features/types.ts` and feature contract tests
|
||||
- `apps/web/components/editors/lsp-status-button.tsx`
|
||||
- `apps/web/src/locales/{en,pt-pt,zh-cn,zh-hk,zh-tw,ja}/lsp.json` and verbatim records only if needed
|
||||
|
||||
## Dependencies
|
||||
|
||||
Task 01 supplies the lease/resume protocol and close-code distinction.
|
||||
|
||||
## Risks
|
||||
|
||||
- Monaco provider disposal during reconnect can erase another session's markers or leave stale built-in suppression.
|
||||
- A tab with unsaved edits must not show diagnostics produced for the previous attachment's buffer.
|
||||
- A server that omits diagnostic versions cannot restore markers safely on a resumed lease; the editor keeps markers clear until a fresh process starts.
|
||||
- Automatic retries must be bounded and cancel when the user explicitly stops or leaves the editor.
|
||||
- Disabled rollout must preserve the old `4006` proxy-stream handling; `4009` must never inherit the true process-exit label.
|
||||
|
||||
## Parallelism
|
||||
|
||||
`sequential`
|
||||
|
||||
## Inputs
|
||||
|
||||
- [Plan and preview](plan.md)
|
||||
- `REQ-PLATFORM-LSP-FILE-INTELLIGENCE-002` and both linked system designs.
|
||||
- Task 01's tested handshake and close-code contract.
|
||||
|
||||
## Results
|
||||
|
||||
Implemented lease-hint restoration, bounded reconnect, resumed capability/progress state, document synchronization before diagnostics, acknowledged Stop/idle release, localized reconnect status, feature gating, and the phone LSP boundary. The focused web LSP, hook, storage, dynamic-capability, and feature-contract suite passed (58 tests); web typecheck, changed-file ESLint, and the pseudo-locale/i18n checks passed.
|
||||
|
||||
Code-review remediation advertises dynamic registration only for the supported completion, hover, definition, references, signature-help, and semantic-token providers. The semantic-token registration maps to Monaco's supported capability shape. A resumed handshake that reports `initialized: false` sends the one-time `initialized` notification before document synchronization and attachment readiness. The final focused LSP suite passed (150 tests across 15 files); web typecheck and changed-file ESLint passed.
|
||||
@@ -0,0 +1,109 @@
|
||||
---
|
||||
id: "03-browser-proof-and-docs"
|
||||
title: "Browser proof and public docs"
|
||||
status: done
|
||||
wave: 3
|
||||
depends_on:
|
||||
- "01-runtime-lsp-leases"
|
||||
- "02-editor-reconnection"
|
||||
plan: "plan.md"
|
||||
requirements:
|
||||
- REQ-PLATFORM-LSP-FILE-INTELLIGENCE-002
|
||||
acceptance_criteria:
|
||||
- AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.1
|
||||
- AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.2
|
||||
- AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.3
|
||||
- AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.4
|
||||
- AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.5
|
||||
- AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.6
|
||||
- AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.7
|
||||
- AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.8
|
||||
- AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.9
|
||||
system_design:
|
||||
- ../../specs/platform/system-design/lsp-file-intelligence-01.md
|
||||
- ../../specs/platform/system-design/lsp-file-intelligence-02.md
|
||||
---
|
||||
|
||||
# Task 03: Browser proof and public docs
|
||||
|
||||
## Summary
|
||||
|
||||
Prove browser close and return against a real task runtime and a deterministic fake language server. Update public guidance to describe retained processes, capacity, Stop, and restart recovery.
|
||||
|
||||
## In scope
|
||||
|
||||
- Extend the existing fake LSP fixture with a count or process marker that distinguishes reattachment from a fresh process and initialize.
|
||||
- Enable the release flag explicitly in the E2E fixture. Add desktop E2E for page close/reopen after a completed agent turn and reaper interval, fresh diagnostics and progress, duplicated-tab isolation, explicit Stop, editor-idle release, detached-lease eviction, all-attached capacity, and task-host restart. Add tablet reattachment coverage and retain the phone no-socket assertion. Include a disabled-flag regression of the existing browser-owned lifecycle.
|
||||
- Update the existing public developer-tools, configuration, WebSocket API, and feature-status text where the previous browser-owned lifecycle is documented.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- New LSP capabilities, executor support, mobile phone LSP, or a new status surface.
|
||||
|
||||
## Acceptance
|
||||
|
||||
1. A desktop browser closes and reopens on the task; the same fake server process remains, the editor reaches ready with current-file diagnostics and project status, and no second initialize occurs.
|
||||
2. A second active window and duplicated tab remain independent, idle release and capacity eviction free task-host resources, explicit Stop and host restart have the specified effects, and the tablet drawer regains status while phone opens no LSP socket.
|
||||
3. Public docs accurately describe retained-lease capacity, Stop, restart recovery, and transport versus process-exit statuses; focused docs validators pass.
|
||||
|
||||
## ASCII UI preview
|
||||
|
||||
`UI-01: Desktop active Monaco file`, `UI-02: Coarse-pointer tablet drawer`, and `UI-03: Phone file viewer` use the [combined preview](plan.md#ascii-ui-preview) (AC .2, .3, .7).
|
||||
|
||||
```text
|
||||
Desktop: [Go LSP: Reconnecting] -> [Go LSP: Ready + project report]
|
||||
Tablet: [Go LSP] -> bottom drawer -> Ready + project report
|
||||
Phone: main.go -> file content; no LSP control
|
||||
```
|
||||
|
||||
No new navigation is required. Test the existing desktop and tablet surfaces and the phone absence contract.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
(cd apps/web && pnpm e2e:run tests/lsp/lsp-file-intelligence.spec.ts -- --grep "retains|reattaches|disconnects|evicts|releases|capacity")
|
||||
(cd apps/web && pnpm e2e:run --project mobile-chrome tests/lsp/mobile-lsp-file-intelligence.spec.ts -- --grep "reattaches|without starting")
|
||||
node --test scripts/validate-public-docs.test.mjs
|
||||
node scripts/validate-public-docs.mjs
|
||||
```
|
||||
|
||||
The implementation must use test names matching the focused grep expressions and confirm Playwright discovers the intended scenarios. The managed runner builds production frontend and backend assets for each command.
|
||||
|
||||
## Files likely touched
|
||||
|
||||
- `apps/web/e2e/fixtures/fake-lsp-server.mjs`
|
||||
- `apps/web/e2e/tests/lsp/lsp-file-intelligence.spec.ts`
|
||||
- `apps/web/e2e/tests/lsp/mobile-lsp-file-intelligence.spec.ts`
|
||||
- `apps/web/e2e/tests/lsp/lsp-e2e-helpers.ts`
|
||||
- `apps/web/e2e/fixtures/backend.ts` or the nearest per-spec release-flag override
|
||||
- `docs/public/developer-tools.md`
|
||||
- `docs/public/configuration.md`
|
||||
- `docs/public/websocket-api.md`
|
||||
- `docs/public/feature-status.md` if its current lifecycle summary changes
|
||||
|
||||
## Dependencies
|
||||
|
||||
Tasks 01 and 02 must complete so the E2E scenarios exercise the final protocol and UI.
|
||||
|
||||
## Risks
|
||||
|
||||
- Browser page close may race process teardown; the fake server marker must prove the same process survived rather than merely a fast restart.
|
||||
- The tablet test must exercise its actual drawer and touch action; the phone test must confirm no socket is opened.
|
||||
- The public config key name remains unchanged even though its resource count becomes retained leases.
|
||||
- A retained lease pins the whole Local PC or Docker task host until release, eviction, or task stop; public guidance should make this cost and fresh-start-after-eviction behavior clear.
|
||||
|
||||
## Parallelism
|
||||
|
||||
`sequential`
|
||||
|
||||
## Inputs
|
||||
|
||||
- [Plan and preview](plan.md)
|
||||
- `REQ-PLATFORM-LSP-FILE-INTELLIGENCE-002` and both linked system designs.
|
||||
- Completed Task 01 and Task 02 results.
|
||||
|
||||
## Results
|
||||
|
||||
Added managed desktop E2E coverage for retained process identity and fresh diagnostics/progress after reattachment, independent windows and duplicate-tab isolation, Stop and idle release, detached eviction and all-attached capacity, disabled-flag compatibility, and fresh startup after task-host restart. Tablet drawer reattachment and the phone no-socket boundary passed on the managed mobile project. Updated public developer, configuration, feature-status, and WebSocket guidance; both public-doc validators passed. Spec catalog validation, spec lint, and `git diff --check` passed.
|
||||
|
||||
Code-review remediation extends the close/reopen scenario to send and observe a second completed agent turn after LSP admission. It then waits 95 seconds to pass the 60-second idle age threshold and the 30-second reaper tick, and verifies the same fake LSP process and initialize count after reattachment. The focused managed desktop E2E passed (1 test, 1.7 minutes); the frontend production build and backend build both completed as part of the managed runner.
|
||||
@@ -147,19 +147,19 @@ The Docker socket is effectively root-equivalent on many hosts. Do not publish i
|
||||
|
||||
### Core agent service
|
||||
|
||||
| YAML key | Environment variable | Default | Current behavior |
|
||||
| ---------------------- | ------------------------------------------------- | ----------- | ---------------------------------------------------------------------------- |
|
||||
| YAML key | Environment variable | Default | Current behavior |
|
||||
| ---------------------- | ------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------- |
|
||||
| `agent.standaloneHost` | `KANDEV_AGENT_STANDALONE_HOST` | `127.0.0.1` | Host of the core `agentctl` control server. The literal avoids IPv6 loopback resolution variance. |
|
||||
| `agent.standalonePort` | `AGENTCTL_PORT` or `KANDEV_AGENT_STANDALONE_PORT` | `39429` | Preferred control port. The launcher may supply a free fallback. |
|
||||
| `agent.standalonePort` | `AGENTCTL_PORT` or `KANDEV_AGENT_STANDALONE_PORT` | `39429` | Preferred control port. The launcher may supply a free fallback. |
|
||||
|
||||
The launcher starts `agentctl`, performs a one-time nonce handshake, and supplies the resulting per-launch token internally. Do not persist or proxy its bootstrap/auth state. Agent command, model, environment, permission, and MCP configuration belongs in agent profiles rather than this section.
|
||||
|
||||
### Setup and launch timing
|
||||
|
||||
| YAML key | Environment variable | Default | Current behavior |
|
||||
| -------------------------- | --------------------------------- | ------- | ------------------------------------------------------------------------------- |
|
||||
| `tasks.preparationTimeout` | `KANDEV_TASK_PREPARATION_TIMEOUT` | `10m` | Positive Go duration for repository setup and executor-profile prepare scripts. |
|
||||
| `tasks.stallDetectionThreshold` | `KANDEV_TASK_STALL_DETECTION_THRESHOLD` | `2h` | Positive Go duration for the session reconciliation sweep's stall threshold. |
|
||||
| YAML key | Environment variable | Default | Current behavior |
|
||||
| ------------------------------- | --------------------------------------- | ------- | ------------------------------------------------------------------------------- |
|
||||
| `tasks.preparationTimeout` | `KANDEV_TASK_PREPARATION_TIMEOUT` | `10m` | Positive Go duration for repository setup and executor-profile prepare scripts. |
|
||||
| `tasks.stallDetectionThreshold` | `KANDEV_TASK_STALL_DETECTION_THRESHOLD` | `2h` | Positive Go duration for the session reconciliation sweep's stall threshold. |
|
||||
|
||||
`tasks.preparationTimeout` controls how long Kandev allows repository setup and
|
||||
executor-profile prepare scripts to run. The value uses Go duration syntax,
|
||||
@@ -197,28 +197,28 @@ variable overrides YAML, and changes require a backend restart.
|
||||
|
||||
### Capacity and managed-process startup settings
|
||||
|
||||
| YAML key | Environment variable | Type and default | Current behavior |
|
||||
| ------------------------------------ | --------------------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `credentials.file` | `KANDEV_CREDENTIALS_FILE` | path, empty | Optional fallback JSON credential file. The file is read lazily and must be restricted to the Kandev service account. |
|
||||
| `limits.ghMaxConcurrent` | `KANDEV_GH_MAX_CONCURRENT` | positive integer, `8` | Process-wide `gh` subprocess admission cap. |
|
||||
| `limits.gitMaxConcurrent` | `KANDEV_GIT_MAX_CONCURRENT` | positive integer, `12` | Process-wide `git` subprocess admission cap. |
|
||||
| `limits.lspMaxConnections` | `KANDEV_LSP_MAX_CONNECTIONS` | positive integer, `8` | Active browser-to-task-host language-server connection cap. |
|
||||
| `messageQueue.maxPerSession` | `KANDEV_QUEUE_MAX_PER_SESSION` | integer `>= 0`, `10` | Per-session pending-message cap. Zero means unlimited. A non-negative YAML or environment value locks capacity in Settings; a negative environment value means unlimited, and invalid environment input falls through to the lower-precedence source. |
|
||||
| `agentctl.idleTimeout` | `KANDEV_ACP_IDLE_TIMEOUT` | Go duration, `1h` | Idle managed-agent reaping timeout. Zero disables reaping. |
|
||||
| `agentctl.idleReaperInterval` | `KANDEV_ACP_IDLE_REAPER_INTERVAL` | Go duration, `1m` | Interval between idle-agent scans. |
|
||||
| `agentctl.notificationQueueCapacity` | `KANDEV_ACP_NOTIF_QUEUE` | integer `1024`-`131072`, `131072` | ACP inbound notification queue capacity. An out-of-range YAML value fails startup; an invalid or out-of-range environment value uses the built-in default. |
|
||||
| `agentctl.recoveryDeadline` | `KANDEV_ACP_RECOVERY_DEADLINE` | Go duration `1s`-`5m`, `30s` | Single bound covering one startup's control-server adoption, instance enumeration, and session reconstruction. Unlike the settings above, an invalid or out-of-range environment value **fails startup** rather than falling back to the default. |
|
||||
| `agentctl.recoveryReadTimeout` | `KANDEV_ACP_RECOVERY_READ_TIMEOUT` | Go duration `100ms`-`1h`, `2s` | Per-attempt timeout for each read of a surviving control server during recovery. An invalid or out-of-range environment value fails startup. The combined budget with `agentctl.recoveryReadRetries` must also fit the lesser of 6s and one fifth of `agentctl.recoveryDeadline`, so values above a few seconds fail startup regardless of the range. |
|
||||
| `agentctl.recoveryReadRetries` | `KANDEV_ACP_RECOVERY_READ_RETRIES` | integer `0`-`10`, `2` | Retries per recovery read, on top of the first attempt. The resulting budget must not exceed the lesser of 6s and one fifth of `agentctl.recoveryDeadline`, or startup fails. An invalid or out-of-range environment value fails startup. |
|
||||
| `agentctl.unownedPeriod` | `KANDEV_ACP_UNOWNED_PERIOD` | Go duration, `10m` | How long a standalone control server keeps running with no backend renewing ownership before it shuts itself down. Raised to a one-minute floor, and clamped to half `agentctl.idleTimeout` when that is enabled and shorter; an invalid or negative environment value uses the default. |
|
||||
| `agentctl.detachedEventLimit` | `KANDEV_ACP_DETACHED_EVENT_LIMIT` | integer `1`-`10000`, `100` | Per-instance agent events retained while no backend is attached. An invalid or out-of-range environment value fails startup. |
|
||||
| `planning.coalesceWindowMs` | `KANDEV_PLAN_COALESCE_WINDOW_MS` | integer `>= 0`, `300000` | Same-author plan revision coalescing window in milliseconds. |
|
||||
| `observability.otlpEndpoint` | `OTEL_EXPORTER_OTLP_ENDPOINT` | URL, empty | OTLP tracing endpoint. Treat the value and emitted spans as sensitive. |
|
||||
| `executors.sshReachabilityIntervalSeconds` | `KANDEV_EXECUTORS_SSHREACHABILITYINTERVALSECONDS` | integer `>= 0`, `60` | Interval between background SSH-executor reachability probes, in seconds. `0` disables the poller entirely; a probed executor then keeps its last recorded state rather than going stale. The effective cadence is otherwise clamped to `15`-`3600`: a value from `1` to `14` becomes `15`, and a value above `3600` becomes `3600`, each logged once at startup. Valid environment and YAML values use this clamp. An invalid environment value falls back to `60`; an invalid YAML value fails startup like any other typed catalog key. |
|
||||
| `office.schedulerTickMs` | `KANDEV_OFFICE_SCHEDULER_TICK_MS` | positive integer, `5000` | Office queued/retry run safety-net interval in milliseconds. |
|
||||
| `launcher.webPort` | `KANDEV_WEB_PORT` | automatic, `0` | Development web-server port. It is used with `dev` and ignored by embedded-asset launches. |
|
||||
| `launcher.healthTimeoutMs` | `KANDEV_HEALTH_TIMEOUT_MS` | positive integer, `45000` | Launcher startup-health timeout in milliseconds. Development and E2E profiles use a longer default. |
|
||||
| `launcher.noBrowser` | `KANDEV_NO_BROWSER` | `false` | Suppresses browser opening when true or `1`, depending on launch mode. |
|
||||
| YAML key | Environment variable | Type and default | Current behavior |
|
||||
| ------------------------------------------ | ------------------------------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `credentials.file` | `KANDEV_CREDENTIALS_FILE` | path, empty | Optional fallback JSON credential file. The file is read lazily and must be restricted to the Kandev service account. |
|
||||
| `limits.ghMaxConcurrent` | `KANDEV_GH_MAX_CONCURRENT` | positive integer, `8` | Process-wide `gh` subprocess admission cap. |
|
||||
| `limits.gitMaxConcurrent` | `KANDEV_GIT_MAX_CONCURRENT` | positive integer, `12` | Process-wide `git` subprocess admission cap. |
|
||||
| `limits.lspMaxConnections` | `KANDEV_LSP_MAX_CONNECTIONS` | positive integer, `8` | Maximum active and detached task-host language-server leases when browser continuity is enabled. At capacity, Kandev evicts the least-recently detached lease; if all leases are attached, it rejects the new request. |
|
||||
| `messageQueue.maxPerSession` | `KANDEV_QUEUE_MAX_PER_SESSION` | integer `>= 0`, `10` | Per-session pending-message cap. Zero means unlimited. A non-negative YAML or environment value locks capacity in Settings; a negative environment value means unlimited, and invalid environment input falls through to the lower-precedence source. |
|
||||
| `agentctl.idleTimeout` | `KANDEV_ACP_IDLE_TIMEOUT` | Go duration, `1h` | Idle managed-agent reaping timeout. Zero disables reaping. |
|
||||
| `agentctl.idleReaperInterval` | `KANDEV_ACP_IDLE_REAPER_INTERVAL` | Go duration, `1m` | Interval between idle-agent scans. |
|
||||
| `agentctl.notificationQueueCapacity` | `KANDEV_ACP_NOTIF_QUEUE` | integer `1024`-`131072`, `131072` | ACP inbound notification queue capacity. An out-of-range YAML value fails startup; an invalid or out-of-range environment value uses the built-in default. |
|
||||
| `agentctl.recoveryDeadline` | `KANDEV_ACP_RECOVERY_DEADLINE` | Go duration `1s`-`5m`, `30s` | Single bound covering one startup's control-server adoption, instance enumeration, and session reconstruction. Unlike the settings above, an invalid or out-of-range environment value **fails startup** rather than falling back to the default. |
|
||||
| `agentctl.recoveryReadTimeout` | `KANDEV_ACP_RECOVERY_READ_TIMEOUT` | Go duration `100ms`-`1h`, `2s` | Per-attempt timeout for each read of a surviving control server during recovery. An invalid or out-of-range environment value fails startup. The combined budget with `agentctl.recoveryReadRetries` must also fit the lesser of 6s and one fifth of `agentctl.recoveryDeadline`, so values above a few seconds fail startup regardless of the range. |
|
||||
| `agentctl.recoveryReadRetries` | `KANDEV_ACP_RECOVERY_READ_RETRIES` | integer `0`-`10`, `2` | Retries per recovery read, on top of the first attempt. The resulting budget must not exceed the lesser of 6s and one fifth of `agentctl.recoveryDeadline`, or startup fails. An invalid or out-of-range environment value fails startup. |
|
||||
| `agentctl.unownedPeriod` | `KANDEV_ACP_UNOWNED_PERIOD` | Go duration, `10m` | How long a standalone control server keeps running with no backend renewing ownership before it shuts itself down. Raised to a one-minute floor, and clamped to half `agentctl.idleTimeout` when that is enabled and shorter; an invalid or negative environment value uses the default. |
|
||||
| `agentctl.detachedEventLimit` | `KANDEV_ACP_DETACHED_EVENT_LIMIT` | integer `1`-`10000`, `100` | Per-instance agent events retained while no backend is attached. An invalid or out-of-range environment value fails startup. |
|
||||
| `planning.coalesceWindowMs` | `KANDEV_PLAN_COALESCE_WINDOW_MS` | integer `>= 0`, `300000` | Same-author plan revision coalescing window in milliseconds. |
|
||||
| `observability.otlpEndpoint` | `OTEL_EXPORTER_OTLP_ENDPOINT` | URL, empty | OTLP tracing endpoint. Treat the value and emitted spans as sensitive. |
|
||||
| `executors.sshReachabilityIntervalSeconds` | `KANDEV_EXECUTORS_SSHREACHABILITYINTERVALSECONDS` | integer `>= 0`, `60` | Interval between background SSH-executor reachability probes, in seconds. `0` disables the poller entirely; a probed executor then keeps its last recorded state rather than going stale. The effective cadence is otherwise clamped to `15`-`3600`: a value from `1` to `14` becomes `15`, and a value above `3600` becomes `3600`, each logged once at startup. Valid environment and YAML values use this clamp. An invalid environment value falls back to `60`; an invalid YAML value fails startup like any other typed catalog key. |
|
||||
| `office.schedulerTickMs` | `KANDEV_OFFICE_SCHEDULER_TICK_MS` | positive integer, `5000` | Office queued/retry run safety-net interval in milliseconds. |
|
||||
| `launcher.webPort` | `KANDEV_WEB_PORT` | automatic, `0` | Development web-server port. It is used with `dev` and ignored by embedded-asset launches. |
|
||||
| `launcher.healthTimeoutMs` | `KANDEV_HEALTH_TIMEOUT_MS` | positive integer, `45000` | Launcher startup-health timeout in milliseconds. Development and E2E profiles use a longer default. |
|
||||
| `launcher.noBrowser` | `KANDEV_NO_BROWSER` | `false` | Suppresses browser opening when true or `1`, depending on launch mode. |
|
||||
|
||||
These settings are read at startup. Their environment aliases remain
|
||||
compatible overrides, and a YAML value is not copied into a public process
|
||||
@@ -532,16 +532,16 @@ Copying this entire file is unnecessary and can freeze old defaults in a deploym
|
||||
|
||||
**Settings → System → Feature Toggles** manages startup-time flags:
|
||||
|
||||
| Key | Environment lock | Production default | Effect |
|
||||
|---|---|---|---|
|
||||
| `features.auth` | `KANDEV_FEATURES_AUTH` | off | Experimental authentication, users, per-user workspaces, and team access. |
|
||||
| `features.multiTenancy` | `KANDEV_FEATURES_MULTI_TENANCY` | off | Experimental organizations above authenticated users. Requires `features.auth`; startup is refused otherwise. |
|
||||
| `features.dynamicAgentRouting` | `KANDEV_FEATURES_DYNAMIC_AGENT_ROUTING` | off | Experimental dynamic profiles with ordered provider-error fallback. |
|
||||
| `features.canvases` | `KANDEV_FEATURES_CANVASES` | off | Experimental agent-authored isolated web-app canvases for tasks and workspaces. High risk. |
|
||||
| `features.needsYouInbox` | `KANDEV_FEATURES_NEEDS_YOU_INBOX` | off | Inbox for clarification questions and failed tasks in the active workspace. The Failed tab shows a bounded list with its own count. |
|
||||
| `features.officeSessionIdentity` | `KANDEV_FEATURES_OFFICE_SESSION_IDENTITY` | on | Office participant sessions: each participant agent gets its own session per task. The live `(task_id, agent_profile_id)` pair is guarded in-transaction, not by a table-level index. Pre-existing duplicate rows are retained and resolved by selection. Two Kandev processes must not write the same SQLite file. |
|
||||
| `features.agentSurvival` | `KANDEV_FEATURES_AGENT_SURVIVAL` | off | Experimental. Lets the `agentctl` control server and its running agents outlive a backend restart or upgrade, for the `local_pc` and worktree executors only. The restarted backend takes the surviving server over and reconnects to the sessions still running on it. **Unavailable on Windows**, where surviving the backend means giving up the Job Object that guarantees agent processes are cleaned up. |
|
||||
| `debug.devMode` | `KANDEV_DEBUG_DEV_MODE` | off | High-risk diagnostic endpoints and ACP frame logging. |
|
||||
| Key | Environment lock | Production default | Effect |
|
||||
| -------------------------------- | ----------------------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `features.auth` | `KANDEV_FEATURES_AUTH` | off | Experimental authentication, users, per-user workspaces, and team access. |
|
||||
| `features.multiTenancy` | `KANDEV_FEATURES_MULTI_TENANCY` | off | Experimental organizations above authenticated users. Requires `features.auth`; startup is refused otherwise. |
|
||||
| `features.dynamicAgentRouting` | `KANDEV_FEATURES_DYNAMIC_AGENT_ROUTING` | off | Experimental dynamic profiles with ordered provider-error fallback. |
|
||||
| `features.canvases` | `KANDEV_FEATURES_CANVASES` | off | Experimental agent-authored isolated web-app canvases for tasks and workspaces. High risk. |
|
||||
| `features.needsYouInbox` | `KANDEV_FEATURES_NEEDS_YOU_INBOX` | off | Inbox for clarification questions and failed tasks in the active workspace. The Failed tab shows a bounded list with its own count. |
|
||||
| `features.officeSessionIdentity` | `KANDEV_FEATURES_OFFICE_SESSION_IDENTITY` | on | Office participant sessions: each participant agent gets its own session per task. The live `(task_id, agent_profile_id)` pair is guarded in-transaction, not by a table-level index. Pre-existing duplicate rows are retained and resolved by selection. Two Kandev processes must not write the same SQLite file. |
|
||||
| `features.agentSurvival` | `KANDEV_FEATURES_AGENT_SURVIVAL` | off | Experimental. Lets the `agentctl` control server and its running agents outlive a backend restart or upgrade, for the `local_pc` and worktree executors only. The restarted backend takes the surviving server over and reconnects to the sessions still running on it. **Unavailable on Windows**, where surviving the backend means giving up the Job Object that guarantees agent processes are cleaned up. |
|
||||
| `debug.devMode` | `KANDEV_DEBUG_DEV_MODE` | off | High-risk diagnostic endpoints and ACP frame logging. |
|
||||
|
||||
The `KANDEV_FEATURES_*` values have no canonical YAML keys. They are selected
|
||||
from the embedded profile and can be overridden by the environment or the
|
||||
@@ -615,32 +615,32 @@ These settings are startup-only unless stated otherwise. Their YAML keys are
|
||||
the canonical file form; the environment aliases remain compatibility
|
||||
overrides.
|
||||
|
||||
| YAML key | Environment variable | Default | Parsing and effect |
|
||||
| ------------------------------------ | --------------------------------- | -------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `limits.ghMaxConcurrent` | `KANDEV_GH_MAX_CONCURRENT` | `8` | Positive integer process-wide cap for `gh` subprocesses; invalid/non-positive uses default. |
|
||||
| `limits.gitMaxConcurrent` | `KANDEV_GIT_MAX_CONCURRENT` | `12` | Positive integer process-wide cap for `git` subprocesses; invalid/non-positive uses default. |
|
||||
| `limits.lspMaxConnections` | `KANDEV_LSP_MAX_CONNECTIONS` | `8` | Positive integer cap for active browser-to-task-host language-server streams; invalid/non-positive uses default. |
|
||||
| `messageQueue.maxPerSession` | `KANDEV_QUEUE_MAX_PER_SESSION` | `10` | Pending messages per session. A non-negative YAML value locks the saved UI capacity; a valid environment value overrides YAML and locks it; a negative environment value means unlimited; malformed environment input falls through to YAML, the saved setting, or default. |
|
||||
| `agentctl.idleTimeout` | `KANDEV_ACP_IDLE_TIMEOUT` | `1h` | Go duration after which idle managed agentctl instances are reaped; `0` disables. Invalid uses default. |
|
||||
| `agentctl.idleReaperInterval` | `KANDEV_ACP_IDLE_REAPER_INTERVAL` | `1m` | Go duration between idle scans. |
|
||||
| `agentctl.notificationQueueCapacity` | `KANDEV_ACP_NOTIF_QUEUE` | `131072` | Per-connection ACP inbound notification capacity. YAML values outside `1024`-`131072` fail startup; invalid or out-of-range environment values use `131072`. |
|
||||
| `agentctl.recoveryDeadline` | `KANDEV_ACP_RECOVERY_DEADLINE` | `30s` | Bound on one startup's adoption, enumeration, and session reconstruction. Accepts `1s`-`5m`. Out-of-range or unparseable values fail startup from either source; they are never replaced by the default. |
|
||||
| `agentctl.recoveryReadTimeout` | `KANDEV_ACP_RECOVERY_READ_TIMEOUT` | `2s` | Per-attempt read timeout during recovery. Accepts `100ms`-`1h`, but the combined budget below caps the usable value at the lesser of 6s and one fifth of `agentctl.recoveryDeadline`. Out-of-range or unparseable values fail startup. |
|
||||
| `agentctl.recoveryReadRetries` | `KANDEV_ACP_RECOVERY_READ_RETRIES` | `2` | Retries after the first recovery read attempt. Accepts `0`-`10`. Startup also fails when `recoveryReadTimeout * (retries + 1)` exceeds the lesser of 6s and one fifth of `recoveryDeadline`. |
|
||||
| `agentctl.unownedPeriod` | `KANDEV_ACP_UNOWNED_PERIOD` | `10m` | Time a standalone control server tolerates having no backend renew ownership before shutting itself down. Effective value is raised to a one-minute floor and, when `agentctl.idleTimeout` is enabled and not longer, clamped to half of it. Invalid or negative environment values use the default. |
|
||||
| `agentctl.detachedEventLimit` | `KANDEV_ACP_DETACHED_EVENT_LIMIT` | `100` | Agent events retained per instance while no backend is attached. Accepts `1`-`10000`. Out-of-range or unparseable values fail startup. |
|
||||
| `planning.coalesceWindowMs` | `KANDEV_PLAN_COALESCE_WINDOW_MS` | `300000` | Non-negative milliseconds for same-author plan revision coalescing; invalid/negative uses five minutes. |
|
||||
| `office.schedulerTickMs` | `KANDEV_OFFICE_SCHEDULER_TICK_MS` | `5000` | Positive integer safety-net interval for queued/retry run claiming. New-run signals are event-driven. |
|
||||
| `office.maxConcurrentInstance` | `KANDEV_OFFICE_MAX_CONCURRENT_INSTANCE` | `8` | Maximum concurrent runs for one Office agent instance. Invalid or non-positive values use the default. |
|
||||
| `office.maxConcurrentWorkspace` | `KANDEV_OFFICE_MAX_CONCURRENT_WORKSPACE` | `4` | Maximum concurrent runs for one workspace. Invalid or non-positive values use the default. |
|
||||
| `office.workspaceBudgetPerHour` | `KANDEV_OFFICE_WORKSPACE_BUDGET_PER_HOUR` | `120` | Maximum runs charged to one workspace in the rolling hourly budget. Invalid or non-positive values use the default. |
|
||||
| `office.routineBudgetPerHour` | `KANDEV_OFFICE_ROUTINE_BUDGET_PER_HOUR` | `20` | Maximum runs charged to one routine in the rolling hourly budget. Invalid or non-positive values use the default. |
|
||||
| `office.promotionAgeMinutes` | `KANDEV_OFFICE_PROMOTION_AGE_MINUTES` | `15` | Age in minutes after which a queued run receives priority promotion. Invalid or non-positive values use the default. |
|
||||
| `office.maxCausationDepth` | `KANDEV_OFFICE_MAX_CAUSATION_DEPTH` | `8` | Maximum causation depth for a run. An enqueue beyond this depth is refused. Invalid or non-positive values use the default. |
|
||||
| `office.selfTriggerAllowance` | `KANDEV_OFFICE_SELF_TRIGGER_ALLOWANCE` | `3` | Maximum self-triggered runs for one reason in the rolling hourly window. Invalid or non-positive values use the default. |
|
||||
| `office.selfTriggerTotalAllowance` | `KANDEV_OFFICE_SELF_TRIGGER_TOTAL_ALLOWANCE` | `8` | Maximum self-triggered runs across all reasons in the rolling hourly window. Invalid or non-positive values use the default. |
|
||||
| `office.gateFailureThreshold` | `KANDEV_OFFICE_GATE_FAILURE_THRESHOLD` | `3` | Consecutive launch-safety gate failures before the durable alert state is raised. Invalid or non-positive values use the default. |
|
||||
| `observability.otlpEndpoint` | `OTEL_EXPORTER_OTLP_ENDPOINT` | unset | Enables OTLP/HTTP tracing for backend and agentctl spans; unset uses a no-op tracer. |
|
||||
| YAML key | Environment variable | Default | Parsing and effect |
|
||||
| ------------------------------------ | -------------------------------------------- | -------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `limits.ghMaxConcurrent` | `KANDEV_GH_MAX_CONCURRENT` | `8` | Positive integer process-wide cap for `gh` subprocesses; invalid/non-positive uses default. |
|
||||
| `limits.gitMaxConcurrent` | `KANDEV_GIT_MAX_CONCURRENT` | `12` | Positive integer process-wide cap for `git` subprocesses; invalid/non-positive uses default. |
|
||||
| `limits.lspMaxConnections` | `KANDEV_LSP_MAX_CONNECTIONS` | `8` | Positive integer cap for active and detached task-host language-server leases when continuity is enabled; Kandev evicts the least-recently detached lease or rejects the request if all leases are attached. Invalid/non-positive uses default. |
|
||||
| `messageQueue.maxPerSession` | `KANDEV_QUEUE_MAX_PER_SESSION` | `10` | Pending messages per session. A non-negative YAML value locks the saved UI capacity; a valid environment value overrides YAML and locks it; a negative environment value means unlimited; malformed environment input falls through to YAML, the saved setting, or default. |
|
||||
| `agentctl.idleTimeout` | `KANDEV_ACP_IDLE_TIMEOUT` | `1h` | Go duration after which idle managed agentctl instances are reaped; `0` disables. Invalid uses default. |
|
||||
| `agentctl.idleReaperInterval` | `KANDEV_ACP_IDLE_REAPER_INTERVAL` | `1m` | Go duration between idle scans. |
|
||||
| `agentctl.notificationQueueCapacity` | `KANDEV_ACP_NOTIF_QUEUE` | `131072` | Per-connection ACP inbound notification capacity. YAML values outside `1024`-`131072` fail startup; invalid or out-of-range environment values use `131072`. |
|
||||
| `agentctl.recoveryDeadline` | `KANDEV_ACP_RECOVERY_DEADLINE` | `30s` | Bound on one startup's adoption, enumeration, and session reconstruction. Accepts `1s`-`5m`. Out-of-range or unparseable values fail startup from either source; they are never replaced by the default. |
|
||||
| `agentctl.recoveryReadTimeout` | `KANDEV_ACP_RECOVERY_READ_TIMEOUT` | `2s` | Per-attempt read timeout during recovery. Accepts `100ms`-`1h`, but the combined budget below caps the usable value at the lesser of 6s and one fifth of `agentctl.recoveryDeadline`. Out-of-range or unparseable values fail startup. |
|
||||
| `agentctl.recoveryReadRetries` | `KANDEV_ACP_RECOVERY_READ_RETRIES` | `2` | Retries after the first recovery read attempt. Accepts `0`-`10`. Startup also fails when `recoveryReadTimeout * (retries + 1)` exceeds the lesser of 6s and one fifth of `recoveryDeadline`. |
|
||||
| `agentctl.unownedPeriod` | `KANDEV_ACP_UNOWNED_PERIOD` | `10m` | Time a standalone control server tolerates having no backend renew ownership before shutting itself down. Effective value is raised to a one-minute floor and, when `agentctl.idleTimeout` is enabled and not longer, clamped to half of it. Invalid or negative environment values use the default. |
|
||||
| `agentctl.detachedEventLimit` | `KANDEV_ACP_DETACHED_EVENT_LIMIT` | `100` | Agent events retained per instance while no backend is attached. Accepts `1`-`10000`. Out-of-range or unparseable values fail startup. |
|
||||
| `planning.coalesceWindowMs` | `KANDEV_PLAN_COALESCE_WINDOW_MS` | `300000` | Non-negative milliseconds for same-author plan revision coalescing; invalid/negative uses five minutes. |
|
||||
| `office.schedulerTickMs` | `KANDEV_OFFICE_SCHEDULER_TICK_MS` | `5000` | Positive integer safety-net interval for queued/retry run claiming. New-run signals are event-driven. |
|
||||
| `office.maxConcurrentInstance` | `KANDEV_OFFICE_MAX_CONCURRENT_INSTANCE` | `8` | Maximum concurrent runs for one Office agent instance. Invalid or non-positive values use the default. |
|
||||
| `office.maxConcurrentWorkspace` | `KANDEV_OFFICE_MAX_CONCURRENT_WORKSPACE` | `4` | Maximum concurrent runs for one workspace. Invalid or non-positive values use the default. |
|
||||
| `office.workspaceBudgetPerHour` | `KANDEV_OFFICE_WORKSPACE_BUDGET_PER_HOUR` | `120` | Maximum runs charged to one workspace in the rolling hourly budget. Invalid or non-positive values use the default. |
|
||||
| `office.routineBudgetPerHour` | `KANDEV_OFFICE_ROUTINE_BUDGET_PER_HOUR` | `20` | Maximum runs charged to one routine in the rolling hourly budget. Invalid or non-positive values use the default. |
|
||||
| `office.promotionAgeMinutes` | `KANDEV_OFFICE_PROMOTION_AGE_MINUTES` | `15` | Age in minutes after which a queued run receives priority promotion. Invalid or non-positive values use the default. |
|
||||
| `office.maxCausationDepth` | `KANDEV_OFFICE_MAX_CAUSATION_DEPTH` | `8` | Maximum causation depth for a run. An enqueue beyond this depth is refused. Invalid or non-positive values use the default. |
|
||||
| `office.selfTriggerAllowance` | `KANDEV_OFFICE_SELF_TRIGGER_ALLOWANCE` | `3` | Maximum self-triggered runs for one reason in the rolling hourly window. Invalid or non-positive values use the default. |
|
||||
| `office.selfTriggerTotalAllowance` | `KANDEV_OFFICE_SELF_TRIGGER_TOTAL_ALLOWANCE` | `8` | Maximum self-triggered runs across all reasons in the rolling hourly window. Invalid or non-positive values use the default. |
|
||||
| `office.gateFailureThreshold` | `KANDEV_OFFICE_GATE_FAILURE_THRESHOLD` | `3` | Consecutive launch-safety gate failures before the durable alert state is raised. Invalid or non-positive values use the default. |
|
||||
| `observability.otlpEndpoint` | `OTEL_EXPORTER_OTLP_ENDPOINT` | unset | Enables OTLP/HTTP tracing for backend and agentctl spans; unset uses a no-op tracer. |
|
||||
|
||||
Changing concurrency values trades process pressure against throughput and requires a restart. Under **Settings > Preferences > Task Behavior > Runtime**, an admin can save an install-wide capacity and independently control manual and automatic merging. The merge switches apply live and persist across restarts. `messageQueue.maxPerSession` resolves as environment, YAML, saved setting, then default; a non-negative YAML value or any valid environment value locks only capacity. A negative environment value means unlimited. See [Operations](operations.md#message-queue-settings) for capacity and automatic-fold behavior.
|
||||
|
||||
|
||||
@@ -301,7 +301,11 @@ The status surface separates process startup, the LSP `initialize` request, and
|
||||
|
||||
Kandev does not impose an automatic initialization timeout or invent a percentage or ETA. Some valid project imports take several minutes, and LSP has no universal indexing-progress contract. When a server reports standard work-done progress, Kandev shows its title, message, percentage, and concurrent work-item count when available. Those values describe only the work item the server reported; its completion does not guarantee that every cross-file definition or reference is ready. If cross-file navigation is still incomplete, leave the server running while its project model warms up, or stop it explicitly if the wait is unexpected.
|
||||
|
||||
Each browser connection owns a language-server process; editors in one browser window share the connection for the same session and language. Kandev allows eight active connections by default; operators can change that startup limit with `KANDEV_LSP_MAX_CONNECTIONS`. A request above that limit is rejected before it can start or resume the task host. Stopping a server, closing its connection, or stopping the task reaps the task-host process tree. If the toolbar says the server is unavailable, distinguish a missing task-host binary from an unsupported executor or the active-connection limit before retrying.
|
||||
Task-owned browser continuity is controlled by the restart-required `features.lspBrowserContinuity` runtime flag. It is off in shipped profiles. When enabled, each browser window owns an independent lease for the task-host language-server process. Closing a browser window or losing its network connection detaches the window while the lease continues to drain server messages. Reopening the task can reattach to that lease without a second `initialize`; duplicated tabs and other windows receive independent leases. Current diagnostics are cleared on detach and restored only after the editor resends its open files.
|
||||
|
||||
With continuity enabled, **Stop** releases that window's lease, and closing the last editor releases it after two minutes. Closing the browser retains a detached lease for up to one hour; reopening it resets that deadline, and an attached editor has no expiry deadline. Expiry releases the language server and lets normal task-host cleanup resume. Archiving or deleting the task starts background runtime cleanup that releases its leases. Stopping the task runtime or Kandev also releases its leases. Kandev allows eight active or detached leases by default; operators can change that startup limit with `KANDEV_LSP_MAX_CONNECTIONS`. If the limit is reached, a new request is rejected while every lease is attached. When a detached lease is available, Kandev evicts the one detached longest. After expiry, eviction, or a backend restart, the next eligible connection starts a fresh server and project analysis must run again. With continuity disabled, closing the browser connection reaps its process as before. If the toolbar says the server is unavailable, distinguish a missing task-host binary from an unsupported executor or the active-lease limit before retrying.
|
||||
|
||||
During a temporary browser-to-backend transport failure, the status changes to **Reconnecting** while Kandev attempts to reattach; browser closes `1005` and `1006`, backend restart close `1001`, and backend transport close `4009` do not prove that the language server exited. A confirmed server process exit uses `4006` and shows the server-exited state with **Retry**. Close `4010` means the task runtime stopped, so Kandev ends that lease without reconnecting it.
|
||||
|
||||
## Integrated terminal
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ Released builds can lag this page. Check **Settings > System > About** or `kande
|
||||
| Voice input | Plugin | Not built in. Provided by the [Voice Mode plugin](https://github.com/kdlbs/kandev-plugin-voice): browser speech, an in-browser Whisper model, and an operator-keyed server relay. Selection does not retry another engine after a recognition error. |
|
||||
| Embedded VS Code | Limited | code-server runs inside the task runtime with `--auth none` on `0.0.0.0:<random-port>`. It relies on task-runtime and network isolation rather than its own login. Native Windows Local and Worktree sessions omit it; Linux-backed Local Docker, Kubernetes, Sprites, and supported SSH sessions can offer it even when Kandev runs on Windows. The active session executor, not the visitor's browser platform, controls the topbar entry. |
|
||||
| Built-in editors and host editor launch | Dependency-bound | Built-in editing is shipped. External editors depend on host discovery/custom commands. A multi-worktree session presents a repository/worktree picker before host launch; clients that omit the worktree ID retain a first-worktree fallback. A remote executor path may not work without remote-editor configuration. |
|
||||
| Language servers | Limited | Desktop Monaco maps TypeScript/JavaScript, Go, Rust, Python, and experimental Kotlin to task-host servers with diagnostics, completion, hover, navigation, signature help, and semantic tokens. Status distinguishes a launched process awaiting `initialize` from server-reported project work; slow Kotlin Gradle imports can take minutes, with no automatic timeout or universal percentage/ETA. Fine-pointer desktop can place active-file status in the editor toolbar or the opt-in status surface controlled by **Show status bar**; when that surface is off it falls back to the toolbar. Touch tablet uses the toolbar, and phone has no LSP control. Auto-start/installation default off; Kotlin is manual-only. Local PC and Local Docker are supported, while Kubernetes, SSH, Sprites, remote Docker, and mobile LSP are not. |
|
||||
| Language servers | Limited | Desktop Monaco maps TypeScript/JavaScript, Go, Rust, Python, and experimental Kotlin to task-host servers with diagnostics, completion, hover, navigation, signature help, and semantic tokens. Status distinguishes a launched process awaiting `initialize` from server-reported project work; slow Kotlin Gradle imports can take minutes, with no automatic timeout or universal percentage/ETA. Fine-pointer desktop can place active-file status in the editor toolbar or the opt-in status surface controlled by **Show status bar**; when that surface is off it falls back to the toolbar. Touch tablet uses the toolbar, and phone has no LSP control. Auto-start/installation default off; Kotlin is manual-only. Local PC and Local Docker are supported, while Kubernetes, SSH, Sprites, remote Docker, and mobile LSP are not. Task-owned browser continuity is controlled by the restart-required `features.lspBrowserContinuity` toggle and is off in shipped profiles. When enabled, browser close/reopen can retain and reattach a task-host language-server lease for up to one hour while detached; explicit Stop, two minutes without an open editor, detached expiry, capacity eviction, task-runtime stop, or backend shutdown releases it.|
|
||||
| Integrated shell terminals | Dependency-bound | Create, reopen, rename, and terminate are shipped. Shell settings affect new terminals, and executor/task-environment availability controls where the PTY runs. |
|
||||
| Prompts, keyboard shortcuts, notifications, appearance, and task actions | Supported | These are normal settings surfaces. Browser notification permission, custom command validity, and local shell/editor availability still apply to their effects. |
|
||||
| Web UI localization | Limited | **Settings > General > Appearance** offers English, Português (Portugal), 简体中文, 繁體中文(台灣), 繁體中文(香港), and 日本語. The choice applies immediately, persists per browser through a `kandev_locale` cookie, and is negotiated from `Accept-Language` before defaulting to English when no cookie is set. Externalization is complete: every reachable web UI surface renders through the translation layer, and dates, numbers, and relative timestamps follow the selected locale. Per-language completeness still differs. Portuguese tracks English closely; Simplified Chinese is maintained out of band and lags, most visibly in the task workbench. An untranslated key falls back to English rather than failing. About fifteen shared `@kandev/ui` accessibility labels (mostly screen-reader-only text such as a dialog's **Close**) stay English even where the visible UI is translated. Desktop-shell and CLI surfaces, API/log diagnostics, and agent output were never in scope; user content such as task titles, chat, and diffs is never translated. Development and E2E builds list an extra **Pseudo (QA)** locale that production builds do not ship. |
|
||||
|
||||
@@ -891,4 +891,8 @@ Routing is an efficiency mechanism, not the access-control boundary. With authen
|
||||
|
||||
Dedicated `/terminal/*target` and `/lsp/:sessionId` WebSockets, plus `/vscode/:sessionId/*path` and `/port-proxy/:sessionId/:port/*path` proxies, are separate protocols. They do not use this JSON envelope and should not be sent `/ws` actions.
|
||||
|
||||
The browser language-server socket at `/lsp/:sessionId?language=...` carries raw LSP JSON-RPC plus private Kandev control frames. With the restart-required `features.lspBrowserContinuity` flag enabled, a browser close or temporary network loss detaches the window while its task-host lease keeps running for up to one hour. Reopening the task within that hour can reattach to that lease without another `initialize`; each window and duplicated tab has its own lease. **Stop**, two minutes with no open editor, one hour detached, task-runtime shutdown, backend shutdown, or capacity eviction releases a lease. Reattachment cancels the detached deadline; an attached lease does not expire. `KANDEV_LSP_MAX_CONNECTIONS` counts attached and detached leases; at capacity, Kandev evicts the least-recently detached lease, or rejects a new request when all leases are attached. When continuity is disabled, closing the browser socket stops its process as before.
|
||||
|
||||
The browser shows **Reconnecting** for an uncertain transport loss and retries attachment. A browser-reported `1005` or `1006`, graceful backend restart close `1001`, or backend transport close `4009` does not confirm that the language-server process exited. Close `4006` means the task-host process exited; the editor clears that generation's providers and diagnostics and offers **Retry**. Close `4010` means the task runtime stopped, so the editor ends the lease without reconnecting it. A backend restart releases leases, so the next connection starts a fresh process and repeats project analysis.
|
||||
|
||||
Related guides: [Configuration](configuration.md), [Executors](executors.md), [Git Operations](git-operations.md), [Operations](operations.md), [Workflow Import / Export](workflow-import-export.md), and [Workflow Sync](workflow-sync.md).
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
status: active
|
||||
system: platform
|
||||
created: 2026-07-09
|
||||
updated: 2026-08-11
|
||||
updated: 2026-09-24
|
||||
owners:
|
||||
- tbd
|
||||
---
|
||||
@@ -29,6 +29,29 @@ Users inspect and edit code inside Kandev task file tabs, but code navigation an
|
||||
- **AC-PLATFORM-LSP-FILE-INTELLIGENCE-001.7:** Wired editor capabilities are diagnostics and the server-advertised completion, hover, go-to-definition, references, signature-help, and semantic-token providers.
|
||||
- **AC-PLATFORM-LSP-FILE-INTELLIGENCE-001.8:** Global editor settings select languages that auto-start, languages Kandev may auto-install, and per-language configuration returned through `workspace/configuration`. Saving changed configuration updates the existing server through `workspace/didChangeConfiguration` without waiting for an idle disconnect or process restart.
|
||||
|
||||
### REQ-PLATFORM-LSP-FILE-INTELLIGENCE-002: Browser-independent language-server continuity
|
||||
|
||||
**Intent:** Closing a browser must not discard a task's live language analysis. A returning editor shall regain useful LSP information without the user restarting the server.
|
||||
|
||||
#### Acceptance criteria
|
||||
|
||||
- **AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.1:** When a desktop browser tab closes or loses its connection, an enabled language server shall continue running and analyzing that task workspace while its lease is retained. The lease shall keep the task host active across a completed agent turn and idle-reclaim interval, unless the user stops the task.
|
||||
- **AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.2:** When a desktop editor for the same task and language opens again while its lease is retained, it shall attach to the same server, restore providers and reported project progress without a manual Retry or second server start, synchronize its current file contents, and show diagnostics only after the server publishes them for the reopened document.
|
||||
- **AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.3:** When a connected browser briefly loses the LSP transport, the editor shall show a connection state, attempt to reattach, and reserve the server-exited error for an actual server exit. Diagnostics from a previous attachment shall remain hidden until the server analyzes the reopened document.
|
||||
- **AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.4:** An explicit Stop shall release that editor's server. Other concurrently open browser windows for the same task and language shall keep their independent server and editor state. Stopping the task host shall stop all of its retained servers and descendants.
|
||||
- **AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.5:** Retained servers shall count against the configured LSP resource limit. Reattaching to a matching retained lease shall remain possible at the limit. Admission of a new server shall release the least recently detached lease when necessary; if every lease is attached, it shall show the existing capacity state. An evicted lease shall start fresh on a later eligible editor open.
|
||||
- **AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.6:** If the task host or Kandev restarts, the editor shall start a new server when its existing auto-start or manual-enable policy requests one and shall show fresh initialization until that server is ready. It shall not present a lost server's progress or diagnostics as live.
|
||||
- **AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.7:** On phone, opening the file viewer shall not start or attach to a language server in the background. A coarse-pointer tablet editor shall regain the same status and lifecycle action through its existing toolbar drawer.
|
||||
- **AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.8:** When the last editor in a connected browser remains closed for the existing two-minute idle timeout, that browser shall release its lease and free its capacity. Closing the browser or losing the network before the timeout shall retain the lease until its detached deadline. A saved per-language configuration change shall reach a detached server without requiring a restart.
|
||||
- **AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.9:** A returning tab shall attach to its retained lease when the user has not explicitly stopped it, even if auto-start was subsequently disabled. A new tab without a lease hint shall follow current auto-start or browser-local manual-enable policy. A duplicated tab shall not take over the original tab's attached lease.
|
||||
- **AC-PLATFORM-LSP-FILE-INTELLIGENCE-002.10:** A lease shall expire one hour after its most recent browser detachment if no browser reattaches. Expiry shall stop its language server, release its capacity, and allow normal task-host idle reclaim. An attached lease shall not expire. A later eligible editor open shall start a fresh server and project analysis.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Keeping a server running after its task host stops.
|
||||
- Sharing one live protocol session between concurrently open browser windows.
|
||||
- Adding LSP controls to the phone file viewer or extending LSP to unsupported executors.
|
||||
|
||||
## System design
|
||||
|
||||
The migrated technical source is split into [part 1](../system-design/lsp-file-intelligence-01.md), [part 2](../system-design/lsp-file-intelligence-02.md).
|
||||
|
||||
@@ -3,8 +3,9 @@ status: draft
|
||||
system: platform
|
||||
requirements:
|
||||
- REQ-PLATFORM-LSP-FILE-INTELLIGENCE-001
|
||||
- REQ-PLATFORM-LSP-FILE-INTELLIGENCE-002
|
||||
created: 2026-07-09
|
||||
updated: 2026-08-11
|
||||
updated: 2026-09-24
|
||||
owners:
|
||||
- tbd
|
||||
---
|
||||
@@ -19,6 +20,7 @@ This design preserves the technical source detail for `REQ-PLATFORM-LSP-FILE-INT
|
||||
| Requirement | Design section |
|
||||
| --- | --- |
|
||||
| `REQ-PLATFORM-LSP-FILE-INTELLIGENCE-001` | [Migrated source detail](#migrated-source-detail) |
|
||||
| `REQ-PLATFORM-LSP-FILE-INTELLIGENCE-002` | [Browser-independent leases](#browser-independent-leases) |
|
||||
|
||||
## Migrated source detail
|
||||
|
||||
@@ -68,14 +70,14 @@ Users inspect and edit code inside Kandev task file tabs, but code navigation an
|
||||
- Completion requests translate Monaco invocation, trigger-character, and incomplete-result context into the corresponding LSP enum values and forward the trigger character when present. A server item without `textEdit` receives Monaco's current-word insertion range; explicit LSP `TextEdit` and `InsertReplaceEdit` ranges remain authoritative.
|
||||
- Successful Monaco file saves synchronize every matching open language-server document to the newest editor snapshot, then notify servers that requested `textDocument/didSave`. When that live snapshot still matches the persisted snapshot, the persisted text is included only for servers that advertise `includeText`. If editing advanced while persistence was in flight, the newer buffer stays dirty and synchronized and the optional stale save text is omitted so the language server cannot be rewound. Failed saves emit no save notification.
|
||||
- V1 task-host support is limited to Local PC and local Docker executors. Remote Docker, SSH, and Sprites report an unsupported-executor state.
|
||||
- Each active browser WebSocket owns one language-server process. The browser shares a connection for the same session and language inside one window and closes it after its idle timeout; separate browser windows may own separate processes.
|
||||
- The backend caps active LSP WebSocket connections at 8 by default. `KANDEV_LSP_MAX_CONNECTIONS` overrides the cap.
|
||||
- Each runtime LSP lease owns one language-server process. Editors in one browser window share an attachment for the same session and language; separate concurrently attached browser windows keep separate leases. An intentional editor-idle timeout releases the lease; tab closure and transport loss detach it for at most one hour.
|
||||
- The backend caps active LSP leases, whether attached or detached, at 8 by default. The existing `KANDEV_LSP_MAX_CONNECTIONS` setting remains the compatible operator override for this resource cap. At capacity, admission of a new process releases the least recently detached lease; attached leases cannot be evicted.
|
||||
- Language-server processes and npm/Go auto-install commands are owned by the existing agentctl process manager. Instance teardown cancels and drains install work, then reaps full process trees on Unix and Windows before releasing resources.
|
||||
- During auto-install, one pending task-host WebSocket read cancels the connection-owned installer context if the browser stops or disconnects. After a successful install, that same read becomes the bridge's first inbound frame so readiness handoff does not race or lose an initialize request.
|
||||
- Agentctl must deliver the bounded `installing` status before acquiring auto-install work. If that write fails or times out, it closes the stream without starting the installer because no live consumer can observe or control the operation.
|
||||
- During auto-install, the runtime lease retains the task-host WebSocket when the browser detaches. Explicit Stop or task-host teardown cancels installation; the lease consumes the first inbound frame after installation without racing the ready handshake.
|
||||
- Agentctl must deliver the bounded `installing` status to the backend lease before acquiring auto-install work. If that write fails or times out, it closes the stream without starting the installer.
|
||||
- Kandev-managed npm and release binaries live under the task host's `~/.kandev/lsp-servers`; `gopls` is installed through the task host's Go toolchain. No managed server cache lives inside a checked-out project.
|
||||
- LSP JSON-RPC bodies are limited to 16 MiB across stdio and WebSocket transport; stdio headers are bounded separately. Oversized frames close the affected connection instead of allocating unbounded memory.
|
||||
- Every task-host LSP WebSocket write has a five-second deadline, including installing, installed, failure, ready, close, and bridged JSON-RPC frames. A stalled browser peer cannot retain the stream handler or its owned language-server process indefinitely.
|
||||
- Every task-host LSP WebSocket write has a five-second deadline, including installing, installed, failure, ready, close, and bridged JSON-RPC frames. The backend lease continuously drains that stream, including while no browser is attached; a stalled browser attachment is detached without blocking the server reader.
|
||||
- Each browser-to-server stdio frame has a 30-second write cutoff, and stdout-forwarder termination closes stdin immediately. A language server that stops reading cannot leave the bridge handler or its owned process pinned indefinitely.
|
||||
- Mobile file viewing does not start language servers in the background.
|
||||
|
||||
@@ -98,7 +100,7 @@ There is no durable per-task or per-session LSP policy in V1. Manual toolbar sta
|
||||
|
||||
`GET /lsp/:sessionId?language=<language>`
|
||||
|
||||
The main backend resolves or restores the task execution, checks executor support and global capacity, authenticates to that execution's agentctl instance, and proxies WebSocket frames.
|
||||
The main backend resolves or restores the task execution, checks executor support and global capacity, and authenticates to that execution's agentctl instance. With browser continuity enabled, the broker owns the upstream and attaches the browser to a lease; with it disabled, the route retains its current WebSocket proxy behavior.
|
||||
|
||||
### Task-host stream
|
||||
|
||||
@@ -123,10 +125,11 @@ Application close codes are:
|
||||
| `4002` | Session, execution, or agentctl stream unavailable. |
|
||||
| `4003` | Auto-install failed. |
|
||||
| `4004` | Executor unsupported in V1. |
|
||||
| `4005` | Active LSP connection cap reached. |
|
||||
| `4006` | Language server exit or unexpected LSP proxy stream failure. |
|
||||
| `4005` | Active LSP lease cap reached. |
|
||||
| `4006` | Language server exited unexpectedly. |
|
||||
| `4007` | Server binary missing and the task host has no supported installer. |
|
||||
| `4008` | Language-server process failed to start. |
|
||||
| `4009` | LSP transport or broker connection failed while the server may still run. |
|
||||
|
||||
The browser translates categorical close statuses from the close code instead of rendering transport prose. A preceding `install_failed` status payload may retain its actionable task-host diagnostic when the stream then closes with `4003`; without that payload, `4003` uses the localized installation-failure fallback. A JSON-RPC initialization rejection preserves the server-provided `error.message`.
|
||||
|
||||
@@ -134,7 +137,7 @@ The browser translates categorical close statuses from the close code instead of
|
||||
|
||||
The browser advertises standard LSP `window.workDoneProgress` support and includes a client-generated `workDoneToken` in `initialize`. This lets servers such as JetBrains Kotlin LSP report project-import phases before the initialize response.
|
||||
|
||||
The browser accepts both server-created progress tokens through `window/workDoneProgress/create` and `$/progress` notifications for the initialize token. Tokens can be strings or numbers and are scoped to one browser-owned session, language, and connection generation.
|
||||
The broker accepts both server-created progress tokens through `window/workDoneProgress/create` and `$/progress` notifications for the initialize token. Tokens can be strings or numbers and are scoped to one LSP lease and server generation; the attached browser receives the current snapshot.
|
||||
|
||||
Supported work-done payloads are:
|
||||
|
||||
@@ -146,7 +149,7 @@ Supported work-done payloads are:
|
||||
|
||||
Percentages are clamped to 0–100 for presentation. Unknown tokens, malformed payloads, and late notifications from a replaced connection are ignored.
|
||||
|
||||
No backend or task-host payload transforms are required: both WebSocket proxy hops transport the JSON-RPC body unchanged.
|
||||
The broker preserves LSP method semantics while mapping request IDs, tracking progress and diagnostics, and separating browser attachments from the task-host process lifetime.
|
||||
|
||||
## Readiness and progress state
|
||||
|
||||
@@ -157,17 +160,41 @@ No backend or task-host payload transforms are required: both WebSocket proxy ho
|
||||
- Work-done progress is runtime-only activity attached to a live connection. Multiple active tokens are a flat list because LSP defines no parent/child relationship.
|
||||
- The oldest active work item is the primary summary; additional active items are shown as a count. Percentages from unrelated work items are never averaged.
|
||||
- The most recently ended item can remain visible as “server-reported work finished” for the lifetime of that connection. It is not described as project-wide success.
|
||||
- Stop, idle disconnect, crash, socket close, connection replacement, and retry clear all active and completed progress. A replacement generation starts with no inherited work state.
|
||||
- Explicit Stop, server crash, or task-host teardown clears active and completed progress. Browser detach leaves progress in the lease; reattachment receives its current snapshot. A new server generation starts without stale progress.
|
||||
- Progress activity is scoped to the current editor's session and language. It is not a global task-wide language-server dashboard.
|
||||
|
||||
## Browser-independent leases
|
||||
|
||||
The backend lease manager owns the browser-to-agentctl upstream stream. It keys a lease by the task execution, language, and an opaque lease ID; it keeps the upstream open and reads it even when no browser is attached. Agentctl still owns and reaps the actual process. The lease is runtime-only and ends on editor-idle release, one-hour detached expiry, explicit Stop, capacity eviction, server exit, execution replacement or stop, or backend shutdown. Backend restart drops runtime-only leases. This boundary follows [ADR-2026-09-23](../../../decisions/2026-09-23-task-owned-lsp-leases.md).
|
||||
|
||||
All three orchestrator paths that call `reclaimIdleSession` (the periodic idle reaper and the agent and streaming event handlers) treat an active lease as live task-runtime work, including when no browser is attached and no agent turn is running. The lease thereby keeps the whole Local PC or Docker task host, including agentctl, container, and worktree runtime resources, active. Lease admission and reclaim use the same session lifecycle fence or a generation-checked equivalent, and reclaim rechecks the lease immediately before cleanup. A new lease cannot be admitted onto an execution already being reclaimed. Explicit task stop still wins and closes all leases for that execution. Editor-idle release, detached expiry, and capacity eviction remove the pin when the lease ends.
|
||||
|
||||
The existing browser-facing `/lsp/:sessionId` route authorizes the caller before attachment or lease lookup. The browser includes its lease ID when it has one. The backend reattaches to that detached lease if its execution and language still match; otherwise it may claim the newest detached lease for the same execution and language, or create a new one when admission permits. An attached lease is never stolen from another live browser window, including when a duplicated tab copies its `sessionStorage` hint. In that case the new tab receives a separate lease if admitted. The opaque ID is a routing hint, not an access credential, and is never logged. Browser `sessionStorage` can retain it across a tab reload; a new tab can still claim a detached lease without it when its current auto-start or local manual-enable policy requests LSP. A previously attached tab may resume its hinted lease after auto-start is disabled, unless it explicitly stopped LSP. A lease is counted against `limits.lspMaxConnections` from process admission until teardown, including periods without a browser. Reattachment needs no new capacity slot. Admission under a full cap first releases the oldest detached lease, waits for its upstream/process cleanup, and then admits the replacement; if all leases are attached it returns `4005` without starting a process. An evicted hint becomes a fresh start under the current browser policy.
|
||||
|
||||
The backend broker is the stable LSP protocol peer. It retains the first `initialize` result, workspace identity, effective per-language configuration, active work progress, dynamic `client/registerCapability` registrations, and per-URI server-facing document-version counters within a 4 MiB state budget. It applies `client/unregisterCapability` and answers both methods while detached; while attached it also forwards effective registration changes so Monaco providers update immediately. The resumed handshake includes the effective registrations so providers match the server's current capabilities. It answers `workspace/configuration` from the latest persisted user settings, scoped to the lease owner, rather than from a detached browser snapshot. A settings-save event updates affected leases and sends `workspace/didChangeConfiguration` even while detached; reattachment also reconciles the latest settings revision before reporting ready. This preserves AC .8 without keeping a browser connection open.
|
||||
|
||||
Pending browser-to-server requests are independently bounded to 256 requests, 256 bytes per JSON-RPC ID, and 64 KiB of tracked request state per lease. The broker cancels and returns a timeout error after 30 seconds; detach cancels requests from that browser generation. Request admission stops before forwarding when any bound is exceeded.
|
||||
|
||||
The broker continues reading server output while detached, answers supported server-to-client requests, and gives unsupported requests a JSON-RPC method error so they cannot hang indefinitely. It assigns server-facing request IDs and maps them to the current browser attachment generation. Browser `$/cancelRequest` maps to the corresponding server-facing ID; on detach it cancels all that generation's pending browser requests toward the server and rejects late responses. Replies to a detached generation are discarded. A browser close or write timeout detaches that generation without closing the upstream. Explicit Stop or idle release uses an acknowledged broker control frame: the broker rejects outstanding browser requests, sends LSP `shutdown`, waits within a bounded deadline, sends `exit`, then closes the upstream and waits for agentctl process cleanup. The UI cannot report Stop complete before acknowledgment. An unexpected agentctl process exit sends an explicit upstream `4006`; an upstream read failure without confirmed process exit maps to transport `4009`. Invalid WebSocket reserved close codes such as `1005` and `1006` are normalized before forwarding.
|
||||
|
||||
On detach, the broker sends `textDocument/didClose` for each open document, clears all diagnostic state, and drops later diagnostic publications for closed documents. It retains no document text. On reattachment, the ready handshake identifies the retained lease and supplies server capabilities, effective dynamic registrations, workspace metadata, and current progress. The browser rebuilds Monaco providers without sending a second LSP `initialize`. It reopens current documents with their current text and sends attachment-ready after handlers are registered. The broker assigns each `didOpen` and `didChange` a strictly increasing server-facing version for that URI over the lease lifetime, independent of browser-local versions, and acknowledges synchronization before the UI reports `ready`. On a resumed lease it forwards `publishDiagnostics` only for a currently open URI after synchronization and only when the publication's version equals the current server-facing version. Unversioned publications cannot be bound to the reopened text and remain hidden for that URI until a fresh server generation; initial connections may still display unversioned diagnostics. Old markers remain clear until a qualifying new publication. Two simultaneously attached windows retain separate leases, request maps, documents, and progress, preserving their current independent unsaved-buffer behavior.
|
||||
|
||||
`features.lspBrowserContinuity` (`KANDEV_FEATURES_LSP_BROWSER_CONTINUITY`) is a restart-required runtime release toggle, off in prod, dev, and e2e shipped profiles. The backend gates lease manager construction and the browser-facing WebSocket path; when disabled, the existing browser-owned proxy and idle-reclaim behavior remain unchanged. The frontend uses the matching boot feature value to keep its existing LSP client behavior when disabled. An enabled test installation or E2E fixture turns the flag on explicitly.
|
||||
|
||||
The browser manager uses bounded reconnect attempts after unexpected transport loss. During those attempts it shows a localized reconnecting state with no stale markers. A lost or expired lease starts a fresh server under the existing auto-start or manual-enable preference and clears the previous generation's progress. A confirmed server process exit uses `4006` and the server-exited state; a broker or proxy transport failure uses `4009` or the browser's abnormal-close signal and offers Retry after automatic attempts fail. The toolbar, fine-pointer status bar, and coarse-pointer tablet drawer share this state. The phone viewer neither creates nor attaches to a lease.
|
||||
|
||||
Each detachment arms a one-hour lease deadline independent of LSP progress. Reattachment cancels the deadline before the new attachment becomes visible; a later detachment starts a full new hour. The deadline callback checks the original detachment time and detached state under the lease lock used by attach and termination, so a stale callback cannot close a reattached lease. Expiry closes the upstream and releases its capacity with reason `detached_timeout`; idle reclaim can then release the task host. Task archive and delete continue to stop the runtime through their background cleanup, without waiting for this deadline. Backend shutdown cancels pending deadlines.
|
||||
|
||||
The broker logs lease creation, attachment, detachment, reattachment, Stop, idle release, detached expiry, capacity eviction, process exit, and teardown reason with bounded metadata. Dev expvar exposes `lsp_lease_active` and `lsp_lease_detached` gauges, `lsp_lease_evicted_total`, and `lsp_lease_released_total` with a closed `reason` set (`stop`, `editor_idle`, `detached_timeout`, `capacity`, `server_exit`, `runtime_stop`, `backend_stop`). No metric uses task, session, browser, lease, or language IDs as labels. Capacity and active/detached counts are observable without IDs. It releases capacity exactly once on every terminal path and never lets a slow browser block task-host stdout draining.
|
||||
|
||||
## State and persistence
|
||||
|
||||
- User settings persist in the existing user-settings store.
|
||||
- Manual enablement persists only in browser local storage under the session and language.
|
||||
- Processes, open documents, diagnostics, and semantic-token caches are runtime-only.
|
||||
- Processes, open documents, capabilities, and progress are runtime-only. The backend lease retains bounded metadata but discards diagnostics on detach; no new durable task or user record is created.
|
||||
- Save notifications are runtime-only and follow confirmed workspace persistence; they are not emitted for closed documents, failed writes, or servers that did not request them.
|
||||
- A missing server starts only when a supported file is opened and auto-start or a toolbar action requests it.
|
||||
- Closing the browser connection stops its process; stopping the task reaps every owned language-server process even if a browser connection remains open.
|
||||
- Closing a browser attachment leaves its server lease running for up to one hour. Explicit Stop, intentional editor-idle timeout, and detached expiry release it; capacity admission may evict a detached lease sooner. Stopping the task or its runtime reaps every owned language-server process even if a browser connection remains open.
|
||||
|
||||
## Failure modes
|
||||
|
||||
@@ -183,7 +210,7 @@ No backend or task-host payload transforms are required: both WebSocket proxy ho
|
||||
- **Cold Monaco initialization:** TypeScript built-ins are wrapped before model-scoped LSP suppression is registered; the LSP provider registration guard does not depend on suppression state.
|
||||
- **Capacity exceeded:** the UI reports that too many language servers are active; the backend rejects the request before starting or resuming its supported task host.
|
||||
- **Process start failure:** agentctl logs the task-host execution error, closes with categorical `4008` and no transport prose, and the UI shows a localized start-failure status with Retry.
|
||||
- **Server crash:** the connection closes with categorical `4006`, Monaco providers and markers are cleaned up, and the status shows the localized server-exited message with a Retry action. Only intentional stop or idle teardown returns to Off.
|
||||
- **Server crash:** the lease ends with categorical `4006`, Monaco providers and markers are cleaned up, and the status shows the localized server-exited message with a Retry action. Only intentional Stop returns to Off.
|
||||
- **Initialize rejection:** when the server rejects the JSON-RPC `initialize` request, the UI preserves its `error.message` instead of stringifying the error object as `[object Object]`.
|
||||
- **No progress support or reports:** initialization still shows an indeterminate state and elapsed time; after initialize succeeds, the status surface says the server has not reported background analysis progress.
|
||||
- **Initialize response is slow or never arrives:** the UI confirms that the process launched, changes to a long-running initialization warning after 60 seconds, and keeps Stop available. Kandev does not automatically kill a cold project import or claim that the server is indexing.
|
||||
@@ -192,8 +219,8 @@ No backend or task-host payload transforms are required: both WebSocket proxy ho
|
||||
- **Cross-file intelligence remains incomplete after ready:** the UI does not claim that the server is still indexing unless a work item is active; the status surface explains that project import, dependencies, or module resolution may require investigation.
|
||||
- **Task stop:** agentctl closes process admission and reaps the language-server process tree before releasing task resources.
|
||||
- **Instance teardown during auto-install:** agentctl cancels the install, removes an unpublished partial release download, drains the shared cache mutation, and reaps npm/Go descendants before releasing task resources.
|
||||
- **Browser disconnect during auto-install:** the task-host read watcher cancels and drains that connection's owned install instead of allowing npm, Go, or release downloads to continue without a consumer.
|
||||
- **Unread install status:** if the browser cannot receive the initial `installing` frame, agentctl closes the connection and returns before starting npm, Go, or release install work.
|
||||
- **Stalled browser peer:** bounded task-host WebSocket writes fail and enter the existing connection cleanup path, including stopping a language-server process that was started before its ready frame could be delivered.
|
||||
- **Browser disconnect during auto-install:** the lease continues a requested installation after browser detach. Explicit Stop or runtime teardown cancels and drains it.
|
||||
- **Unread install status:** if the backend lease cannot receive the initial `installing` frame, agentctl closes the stream before starting npm, Go, or release install work.
|
||||
- **Stalled browser peer:** bounded writes detach that browser without blocking task-host output or terminating the language-server process.
|
||||
- **Stalled language-server stdin:** stdout-forwarder termination releases an active stdin write immediately; otherwise the write cutoff closes stdin and enters owned-process cleanup after 30 seconds.
|
||||
- **Unknown language:** no LSP control is shown.
|
||||
|
||||
@@ -3,8 +3,9 @@ status: draft
|
||||
system: platform
|
||||
requirements:
|
||||
- REQ-PLATFORM-LSP-FILE-INTELLIGENCE-001
|
||||
- REQ-PLATFORM-LSP-FILE-INTELLIGENCE-002
|
||||
created: 2026-07-09
|
||||
updated: 2026-08-11
|
||||
updated: 2026-09-24
|
||||
owners:
|
||||
- tbd
|
||||
---
|
||||
@@ -19,6 +20,7 @@ This design preserves the technical source detail for `REQ-PLATFORM-LSP-FILE-INT
|
||||
| Requirement | Design section |
|
||||
| --- | --- |
|
||||
| `REQ-PLATFORM-LSP-FILE-INTELLIGENCE-001` | [Migrated source detail](#migrated-source-detail) |
|
||||
| `REQ-PLATFORM-LSP-FILE-INTELLIGENCE-002` | [Continuity scenarios](#continuity-scenarios) |
|
||||
|
||||
## Migrated source detail
|
||||
|
||||
@@ -50,7 +52,7 @@ This design preserves the technical source detail for `REQ-PLATFORM-LSP-FILE-INT
|
||||
- **GIVEN** an open Monaco document has a debounced content change and its server requests save synchronization, **WHEN** Kandev successfully persists the current editor snapshot, **THEN** the server receives the final `textDocument/didChange` before `textDocument/didSave` for its canonical task-host URI and receives that persisted snapshot on the save notification only when it requests `includeText`; a rejected write sends no save notification.
|
||||
- **GIVEN** the user types again while a file save is in flight, **WHEN** the older snapshot finishes persisting, **THEN** the newer editor snapshot remains dirty and is the language server's current document, while `textDocument/didSave` omits the stale optional text instead of rewinding the document.
|
||||
- **GIVEN** an SSH, Sprites, or remote-Docker task, **WHEN** a user starts LSP, **THEN** the UI reports an unsupported executor and no language-server or task execution is started or resumed for that request.
|
||||
- **GIVEN** the configured connection cap is reached, **WHEN** another editor starts LSP for a stopped supported task, **THEN** the new connection closes with `4005` before Kandev starts or resumes that task host.
|
||||
- **GIVEN** the configured lease cap is reached and every lease is attached, **WHEN** another editor needs a new server, **THEN** the request closes with `4005` before Kandev starts or resumes that task host; an editor reattaching to its retained lease remains admissible.
|
||||
- **GIVEN** a discovered language-server executable cannot be launched, **WHEN** agentctl starts it, **THEN** the task-host error stays in logs while the browser receives `4008` with no reason and shows the localized start-failure status.
|
||||
- **GIVEN** a language server rejects `initialize` with a JSON-RPC error object, **WHEN** the browser handles that response, **THEN** the error state shows the server's `error.message` rather than `[object Object]`.
|
||||
- **GIVEN** two task/session connections have active providers, placeholder models, or diagnostics, **WHEN** one connection stops or crashes, **THEN** cleanup removes only that connection's state and leaves the other connection fully functional.
|
||||
@@ -83,17 +85,35 @@ This design preserves the technical source detail for `REQ-PLATFORM-LSP-FILE-INT
|
||||
- **GIVEN** a repository contains `.kandev/lsp-servers/kotlin-lsp`, **WHEN** Kotlin LSP starts, **THEN** Kandev ignores that project-controlled executable.
|
||||
- **GIVEN** a mobile viewport, **WHEN** a supported file opens, **THEN** the mobile viewer does not start an LSP process invisibly.
|
||||
|
||||
## Continuity scenarios
|
||||
|
||||
- **GIVEN** a ready Go LSP lease, **WHEN** its browser tab closes, **THEN** the backend detaches the browser, continues draining the task-host stream, and does not interrupt `gopls`.
|
||||
- **GIVEN** that detached lease, **WHEN** an eligible desktop editor for the same task and language opens, **THEN** it reuses the same process, registers providers from retained and dynamic capabilities, synchronizes its current document, reaches ready with current progress, and receives diagnostics only from a qualifying new publication for that document.
|
||||
- **GIVEN** an editor has an unsaved buffer when its tab closes, **WHEN** it reattaches with the persisted file contents, **THEN** prior diagnostics for the unsaved buffer stay hidden until the server analyzes the reopened contents.
|
||||
- **GIVEN** two browser windows already have independent LSP leases, **WHEN** one closes or explicitly stops, **THEN** the other retains its process, requests, providers, progress, and diagnostics.
|
||||
- **GIVEN** a temporary WebSocket failure, **WHEN** the browser remains on the editor, **THEN** it shows reconnecting and reattaches without representing the failure as a language-server exit; a true process exit uses `4006` and Retry.
|
||||
- **GIVEN** the last editor in a connected tab remains closed for two minutes, **WHEN** its idle timer fires, **THEN** it explicitly releases the lease; tab close or network loss before that timer detaches without release.
|
||||
- **GIVEN** a browser has remained detached for one hour, **WHEN** its lease deadline passes, **THEN** the backend releases the lease and permits task-host idle reclaim; reattachment before the deadline cancels it, and a later eligible editor open starts fresh analysis.
|
||||
- **GIVEN** all LSP lease slots are occupied and at least one lease is detached, **WHEN** a new editor starts another language server, **THEN** the oldest detached lease is released and the new server is admitted; if every lease is attached, the editor gets `4005`.
|
||||
- **GIVEN** a returning tab has a valid lease hint, **WHEN** auto-start is later disabled for that language, **THEN** the tab still resumes its existing lease unless it explicitly stopped LSP; a new tab without a hint follows current auto-start or local manual-enable policy.
|
||||
- **GIVEN** a duplicated tab copied `sessionStorage`, **WHEN** it opens the same file while the original tab's lease is attached, **THEN** the original connection remains attached and the duplicate gets a separate lease or a capacity state.
|
||||
- **GIVEN** a server dynamically registers or unregisters a capability while its browser is detached, **WHEN** the editor reattaches, **THEN** the effective registration set is restored without a second `initialize`.
|
||||
- **GIVEN** per-language settings change while a browser is detached, **WHEN** the save completes, **THEN** the retained server receives `workspace/didChangeConfiguration` and later `workspace/configuration` responses use the new value.
|
||||
- **GIVEN** a request is pending at detach or explicit Stop, **WHEN** the browser generation ends, **THEN** the broker cancels its server-facing request and ignores its late response; a browser `$/cancelRequest` is translated to the mapped server ID.
|
||||
- **GIVEN** the task host or backend has stopped, **WHEN** the editor opens again, **THEN** no stale lease snapshot is shown as live and existing auto-start or manual enablement starts a new process when allowed.
|
||||
- **GIVEN** a phone file viewer, **WHEN** it opens a task with a detached LSP lease, **THEN** it does not attach or start an LSP process. The coarse-pointer tablet editor uses its existing toolbar drawer after reattachment.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Remote Docker, SSH, and Sprites executor support.
|
||||
- Durable per-task/session enablement and deny lists.
|
||||
- Sharing one server process across browser windows.
|
||||
- Sharing one server process between concurrently attached browser windows.
|
||||
- Rename, code actions, document symbols, formatting, and workspace-edit application.
|
||||
- CodeMirror/mobile LSP parity.
|
||||
- A global dashboard across every session/language connection; the application status-bar item represents only the active Monaco file.
|
||||
- Estimated time remaining, predicted completion, or any guarantee that a percentage maps linearly to project readiness.
|
||||
- Inferring actual indexing state, percentage, or completion from `window/logMessage`, `window/showMessage`, process output, elapsed time, or language-specific text heuristics. Elapsed time is used only to disclose that initialization is long-running.
|
||||
- Request-scoped partial-result streaming, `partialResultToken`, `$/cancelRequest`, and progress cancellation.
|
||||
- Request-scoped partial-result streaming, `partialResultToken`, and progress cancellation. Browser `$/cancelRequest` forwarding for mapped requests is in scope.
|
||||
- Bootstrapping project dependencies such as Gradle import, `npm install`, `go mod download`, or Python virtual environments.
|
||||
- Replacing external editors or embedded VS Code.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user