@abelxiaoxing/cadence 2.0.3 → 2.0.5

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
@@ -3,12 +3,53 @@
3
3
  `@abelxiaoxing/cadence` is a shared Pi task workspace with two independently usable clients: native `/cadence` and a browser WebUI.
4
4
  One persistent service owns tasks, bounded subagent collaboration, real parent checks and retained history.
5
5
  Ordinary conversations, resource discovery and opening history never start work or a service.
6
- On Linux, Pi's user-scope install registers that service as a systemd user unit and starts its WebUI on the default address.
6
+ `cadence server install` registers that service with the user's service manager and starts its WebUI on the default address.
7
7
 
8
8
  This is a trusted workflow assistant, **not a sandbox, certified executor or transactional application system**.
9
9
  Code workers edit the chosen working copy directly; recovery can require human work.
10
10
  Model prose, tool results and human Done do not certify checks or code.
11
11
 
12
+ ## Install
13
+
14
+ Cadence needs Node `>=22.19.0` with npm and [Pi](https://www.npmjs.com/package/@earendil-works/pi-coding-agent), installed and logged in to a model provider: the service reads Pi's `auth.json` and `models.json` and never creates its own login.
15
+
16
+ ```sh
17
+ npm install -g @abelxiaoxing/cadence
18
+ cadence server install
19
+ cadence token-link --workspace ~/cadence --reveal
20
+ ```
21
+
22
+ The global install puts `cadence` (and its alias `cadence-service`) on `PATH`.
23
+ It needs no install script: the postinstall only creates a missing `~/.pi/agent/cadence/.env` research template, and npm may skip it (npm 12 blocks install scripts by default) without any other effect.
24
+ `cadence server install` registers and starts the background service for `~/cadence` on `http://127.0.0.1:17423`; `--workspace /absolute/project` selects another workspace.
25
+
26
+ | Platform | Service | Starts |
27
+ | -------- | ------------------------------------------------------------------------------------------- | -------------------------------------------- |
28
+ | Linux | systemd user unit `~/.local/share/systemd/user/cadence.service` | At boot through linger, else at login |
29
+ | macOS | LaunchAgent `~/Library/LaunchAgents/com.abelxiaoxing.cadence.plist` | At login |
30
+ | Windows | `HKCU\…\CurrentVersion\Run\Cadence`, starting `%LOCALAPPDATA%\cadence-server\cadence.vbs` | At login, without a console window |
31
+
32
+ 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.
33
+ 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.
34
+ 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.
35
+
36
+ ```sh
37
+ cadence server status
38
+ cadence server restart # after an upgrade or a models.json change
39
+ cadence server stop
40
+ cadence server uninstall # before npm uninstall -g, which runs no Cadence script
41
+ ```
42
+
43
+ Upgrade with `npm install -g @abelxiaoxing/cadence@latest`, then `cadence server restart`, which rewrites the service definition with the current Node and package paths.
44
+ 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.
45
+ `stop` keeps the registration, so the service starts again at the next boot or login.
46
+ Uninstall removes only the generated definition; workspace, history, linger and Pi sources stay.
47
+
48
+ Pi's own package install (`pi install npm:@abelxiaoxing/cadence`) still loads the native client, but its copy has no `cadence` on `PATH`; register the service with `node ~/.pi/agent/npm/node_modules/@abelxiaoxing/cadence/src/service-cli.mjs server install`, the command Pi's startup warning names while no service is registered.
49
+ Keep one copy: two copies drift apart on upgrade, and a native client refuses an owner whose service contract differs from its own.
50
+ A unit registered by an earlier version's Pi install hook keeps running; `cadence server restart` from the copy you keep rewrites it, and `pi update` no longer restarts it.
51
+ See [the background service](docs/service-management.md#background-service) for exact behavior and limits.
52
+
12
53
  ## Load and start explicitly
13
54
 
14
55
  The package can be loaded as a reviewed local package, extracted tarball or installed npm package through Pi's package mechanism.
@@ -25,31 +66,17 @@ Before using the shared owner, stop older Cadence sessions and choose one coordi
25
66
  No old records, databases, credentials or uncertain locks are imported, reset or cleaned.
26
67
 
27
68
  ```sh
28
- cadence-service start --workspace /absolute/project
29
- cadence-service status --workspace /absolute/project
30
- cadence-service token-link --workspace /absolute/project --reveal
69
+ cadence start --workspace /absolute/project
70
+ cadence status --workspace /absolute/project
71
+ cadence token-link --workspace /absolute/project --reveal
31
72
  ```
32
73
 
33
- Use the installed `cadence-service` bin, or `node /absolute/cadence/src/service-cli.mjs` followed by those arguments.
74
+ Use the installed `cadence` bin (or `cadence-service`), or `node /absolute/cadence/src/service-cli.mjs` followed by those arguments.
34
75
  Default binding is numeric loopback `127.0.0.1:17423`; `--foreground` is available for an explicitly configured supervisor.
35
76
  Start reads the existing Pi agent directory (or an explicit private configuration), never creates another login/profile.
36
77
  A directory that contains the private state home (the home directory and its ancestors, such as `C:\Users\<you>`) cannot be a workspace, so commands there use `~/cadence` instead of refusing; only `start` creates it, and `/cadence` started there attaches to that owner.
37
78
  See [service management](docs/service-management.md) for configuration, model/auth policy, Stop, diagnosis and informed recovery.
38
79
 
39
- ### Linux autostart after `pi install`
40
-
41
- `pi install npm:@abelxiaoxing/cadence` (user scope) writes `~/.local/share/systemd/user/cadence.service`, enables it and starts the owner for `~/cadence` on `http://127.0.0.1:17423`.
42
- Set an existing absolute `CADENCE_WORKSPACE` at install to choose another workspace, or `CADENCE_AUTOSTART=0` to skip registration.
43
- That first registration also enables linger when logind allows it without any authorization prompt, so the service starts at boot instead of at login; `CADENCE_LINGER=0` skips it, and later updates keep `loginctl disable-linger`.
44
- Enter the WebUI once with the link from `cadence-service token-link --workspace ~/cadence --reveal`; installation prints that command, not the token.
45
- Each `pi update` restarts the unit through orderly Stop, so running work is stopped rather than detached; `systemctl --user disable --now cadence` or `mask` is kept across upgrades.
46
- Run `cadence-service autostart remove` before `pi remove`; after `remove`, a later `pi update` registers the unit again as a first registration, so keep it off with `disable` or `mask` instead.
47
- The npm client hides dependency install-script output, and with `strict-allow-scripts` it skips those scripts entirely.
48
- Approve Cadence once with `npm install-scripts approve @abelxiaoxing/cadence` inside `~/.pi/agent/npm` (or `npm config set allow-scripts=@abelxiaoxing/cadence --location=user`), or run `cadence-service autostart install --linger`, which may ask for your password.
49
- Pi's session-start card names that command (without `--linger` under `CADENCE_LINGER=0`) while Pi's copy has no unit on a systemd machine, and `loginctl enable-linger` while the enabled unit only starts at login.
50
- Until the optional SDK and Pi's `auth.json` exist, registration or start waits instead of quarantining an owner.
51
- See [the systemd user unit](docs/service-management.md#linux-systemd-user-unit) for exact behavior and limits.
52
-
53
80
  ### Native terminal
54
81
 
55
82
  In a trusted, idle Pi TUI, type `/cadence [goal]` using fresh native Enter.
@@ -140,7 +167,8 @@ Outbound provider proxy policy is independent of inbound HTTPS and refuses unsup
140
167
  - `skills/git-commit` requires an explicit commit request and deterministic audit; task review never commits.
141
168
  - Three pinned professional profiles in `agents/` provide readonly design/diagnosis and direct-edit implementation instructions, not execution authority.
142
169
  - `config/` contains optional research/check examples.
143
- Missing-only user `.env` template creation and the Linux autostart refresh above are the only postinstall behaviors; no build/Git/provider operation runs.
170
+ Missing-only user `.env` template creation is the only postinstall behavior; it registers no service, and nothing depends on npm running it.
171
+ No build/Git/provider operation runs.
144
172
  - `node /absolute/cadence/src/operator-cli.mjs doctor|runs [workspace]` remains readonly and independent of service startup.
145
173
 
146
174
  ## Development and evidence
@@ -179,7 +207,7 @@ Historical logs are not runtime dependencies, and removing development tooling m
179
207
  | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- |
180
208
  | `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 |
181
209
  | `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 |
182
- | `src/service/host.ts`, `src/service/cli.ts`, `src/service/autostart.ts` | Real single-owner composition, explicit management and the generated Linux user unit; management entry stays SDK-free |
210
+ | `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 |
183
211
  | `src/service/{coordination,private-files,owner,recovery}.ts` | Canonical overlap exclusion, exact locks, original-operation lifetime and informed local recovery |
184
212
  | `src/service/{admission,task-actions,graph,scheduler}.ts` | Context-bound actions, actual current views, finite graph scheduling, check/review barriers |
185
213
  | `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 |
@@ -326,12 +354,14 @@ Byte values are UTF-8/encoded bounds where specified; they are not targets to si
326
354
  | Invalid read proof or changed source/key | Refuse; never rebase or interpret budget exhaustion as absence |
327
355
  | Dirty/partial/extra compiled assets or unsafe archive | Refuse in place; no source refill, wildcard serving or fallback build |
328
356
  | Extension import outside Pi aliases declared only as a peer | Exact production dependency; Pi's install omits peers and Pi fails to start |
329
- | Install without the optional SDK or Pi `auth.json` | No unit registration or start; Pi sources refuse unregistered, no SDK quarantines |
357
+ | Server install without the optional SDK or Pi `auth.json` | No SDK: nothing written; no `auth.json`: registered, not started (no quarantine) |
330
358
  | Package files hard-linked into a package manager's store (Bun) | Packaged reads ignore link count, bind exact bodies; other files stay single-link |
331
359
  | Host refuses during startup | Fixed code/stage only; unregistered exits `refused`, registered stays `quarantined` |
332
360
  | Workspace containing the private state home | Land on `~/cadence` (only start creates it); never move state into the working copy |
333
- | Upgrade changes the unit workspace or Node path | Stop with the loaded unit definition before rewriting, not only SIGTERM |
334
- | Install hook cannot prompt and npm hides its output | Only a first registration tries passwordless linger; the startup card names the gap |
361
+ | Restart changes the definition's workspace or Node path | Stop with the loaded definition before rewriting, not only a signal |
362
+ | npm skips or hides the install hook (`allow-scripts`) | Nothing depends on it; only `cadence server install` registers a service |
363
+ | Service manager that only signals (launchd, Windows login) | Orderly Stop first; an unconfirmed Stop changes nothing; no SIGTERM or `taskkill` |
364
+ | Pi's TUI replaces an info status with the next one | The missing-service hint is its own warning notification |
335
365
 
336
366
  Good: current context → explicit grant → durable acceptance → genuine original native/storage settlement → exact retained dependency → real checks → human review.
337
367
  Base: a client leaves and another reattaches to the same accepted run without launching it again.
@@ -365,7 +395,7 @@ Regression owners:
365
395
  - `agent-panel`, `agent-panel-model`, `agent-panel-cadence`: panel keys/rendering and readonly owner observation that never starts, cancels or replays work.
366
396
  - `test/web/`: adapted pure/SSR tests separately from actual built-browser contexts, HTTPS, mixed clients, focus/viewports and complete downloads.
367
397
  - `package-*`, `distribution`, `service-package`, `independence`: exact tar/member/body/import/notice closure and nonactivating discovery.
368
- - `service-autostart`, `install-config`: Pi user-scope gating, unit escaping, upgrade/disable/mask/foreign handling and orderly removal.
398
+ - `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 and a postinstall that registers nothing.
369
399
  - `workspace-landing`: home-directory redirection to `~/cadence` across management, native attach and observation.
370
400
 
371
401
  Regenerate operator/service/native/browser outputs only through their canonical builders; `--check` variants compare new private output without overwriting canonical bytes.
@@ -374,6 +404,19 @@ Review the independently enumerated package member list and every emitted body,
374
404
  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.
375
405
  Preserve original license bytes through their precise Git attributes; line-ending normalization must not invalidate audited source/notice hashes.
376
406
 
407
+ ### Release 2.0.5 acceptance snapshot
408
+
409
+ Full `verify` passed on the 2.0.5 release tree (`dce261b` plus the version bump): 1940 root tests in 78 files (27 Windows-only controls skipped on Linux), 111 web tests, 5 real-browser scenarios and the 133-member pack audit, in the 2.0.3 environment below.
410
+ On that host `cadence server status` read the live unit that 2.0.4's Pi install hook had registered as `active` and `enabled` with linger on, so earlier units stay manageable.
411
+ The launchd and Windows Run backends ran only against injected launchctl, reg.exe and orderly-Stop fakes; real macOS and Windows registration remains unqualified.
412
+
413
+ ### Release 2.0.4 acceptance snapshot
414
+
415
+ Full `verify` passed on `3987f65` (the `max` thinking change): 1925 root tests in 78 files, 111 web tests, 5 real-browser scenarios and the 132-member pack audit, in the 2.0.3 environment below.
416
+ On the same host, against a real Pi 1.4.0 user-scope install and a user-configured OpenAI-compatible provider, an owner model chosen in the WebUI ran a readonly research Start end to end: the child used `ls`/`find`/`read`, settled `completed`, published its summary and recorded hidden thinking as omitted.
417
+ A concurrent Pi TUI's agent panel followed that child from launch through its tool calls to `finished (ok=1)`.
418
+ A DeepSeek model whose `thinkingLevelMap` maps only `max` became selectable at `max` and its child made tool calls at that level; editing `models.json` during that run turned the owner's views `stale` until restart, as now documented.
419
+
377
420
  ### Release 2.0.3 acceptance snapshot
378
421
 
379
422
  Full `verify` passed on `c27b8ca`: 1924 root tests in 78 files (27 Windows-only controls skipped on Linux), 111 web tests in 8 files, 5 real-browser scenarios in 3 files and the 132-member real pack audit.
@@ -1,20 +1,20 @@
1
1
  # Explicit service management
2
2
 
3
- The native `/cadence` view and browser are clients of the same owner, started explicitly or by the [Linux systemd user unit](#linux-systemd-user-unit).
4
- Opening either view, extension discovery and login do not start it.
3
+ The native `/cadence` view and browser are clients of the same owner, started explicitly or by the [background service](#background-service).
4
+ Opening either view, extension discovery, login and package installation do not start it.
5
5
  Use one coordination state-home for the checkout; a different state-home is not a recovery route.
6
6
  Stop older Cadence sessions before starting the shared service.
7
7
 
8
8
  ```sh
9
- cadence-service start --workspace /absolute/project
10
- cadence-service status --workspace /absolute/project
11
- cadence-service diagnose --workspace /absolute/project
12
- cadence-service token-link --workspace /absolute/project --reveal
13
- cadence-service token-rotate --workspace /absolute/project
14
- cadence-service stop --workspace /absolute/project
9
+ cadence start --workspace /absolute/project
10
+ cadence status --workspace /absolute/project
11
+ cadence diagnose --workspace /absolute/project
12
+ cadence token-link --workspace /absolute/project --reveal
13
+ cadence token-rotate --workspace /absolute/project
14
+ cadence stop --workspace /absolute/project
15
15
  ```
16
16
 
17
- The equivalent package-relative command is `node <cadence>/src/service-cli.mjs ...`.
17
+ `cadence-service` is the same command, and the equivalent package-relative command is `node <cadence>/src/service-cli.mjs ...`.
18
18
  `--help` lists the finite interface.
19
19
  `start` also accepts `--foreground`, `--config`, `--agent-dir`, `--host` (loopback only), `--port`, `--token-file` and `--state-home`.
20
20
  Defaults are loopback `127.0.0.1:17423`, normal Pi agent configuration, and detached service lifetime.
@@ -23,55 +23,82 @@ 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
- Apart from the Linux user unit below, there is no supervisor installation, provider login or credential migration.
27
-
28
- ## Linux systemd user unit
29
-
30
- On Linux, Pi's user-scope install (`pi install npm:@abelxiaoxing/cadence`, npm in `<agent-dir>/npm`) and every `pi update` run the package postinstall, which calls `cadence-service autostart refresh`.
31
- Refresh acts only when the package is exactly Pi's managed user copy; checkouts, project (`-l`), temporary and global installs, other platforms and `CADENCE_AUTOSTART=0` are left alone.
32
-
33
- The first refresh writes `~/.local/share/systemd/user/cadence.service`, enables it for `default.target` and starts it.
34
- A user unit runs with the user manager, which starts at login unless logind lingers the user, so that first registration also asks logind to enable linger when it is off; `CADENCE_LINGER=0` at install skips it.
35
- It never prompts: it 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`.
36
- Where polkit requires authorization for `set-self-linger` (older systemd defaults), the unit stays login-only and the result reports `"linger":"refused"`.
37
- Later refreshes do not change linger, so `loginctl disable-linger` is kept across upgrades.
38
- The workspace is `~/cadence` (created if missing) or an existing absolute `CADENCE_WORKSPACE`, which lands on `~/cadence` with a note when it contains the unit's state home; later refreshes keep the unit's workspace unless that variable is set again.
39
- The unit runs `start --foreground --workspace` without `--port`, so a new workspace selects `http://127.0.0.1:17423`.
40
- A port saved by an earlier start of that workspace still wins, and an occupied endpoint fails the unit instead of falling back.
41
- It bakes the installer's PATH (minus npm lifecycle entries), LANG/LC_ALL, an absolute XDG_STATE_HOME and a custom PI_CODING_AGENT_DIR, so shell management sees the same state home and Pi sources.
42
- Proxy variables are not baked; add a validated pair with `systemctl --user edit cadence`.
43
- Browser entry still requires the explicitly revealed token link; installation prints the `token-link --reveal` command, never the token.
26
+ Apart from the background service below, there is no supervisor installation, provider login or credential migration.
44
27
 
45
- An upgrade refresh stops an active unit with its loaded definition, rewrites the file and starts it again.
46
- `ExecStop` is the authenticated orderly Stop, so admitted work is stopped, not detached.
47
- A disabled unit is refreshed but not re-enabled, and restarted only if it was running; a masked unit is never enabled or started; a unit file Cadence did not generate is left unchanged.
48
- `KillMode=mixed` sends systemd's SIGTERM only to the management client.
49
- An owner still settling after `TimeoutStopSec=90` (for example a quarantined one) is SIGKILLed, which is not orderly Stop and can leave recovery fences.
50
- `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.
28
+ ## Background service
29
+
30
+ ```sh
31
+ cadence server install [--workspace /absolute/project] [--no-linger]
32
+ cadence server status
33
+ cadence server restart
34
+ cadence server stop
35
+ cadence server uninstall
36
+ ```
37
+
38
+ Only these explicit commands register, change or remove the service; no package install, update hook, `/cadence` or browser visit does.
39
+ The package postinstall only creates a missing research `.env` template, so npm skipping it (`allow-scripts`, npm 12's default) changes nothing else.
40
+ `install` works from any package location and runs `start --foreground --workspace` (Linux, macOS) or `start --workspace` (Windows) from that copy with the installing Node, without `--port`.
41
+ 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.
42
+ A new workspace selects `http://127.0.0.1:17423`; a port saved by an earlier start of that workspace still wins, and an occupied endpoint fails instead of falling back.
43
+ Browser entry still requires the explicitly revealed token link; install prints the `token-link --reveal` command, never the token.
44
+ `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.
45
+ `stop` stops the owner orderly and keeps the registration for the next boot or login.
46
+ `uninstall` stops it orderly and deletes only what Cadence generated; workspace, state home, history, linger and Pi sources stay.
47
+ Because npm runs no uninstall script, uninstall the service before `npm uninstall -g`; a left-behind definition fails to start once its CLI path disappears.
48
+ A definition Cadence did not generate is left unchanged (`foreign`), and `status` reports `absent` when none is registered.
51
49
 
52
50
  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.
53
51
  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.
54
52
  Missing or unusable Pi sources refuse before an owner registers; a new owner without the SDK quarantines.
55
- Refresh therefore does not register the unit while the SDK is unresolvable (for example, optional dependencies omitted), and enables without starting it while `auth.json` is missing; the unit carries `ConditionPathExists=` for all three files.
56
- After logging in to Pi, start it with `systemctl --user start cadence`.
53
+ `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`.
54
+
55
+ 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.
56
+ It is a warning rather than part of the info card because Pi's TUI replaces one info status with the next.
57
+ `/cadence` names the same command when it finds no owner.
58
+ A machine without a supported user service manager and `CADENCE_AUTOSTART=0` keep both quiet; with `CADENCE_LINGER=0` they suggest `--no-linger`.
59
+
60
+ ### Linux systemd user unit
61
+
62
+ `install` writes `~/.local/share/systemd/user/cadence.service`, enables it for `default.target` (also undoing a `disable`, never a `mask`) and starts it.
63
+ 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.
64
+ 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`.
65
+ 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.
66
+ `--no-linger` or `CADENCE_LINGER=0` skips linger; `restart` never changes it, so `loginctl disable-linger` is kept.
67
+ The unit bakes the installer's PATH (minus npm lifecycle entries), LANG/LC_ALL, an absolute XDG_STATE_HOME and a custom PI_CODING_AGENT_DIR, so shell management sees the same state home and Pi sources.
68
+ Proxy variables are not baked; add a validated pair with `systemctl --user edit cadence`.
69
+ It carries `ConditionPathExists=` for the CLI, `auth.json` and both SDK packages.
70
+
71
+ `restart` stops an active unit with its loaded definition, rewrites the file and starts it, without enabling a disabled unit; a masked unit is never enabled or started.
72
+ `ExecStop` is the authenticated orderly Stop, so admitted work is stopped, not detached; `stop` and `uninstall` (`disable --now`) use it too.
73
+ `KillMode=mixed` sends systemd's SIGTERM only to the management client.
74
+ An owner still settling after `TimeoutStopSec=90` (for example a quarantined one) is SIGKILLed, which is not orderly Stop and can leave recovery fences.
75
+ `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.
57
76
  Without linger, user units start at login and stop at logout through the same Stop.
58
77
  Linger starts the user manager, with every enabled user unit, at boot and keeps it running after logout.
59
- Because npm hides the install guidance, Pi's session-start card names `autostart install --linger` while Pi's managed copy has no unit, and `loginctl enable-linger` while the enabled unit is login-only.
60
- A machine not booted with systemd (no `/run/systemd/system`), a disabled or masked unit (also `mask --runtime`) and `CADENCE_AUTOSTART=0` keep the card quiet; with `CADENCE_LINGER=0` in Pi's environment it never suggests linger and names `autostart install` without `--linger`.
78
+ `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.
79
+ To keep the service off while Cadence stays installed, use `systemctl --user disable --now cadence` or `mask`.
61
80
 
62
- ```sh
63
- cadence-service autostart install [--workspace /absolute/project] [--linger]
64
- cadence-service autostart status
65
- cadence-service autostart remove
66
- ```
81
+ ### macOS LaunchAgent
67
82
 
68
- `install` registers, enables and starts the unit from any package location unless it is masked.
69
- With `--linger` it also runs `loginctl enable-linger`, which may ask for your password; it asks only after the unit has started again, so an interrupted prompt never leaves the owner stopped.
70
- `status` reports `linger` as `on` or `off`.
71
- `remove` disables it through `ExecStop` and deletes only the generated file; workspace, state home, history and Pi sources stay.
72
- Because npm runs no uninstall script, run `remove` before `pi remove`; a left-behind unit skips start once its CLI path disappears.
73
- `remove` is for uninstalling: without the generated file the next `pi update` is a first registration again (unit and linger).
74
- To keep the service off while Cadence stays installed, use `systemctl --user disable --now cadence` or `mask`.
83
+ `install` writes `~/Library/LaunchAgents/com.abelxiaoxing.cadence.plist` with `RunAtLoad`, runs `launchctl enable` (undoing a `launchctl disable`) and loads it into `gui/<uid>` with `launchctl bootstrap`, which starts it; launchd loads it again at every login.
84
+ It bakes the same PATH, LANG/LC_ALL, XDG_STATE_HOME and PI_CODING_AGENT_DIR as the Linux unit and logs to `~/Library/Logs/cadence.log`.
85
+ Without `auth.json` the agent is written but not loaded.
86
+ Because launchd stops a job only by signaling it, `restart`, `stop`, `uninstall` and a reinstall first stop the owner through the authenticated orderly Stop, and change nothing when that Stop is `unconfirmed`, `quarantined` or `unavailable`.
87
+ Only then does Cadence unload (`bootout`) or replace the agent; `stop` leaves it loaded, and the finished job starts again at the next login.
88
+ There is no `KeepAlive`: launchd has no start limit, and a restart never clears fences, so a failed owner stays down until `cadence server restart`.
89
+ `restart` keeps a `launchctl disable` and reports `disabled` without loading.
90
+ At logout or shutdown launchd signals the job, which is not orderly Stop and can leave recovery fences; run `cadence server stop` first while work is running.
91
+
92
+ ### Windows login Run entry
93
+
94
+ `install` writes `%LOCALAPPDATA%\cadence-server\cadence.vbs`, beside rather than inside the private state home, and the per-user value `HKCU\Software\Microsoft\Windows\CurrentVersion\Run\Cadence`, which runs it with `wscript.exe` at login; no administrator rights are needed.
95
+ The launcher is UTF-16 with a byte-order mark, so non-ASCII paths work, and runs `start --workspace` with window style 0, so no console window appears; the detached owner keeps running after the launcher exits.
96
+ It bakes only a custom PI_CODING_AGENT_DIR, because the login environment already carries PATH and `%LOCALAPPDATA%`.
97
+ `install` also removes Task Manager's `StartupApproved` switch for the entry (undoing a disable there); `restart` keeps both the entry and that switch, and `status` reports a disabled entry.
98
+ Without `auth.json` the entry is registered but nothing starts.
99
+ Nothing supervises the detached owner, so a failed owner is not restarted, and every `restart`, `stop`, `uninstall` and reinstall first runs the authenticated orderly Stop and changes nothing unless it is confirmed; Cadence never kills a process.
100
+ A `Cadence` Run value without Cadence's launcher is `foreign` and left unchanged; registry values are only tested for presence, because `reg.exe` prints them in the console code page.
101
+ At sign-out or shutdown Windows ends the owner without orderly Stop, which can leave recovery fences; run `cadence server stop` first while work is running.
75
102
 
76
103
  ## Status, departure and Stop
77
104
 
@@ -93,6 +120,9 @@ To keep the service off while Cadence stays installed, use `systemctl --user dis
93
120
  Their mode bits are not checked: Pi creates its agent directory with your umask (0755, or 0775 under umask 002), and Cadence's own state stays 0700 regardless.
94
121
  `models.json` may use only fields the pinned SDK supports.
95
122
  `supportsMidConvoSystemMessages` from newer Pi is accepted because the pinned SDK has no system-role conversation message; other unknown fields refuse rather than being silently ignored.
123
+ The owner model choice offers every thinking level the pinned SDK supports for a model, `off` through `xhigh` and `max`; a model whose `thinkingLevelMap` maps only `max` (as some DeepSeek configurations do) is chosen at `max`, and a child runs only at exactly that level.
124
+ The owner loads `models.json` once at start: after it changes, the model catalog, the chosen model and the workspace view refuse as `stale` (credential changes in `auth.json` are read fresh).
125
+ Restart the service to reload it (`cadence server restart`, or `stop` then `start`), then choose the owner model again.
96
126
  - A foreground owner can be managed from another terminal.
97
127
  Ctrl-C, SIGTERM, machine crash or terminal destruction is **not** the authenticated orderly Stop route and can leave recovery fences.
98
128
  A recovered `stopped` registration means acknowledged fence release, not retroactive native closure certification.
@@ -135,8 +165,8 @@ Local fabricated-provider plain/gzip routing tests are not paid-provider, all-ad
135
165
  ## Informed local recovery
136
166
 
137
167
  ```sh
138
- cadence-service recover inspect --workspace /absolute/project
139
- cadence-service recover resolve --workspace /absolute/project
168
+ cadence recover inspect --workspace /absolute/project
169
+ cadence recover resolve --workspace /absolute/project
140
170
  ```
141
171
 
142
172
  Inspection is readonly.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abelxiaoxing/cadence",
3
- "version": "2.0.3",
3
+ "version": "2.0.5",
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",
@@ -20,6 +20,7 @@
20
20
  "src/agent-panel-cadence.ts",
21
21
  "src/agent-panel-model.ts",
22
22
  "src/agent-panel.ts",
23
+ "src/cadence-service.mjs",
23
24
  "src/index.ts",
24
25
  "src/install-config.mjs",
25
26
  "src/native-build.ts",
@@ -150,6 +151,7 @@
150
151
  "@earendil-works/pi-coding-agent": "0.84.3"
151
152
  },
152
153
  "bin": {
153
- "cadence-service": "./src/service-cli.mjs"
154
+ "cadence": "./src/service-cli.mjs",
155
+ "cadence-service": "./src/cadence-service.mjs"
154
156
  }
155
157
  }
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ // `cadence-service`, the earlier name of `cadence`. Its own file: `bun pm pack`
3
+ // writes a file once per bin name, and the package audit refuses duplicates.
4
+ await import("./service-cli.mjs");
package/src/index.ts CHANGED
@@ -26,10 +26,10 @@ export default function register(pi: ExtensionAPI): void {
26
26
  projectTrusted: ctx.isProjectTrusted(),
27
27
  });
28
28
  const shown = lines.length > 1 ? lines.slice(1) : lines;
29
- // npm hides the install hook's guidance; boot-start gaps surface here.
30
- const autostart = autostartNotice();
31
- if (autostart) shown.push(autostart);
32
29
  ctx.ui.notify(shown.join("\n"), "info");
30
+ // A later info status replaces this card in Pi's TUI; a warning stays.
31
+ const autostart = autostartNotice();
32
+ if (autostart) ctx.ui.notify(autostart, "warning");
33
33
  } catch {
34
34
  // Presentation only; shell lifecycle still runs.
35
35
  }
@@ -1,4 +1,3 @@
1
- import { spawnSync } from "node:child_process";
2
1
  import { closeSync, constants, mkdirSync, openSync, writeSync } from "node:fs";
3
2
  import { homedir } from "node:os";
4
3
  import path from "node:path";
@@ -41,22 +40,8 @@ export function ensureUserConfig(home = homedir()) {
41
40
  }
42
41
  }
43
42
 
44
- /** Pi install/update hook. The management client decides whether this is Pi's
45
- * Linux user-scope install and refreshes the systemd user unit; only its human
46
- * guidance (stderr) reaches the installer. */
47
- export function refreshAutostart(run = spawnSync) {
48
- if (process.platform !== "linux" || process.versions.bun) return;
49
- run(
50
- process.execPath,
51
- [
52
- fileURLToPath(new URL("./service-cli.mjs", import.meta.url)),
53
- "autostart",
54
- "refresh",
55
- ],
56
- { stdio: ["ignore", "ignore", "inherit"] },
57
- );
58
- }
59
-
43
+ // The only install hook behavior. npm may skip it (allow-scripts), so nothing
44
+ // else depends on it; services are registered by `cadence server install`.
60
45
  if (
61
46
  process.argv[1] &&
62
47
  path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url))
@@ -71,12 +56,4 @@ if (
71
56
  );
72
57
  process.exitCode = 0;
73
58
  }
74
- try {
75
- refreshAutostart();
76
- } catch (error) {
77
- process.stderr.write(
78
- `Cadence could not refresh its systemd user unit: ${error instanceof Error ? error.message : String(error)}\n`,
79
- );
80
- process.exitCode = 0;
81
- }
82
59
  }
@@ -2,5 +2,5 @@
2
2
  export const nativeBuild = {
3
3
  kind: "native",
4
4
  contractHash:
5
- "e7e5248f8273a373827ac70d2d8be1d5aaea1e329379e59e4947fc10f82be818",
5
+ "ff017feffc6b6b4521e470d82514d0ca008e15472fa1442ec498b243b0f7cf87",
6
6
  } as const;
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { existsSync, realpathSync } from "node:fs";
4
4
  import { homedir, userInfo } from "node:os";
5
- import { isAbsolute, join, resolve } from "node:path";
5
+ import { isAbsolute, join, resolve, sep } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { Agent, Dispatcher1Wrapper } from "undici";
8
8
  import { nativeBuild } from "./native-build.ts";
@@ -20,34 +20,59 @@ export const MANAGEMENT_GUIDANCE =
20
20
  "Cadence owner absent, unavailable or incompatible. Explicitly run cadence-service status --workspace <path>, cadence-service diagnose --workspace <path>, or cadence-service start --workspace <path>. Stop older Cadence sessions first. For retained fences use cadence-service recover inspect; resolve requires a local terminal and separate process, credential and storage acknowledgments. Never treat unavailable/quarantined as stopped. No service was started and no embedded executor is available.";
21
21
 
22
22
  const UNIT = "cadence.service";
23
+ const LABEL = "com.abelxiaoxing.cadence";
23
24
  /** Exists only when systemd booted the machine (sd_booted); without it there
24
- * is no user manager, and `autostart install` reports unavailable. */
25
+ * is no user manager, and `server install` reports unavailable. */
25
26
  const SYSTEMD_BOOTED = "/run/systemd/system";
26
27
  const xdg = (value: string | undefined, fallback: string) =>
27
28
  value && isAbsolute(value) ? value : fallback;
28
- const cliPath = () =>
29
- JSON.stringify(fileURLToPath(new URL("./service-cli.mjs", import.meta.url)));
30
- /** CADENCE_LINGER=0 keeps the explicit command login-only, too. */
31
- const installCommand = (env: NodeJS.ProcessEnv) =>
32
- `node ${cliPath()} autostart install${env.CADENCE_LINGER === "0" ? "" : " --linger"}`;
29
+ /** The definition `cadence server install` writes, or undefined without a
30
+ * supported user service manager. */
31
+ function definition(o: AutostartObservation): string | undefined {
32
+ switch (o.platform) {
33
+ case "linux":
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
+ )
41
+ : undefined;
42
+ case "darwin":
43
+ return join(o.home, "Library", "LaunchAgents", `${LABEL}.plist`);
44
+ case "win32":
45
+ return join(
46
+ xdg(o.env.LOCALAPPDATA, join(o.home, "AppData", "Local")),
47
+ "cadence-server",
48
+ "cadence.vbs",
49
+ );
50
+ default:
51
+ return undefined;
52
+ }
53
+ }
54
+ const bootStart = (o: AutostartObservation) =>
55
+ o.platform === "linux" && o.env.CADENCE_LINGER !== "0";
56
+ /** Pi's managed copy puts no `cadence` bin on PATH; CADENCE_LINGER=0 keeps
57
+ * the suggested command login-only, too. */
58
+ 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" : ""}`;
33
60
 
34
- /** Linux: npm may skip or hide the Pi install hook that registers the user
35
- * unit, so name the explicit command when it is absent. Reads two paths only. */
36
- export function autostartHint(): string {
37
- if (process.platform !== "linux" || !existsSync(SYSTEMD_BOOTED)) return "";
38
- const data = xdg(
39
- process.env.XDG_DATA_HOME,
40
- join(homedir(), ".local", "share"),
41
- );
42
- if (existsSync(join(data, "systemd", "user", UNIT))) return "";
43
- return `\nTo run the shared service and WebUI (default http://127.0.0.1:17423) ${process.env.CADENCE_LINGER === "0" ? "at login" : "at boot"}: ${installCommand(process.env)}`;
61
+ /** Name the explicit command while no background service is registered.
62
+ * Reads paths only. */
63
+ export function autostartHint(
64
+ o: AutostartObservation = hostObservation(),
65
+ ): string {
66
+ const path = definition(o);
67
+ if (!path || existsSync(path)) return "";
68
+ return `\nTo run the shared service and WebUI (default http://127.0.0.1:17423) ${bootStart(o) ? "at boot" : "at login"}: ${installCommand(o)}`;
44
69
  }
45
70
 
46
71
  export interface AutostartObservation {
47
72
  platform: NodeJS.Platform;
48
73
  env: NodeJS.ProcessEnv;
49
74
  home: string;
50
- /** Package root; only Pi's managed user copy registers itself. */
75
+ /** Package root; installed copies, not checkouts, get the startup notice. */
51
76
  root: string;
52
77
  /** logind keeps one flag file per lingering user name. */
53
78
  lingerFile: string;
@@ -95,26 +120,27 @@ function piUserCopy(o: AutostartObservation): boolean {
95
120
  return false;
96
121
  }
97
122
  }
98
- /** Startup-card line while boot start of the Linux user unit needs a human
99
- * step. Path observations only: nothing is created, spawned or started. A
100
- * machine without systemd, a disabled or (also runtime-)masked unit and
101
- * CADENCE_AUTOSTART=0 are quiet; CADENCE_LINGER=0 never suggests linger. */
123
+ /** An npm or Pi package copy; a checkout loaded with `pi -e` is explicit by design. */
124
+ function installedCopy(o: AutostartObservation): boolean {
125
+ return piUserCopy(o) || resolve(o.root).split(sep).includes("node_modules");
126
+ }
127
+ /** Startup-card warning while the background service needs a human step.
128
+ * Path observations only: nothing is created, spawned or started. A machine
129
+ * without a supported user service manager, a disabled or (also runtime-)
130
+ * masked Linux unit and CADENCE_AUTOSTART=0 are quiet; CADENCE_LINGER=0 never
131
+ * suggests linger. */
102
132
  export function autostartNotice(
103
133
  o: AutostartObservation = hostObservation(),
104
134
  ): string | undefined {
105
- if (
106
- o.platform !== "linux" ||
107
- o.env.CADENCE_AUTOSTART === "0" ||
108
- !existsSync(o.systemd)
109
- )
110
- return;
111
- const data = xdg(o.env.XDG_DATA_HOME, join(o.home, ".local", "share"));
112
- const config = xdg(o.env.XDG_CONFIG_HOME, join(o.home, ".config"));
113
- const boot = o.env.CADENCE_LINGER !== "0";
114
- if (!existsSync(join(data, "systemd", "user", UNIT)))
115
- return piUserCopy(o)
116
- ? `Cadence服务:未注册${boot ? "开机" : ""}自启(npm 可能跳过或隐藏了安装脚本)。注册并${boot ? "开机" : "登录后"}启动:${installCommand(o.env)}`
135
+ if (o.env.CADENCE_AUTOSTART === "0") return;
136
+ const path = definition(o);
137
+ if (!path) return;
138
+ if (!existsSync(path))
139
+ return installedCopy(o)
140
+ ? `Cadence服务:后台服务未安装,WebUI(默认 http://127.0.0.1:17423)不会运行。安装并${bootStart(o) ? "开机" : "登录后"}自启:${installCommand(o)}`
117
141
  : undefined;
142
+ if (o.platform !== "linux") return;
143
+ const config = xdg(o.env.XDG_CONFIG_HOME, join(o.home, ".config"));
118
144
  const user = join(config, "systemd", "user");
119
145
  const runtime = o.env.XDG_RUNTIME_DIR;
120
146
  if (
@@ -35,7 +35,7 @@ export interface ModelCandidate {
35
35
  model: string;
36
36
  api: ModelSelection["api"] | null;
37
37
  thinking: {
38
- level: ModelSelection["thinking"] | "max";
38
+ level: ModelSelection["thinking"];
39
39
  selectable: boolean;
40
40
  }[];
41
41
  eligibility: "candidate" | "unsupported" | "auth-unavailable";
@@ -93,7 +93,7 @@ export function decodeModelCandidate(value: unknown): ModelCandidate {
93
93
  thinking: list(o.thinking, 7, (v) => {
94
94
  const t = closed(v, ["level", "selectable"]);
95
95
  return {
96
- level: oneOf(t.level, [...thinkingLevels, "max"] as const),
96
+ level: oneOf(t.level, thinkingLevels),
97
97
  selectable: bool(t.selectable),
98
98
  };
99
99
  }),
@@ -109,9 +109,7 @@ export function decodeModelCandidate(value: unknown): ModelCandidate {
109
109
  candidate.thinking.some(
110
110
  (t) =>
111
111
  t.selectable &&
112
- (t.level === "max" ||
113
- candidate.api === null ||
114
- candidate.eligibility !== "candidate"),
112
+ (candidate.api === null || candidate.eligibility !== "candidate"),
115
113
  ) ||
116
114
  (candidate.eligibility === "candidate" &&
117
115
  !candidate.thinking.some((t) => t.selectable))