@abelxiaoxing/cadence 2.0.10 → 2.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -36,13 +36,15 @@ The token admits anyone who has it to the workspace, so keep the file and the li
36
36
  To change either, edit the line and run `cadence server restart`, which prints the new link; old links and browser cookies stop working.
37
37
  A workspace that an earlier version started with a generated token switches to the file at that restart and keeps its history.
38
38
 
39
- | Platform | Service | Starts |
40
- | -------- | ------------------------------------------------------------------------------------------- | -------------------------------------------- |
41
- | Linux | systemd user unit `~/.local/share/systemd/user/cadence.service` | At boot through linger, else at login |
42
- | macOS | LaunchAgent `~/Library/LaunchAgents/com.abelxiaoxing.cadence.plist` | At login |
43
- | Windows | `HKCU\…\CurrentVersion\Run\Cadence`, starting `%LOCALAPPDATA%\cadence-server\cadence.vbs` | At login, without a console window |
44
-
45
- On Linux, install enables linger without a prompt when logind allows it, and otherwise asks `loginctl enable-linger`, which may want your password; `--no-linger` (or `CADENCE_LINGER=0`) keeps the service to login.
39
+ | Platform | Service | Starts |
40
+ | ------------ | ----------------------------------------------------------------------------------------- | ------------------------------------- |
41
+ | Linux (root) | System unit `/etc/systemd/system/cadence.service` | At boot, no linger |
42
+ | Linux (user) | User unit `~/.local/share/systemd/user/cadence.service` | At boot through linger, else at login |
43
+ | macOS | LaunchAgent `~/Library/LaunchAgents/com.abelxiaoxing.cadence.plist` | At login |
44
+ | Windows | `HKCU\…\CurrentVersion\Run\Cadence`, starting `%LOCALAPPDATA%\cadence-server\cadence.vbs` | At login, without a console window |
45
+
46
+ On Linux, root defaults to `--system` and never uses linger; ordinary accounts default to `--user`.
47
+ In user scope, install enables linger without a prompt when logind allows it, and otherwise asks `loginctl enable-linger`, which may want your password; `--no-linger` (or `CADENCE_LINGER=0`) keeps the service to login.
46
48
  Open the printed private link once; the browser then keeps an HttpOnly cookie, and `http://127.0.0.1:17423/` alone only says how to get the link.
47
49
  For native `/cadence`, let Pi load the same copy: `pi install "$(npm root -g)/@abelxiaoxing/cadence"` records that path and loads it in place, so an npm upgrade updates both.
48
50
 
@@ -54,7 +56,7 @@ cadence server uninstall # before npm uninstall -g, which runs no Cadence scri
54
56
  ```
55
57
 
56
58
  Upgrade with `npm install -g @abelxiaoxing/cadence@latest`, then `cadence server restart`, which rewrites the service definition with the current Node and package paths.
57
- Restart, stop and uninstall go through the authenticated orderly Stop, so running work is stopped, not detached; on macOS and Windows a Stop that is not confirmed changes nothing.
59
+ Restart, stop and uninstall go through the authenticated orderly Stop, so running work is stopped, not detached; system/root, macOS and Windows management refuse to signal or replace a service when its Stop is not confirmed.
58
60
  `stop` keeps the registration, so the service starts again at the next boot or login.
59
61
  Uninstall removes only the generated definition; workspace, history, linger and Pi sources stay.
60
62
 
@@ -138,6 +140,32 @@ Its chrome follows pi-web-ui: a compact topbar with view tabs and icon actions,
138
140
  The interface is in English or Simplified Chinese: it follows the browser's first preferred language, and the topbar's globe chip (`中文` / `EN`) switches it, remembered only in that browser.
139
141
  Error codes, identifiers, commands and recorded content stay untranslated.
140
142
 
143
+ ### Root services and independent projects
144
+
145
+ Linux can run Cadence as a system-level root service, without a user bus or linger:
146
+
147
+ ```sh
148
+ sudo -H cadence server install --system --workspace /srv/project
149
+ sudo -H cadence server status --system
150
+ ```
151
+
152
+ Root defaults to `--system`; an ordinary account still defaults to `--user`, which retains the existing linger behavior.
153
+ There is no automatic sudo or credential/profile copying: the root owner uses root's own Pi sources and private state, and has full host permissions, not a sandbox.
154
+ Stop old owners before changing accounts or state homes; a different account's private namespace cannot coordinate the former owner's work.
155
+
156
+ Open another existing folder explicitly, or create a new project directory:
157
+
158
+ ```sh
159
+ cadence project open /absolute/project
160
+ cadence project open /absolute/new-project --create
161
+ ```
162
+
163
+ A new project selects an available loopback port once; reopening keeps its exact owner, port, cwd and history.
164
+ The command prints its private WebUI entry link; it does not create a task, choose a model or Start execution, and existing projects' admitted work continues.
165
+ Each project has its own WebUI entry and native `/cadence` attachment, not a global mutable cwd or a same-page cross-project router.
166
+ For independent boot services, use `cadence server install --system --name api --workspace /srv/api --port 17424` as root, and manage it with the same `--system --name api` flags.
167
+ See [service management](docs/service-management.md) for migration, named port/token files and limits.
168
+
141
169
  ## Execution, checks and review
142
170
 
143
171
  - Graphs contain 1–16 required assignments with explicit purposes/dependencies and exact model choice; no recursive spawning or autonomous retry.
@@ -217,22 +245,22 @@ Historical logs are not runtime dependencies, and removing development tooling m
217
245
 
218
246
  ### Source ownership and API boundaries
219
247
 
220
- | Owner | Responsibility |
221
- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- |
222
- | `src/index.ts`, `src/task-shell.ts`, `src/native-*.ts` | Nonactivating Pi entry, raw native consent, typed service attachment, paging and verified export; no embedded executor |
223
- | `src/agent-panel.ts`, `src/agent-panel-model.ts`, `src/agent-panel-cadence.ts` | Readonly agent panel over Pi's `subagent:*` bus, fed with a running owner's children; never starts or cancels work |
224
- | `src/service/host.ts`, `src/service/cli.ts`, `src/service/{autostart,launchd,login-run}.ts` | Single-owner composition, explicit management and generated per-user service definitions; management stays SDK-free |
225
- | `src/service/{coordination,private-files,owner,recovery}.ts` | Canonical overlap exclusion, exact locks, original-operation lifetime and informed local recovery |
226
- | `src/service/{admission,task-actions,graph,scheduler}.ts` | Context-bound actions, actual current views, finite graph scheduling, check/review barriers |
227
- | `src/service/{model-config,model-context,model-choice,auth-network}.ts`, `src/sdk-network.ts` | Existing Pi sources, explicit exact model/thinking, original OAuth writeback and strict shared network policy |
228
- | `src/worker-public.mjs` and `.d.mts`, `src/service-contract.ts` | One portable core/domain/capture codec with matching declarations and facade; TS core/domain/capture modules re-export it |
229
- | `src/service/contracts/`, `src/service-client.ts`, `src/service-wire.ts`, `src/service-http-client.ts` | Closed DTOs, typed named operations, reply correlation, transport bounds and one projection tracker |
230
- | `src/subagent-process.ts`, `src/sdk-worker.mjs`, `src/worker-{capture,redaction,tools}.mjs` | Same native worker/check launcher, genuine SDK capture, incremental sanitizer and bounded no-raw-spill bash adapter |
231
- | `src/history/{writer,run-state,publication}.ts` | One committed sequence authority, shared semantic reducers and expected-byte strict-v1 summaries |
232
- | `src/history/` readers/proof/search/export/live modules | Original-source integrity, bounded retained navigation, exact search and complete nonblocking exports |
233
- | `src/task-store.ts`, `src/task-types.ts`, `src/task-diff.ts` | Literal workspace identity, bounded plain summaries/configuration and honest change observation |
234
- | `web/src/` | Reused presentation and thin shared-client adapters, not SDK/session/execution ownership |
235
- | `scripts/`, `provenance/`, `licenses/` | Reproducible builds, exact package/body/reference/notice audits and pinned reuse |
248
+ | Owner | Responsibility |
249
+ | ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
250
+ | `src/index.ts`, `src/task-shell.ts`, `src/native-*.ts` | Nonactivating Pi entry, raw native consent, typed service attachment, paging and verified export; no embedded executor |
251
+ | `src/agent-panel.ts`, `src/agent-panel-model.ts`, `src/agent-panel-cadence.ts` | Readonly agent panel over Pi's `subagent:*` bus, fed with a running owner's children; never starts or cancels work |
252
+ | `src/service/host.ts`, `src/service/cli.ts`, `src/service/{autostart,system-service,launchd,login-run}.ts` | Single-owner composition, explicit project launch and generated system/user service definitions; management stays SDK-free |
253
+ | `src/service/{coordination,private-files,owner,recovery}.ts` | Canonical overlap exclusion, exact locks, original-operation lifetime and informed local recovery |
254
+ | `src/service/{admission,task-actions,graph,scheduler}.ts` | Context-bound actions, actual current views, finite graph scheduling, check/review barriers |
255
+ | `src/service/{model-config,model-context,model-choice,auth-network}.ts`, `src/sdk-network.ts` | Existing Pi sources, explicit exact model/thinking, original OAuth writeback and strict shared network policy |
256
+ | `src/worker-public.mjs` and `.d.mts`, `src/service-contract.ts` | One portable core/domain/capture codec with matching declarations and facade; TS core/domain/capture modules re-export it |
257
+ | `src/service/contracts/`, `src/service-client.ts`, `src/service-wire.ts`, `src/service-http-client.ts` | Closed DTOs, typed named operations, reply correlation, transport bounds and one projection tracker |
258
+ | `src/subagent-process.ts`, `src/sdk-worker.mjs`, `src/worker-{capture,redaction,tools}.mjs` | Same native worker/check launcher, genuine SDK capture, incremental sanitizer and bounded no-raw-spill bash adapter |
259
+ | `src/history/{writer,run-state,publication}.ts` | One committed sequence authority, shared semantic reducers and expected-byte strict-v1 summaries |
260
+ | `src/history/` readers/proof/search/export/live modules | Original-source integrity, bounded retained navigation, exact search and complete nonblocking exports |
261
+ | `src/task-store.ts`, `src/task-types.ts`, `src/task-diff.ts` | Literal workspace identity, bounded plain summaries/configuration and honest change observation |
262
+ | `web/src/` | Reused presentation and thin shared-client adapters, not SDK/session/execution ownership |
263
+ | `scripts/`, `provenance/`, `licenses/` | Reproducible builds, exact package/body/reference/notice audits and pinned reuse |
236
264
 
237
265
  The native path is `index → shell → native client → service → scheduler/process/history`.
238
266
  `src/task-controller.ts` remains a legacy regression subject, not a reachable or shipped production executor.
@@ -245,6 +273,11 @@ Use their current declarations rather than copying DTO parsing, reducers, a seco
245
273
  ### Admission, execution and original lifetimes
246
274
 
247
275
  - One canonical checkout/state-home domain has one persistent owner, including while idle.
276
+ Explicit project launch uses a new port only for a new checkout selection; reopened projects retain exact cwd/port/history, and no project navigation changes another owner's execution.
277
+ Root Git observations require Git's own ownership check: reset `safe.directory` exceptions per command, drop inherited `SUDO_UID` and disable fsmonitor, without configuration writes or ownership changes.
278
+ Merely selecting a foreign-owned repository cannot grant permission to run its clean/process filters; use its owning account instead.
279
+ Ordinary-user Git trust behavior remains unchanged.
280
+ System services require uid0, bypass user managers/logind entirely and require confirmed native Stop before manager stop or definition replacement; changed state-home inputs, old user units and detached running owners refuse implicit adoption.
248
281
  Registry reservation atomically rejects canonical ancestor/descendant overlaps, including nested Git roots and aliases; a different state-home does not coordinate arbitrary other owners.
249
282
  - Native entry requires fresh raw Enter within1500ms, trusted/idle TUI, no pending modal/messages and no child guard.
250
283
  Browser authentication grants access, not task consent; normal mutation ingress also requires independently built compatible client/service/assets identities.
@@ -290,6 +323,11 @@ Task v1 remains a strict human-readable summary; rich graph/transcript data neve
290
323
  `TaskStore.loadSnapshot` includes raw-byte identity; save/publication requires exact expected bytes and revision.
291
324
  A same-revision external edit conflicts and remains preserved.
292
325
  Summary clipping is labeled and never clips retained detail; a subsequent publication failure remains separate from previously completed execution.
326
+ A retained task may have no live `task.json` (for example after a workspace rebuild).
327
+ Current reads then retain the requested task ID in an unavailable context, with no live task/plan or action authority; history snapshots and retained content remain readable without reconstructing the summary.
328
+ Stable absence is not `stale`; appearance/disappearance or changed identities during an observation still are.
329
+ Generic `stale` requests fresh observation/confirmation, not a diagnosis that `models.json` changed or an instruction to restart; build compatibility is evaluated separately from the session and independently built client identities.
330
+ Local operation errors neither hide an independent observation error nor imply that the service needs to be started.
293
331
 
294
332
  ### Capture, retained history and privacy
295
333
 
@@ -302,6 +340,8 @@ Never await a run's own settlement from inside its awaited subscriber.
302
340
  Sanitize known literals, structured sensitive values and terminal controls before stdout and again before owner persistence.
303
341
  Preserve split UTF-8, escaped literals, surrogate pairs and possible secret prefixes across chunks; independent per-chunk string replacement is insufficient.
304
342
  Tool-start is an SDK observation, not proof of native execution; tool error is not a parent check result.
343
+ A prompt/preflight/non-success terminal response is labeled `worker-failure: prompt`, not `capture`; the recorded message stop reason, actual exit and cleanup facts stay independent.
344
+ A browser settlement timeout must retain its last receipt/active-roster facts before fixture teardown: a later Stop and its uncertain publication are not evidence that capture caused the original wait failure.
305
345
  Hidden thinking, signatures, provider envelopes and binary/media bodies remain explicitly omitted.
306
346
  Known-secret sanitation is not universal business-secret detection or an OS privacy sandbox.
307
347
 
@@ -309,6 +349,9 @@ Only the serialized history writer assigns committed sequence and persisted cont
309
349
  Commit actual admission/auth/launch/result facts in order; an index, observer callback or candidate reducer cannot authorize effects.
310
350
  Quota/exhaustion/refusal stops unsafe work without eviction; torn tails retain a verified prefix and visible gaps, while interior corruption refuses affected ranges.
311
351
  Derived indexes are optional accelerators: readers validate original batches/artifacts, use bounded fallback and never repair on view.
352
+ Live task/run/child views share an already observed containing snapshot only within one notification drain, retaining its exact context and committed watermark.
353
+ Narrowing requires the requested parent/child to be present; a historical run outside the task horizon still takes its independent reader, and no observation is cached into the next drain or used as renewed action consent.
354
+ Transient reuse keeps at most one page-sized source per viewer, rather than retaining every unobserved run ring.
312
355
  Current/plan/review reads replay a task's batches, so per-file cost bounds them: on a slow host (Windows Node 22) that replay once exceeded the 100ms read quantum after review, leaving the task `unavailable`.
313
356
  `PrivateFiles` reuses a directory walk only within one synchronous `DirectoryWalks` scope (a read budget or lease check), after re-observing that directory's identity; an await or later call walks again, and the quantum is never raised instead.
314
357
  When a batch settles a task's publication, the owner writer remembers in process memory that task's fold state (check facts, latest run, plan/lineage progress with original admission locators) under its chain head's sequence and hash, a bounded number of tasks and states.
@@ -396,6 +439,8 @@ Read affected source and adjacent tests, identify the actual owner of a contract
396
439
  Use ordinary source editing and directly affected checks while developing; independently review complete capabilities and run full `verify` at stable integration boundaries.
397
440
  Do not run the entire suite after every local edit, but do not replace final integration with unit mocks or citation counts.
398
441
  `bun run test` runs unit files in parallel, then `serialProcessFiles` (real PTY/SDK process flows) and `serialScanFiles` (>8MiB cold scans) from `vitest.config.ts` one at a time; a file belongs there only when it measurably exceeds its own waits under shared CPU.
442
+ `vitest run --config test/fast.config.ts` selects only its reviewed short-test allowlist, without inheriting root projects; `test/integration.config.ts` selects the exact complement and retains the serial projects.
443
+ `test/test-selection.test.ts` compares the runner's actual file discovery across all three configurations, rejecting duplicates, overlap, omissions and moved serial files; new tests belong to integration until explicitly promoted.
399
444
  Create a fresh 0700 root on the checkout's disk filesystem and explicitly set TMPDIR/TEMP/TMP to it; a nearly full tmpfs `/tmp` makes owners refuse at their free-space floor (`receipt-inconclusive`) and `web/dist` publication fail with EXDEV.
400
445
  A test waits for the event it needs (both providers contacted, a row settled), not a wall-clock guess at how long process starts take; fixed waits stay only where time is the contract.
401
446
  Use fabricated credentials, isolated browser/profile/state roots and local fixtures; never personal credentials, automatic downloads or unknown process cleanup.
@@ -413,18 +458,45 @@ Regression owners:
413
458
  - `agent-panel`, `agent-panel-model`, `agent-panel-cadence`: panel keys/rendering and readonly owner observation that never starts, cancels or replays work.
414
459
  - `test/web/`: adapted pure/SSR tests separately from actual built-browser contexts, HTTPS, mixed clients, focus/viewports and complete downloads.
415
460
  - `package-*`, `distribution`, `service-package`, `independence`: exact tar/member/body/import/notice closure and nonactivating discovery.
416
- - `service-autostart`, `install-config`: explicit `cadence server` registration on systemd, launchd and the Windows Run key, definition escaping, restart/disable/mask/foreign handling, orderly Stop before every change, the `.env` port and token lines and a package without install scripts.
461
+ - `service-autostart`, `project-start`, `install-config`: explicit `cadence server` registration on systemd (root system scope without linger, legacy user scope), launchd and the Windows Run key, separate named service definitions and directory owners, definition escaping, restart/disable/mask/foreign handling, orderly Stop before every change, the `.env` port and token lines and a package without install scripts.
417
462
  - `service-cli`: real owners started from the `.env` port and token, reselection of a stopped workspace, edits while running and rotation of the token line.
418
463
  - `workspace-landing`: home-directory redirection to `~/cadence` across management, native attach and observation.
419
464
 
420
465
  Regenerate operator/service/native/browser outputs only through their canonical builders; `--check` variants compare new private output without overwriting canonical bytes.
421
466
  WebUI text is keyed by its English source: fixed text goes through `useT().t`, whose type requires a `web/src/i18n/zh.ts` entry with the same `{name}` marks, and run-time status words or kept notices through `tx`, which passes unknown text and codes through.
422
467
  The timeline client takes the chosen language from its bridge and is remounted on a switch; browser scenarios pin `locale: "en-US"` so English assertions do not follow the host's `LANG`.
468
+ The browser is conversation-first: the left panel groups the current service project, the actual active roster and retained task conversations, with run/subagent children under their task.
469
+ One service still exposes one checkout; the project section is not a cross-project registry, Pi session importer or cwd switcher.
470
+ The center shows retained message text as Markdown by default; tools/context/timeline/run facts and export/link actions live under Details, while the collapsible right inspector remains bound to the task's current plan/check/change/review context even when viewing a child.
471
+ Historical child links bind exact scope and watermark.
472
+ Bounded history and Back never silently rebase; running conversations offer an explicit new-activity update, and source limitations/progress stay visible even when complete-record metadata is folded.
473
+ Cancel uses the independent active roster (including the fallback strip when workspace discovery fails); navigation, project selection and hiding panels never cancel or authorize execution.
474
+
423
475
  Publish `web/dist` from Node: Vite run under Bun lays out its preload helper differently, so a Bun build fails a Node `build:web:check`.
424
476
  Review the independently enumerated package member list and every emitted body, not a count or an expected list generated from the tar under test.
425
477
  Keep public Pi peers external for the extension, the exact Undici/ws runtime edges, the exact optional SDK pins for the standalone owner and all upstream/font/vendor notices.
426
478
  Preserve original license bytes through their precise Git attributes; line-ending normalization must not invalidate audited source/notice hashes.
427
479
 
480
+ ### Release 2.0.12 acceptance snapshot
481
+
482
+ Full `verify` passed on the 2.0.12 release candidate before this evidence-only paragraph: 1983 root tests in 80 files (27 skipped on Linux), 128 web tests, 6 real-browser scenarios and the 132-member pack audit.
483
+ Observed environment: Linux x86_64, Node26.7.0, Bun1.3.14, Pi SDK0.84.3 and existing Chrome for Testing151.0.7922.34.
484
+ Retained conversations remain readable after their live `task.json` disappears and the owner restarts without a selected model; reads neither reconstruct live state nor grant execution authority, and an unused Start grant from before removal is refused without admission.
485
+ Generic stale guidance asks for fresh observation/confirmation without diagnosing a model-source change; local errors no longer hide that observation or suggest starting the service.
486
+ The local-error banner regression failed against the prior built assets and passed against the rebuilt candidate; a new Chinese assertion was corrected to expect its translated status, and an initial integration attempt stopped at provenance JSON formatting before the final full pass.
487
+ No new Windows/macOS, real service-manager installation or paid-provider qualification is claimed.
488
+
489
+ ### Release 2.0.11 acceptance snapshot
490
+
491
+ Full `verify` passed on the 2.0.11 release candidate before this evidence-only paragraph: 1976 root tests in 80 files (27 skipped on Linux), 122 web tests, 5 real-browser scenarios and the 132-member pack audit.
492
+ Observed environment: Linux x86_64, Node26.7.0, Bun1.3.14, Pi SDK0.84.3 and existing Chrome for Testing151.0.7922.34.
493
+ The root Git regressions use real Git with simulated ownership mismatch, and system service management uses isolated filesystem/manager adapters; no real root/systemd installation, new CI matrix, macOS or Windows qualification is claimed.
494
+
495
+ This release adds explicit directory project launch and independent named Linux system/root services, and a conversation-first WebUI with retained task/run/subagent navigation.
496
+ Root observation no longer automatically trusts foreign Git repositories; inactive service definitions cannot stop or adopt detached owners, including through restart or uninstall.
497
+ Fast/integration test selection is checked against actual Vitest discovery, and provider prompt failures remain separate from capture failures.
498
+ Live task/run/child views reuse only an already observed containing snapshot within one notification drain, never a previous round's action context.
499
+
428
500
  ### Release 2.0.10 acceptance snapshot
429
501
 
430
502
  Full `verify` passed on the 2.0.10 release tree (`4843334` plus the version bump), in the 2.0.3 environment below.
@@ -8,9 +8,9 @@ Repository `provenance/webui-reuse.json` records source/destination hashes and e
8
8
 
9
9
  | Source family | Package adaptation |
10
10
  | ------------------------------------ | ------------------------------------------------------------------------ |
11
- | App, TopBar, panels, layout, drawers | Three-pane workspace, pi topbar/statusbar chrome and phone navigation |
12
- | conv-groups, PlanBoard | Task/run/child tree, editable finite graph and separate confirmations |
13
- | MessageList, Message, ToolCallBlock | Paged transcript, stable tool associations and retained detail |
11
+ | App, TopBar, panels, layout, drawers | Conversation-first workspace, project/session sidebar and phone drawers |
12
+ | conv-groups, PlanBoard | Active/history task and subagent tree, finite graph and explicit consent |
13
+ | MessageList, Message, ToolCallBlock | Default Markdown transcript, folded metadata and bounded retained detail |
14
14
  | Markdown, StreamMarkdown, copy | Sanitized Markdown/math/code, no script/external-image execution |
15
15
  | FilePreview, SCM presentation | Readonly observed changes, no Git/file mutation API |
16
16
  | GlobalSearchModal, search, deep link | Literal durable search, exact versioned hits and scoped export |
@@ -23,12 +23,41 @@ Missing/corrupt existing setup is preserved, not filled in or reset.
23
23
  A workspace that contains the private state home (the home directory and its ancestors, such as `C:\Users\<you>` with `%LOCALAPPDATA%` or `~` with `~/.local/state`) would expose private history and locks to change observation, so every command there uses `~/cadence` (`%USERPROFILE%\cadence`) instead of refusing.
24
24
  Only `start` creates it; `status` reports `absent` without creating it, an explicit `--workspace` adds one informational line, and JSON output names the effective `workspace` only when it differs.
25
25
  `/cadence` started in such a directory attaches to the same owner, and real project directories are never redirected.
26
+ A directory inside `cadence-assistant-v1` is never an engineering workspace, even for root or `project open --create`.
26
27
  Apart from the background service below, there is no supervisor installation, provider login or credential migration.
27
28
 
29
+ ## Open a project directory
30
+
31
+ ```sh
32
+ cadence project open /absolute/existing-project
33
+ cadence project open /absolute/new-project --create
34
+ # Optional explicit sources, without copying them:
35
+ cadence project open /srv/project --agent-dir /root/.pi/agent --state-home /root/.local/state
36
+ ```
37
+
38
+ Without a path, `project open` uses the current directory; a missing directory refuses unless `--create` was supplied.
39
+ The project may be a Git checkout or an ordinary directory, including spaces and Unicode names; it need not be inside `~/cadence` or the package directory.
40
+ The command explicitly starts a detached owner, or observes the already running exact owner; it never creates a task, chooses a model, renews a grant or starts a worker.
41
+ New projects bind port0 and retain the resulting positive port, avoiding the default17423 collision; existing selections keep their exact port, cwd, agent sources and history.
42
+ The JSON result reports the canonical workspace, actual task cwd and endpoint; the private entry link is printed separately to stderr, like `server install`.
43
+ Keep that link private.
44
+ Open Pi in the same project and use `/cadence` for the native client; explicit `cadence stop --workspace <path>` stops only that project.
45
+ Opening another project never changes the cwd or cancels work in the former one.
46
+ Each project has its own browser entry and owner; this is not a same-page cross-project browser router.
47
+ Git subdirectories and aliases resolve to the same checkout, while overlapping parent/child owners still refuse.
48
+ An unknown, quarantined or inaccessible selection is never treated as a new project or recovered by choosing another state home.
49
+
50
+ When run as root, Git observation requires a repository Git recognizes as root-owned: Cadence clears `safe.directory` exceptions per command, removes inherited `SUDO_UID` and disables fsmonitor.
51
+ A foreign-owned repository refuses even if a global `safe.directory=*` would allow it; selecting a path is not consent to execute its configured clean/process filters.
52
+ Use the repository's owning account instead, or deliberately provision a root-owned checkout outside Cadence; no Git configuration edit, `chown`, credential import or permission reset is performed.
53
+ Ordinary-user Git trust behavior remains intact.
54
+ Trusted repositories can still carry executable Git configuration; root is not an isolation or sandbox guarantee.
55
+ Changing to root changes the effective account and its private namespace: stop all old Cadence owners before doing so, since different accounts/state homes do not coordinate each other's original work.
56
+
28
57
  ## Background service
29
58
 
30
59
  ```sh
31
- cadence server install [--workspace /absolute/project] [--no-linger]
60
+ cadence server install [--workspace /absolute/project] [--system|--user] [--no-linger]
32
61
  cadence server status
33
62
  cadence server restart
34
63
  cadence server stop
@@ -37,6 +66,11 @@ cadence server uninstall
37
66
 
38
67
  Only these explicit commands register, change or remove the service; no package install, update hook, `/cadence` or browser visit does.
39
68
  The package has no install script, so npm 12 and Bun skipping untrusted scripts by default changes nothing; the pinned Pi SDK's blocked `@google/genai` preinstall only echoes `no-op`, and its `protobufjs` postinstall only warns about version schemes.
69
+ Linux server commands accept `--system` or `--user` after the action; root defaults to system scope and other accounts to user scope.
70
+ `--system` requires root and never runs sudo itself; `--user` explicitly selects the legacy per-user manager, even when invoked by root.
71
+ Linux also accepts `--name <name>` on every server command to select `cadence-<name>.service`, and `install` accepts `--port <1-65535>` for a missing port setting.
72
+ Names must start with a letter or digit and contain only letters, digits, `_` and `-` (at most48characters).
73
+
40
74
  `install` also creates the research template `~/.pi/agent/cadence/.env` when it is missing, never overwriting it, whatever the service result, and adds its [port and token](#port-and-token) lines when they are missing.
41
75
  `install` works from any package location and runs `start --foreground --workspace <workspace> --server-config <file>` (Linux, macOS) or the same without `--foreground` (Windows) from that copy with the installing Node, without `--port`.
42
76
  The workspace is `~/cadence` (created if missing), an existing absolute `--workspace`, or on later installs the one the definition already runs; a workspace containing the service's state home lands on `~/cadence` with a note.
@@ -51,7 +85,7 @@ A definition Cadence did not generate is left unchanged (`foreign`), and `status
51
85
  Pi's installer omits peers, so the standalone owner and workers load the Pi SDK (`pi-coding-agent`, `pi-ai`) from exact 0.84.3 optional dependencies.
52
86
  Package files may be hard links into the package manager's store (Bun's default install); Cadence binds their exact content, not their link count.
53
87
  Missing or unusable Pi sources refuse before an owner registers; a new owner without the SDK quarantines.
54
- `install` therefore writes nothing while the SDK is unresolvable (for example, optional dependencies omitted), and registers without starting while Pi's `auth.json` is missing (`waiting-for-pi-login`); log in to Pi, then run `cadence server restart`.
88
+ `install` therefore writes no service definition while the SDK is unresolvable (for example, optional dependencies omitted), and registers without starting while Pi's `auth.json` is missing (`waiting-for-pi-login`); log in to Pi, then run `cadence server restart`.
55
89
 
56
90
  While no service is registered, Pi's session-start card shows a separate warning naming `cadence server install` for an installed package copy (`node <copy>/src/service-cli.mjs server install` for Pi's managed copy, which puts no bin on `PATH`); a checkout loaded with `pi -e` stays quiet.
57
91
  It is a warning rather than part of the info card because Pi's TUI replaces one info status with the next.
@@ -88,9 +122,53 @@ Under the new owner reservation Cadence retires the previous token with a new ac
88
122
  A different agent directory, host or proxy still conflicts, and a running owner refuses until it has stopped.
89
123
  An interrupted replacement keeps the reservation as a fence and is never repaired automatically, like any interrupted start.
90
124
 
125
+ ### Linux systemd system/root unit (no linger)
126
+
127
+ ```sh
128
+ # Root must already have its own usable Pi credentials and SDK installation.
129
+ sudo -H cadence server install --system --workspace /srv/project
130
+ sudo -H cadence server status --system
131
+ sudo -H cadence server restart --system
132
+ sudo -H cadence server stop --system
133
+ sudo -H cadence server uninstall --system
134
+ ```
135
+
136
+ Use an absolute Node and package CLI path with `sudo -H` if `cadence` is not on root's PATH.
137
+ These commands generate `/etc/systemd/system/cadence.service` with `User=root` and `WantedBy=multi-user.target`, and use `systemctl --system`.
138
+ They never consult or change linger, require no logged-in user manager and start at boot independently of root login.
139
+ The definition freezes HOME, PATH, LANG/LC_ALL, an explicit XDG_STATE_HOME and custom PI_CODING_AGENT_DIR; root must own its selected Pi sources, and no normal-user profile or credentials are copied.
140
+ Root runs the agent and approved checks with full root privileges: browser authentication remains access, not permission to Start, and this is not a sandbox.
141
+ Status and install output identify system scope; Pi's root startup hint recommends `--system`, never `loginctl enable-linger`.
142
+
143
+ A prior user-scope definition of the same name causes `scope-conflict`, never automatic migration.
144
+ For a root account's older user unit, explicitly run `cadence server uninstall --user` first, then install `--system`; this preserves its workspace, private history, credentials and linger setting.
145
+ For another account's service, first stop/uninstall it as that account; root must not silently import its private state or assume its owners are absent.
146
+ A detached owner already running in the target directory causes `project-running`; stop it explicitly before installing a different supervisor.
147
+ An old but inactive unit does not own a subsequently detached owner: install, restart, stop and uninstall also refuse in that case, without sending Stop to it, and changing the requested target does not bypass the old directory's check.
148
+ An unconfirmed or quarantined Stop, changed generated file, changed HOME/XDG state selection, a mask or a foreign unit prevents replacement/adoption.
149
+ `restart`, `stop` and `uninstall` await the authenticated original owner Stop before invoking systemctl, so manager timeouts are never used as proof of settlement.
150
+ Restart keeps disablement and does not unmask anything; uninstall removes only the generated definition, never data or linger.
151
+ Direct systemctl shutdown still has the unit's ExecStop and timeout behavior described below; it is not an alternative recovery mechanism.
152
+
153
+ ### Independent named Linux services
154
+
155
+ ```sh
156
+ sudo -H cadence server install --system --name api --workspace /srv/api --port 17424
157
+ sudo -H cadence server install --system --name web --workspace /srv/web --port 17425
158
+ sudo -H cadence server status --system --name api
159
+ sudo -H cadence server restart --system --name web
160
+ ```
161
+
162
+ The units, ports, access tokens, working directories and owner lifetimes are separate; restarting or stopping one does not touch another.
163
+ A named service uses `~/.pi/agent/cadence/servers/<name>.env` under its effective account, never a copy of the default token or research/provider configuration.
164
+ A new named configuration requires `--port`; existing port/token lines are preserved, and a conflicting `--port` refuses instead of rewriting them.
165
+ Edit the selected named file explicitly and restart that same `--name` to change its port.
166
+ An occupied port still refuses rather than silently falling back; use `project open` when an automatically selected detached port is preferable.
167
+ All projects still share one coordination state home for their account, retaining canonical overlap checks and uncertain fences.
168
+
91
169
  ### Linux systemd user unit
92
170
 
93
- `install` writes `~/.local/share/systemd/user/cadence.service`, enables it for `default.target` (also undoing a `disable`, never a `mask`) and starts it.
171
+ `install --user` writes `~/.local/share/systemd/user/cadence.service`, enables it for `default.target` (also undoing a `disable`, never a `mask`) and starts it.
94
172
  A user unit runs with the user manager, which starts at login unless logind lingers the user, so install also enables linger when it is off.
95
173
  It first calls `SetUserLinger` through `busctl` with `interactive=false` and no interactive authorization (bounded to 15s), because `loginctl enable-linger` always requests an interactive check that a desktop polkit agent can turn into a password dialog even with `--no-ask-password`.
96
174
  Only where polkit requires authorization for `set-self-linger` (older systemd defaults) does it then run `loginctl enable-linger`, which may ask for your password; that comes after the start, so an interrupted prompt never leaves the owner stopped, and a refusal reports `"linger":"refused"` with a login-only unit.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abelxiaoxing/cadence",
3
- "version": "2.0.10",
3
+ "version": "2.0.12",
4
4
  "description": "Shared Pi task workspace: native terminal and WebUI, persistent service, checks and human review.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -2,5 +2,5 @@
2
2
  export const nativeBuild = {
3
3
  kind: "native",
4
4
  contractHash:
5
- "c2bb81519387005a71e88a64364dff47d5c39dab17812833ca898bc51055e59d",
5
+ "e9b1ea4bb8d1c275a0cb0cf70947f9ac58e5de0cc438d551dc96809f3e82dd7c",
6
6
  } as const;
@@ -32,12 +32,14 @@ function definition(o: AutostartObservation): string | undefined {
32
32
  switch (o.platform) {
33
33
  case "linux":
34
34
  return existsSync(o.systemd)
35
- ? join(
36
- xdg(o.env.XDG_DATA_HOME, join(o.home, ".local", "share")),
37
- "systemd",
38
- "user",
39
- UNIT,
40
- )
35
+ ? o.uid === 0
36
+ ? join(o.systemUnitDirectory ?? "/etc/systemd/system", UNIT)
37
+ : join(
38
+ xdg(o.env.XDG_DATA_HOME, join(o.home, ".local", "share")),
39
+ "systemd",
40
+ "user",
41
+ UNIT,
42
+ )
41
43
  : undefined;
42
44
  case "darwin":
43
45
  return join(o.home, "Library", "LaunchAgents", `${LABEL}.plist`);
@@ -52,11 +54,11 @@ function definition(o: AutostartObservation): string | undefined {
52
54
  }
53
55
  }
54
56
  const bootStart = (o: AutostartObservation) =>
55
- o.platform === "linux" && o.env.CADENCE_LINGER !== "0";
57
+ o.platform === "linux" && (o.uid === 0 || o.env.CADENCE_LINGER !== "0");
56
58
  /** Pi's managed copy puts no `cadence` bin on PATH; CADENCE_LINGER=0 keeps
57
59
  * the suggested command login-only, too. */
58
60
  const installCommand = (o: AutostartObservation) =>
59
- `${piUserCopy(o) ? `node ${JSON.stringify(join(o.root, "src", "service-cli.mjs"))}` : "cadence"} server install${o.platform === "linux" && o.env.CADENCE_LINGER === "0" ? " --no-linger" : ""}`;
61
+ `${piUserCopy(o) ? `node ${JSON.stringify(join(o.root, "src", "service-cli.mjs"))}` : "cadence"} server install${o.platform === "linux" && o.uid === 0 ? " --system" : o.platform === "linux" && o.env.CADENCE_LINGER === "0" ? " --no-linger" : ""}`;
60
62
 
61
63
  /** Name the explicit command while no background service is registered.
62
64
  * Reads paths only. */
@@ -78,6 +80,9 @@ export interface AutostartObservation {
78
80
  lingerFile: string;
79
81
  /** Exists only on a systemd-booted machine. */
80
82
  systemd: string;
83
+ uid?: number;
84
+ /** Only supplied by readonly observation tests. */
85
+ systemUnitDirectory?: string;
81
86
  }
82
87
  function hostObservation(): AutostartObservation {
83
88
  let user = "";
@@ -93,6 +98,7 @@ function hostObservation(): AutostartObservation {
93
98
  root: fileURLToPath(new URL("..", import.meta.url)),
94
99
  lingerFile: user ? join("/var/lib/systemd/linger", user) : "",
95
100
  systemd: SYSTEMD_BOOTED,
101
+ uid: process.getuid?.(),
96
102
  };
97
103
  }
98
104
  /** Same selection as Pi's getAgentDir: PI_CODING_AGENT_DIR or ~/.pi/agent. */
@@ -139,7 +145,7 @@ export function autostartNotice(
139
145
  return installedCopy(o)
140
146
  ? `Cadence服务:后台服务未安装,WebUI(默认 http://127.0.0.1:17423)不会运行。安装并${bootStart(o) ? "开机" : "登录后"}自启:${installCommand(o)}`
141
147
  : undefined;
142
- if (o.platform !== "linux") return;
148
+ if (o.platform !== "linux" || o.uid === 0) return;
143
149
  const config = xdg(o.env.XDG_CONFIG_HOME, join(o.home, ".config"));
144
150
  const user = join(config, "systemd", "user");
145
151
  const runtime = o.env.XDG_RUNTIME_DIR;
@@ -210,15 +210,19 @@ function readBounded(path, max, packaged = false) {
210
210
  }
211
211
  }
212
212
  function gitEnvironment() {
213
+ const root = process.getuid?.() === 0;
213
214
  return {
214
- ...Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith("GIT_"))),
215
+ ...Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith("GIT_") && !(root && name === "SUDO_UID"))),
215
216
  GIT_OPTIONAL_LOCKS: "0",
216
217
  LC_ALL: "C"
217
218
  };
218
219
  }
220
+ function gitArguments(args) {
221
+ return process.platform !== "win32" && process.getuid?.() === 0 ? ["-c", "safe.directory=", "-c", "core.fsmonitor=false", ...args] : args;
222
+ }
219
223
  function resolveWorkspace(cwd) {
220
224
  const actual = realpathSync(cwd);
221
- const result = spawnSync("git", ["rev-parse", "--show-toplevel"], {
225
+ const result = spawnSync("git", gitArguments(["rev-parse", "--show-toplevel"]), {
222
226
  cwd: actual,
223
227
  env: gitEnvironment(),
224
228
  encoding: "utf8",
@@ -281,8 +285,13 @@ function stateHomeInside(root, stateHome = defaultStateHome()) {
281
285
  const canonical = realpathSync(root);
282
286
  return contains(canonical, privateRootFor(canonical, stateHome));
283
287
  }
288
+ function privateWorkspace(requested, stateHome = defaultStateHome()) {
289
+ return contains(join(canonicalAmbientRoot(stateHome), "cadence-assistant-v1"), canonicalAmbientRoot(requested));
290
+ }
284
291
  var LANDING_WORKSPACE = "cadence";
285
292
  function effectiveWorkspace(requested, stateHome, home = homedir()) {
293
+ if (privateWorkspace(requested, stateHome))
294
+ throw new Error("Private state cannot be a workspace");
286
295
  if (!stateHomeInside(resolveWorkspace(requested).root, stateHome))
287
296
  return { workspace: requested, redirected: false };
288
297
  return { workspace: join(home, LANDING_WORKSPACE), redirected: true };
@@ -294,6 +303,8 @@ class TaskStore {
294
303
  privateRoot;
295
304
  constructor(root, stateHome = defaultStateHome()) {
296
305
  this.root = realpathSync(root);
306
+ if (privateWorkspace(this.root, stateHome))
307
+ throw new Error("Private state cannot be a workspace");
297
308
  this.tasks = join(this.root, ".cadence", "assistant", "tasks");
298
309
  this.privateRoot = privateRootFor(this.root, stateHome);
299
310
  if (contains(this.root, this.privateRoot))
@@ -63,6 +63,8 @@ const fail = (where, category) => {
63
63
  failure ??= { stage: where, class: category };
64
64
  process.exitCode = 1;
65
65
  if (first) {
66
+ // Prompt/preflight/terminal failure is distinct from a capture failure.
67
+ // A normal provider error can have a completely retained error message.
66
68
  if (
67
69
  emitter?.state.position.workerSeq &&
68
70
  !emitter.state.position.final &&
@@ -77,7 +79,9 @@ const fail = (where, category) => {
77
79
  ? "cleanup"
78
80
  : where === "network"
79
81
  ? "network"
80
- : "capture",
82
+ : ["prompt", "preflight", "assistant-final"].includes(where)
83
+ ? "prompt"
84
+ : "capture",
81
85
  })
82
86
  .catch(() => {});
83
87
  } catch {
@@ -55,6 +55,8 @@ export interface CurrentView {
55
55
  epoch: string;
56
56
  committedSeq: string;
57
57
  context: ControlContext;
58
+ /** Null when no live record is available, even for a non-null request.taskId.
59
+ * This conveys no retained membership or permission to recreate the task. */
58
60
  task: CurrentTask | null;
59
61
  roles: RoleSelection[];
60
62
  readablePlan: null | {
@@ -131,8 +133,13 @@ function correlate(v: {
131
133
  context: ControlContext;
132
134
  task: CurrentTask | null;
133
135
  }) {
136
+ // A requested task without a live record (retained-only history) may answer
137
+ // with task: null while its context reports unavailable for that same task.
138
+ const taskMatches = v.task
139
+ ? v.task.id === v.request.taskId
140
+ : v.request.taskId === null || v.context.status === "unavailable";
134
141
  if (
135
- v.request.taskId !== (v.task?.id ?? null) ||
142
+ !taskMatches ||
136
143
  controlWorkspace(v.context) !== v.workspaceId ||
137
144
  controlTask(v.context) !== v.request.taskId
138
145
  )