@abelxiaoxing/cadence 2.0.4 → 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 +61 -25
- package/docs/service-management.md +79 -52
- package/package.json +4 -2
- package/src/cadence-service.mjs +4 -0
- package/src/index.ts +3 -3
- package/src/install-config.mjs +2 -25
- package/src/native-client.ts +60 -34
- package/src/service-cli.mjs +511 -111
- package/src/service-host.mjs +1 -2
- package/web/dist/assets/cadence.js +1 -1
- package/web/dist/manifest.json +3 -3
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
|
-
|
|
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
|
|
29
|
-
cadence
|
|
30
|
-
cadence
|
|
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`
|
|
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
|
|
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`
|
|
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
|
-
|
|
|
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
|
-
|
|
|
334
|
-
|
|
|
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`:
|
|
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,12 @@ 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
|
+
|
|
377
413
|
### Release 2.0.4 acceptance snapshot
|
|
378
414
|
|
|
379
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.
|
|
@@ -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 [
|
|
4
|
-
Opening either view, extension discovery and
|
|
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
|
|
10
|
-
cadence
|
|
11
|
-
cadence
|
|
12
|
-
cadence
|
|
13
|
-
cadence
|
|
14
|
-
cadence
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
56
|
-
|
|
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
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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`
|
|
69
|
-
|
|
70
|
-
`
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
|
|
@@ -95,7 +122,7 @@ To keep the service off while Cadence stays installed, use `systemctl --user dis
|
|
|
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.
|
|
96
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.
|
|
97
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).
|
|
98
|
-
Restart the service to reload it (`
|
|
125
|
+
Restart the service to reload it (`cadence server restart`, or `stop` then `start`), then choose the owner model again.
|
|
99
126
|
- A foreground owner can be managed from another terminal.
|
|
100
127
|
Ctrl-C, SIGTERM, machine crash or terminal destruction is **not** the authenticated orderly Stop route and can leave recovery fences.
|
|
101
128
|
A recovered `stopped` registration means acknowledged fence release, not retroactive native closure certification.
|
|
@@ -138,8 +165,8 @@ Local fabricated-provider plain/gzip routing tests are not paid-provider, all-ad
|
|
|
138
165
|
## Informed local recovery
|
|
139
166
|
|
|
140
167
|
```sh
|
|
141
|
-
cadence
|
|
142
|
-
cadence
|
|
168
|
+
cadence recover inspect --workspace /absolute/project
|
|
169
|
+
cadence recover resolve --workspace /absolute/project
|
|
143
170
|
```
|
|
144
171
|
|
|
145
172
|
Inspection is readonly.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abelxiaoxing/cadence",
|
|
3
|
-
"version": "2.0.
|
|
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
|
|
154
|
+
"cadence": "./src/service-cli.mjs",
|
|
155
|
+
"cadence-service": "./src/cadence-service.mjs"
|
|
154
156
|
}
|
|
155
157
|
}
|
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
|
}
|
package/src/install-config.mjs
CHANGED
|
@@ -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
|
-
|
|
45
|
-
|
|
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
|
}
|
package/src/native-client.ts
CHANGED
|
@@ -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 `
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
/**
|
|
35
|
-
*
|
|
36
|
-
export function autostartHint(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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;
|
|
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
|
-
/**
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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 (
|