@abelxiaoxing/cadence 2.0.15 → 2.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -137,7 +137,8 @@ For manual checks, `pi -e ./src/index.ts -e ./scripts/agent-panel-demo.ts` adds
137
137
 
138
138
  Open the private link that `cadence server install` printed, or that `cadence token-link --reveal` reveals.
139
139
  Valid bootstrap redirects to a token-free URL and uses an HttpOnly SameSite cookie for subsequent HTTP/WebSocket access; no localStorage token copy.
140
- The unified browser entry first shows projects, without an execution cwd, SDK session or default workspace.
140
+ The unified browser entry opens on the conversation shell, without an execution cwd, SDK session or default workspace.
141
+ Choose a working folder from the lower-right button; before a folder is selected, New conversation offers the same picker and Send stays disabled.
141
142
  Only explicit Open starts or reuses a project owner.
142
143
  Within that project, the browser opens on an ordinary conversation with a real message composer, retained multi-turn context and standard read/write/edit/bash tools; no Cadence task is created by Send.
143
144
  Use `cadence <goal>` or `/cadence <goal>` at the beginning of the input, or select Cadence task, to prepare a separate task.
@@ -151,10 +152,10 @@ Ordinary Send binds its exact text, conversation head, epoch, access and selecte
151
152
  The WebUI provides task/run/assignment navigation, long retained messages and tool detail, exact literal search, timeline and complete scoped NDJSON/Markdown downloads.
152
153
  Desktop panes and phone drawers retain cancellation and keyboard controls.
153
154
  Its chrome follows pi-web-ui: a compact topbar with view tabs and icon actions, a sectioned task sidebar, pill inspector tabs, transcript/tool cards and a bottom statusbar; phones wrap the topbar into an action row and an icon tab row.
154
- The left sidebar lists recent projects and running/retained conversations; a separate lower-left dock observes Cadence subagents and opens their retained transcripts.
155
+ The left sidebar provides New conversation and New task alongside running/retained conversations; a separate lower-left dock observes Cadence subagents and opens their retained transcripts.
155
156
  It is not pi-web-ui's subagent engine.
156
157
  The right sidebar defaults to readonly files, bounded text previews and path-only references; task plan/check/change/review controls occupy its Task details tab.
157
- The lower-right folder button browses directories on the service host and explicitly opens another independent project owner.
158
+ The lower-right folder button contains recent projects, browses directories on the service host and explicitly opens another independent project owner.
158
159
  The unified entry switches project clients on the same origin, including an explicitly configured HTTPS proxy; it never changes a running task's cwd, registers a boot service or cancels the project left behind.
159
160
  Backend cookies and native capabilities never reach the browser; proxied actions remain browser-authority actions, with owner-side consent/build/CSRF checks.
160
161
  The gateway refuses the legacy project-level Open command: all project openings use its own association route, without returning an independent backend token link.
@@ -235,7 +236,9 @@ Storage exhaustion refuses new work rather than silently evicting history.
235
236
  Workbench state lives separately under `<state-home>/cadence-assistant-v1/workbenches-v1/<configuration-hash>/`, not in a hidden substitute workspace.
236
237
  Whole-workbench Stop joins its accepted openings and associated exact-epoch project owners, serializing their final registry releases; unrelated, unassociated owners are not stopped.
237
238
  A changed epoch, uncertain launch or unconfirmed Stop keeps the workbench fenced, but does not prevent Stop from reaching its other known associated owners.
238
- Workbenches never repair missing private state or erase uncertain locks.
239
+ Workbenches never repair missing private state or automatically erase uncertain locks.
240
+ `cadence workbench recover inspect|resolve` offers explicit local recovery after a dead gateway, with exact resources, durable evidence and fresh terminal acknowledgments; it preserves project fences/history and retires only the old workbench associations until another explicit Open.
241
+ Registrations from older versions without a hostname additionally require independent same-host attestation; unknown provenance is not recoverable by guessing a PID.
239
242
  Service restart never resumes the old queue.
240
243
  Uncertain process/auth/storage settlement keeps fences; use documented local informed recovery, not PID-only deletion or automatic cleanup.
241
244
 
@@ -310,7 +313,7 @@ Historical logs are not runtime dependencies, and removing development tooling m
310
313
 
311
314
  `src/service/{gateway,gateway-store,gateway-cli}.ts` owns the no-project entry, private workbench coordination and same-origin project routing, not task execution.
312
315
  A root access rotation retires its proxy attachment IDs and detaches those exact browser viewers at the owners, without cancelling admitted work.
313
- The first version requires a selected project for ordinary Send/tools; the empty workbench is a project selector, not an unbound chat executor.
316
+ The first version requires a selected project for ordinary Send/tools; the empty workbench presents the conversation shell with Send disabled, not an unbound chat executor.
314
317
  `src/service/{workbench,conversations,workbench-files}.ts` owns ordinary conversations, their private retained turns, readonly files and explicit project navigation; `src/service/contracts/workbench.ts` is the portable closed DTO boundary.
315
318
  Conversation auth intents and original settlements use their own flushed private journal, never fabricated task/history receipts.
316
319
  Their original Stop lifetime and exclusive checkout barrier belong to the existing service owner and scheduler.
@@ -337,6 +340,14 @@ Use their current declarations rather than copying DTO parsing, reducers, a seco
337
340
  Concurrent or interrupted resolvers cannot remove a successor lock; all project reservations, locks and history remain intact.
338
341
  Unreserved partial release, unsafe/corrupt registry or receipt bytes, live/remote/ambiguous holders and changed resources stay fenced.
339
342
  Readonly diagnosis and project Open name `registry-locked`; see [global registry recovery](docs/service-management.md#residual-global-registry-lock).
343
+ Missing retained project directories do not invalidate the global registry: their exact reservations and private fences remain, while canonical existing ancestors, aliases and overlaps are still checked.
344
+ Unbound `recover` addresses account/workbench state, not cwd; project recovery requires explicit `--workspace`.
345
+ - Workbench recovery is local and interactive, never acquire-on-dead-PID.
346
+ A bounded flushed receipt precedes exact gateway fence release; project registrations, locks, incomplete opening receipts and history are untouched.
347
+ The receipt retires the exact old project-reference incarnations from proxy/Stop association until explicit Open republishes them, preventing a deleted old directory from re-quarantining the next orderly Stop.
348
+ New workbench registrations record their host; legacy hostless registrations require an extra fresh same-host attestation, never automatic host inference.
349
+ Live/foreign/ambiguous owners and changed or unsafe resources refuse.
350
+ Generated Linux units exclude deterministic foreground refusal exit78 from restart; manager acceptance without authenticated readiness is not reported as successful startup.
340
351
  - Native entry requires fresh raw Enter within1500ms, trusted/idle TUI, no pending modal/messages and no child guard.
341
352
  Browser authentication grants access, not task consent; normal mutation ingress also requires independently built compatible client/service/assets identities.
342
353
  - Boot model choice is absent until explicitly selected.
@@ -503,6 +514,7 @@ Do not run the entire suite after every local edit, but do not replace final int
503
514
  `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.
504
515
  `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.
505
516
  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.
517
+ Keep that root's absolute path short: Chromium appends fixture and singleton-socket components, and an overly long path makes it abort before browser scenarios begin.
506
518
  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.
507
519
  Use fabricated credentials, isolated browser/profile/state roots and local fixtures; never personal credentials, automatic downloads or unknown process cleanup.
508
520
  Outer command budgets must cover whole suites and preserve original runner waits; existing SDK20s/child15s, PTY90s/interaction15s and browser120s/action15s fixture budgets are not increased to obtain green.
@@ -523,11 +535,17 @@ Regression owners:
523
535
  - `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.
524
536
  - `workspace-landing`: no-default-workspace startup, explicit root refusal and noncreating native/readonly entry.
525
537
  - `service-gateway` and `gateway-browser`: real same-origin project routing, browser-only authority, retirement on token rotation, continuing ordinary work and genuine multi-owner Stop; loopback and isolated HTTPS browser lanes.
538
+ - `gateway-recovery`: real crashed owners/resolvers, exact gateway-only release, preserved project fences and unfinished openings, retired/reopened associations, live/remote/changed/linked refusals, generated CLI diagnostics and native-TTY host/process/credential/storage acknowledgments.
526
539
 
527
540
  Regenerate operator/service/native/browser outputs only through their canonical builders; `--check` variants compare new private output without overwriting canonical bytes.
528
541
  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.
529
542
  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`.
530
- The browser is conversation-first: ordinary chat and explicit Cadence task conversations share its sidebar, while a lower-left dock observes actual Cadence child identities.
543
+ The browser is conversation-first, including before any project is selected: ordinary chat and explicit Cadence task conversations share its sidebar, while a lower-left dock observes actual Cadence child identities.
544
+ The empty entry has no project welcome screen or central Create shortcut; New conversation offers folder selection until a project is explicitly opened.
545
+ The bottom-right folder control is always present, and recent projects live inside its picker rather than in the conversation sidebar.
546
+ Browsing or dismissing the picker never opens a project or sends a message.
547
+ Open is available only for the successfully observed folder while its path input is unchanged; editing the path or refreshing its listing disables Open until Browse settles.
548
+ Browse always makes a fresh readonly observation, including retrying the same path after a read failure; it never replays Open.
531
549
  One service still owns one checkout.
532
550
  The recent-project directory records only explicit project openings in the same private namespace; navigation opens that project's independent authenticated entry rather than changing global cwd, importing Pi sessions or adopting another account's state.
533
551
  The center shows ordinary streamed messages or retained task text as Markdown.
@@ -542,6 +560,36 @@ Review the independently enumerated package member list and every emitted body,
542
560
  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.
543
561
  Preserve original license bytes through their precise Git attributes; line-ending normalization must not invalidate audited source/notice hashes.
544
562
 
563
+ ### Release 2.0.16 acceptance snapshot
564
+
565
+ The final code candidate passed all 2070 root tests in 86 files (27 platform skips on Linux), 133 web tests, all 9 real-browser scenarios and the 135-member exact real-pack audit.
566
+ Source/type/build/lint/Agent checks and a separate Bun pack/body audit also passed; this is separate-lane evidence, not a claim that one `verify` invocation was entirely green.
567
+ Observed environment: Linux x86_64, Node26.7.0, Bun1.3.14, Pi SDK0.84.3 and existing Chrome for Testing151.0.7922.34, with `VITEST_MAX_WORKERS=1` and unchanged test deadlines.
568
+ Review added failing-then-passing regressions for a successor lock borrowing a stopped predecessor's PID, unexpected foreground management exceptions being misclassified as exit78, and an accepted Stop abandoning its exact-epoch wait after an inconclusive registration read.
569
+ The stopped-row recovery bridge must belong to the exact current lock; Stop never resends its request or infers closure from a read failure or successor epoch.
570
+ The first full attempt had three pre-existing test deadlines and a workbench Stop with empty CLI output; isolated diagnostics passed the timeout files, and the original timing and empty-output causes remain unconfirmed.
571
+ After the independently reproduced Stop race was fixed, the second full attempt passed root/web checks but every browser launch hit Chromium's explicit singleton-socket path-length refusal because the private temporary root was too long.
572
+ Using a fresh short private root on the same disk, all browser scenarios and the pack audit passed without code changes, larger budgets, skipped tests or downloads.
573
+ Failure logs and isolated receipts remain outside the checkout; no consumer service was installed/restarted, uncertain state cleaned, paid provider used or new native Windows/macOS qualification claimed.
574
+
575
+ ### Workbench recovery review snapshot
576
+
577
+ The recovery review reproduces the still-reserved deleted-project failure, adds gateway-only native recovery (including legacy host attestation), and exercises restart/Stop after retiring old project associations without deleting references or project fences.
578
+ The new gateway recovery file passes 30 tests, including actual owner/resolver exits, concurrent resolvers, interrupted release, unsafe/changed resources, real generated startup refusal/restart/Stop and genuine POSIX TTY confirmation; global registry recovery passes 34 tests.
579
+ Full `verify` passed static/build/lint/Agent checks and reached 2064 passing root tests with 27 platform skips, but one unchanged `service-history-projection` dense-message test exceeded its 15s deadline.
580
+ Its complete 24-test file passed in a subsequent isolated diagnostic run (the affected case took 1.83s); the original timing cause is unconfirmed, not retrospectively successful.
581
+ After the final startup/Stop diagnostic wording correction, 121 targeted CLI/recovery tests, source/generated checks, 133 web tests, all 9 browser scenarios and the 135-member real-pack audit passed.
582
+ A separate Bun pack/body audit also passed; failures and isolated fixture receipts remain outside the checkout, with no increased test budgets, downloaded dependencies, consumer lock cleanup or service installation/restart.
583
+ Actual WSL shutdown, real service-manager installation and new native Windows/macOS recovery qualification were not executed; this is not a full green `verify` claim.
584
+
585
+ ### Conversation-first entry review snapshot
586
+
587
+ The project-less conversation shell and folder-picker review passed 133 web tests, all 9 real-browser scenarios and the 135-member real-pack audit, plus a separate Bun pack/body audit.
588
+ New loopback and HTTPS regressions refuse Open after an unbrowsed path edit, retry the same readonly directory observation after failure, and fence Open while a refresh is pending; the two original picker defects were observed failing before their fixes.
589
+ Full `verify` passed its static/build/lint/Agent checks but stopped in the root suite: 2033 tests passed, 27 platform tests skipped, and the unchanged `service-history-search-export` field-kind/snapshot case exceeded its 5s deadline.
590
+ That complete 16-test file passed in a subsequent isolated diagnostic run (the affected case took 1.16s); the original timing cause is unconfirmed, and this is not a full green `verify` claim.
591
+ Failed logs were retained outside the checkout; no deadline was increased, test skipped to obtain green, dependency downloaded or consumer service installed/restarted.
592
+
545
593
  ### Release 2.0.15 acceptance snapshot
546
594
 
547
595
  The release candidate passed the complete root suite: 2034 tests in 85 files, with 27 platform skips on Linux.
@@ -21,7 +21,8 @@ Defaults are loopback `127.0.0.1:17423`, normal Pi agent configuration, and deta
21
21
  An explicit port0 selects and persists a positive port once; restart uses the selected configuration, not a fallback endpoint.
22
22
  Missing/corrupt existing setup is preserved, not filled in or reset.
23
23
  Commands without `--workspace` address the unified workbench, which has no execution cwd and never creates `~/cadence`.
24
- Use `cadence workbench start|status|diagnose|stop|token-link|token-rotate` to name that target explicitly.
24
+ Use `cadence workbench start|status|diagnose|stop|token-link|token-rotate|recover inspect|recover resolve` to name that target explicitly.
25
+ `recover` without `--workspace` also addresses account/workbench state, never the current directory; project-owner recovery requires an explicit `--workspace`.
25
26
  Its start accepts `--foreground`, `--state-home`, `--agent-dir`, `--server-config` and a private `--config` containing an `HttpPolicy` object (the schema used below for project HTTP policy).
26
27
  The configuration's port/token come from the `.env`; a selected HTTP policy is retained across orderly restarts.
27
28
  A project root containing the private state home is refused, with no alternate directory creation; native `/cadence` also requires a real project selection.
@@ -91,6 +92,8 @@ A reinstall without `--workspace` explicitly selects the unified entry after ord
91
92
  A plain `restart` retains the definition's existing target, including an older explicit project service, rather than silently migrating it.
92
93
  The `.env` [port and token](#port-and-token) select the endpoint (17423 by default) and the token; an occupied endpoint fails instead of falling back.
93
94
  Install and restart print the private entry link once the owner answers; the JSON result on stdout never carries the token.
95
+ A successful manager start request is not owner readiness: an unready owner reports `quarantined` or `unconfirmed`, retains the separate `managerStatus`, exits nonzero and names the recovery inspection command.
96
+ Status also retains the manager's `active`/`activating`/`failed` observation separately when the workbench itself is quarantined.
94
97
  `restart` rewrites an existing definition with the current Node and package paths, keeping its workspace and the user's disable choices, and starts it; run it after an npm upgrade, a Node change or a `models.json` edit.
95
98
  `stop` joins the workbench's accepted openings and associated exact-epoch project owners, then closes the entry; unrelated project owners stay independent.
96
99
  If one associated owner refuses or has a changed epoch, Stop still reaches the other known owners serially, while the workbench remains fenced rather than reporting a successful Stop.
@@ -200,7 +203,10 @@ It carries `ConditionPathExists=` for the CLI; explicit single-project definitio
200
203
  `ExecStop` is the authenticated orderly Stop, so admitted work is stopped, not detached; `stop` and `uninstall` (`disable --now`) use it too.
201
204
  `KillMode=mixed` sends systemd's SIGTERM only to the management client.
202
205
  An owner still settling after `TimeoutStopSec=90` (for example a quarantined one) is SIGKILLed, which is not orderly Stop and can leave recovery fences.
203
- `Restart=on-failure` is bounded to three starts in five minutes; a restart never removes fences, so a crashed owner keeps refusing until informed recovery.
206
+ `Restart=on-failure` is bounded to three starts in five minutes.
207
+ Deterministic foreground startup refusals exit78 (`EX_CONFIG`), and newly generated units use `RestartPreventExitStatus=78` so these refusals do not loop.
208
+ Ordinary unexpected process failures retain the bounded restart policy; a restart never removes fences.
209
+ Existing generated units get this setting on the next successful explicit install/restart, not by package installation or recovery.
204
210
  Without linger, user units start at login and stop at logout through the same Stop.
205
211
  Linger starts the user manager, with every enabled user unit, at boot and keeps it running after logout.
206
212
  `status` reports `linger` as `on` or `off`, and Pi's startup card names `loginctl enable-linger` while an enabled unit is login-only; a disabled or masked unit (also `mask --runtime`) keeps it quiet.
@@ -296,11 +302,18 @@ Local fabricated-provider plain/gzip routing tests are not paid-provider, all-ad
296
302
  ## Informed local recovery
297
303
 
298
304
  ```sh
305
+ # Account/workbench recovery (no cwd or project required):
306
+ cadence workbench recover inspect
307
+ cadence workbench recover resolve
308
+ # A specific project owner:
299
309
  cadence recover inspect --workspace /absolute/project
300
310
  cadence recover resolve --workspace /absolute/project
301
311
  ```
302
312
 
303
- Inspection is readonly and handles the global registry lock before project-owner recovery.
313
+ Inspection is readonly and handles the global registry lock before the selected workbench or project-owner recovery.
314
+ Use the same account and state home as the service; workbench recovery also accepts `--server-config <path>` for a named or custom configuration.
315
+ A recovery refusal reports a fixed code/stage or `blocked-preserve-state`, never raw state/configuration bytes.
316
+ Do not delete state files or move to a different state home to bypass a refusal.
304
317
 
305
318
  ### Residual global registry lock
306
319
 
@@ -315,12 +328,16 @@ cadence recover resolve --workspace /absolute/project --state-home /absolute/sta
315
328
  ```
316
329
 
317
330
  With a registry lock present, inspection reports `scope: "global-registry"`, the exact lock-incarnation digest, holder PID/host, reservation count and any previous resolver's liveness.
318
- A project with no selected owner can be used to address this global operation; it still must be an existing suitable directory.
331
+ A project with no selected owner can be used to address this global operation; explicit `--workspace` must still name an existing suitable directory.
332
+ Alternatively, `cadence workbench recover inspect|resolve --state-home <path>` addresses the same global lock without any project directory or workbench registration, including from outside a project or when every old project directory is gone.
319
333
  Resolve requires the same four fresh local-terminal acknowledgments described below, with the displayed lock-incarnation digest instead of a service epoch.
320
334
  The holder must be on this host and its PID probe must specifically report absence; age, a remote host, permission denial or a reused live PID never authorizes release.
321
335
 
322
336
  Recovery validates the registry and checks that unreserved project selections have no remaining owner/checkout fences.
323
337
  A normally stopped project whose working directory was later removed is checked through its retained private metadata; recovery neither requires nor reconstructs that directory.
338
+ A still-reserved project whose working directory was removed also remains a valid retained reservation: canonical existing ancestors and overlap/endpoint constraints are checked without requiring the missing tail.
339
+ Its reservation, owner/checkout locks and history remain fenced and unchanged; releasing the global lock only restores access to independent projects.
340
+ Dangling/rebound aliases, permission errors and corrupt entries are not treated as absent directories.
324
341
  An interrupted reservation remains reserved even when its project initialization is incomplete: unrelated projects can reopen, but that project's uncertain resources are not repaired.
325
342
  An older release interrupted after removing a reservation but before releasing its project locks is unsafe and remains blocked, as do corrupt/partial registry bytes, linked files and changed resources.
326
343
  Inspection reports `blocked-preserve-state` when it cannot establish those conditions; preserve that state for manual diagnosis rather than deleting locks.
@@ -337,6 +354,42 @@ No Open/Start is replayed, no process is signaled, and no service is installed o
337
354
  After success, explicitly open the desired project again.
338
355
  If its own owner is still fenced, run `recover inspect` again and follow project-owner recovery separately; restoring global registry access does not certify or release that owner's work.
339
356
 
357
+ ### Workbench recovery
358
+
359
+ After a crash or failed Stop, an `alive`, `stopping` or `quarantined` registration may outlive its gateway process.
360
+ Do not keep restarting it or delete `workbenches-v1`; inspect the exact selection instead:
361
+
362
+ ```sh
363
+ cadence workbench recover inspect --server-config /absolute/cadence.env --state-home /absolute/state-home
364
+ cadence workbench recover resolve --server-config /absolute/cadence.env --state-home /absolute/state-home
365
+ ```
366
+
367
+ If the first inspection reports `scope: "global-registry"`, resolve that lock first, then inspect again for `scope: "workbench"`.
368
+ Prevent the supervisor from repeatedly launching the old process while recovering; use its original account/scope and inspect processes externally.
369
+ A live owner, a known remote host, permission-denied PID probe, changed bytes/incarnations, linked or partial state, or a live/ambiguous resolver refuses release.
370
+ PID absence alone never grants recovery; the same four fresh terminal acknowledgments bind the exact workbench epoch and selected resources.
371
+
372
+ New registrations record their hostname.
373
+ Older registrations did not: inspection explicitly reports `legacyHost: true`, `host: null` and `local-host-confirmation-required`, not proven same-host absence.
374
+ Resolve then additionally requires typing `local-host` after independently establishing that this state originated on this host and was not copied/shared from another host.
375
+ If that provenance cannot be established, do not confirm; preserve the state for manual diagnosis.
376
+ This attestation cannot override a recorded foreign hostname or a live/ambiguous local PID.
377
+ No `--yes`, piped or pasted answers, browser recovery or implicit takeover is supported.
378
+
379
+ Before release, an exclusive immutable receipt under `workbenches-v1/<configuration-hash>/recovery-v1/<lock-incarnation>/<attempt>.json` is flushed and closed.
380
+ The resolver then rechecks the resources, receipt, owner absence and fresh terminal authority, marks only the gateway registration stopped and releases only its exact gateway lock.
381
+ Concurrent resolvers cannot release a successor lock; a resolver that crashed after its receipt can be superseded only after its same-host PID is absent and another fresh confirmation.
382
+ A stopped registration is eligible only when its pinned recovery receipt belongs to that exact lock incarnation; an interrupted new startup cannot borrow the predecessor's dead PID to release its new fence.
383
+ Partial/corrupt receipts remain preserved; there are at most128attempts per lock incarnation, with no evidence eviction.
384
+
385
+ Recovery also acknowledges and retires the gateway's **exact old project associations**, recorded by their reference-file incarnations in that receipt.
386
+ It does not delete those recent-project references or settle/stop their owners: all project registrations, reservations, locks, opening receipts and history remain unchanged, including unknown outcomes and unfinished Open operations.
387
+ This prevents a deleted old project from immediately quarantining the recovered workbench again on its next Stop.
388
+ Old references require a new explicit **Open** before proxy access or whole-workbench Stop associates them again; merely restarting, viewing the recent list or recovering never replays an Open.
389
+ After recovery, explicitly start/restart the workbench.
390
+ Separately inspect/recover any still-fenced project owner before opening it.
391
+ The receipt is operator acknowledgment, not proof of native process closure or credential/storage correctness.
392
+
340
393
  ### Project-owner recovery
341
394
 
342
395
  With no registry lock present, inspection displays the epoch, exact coordination/history digests, owner PID/host observation and independent process/credential/storage uncertainty.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abelxiaoxing/cadence",
3
- "version": "2.0.15",
3
+ "version": "2.0.16",
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",
@@ -1,9 +1,11 @@
1
1
  import { randomBytes, randomUUID } from "node:crypto";
2
- import { opendirSync, realpathSync } from "node:fs";
2
+ import { opendirSync } from "node:fs";
3
3
  import { hostname } from "node:os";
4
4
  import { dirname, isAbsolute, join, relative, sep } from "node:path";
5
5
  import {
6
6
  type CheckoutLock,
7
+ canonicalAmbientRoot,
8
+ defaultStateHome,
7
9
  resolveWorkspace,
8
10
  TaskStore,
9
11
  } from "../task-store.ts";
@@ -245,19 +247,30 @@ export type CoordinationStatus =
245
247
  * task creation, stale reclaim or accepting execution route lives here.
246
248
  */
247
249
  export class ServiceCoordination {
248
- readonly store: TaskStore;
250
+ private readonly project?: TaskStore;
249
251
  readonly files: PrivateFiles;
250
- readonly directory: string;
251
252
  readonly registryDirectory: string;
252
253
  readonly stateHome: string;
253
- constructor(workspace: string, stateHome?: string) {
254
- this.store = new TaskStore(resolveWorkspace(workspace).root, stateHome);
255
- const namespace = dirname(this.store.privateRoot);
254
+ /** null addresses only account-wide registry recovery, never an implicit cwd. */
255
+ constructor(workspace: string | null, stateHome?: string) {
256
+ this.project =
257
+ workspace === null
258
+ ? undefined
259
+ : new TaskStore(resolveWorkspace(workspace).root, stateHome);
260
+ this.stateHome = this.project
261
+ ? dirname(dirname(this.project.privateRoot))
262
+ : canonicalAmbientRoot(stateHome ?? defaultStateHome());
263
+ const namespace = join(this.stateHome, "cadence-assistant-v1");
256
264
  this.files = new PrivateFiles(namespace);
257
- this.stateHome = dirname(namespace);
258
- this.directory = join(this.store.privateRoot, "service-v1");
259
265
  this.registryDirectory = join(namespace, "service-registry-v1");
260
266
  }
267
+ get store(): TaskStore {
268
+ if (!this.project) throw new ContractError("forbidden");
269
+ return this.project;
270
+ }
271
+ get directory(): string {
272
+ return join(this.store.privateRoot, "service-v1");
273
+ }
261
274
  path(
262
275
  name:
263
276
  | "format.json"
@@ -289,9 +302,12 @@ export class ServiceCoordination {
289
302
  const o = closed(json(snapshot, REGISTRY_BYTES), ["schema", "entries"]);
290
303
  const entries = list(o.entries, REGISTRY_ENTRIES, decodeReservation);
291
304
  for (const [i, row] of entries.entries()) {
305
+ // A missing consumer directory does not erase its reservation. Validate
306
+ // its canonical existing ancestors without recreating the missing tail;
307
+ // aliases (including dangling ones), permissions and overlaps still refuse.
292
308
  if (
293
309
  row.stateHome !== this.stateHome ||
294
- realpathSync(row.root) !== row.root
310
+ canonicalAmbientRoot(row.root) !== row.root
295
311
  )
296
312
  refused();
297
313
  // An endpoint cannot be shared by two supported owners, even for disjoint roots.
@@ -561,6 +577,7 @@ export class ServiceCoordination {
561
577
  * selected host-config snapshot); only then may the identity change. */
562
578
  reselectExisting?: (hostConfig: FileSnapshot) => void,
563
579
  ): OwnerLease {
580
+ void this.store; // An account-only recovery target cannot acquire an owner.
564
581
  const config = decodeOwnerConfig(input);
565
582
  const bound = endpoint(resolvedEndpoint);
566
583
  if (!endpointMatches(config.endpoint, bound))