@phnx-labs/agents-cli 1.20.92 → 1.21.0

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.
Files changed (146) hide show
  1. package/CHANGELOG.md +385 -0
  2. package/README.md +3 -0
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/beta.js +3 -5
  5. package/dist/commands/doctor.js +23 -0
  6. package/dist/commands/events.js +91 -1
  7. package/dist/commands/exec.js +13 -0
  8. package/dist/commands/mailboxes.js +39 -1
  9. package/dist/commands/message.js +12 -1
  10. package/dist/commands/packages.js +14 -5
  11. package/dist/commands/projects.d.ts +38 -0
  12. package/dist/commands/projects.js +355 -48
  13. package/dist/commands/routines.js +27 -1
  14. package/dist/commands/rules.js +6 -0
  15. package/dist/commands/secrets.d.ts +17 -0
  16. package/dist/commands/secrets.js +198 -7
  17. package/dist/commands/send.d.ts +14 -12
  18. package/dist/commands/send.js +105 -35
  19. package/dist/commands/sessions.d.ts +17 -0
  20. package/dist/commands/sessions.js +16 -8
  21. package/dist/commands/setup-browser.js +5 -1
  22. package/dist/commands/setup-fleet.js +5 -0
  23. package/dist/commands/setup-preferences.d.ts +53 -0
  24. package/dist/commands/setup-preferences.js +142 -0
  25. package/dist/commands/setup.js +8 -9
  26. package/dist/commands/ssh.js +220 -10
  27. package/dist/commands/sync.d.ts +2 -2
  28. package/dist/commands/sync.js +13 -12
  29. package/dist/commands/view.js +4 -0
  30. package/dist/commands/watchdog.js +26 -3
  31. package/dist/index.js +38 -6
  32. package/dist/lib/activity.d.ts +15 -0
  33. package/dist/lib/activity.js +26 -2
  34. package/dist/lib/auto-dispatch.d.ts +6 -1
  35. package/dist/lib/auto-dispatch.js +7 -2
  36. package/dist/lib/beta.d.ts +1 -1
  37. package/dist/lib/beta.js +1 -1
  38. package/dist/lib/channels/send.d.ts +83 -0
  39. package/dist/lib/channels/send.js +112 -0
  40. package/dist/lib/daemon.d.ts +13 -0
  41. package/dist/lib/daemon.js +80 -60
  42. package/dist/lib/device-config.d.ts +82 -0
  43. package/dist/lib/device-config.js +296 -0
  44. package/dist/lib/event-provenance.d.ts +19 -0
  45. package/dist/lib/event-provenance.js +48 -0
  46. package/dist/lib/events-ingest.d.ts +46 -0
  47. package/dist/lib/events-ingest.js +182 -0
  48. package/dist/lib/events.d.ts +17 -5
  49. package/dist/lib/events.js +59 -56
  50. package/dist/lib/feed.d.ts +1 -1
  51. package/dist/lib/feed.js +2 -0
  52. package/dist/lib/git.d.ts +13 -2
  53. package/dist/lib/git.js +38 -6
  54. package/dist/lib/linear-project-counts.d.ts +129 -0
  55. package/dist/lib/linear-project-counts.js +206 -0
  56. package/dist/lib/linear-projects.d.ts +62 -0
  57. package/dist/lib/linear-projects.js +144 -0
  58. package/dist/lib/mailbox-gc.js +30 -7
  59. package/dist/lib/mailbox.d.ts +14 -1
  60. package/dist/lib/mailbox.js +35 -3
  61. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  62. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  63. package/dist/lib/menubar/install-menubar.d.ts +5 -21
  64. package/dist/lib/menubar/install-menubar.js +35 -44
  65. package/dist/lib/menubar/notify-desktop.d.ts +17 -2
  66. package/dist/lib/menubar/notify-desktop.js +8 -2
  67. package/dist/lib/migrate.js +1 -1
  68. package/dist/lib/models.js +21 -11
  69. package/dist/lib/overdue.d.ts +1 -1
  70. package/dist/lib/overdue.js +54 -19
  71. package/dist/lib/project-import.d.ts +96 -0
  72. package/dist/lib/project-import.js +171 -0
  73. package/dist/lib/project-probe.d.ts +75 -0
  74. package/dist/lib/project-probe.js +160 -0
  75. package/dist/lib/project-resources.d.ts +8 -0
  76. package/dist/lib/project-resources.js +31 -3
  77. package/dist/lib/project-status.d.ts +32 -1
  78. package/dist/lib/project-status.js +82 -1
  79. package/dist/lib/projects.d.ts +6 -0
  80. package/dist/lib/projects.js +12 -0
  81. package/dist/lib/remote-agents-json.d.ts +14 -1
  82. package/dist/lib/remote-agents-json.js +21 -3
  83. package/dist/lib/routine-notify.d.ts +11 -0
  84. package/dist/lib/routine-notify.js +22 -0
  85. package/dist/lib/routines.d.ts +53 -0
  86. package/dist/lib/routines.js +105 -4
  87. package/dist/lib/rules/run-sync.d.ts +18 -0
  88. package/dist/lib/rules/run-sync.js +92 -0
  89. package/dist/lib/run-notify.js +3 -0
  90. package/dist/lib/scheduler.js +12 -2
  91. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  92. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  93. package/dist/lib/secrets/audit.d.ts +1 -1
  94. package/dist/lib/secrets/audit.js +53 -10
  95. package/dist/lib/secrets/index.d.ts +4 -5
  96. package/dist/lib/secrets/index.js +4 -5
  97. package/dist/lib/secrets/list-filter.d.ts +20 -5
  98. package/dist/lib/secrets/list-filter.js +22 -6
  99. package/dist/lib/secrets/usage-db.d.ts +106 -0
  100. package/dist/lib/secrets/usage-db.js +236 -0
  101. package/dist/lib/session/active.d.ts +16 -2
  102. package/dist/lib/session/active.js +8 -4
  103. package/dist/lib/session/bundle.js +5 -1
  104. package/dist/lib/session/remote-active.d.ts +5 -1
  105. package/dist/lib/session/remote-active.js +4 -1
  106. package/dist/lib/session/remote-list.d.ts +1 -15
  107. package/dist/lib/session/remote-list.js +22 -111
  108. package/dist/lib/session/sync/config.d.ts +13 -5
  109. package/dist/lib/session/sync/config.js +21 -13
  110. package/dist/lib/sqlite.js +28 -1
  111. package/dist/lib/startup/command-registry.d.ts +0 -2
  112. package/dist/lib/startup/command-registry.js +1 -5
  113. package/dist/lib/state.d.ts +19 -2
  114. package/dist/lib/state.js +99 -10
  115. package/dist/lib/sync-umbrella.d.ts +11 -23
  116. package/dist/lib/sync-umbrella.js +14 -45
  117. package/dist/lib/teams/agents.d.ts +35 -3
  118. package/dist/lib/teams/agents.js +52 -8
  119. package/dist/lib/teams/scheduler.d.ts +29 -2
  120. package/dist/lib/teams/scheduler.js +60 -16
  121. package/dist/lib/types.d.ts +22 -5
  122. package/dist/lib/versions.d.ts +6 -0
  123. package/dist/lib/versions.js +6 -4
  124. package/package.json +1 -2
  125. package/dist/commands/drive.d.ts +0 -10
  126. package/dist/commands/drive.js +0 -183
  127. package/dist/commands/hq.d.ts +0 -2
  128. package/dist/commands/hq.js +0 -58
  129. package/dist/commands/sessions-sync.d.ts +0 -17
  130. package/dist/commands/sessions-sync.js +0 -135
  131. package/dist/commands/sync-provision.d.ts +0 -23
  132. package/dist/commands/sync-provision.js +0 -107
  133. package/dist/lib/drive-sync.d.ts +0 -45
  134. package/dist/lib/drive-sync.js +0 -238
  135. package/dist/lib/hq/floor.d.ts +0 -87
  136. package/dist/lib/hq/floor.js +0 -243
  137. package/dist/lib/session/sync/crdt.d.ts +0 -44
  138. package/dist/lib/session/sync/crdt.js +0 -119
  139. package/dist/lib/session/sync/manifest.d.ts +0 -62
  140. package/dist/lib/session/sync/manifest.js +0 -100
  141. package/dist/lib/session/sync/provision.d.ts +0 -49
  142. package/dist/lib/session/sync/provision.js +0 -91
  143. package/dist/lib/session/sync/r2.d.ts +0 -32
  144. package/dist/lib/session/sync/r2.js +0 -121
  145. package/dist/lib/session/sync/sync.d.ts +0 -106
  146. package/dist/lib/session/sync/sync.js +0 -374
package/CHANGELOG.md CHANGED
@@ -1,5 +1,390 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.21.0
4
+
5
+ - **A clone of your own DotAgents repo no longer hijacks project-layer rule resolution (RUSH-2037).**
6
+ Cloning `~/.agents` to the canonical `~/src/github.com/<you>/.agents` path (to edit
7
+ rules in an editor) made that checkout eligible as a *project* layer whenever you
8
+ worked from its parent directory. Because project outranks user, a stale clone's
9
+ `rules/subrules/*` then silently shadowed the live user rules by filename, and the
10
+ compile planted an out-of-date `AGENTS.md` in an ancestor dir that every session
11
+ beneath it ingested. Project-layer discovery now identifies a DotAgents repo by
12
+ **repo identity** (git origin), not path: a `.agents/` that is itself a git checkout
13
+ whose origin matches the user's or system's DotAgents repo is skipped, so the live
14
+ user layer wins. Legitimate project `.agents/` layers (a plain subdirectory of a
15
+ project, or a git repo with an unrelated origin) are unaffected.
16
+ Source: `apps/cli/src/lib/state.ts`.
17
+
18
+ - **`agents sessions --local` no longer dials remote-host teammates over ssh,
19
+ in the default listing or `--active` (RUSH-2118).** `--local` is supposed to
20
+ mean this-machine-only, but the underlying `AgentManager` poll still fired a
21
+ real ssh round-trip for every teammate dispatched via
22
+ `agents teams add --device` — even a teammate that had already finished.
23
+ On a box with 30 completed remote-host teammates that measured out to 180
24
+ real `ssh` execve calls (6 per teammate: two ssh calls in
25
+ `syncRemoteMirror`, run three times per poll) and a ~4.3s
26
+ `--active --local` call. A `--local` query now reads a remote-host
27
+ teammate's last-persisted `meta.json` state instead of dialing it, and a
28
+ teammate that has already reached a terminal status (completed/failed/
29
+ stopped) is never re-dialed by ANY `--active` query, local or not — its
30
+ final log bytes and exit code were already captured on the poll that
31
+ resolved it. The same gate now covers every `--local` surface: the bare
32
+ default listing's live-glyph enrichment (`maybeLiveIndex`) and `--preview`
33
+ (`renderSessionPreview`, freely combinable with `--local`) both called the
34
+ local-only `getActiveSessions()` with no `localOnly` threaded through,
35
+ despite the `--local` help text already promising this-machine-only for
36
+ all of them. Source: `apps/cli/src/lib/teams/agents.ts`
37
+ (`syncRemoteMirror`, `readNewEvents`, `updateStatusFromProcess`,
38
+ `AgentManager`), `apps/cli/src/lib/session/active.ts` (`listTeamsActive`,
39
+ `getActiveSessions`), `apps/cli/src/commands/sessions.ts`
40
+ (`gatherActiveSessions`, `maybeLiveIndex`, `renderSessionPreview`).
41
+
42
+ - **A rules preset now applies at `agents run` time, not only after `agents
43
+ rules switch` (RUSH-2128).** `setActiveRulesPreset` used to take effect only on the next
44
+ explicit `agents rules switch`/`agents add`/`agents use` — a preset change
45
+ made any other way left the harness launching against a stale rules file
46
+ until someone remembered to re-sync. `agents run` now re-applies the active
47
+ preset for the resolved agent+version immediately before dispatch, every
48
+ time, with a skip-fast sentinel so an unchanged preset costs no recompose or
49
+ rewrite. Version-scoped only; per-model preset scoping is a follow-up.
50
+ Source: `apps/cli/src/lib/rules/run-sync.ts`, `apps/cli/src/commands/exec.ts`.
51
+
52
+ - **Activity events now carry the same actor and session lineage as operational events.**
53
+ The TypeScript activity writer and the embedded PostToolUse hook stamp actor kind,
54
+ launch id, and parent session id from the shared execution provenance floor, so
55
+ `agents events` no longer invents an agent name as the activity record's OS user.
56
+ Source: `apps/cli/src/lib/event-provenance.ts`, `apps/cli/src/lib/activity.ts`.
57
+
58
+ - **`agents view` no longer re-scans every installed Claude binary on each run.**
59
+ When a Claude model extractor produced zero models (a broken regex, or a
60
+ mid-install CLI), the result was never cached — so `getModelCatalog` re-ran a
61
+ full `readFileSync` scan of the 230-270MB Claude binary for every affected
62
+ installed version, on every invocation (~1.85s each). With 4 affected
63
+ versions installed, that was ~7.5s added to every `agents view`. A 0-model
64
+ extraction is now cached too, stamped with when it was attempted, and served
65
+ for 24 hours before self-healing by retrying; an upgrade/reinstall (a new
66
+ source mtime) still re-extracts immediately, as before. Measured on a real
67
+ install with 7 Claude versions (4 of them hitting the broken extractor): the
68
+ cold first-call cost (~12.5s, unavoidable) drops to ~1-2ms on every
69
+ subsequent call. Source: `apps/cli/src/lib/models.ts`.
70
+
71
+ - **Per-device and fleet-wide config keys now have a home: the `config:` block in
72
+ the two-tier agents.yaml store.** Three new subcommands under `agents devices`
73
+ (no new top-level noun): `agents devices set-interactive <name>` records the one
74
+ device agents show YOU artifacts on (browser opens, dashboards) as
75
+ `config.interactiveHost` in the central, synced agents.yaml — skills no longer
76
+ guess "the online macOS box", and the host is marked `★ interactive` in
77
+ `agents devices list`. `agents devices configure <name> --max-agents N
78
+ --scheduler on|off` and `agents devices note <name> "…"` (repeat
79
+ to append, `--clear` to empty) write device-scope keys under `config:` in
80
+ `~/.agents/devices/<name>/agents.yaml` — targetable for any device from any box
81
+ (the devices/ tree syncs; each machine reads only its own). The default browser
82
+ profile joins the same registry as `browser.profile`, routed to the existing
83
+ device-local `defaultBrowserProfile` field (no duplicate key, resolution order
84
+ unchanged). Unset keys always mean today's behavior; everything is scriptable
85
+ with `--json`, and `devices list --json` now carries each row's `config` and an
86
+ `interactive` flag. agents.yaml files the CLI writes now carry a
87
+ `yaml-language-server` hint pointing at the new
88
+ `apps/cli/schema/agents-yaml.schema.json`.
89
+
90
+ The keys are live inputs, not just stored values. `--scheduler off` stops the
91
+ routines scheduler from starting on that device — `routines add` skips the
92
+ auto-start with the reason, a manual `routines start` refuses, and the daemon
93
+ re-evaluates the gate on every SIGHUP reload (boot it again with
94
+ `agents devices configure <host> --scheduler on` + any reload, no daemon
95
+ restart). `--max-agents` feeds host ranking: Factory auto-launch excludes a
96
+ device at its cap (counting device-wide running agents) and names the cap when
97
+ a pool is exhausted; teams placement excludes it from the least-loaded
98
+ auto-pick (counting the team's own roster, local teammates included) and an
99
+ all-capped pool fails loud instead of over-filling a machine. Setup asks
100
+ instead of guessing: bare `agents setup` ends with a skippable preferences
101
+ step (which machine you sit at → interactive host; which browser agents drive
102
+ here → device default), `agents setup fleet` offers the interactive host after
103
+ a sync, and the `agents setup browser` picker highlights the auto-detect
104
+ winner. Source: `apps/cli/src/lib/device-config.ts`,
105
+ `apps/cli/src/lib/state.ts`, `apps/cli/src/lib/daemon.ts`,
106
+ `apps/cli/src/lib/teams/scheduler.ts`, `apps/cli/src/commands/ssh.ts`,
107
+ `apps/cli/src/commands/setup-preferences.ts`,
108
+ `apps/factory/src/core/launchHost.ts`,
109
+ `apps/cli/schema/agents-yaml.schema.json`.
110
+
111
+ - **Mailbox messages now expire and dead boxes are reaped automatically.** Messages
112
+ enqueued without an explicit TTL used to sit in the spool forever, so pending mail
113
+ would outlive the session that needed it. They now get a 24-hour default TTL
114
+ (`AGENTS_MAILBOX_TTL` overrides the default; `agents message … --ttl 2h` sets it
115
+ per message). When a message expires, a live-but-idle box archives it with a
116
+ `dropped: expired` receipt. The watchdog tick also runs a liveness sweep using the
117
+ same live-session set as `agents sessions --active`, archiving pending mail in dead
118
+ boxes as `dropped: dead` and pruning stale consumed entries. Dropped messages tied
119
+ to a feed block surface a failure receipt (`status: dropped` / `expired`) so the
120
+ sender sees the bounce instead of silence. Run the sweep manually with
121
+ `agents mailboxes gc` (`--json` supported). Source:
122
+ `apps/cli/src/lib/mailbox.ts`, `apps/cli/src/lib/mailbox-gc.ts`,
123
+ `apps/cli/src/commands/message.ts`, `apps/cli/src/commands/mailboxes.ts`,
124
+ `apps/cli/src/commands/watchdog.ts`, `apps/cli/src/lib/feed.ts`.
125
+
126
+ - **The menu-bar helper can no longer leak CLI processes until the machine is unusable.**
127
+ Its poll shelled `agents doctor --json` through an unbounded `Process` +
128
+ `readDataToEndOfFile()`. Two properties composed badly: the call had no deadline
129
+ (`doctor --json` measures **136s on an idle box**, against a 60s poll interval), and a
130
+ helper that died mid-call left the child reparented to launchd (PPID 1) with nothing
131
+ to reap it — along with the `node -e` version probes that child had forked. Both fire
132
+ together, because the helper crashes under exactly the conditions that make the CLI
133
+ slow: `NSApplication.shared` segfaults inside `SLSNewConnection` when WindowServer is
134
+ too starved to hand out a connection, launchd's `KeepAlive` restarts it, and the
135
+ restart spawns a new doctor while the old one keeps burning a core. Observed on a real
136
+ machine: 38 orphaned doctors + 92 orphaned probes, ~13 of 18 cores consumed, load
137
+ average 490, keystrokes visibly lagging.
138
+ The crash itself cannot be prevented from inside the app — it is AppKit dereferencing
139
+ a null connection before any of our code runs — so a crash no longer costs anything
140
+ permanent: every child carries a deadline (30s; 180s for `doctor --json`, above its
141
+ real measured cost); it is spawned as its own process-group leader so a timeout kills
142
+ the whole subtree rather than just the CLI; and each live child is recorded on disk so
143
+ the *next* launch reaps whatever a crash abandoned (no exit handler runs on SIGSEGV).
144
+ The doctor refresh also drops from every 60s to every 15 minutes, and the launchd job
145
+ gains `ThrottleInterval` 30 so a startup crash-loop cannot respawn every 10s.
146
+ A poll that blows its deadline now shows a stale menu instead of taking the machine
147
+ down with it. Source: `apps/cli/menubar/Sources/MenubarHelper/ChildProcess.swift`,
148
+ `AgentsCLI.swift`, `StatusItemController.swift`, `main.swift`,
149
+ `apps/cli/src/lib/menubar/install-menubar.ts`.
150
+
151
+ - **The macOS menu-bar helper is now notarized, ending the "app is damaged"
152
+ dialog and the per-run `no valid code signature; skipping launch` spam
153
+ (RUSH-2134).** The helper shipped Developer-ID signed but *not* notarized, so
154
+ Gatekeeper on macOS 26+ rejected it as damaged and the install path tried to
155
+ heal it by re-signing ad-hoc on every `agents` invocation — which can never
156
+ satisfy Gatekeeper, so the dialog and the noise persisted. The release now
157
+ notarizes + staples the helper (`menubar/scripts/build.sh`, mandatory for any
158
+ Developer-ID build, run under the release's `agents secrets exec apple.com`
159
+ context), the `prepack` gate refuses to pack an un-notarized bundle
160
+ (`scripts/verify-menubar-helper.sh` now requires a stapled ticket), and the
161
+ runtime ad-hoc re-sign band-aid is deleted — a notarized + stapled bundle
162
+ survives npm's tarball round-trip untouched, so the helper launches with no
163
+ per-machine healing. The launch guards now verify Gatekeeper acceptance (not
164
+ just `codesign --verify`) and fail loud pointing at an upgrade rather than
165
+ re-signing over it. Source: `apps/cli/menubar/scripts/build.sh`,
166
+ `apps/cli/scripts/verify-menubar-helper.sh`, `apps/cli/scripts/release.sh`,
167
+ `apps/cli/src/lib/menubar/install-menubar.ts`.
168
+
169
+ - **`agents projects import` gains Linear as a source, and gates the Factory guess.**
170
+ `import --from-linear` turns the workspace's Linear projects into definitions via the
171
+ `linear` CLI, binding a local checkout only on an exact name match so it never
172
+ silently points a project at the wrong repo. `--from-factory` now imports only
173
+ `high`-confidence rows by default (`--min-confidence low|medium|high`, `--all` to
174
+ take everything), and prints why each row was skipped — the auto-detected registry
175
+ used to absorb every stale clone it found. Source: `apps/cli/src/lib/project-import.ts`.
176
+ - **`agents projects list` columns line up again.** Widths are computed from the rows
177
+ being printed instead of a fixed 32-character path pad that every home-relative root
178
+ ran straight through. Source: `apps/cli/src/commands/projects.ts`.
179
+
180
+ - **`agents projects status` shows the next Linear milestone.** A new `next` line names
181
+ the project's earliest unfinished milestone with its progress and a human due date
182
+ (`Beta cut · 3/8 · due in 6 days`, `overdue by 3 days`, `due Aug 21`) — a percentage
183
+ says how far along a project is, the milestone says what it is due to hit next. The
184
+ milestone list comes from the project rather than from issue assignments, so a
185
+ milestone with nothing filed under it yet still shows; it rides along on the first
186
+ page of the existing issue fetch, costing no extra request. Source:
187
+ `apps/cli/src/lib/linear-project-counts.ts`.
188
+
189
+ - **`agents publish --branch <b>` now pushes the index to `<b>`, not just the printed URL (#1061).**
190
+ The flag rewrote the printed `raw.githubusercontent.com/.../<b>/skills-index.json`
191
+ URL, but the commit still landed on the checked-out branch — so `--branch dev` from a
192
+ `main` checkout published the index to `main` while advertising a `dev` URL that didn't
193
+ resolve. `commitAndPush` now takes an optional target branch and pushes
194
+ `<current>:<target>`, reporting back the branch the index actually landed on so the URL
195
+ references it. Omitting `--branch` still publishes to the repo's current branch.
196
+ Source: `apps/cli/src/lib/git.ts` (`commitAndPush`, `pushOrigin`, `getCurrentBranch`),
197
+ `apps/cli/src/commands/packages.ts`.
198
+
199
+ - **Remove the unused `agents hq` command.** `agents hq floor --json` was a
200
+ machine-readable bridge for an interactive Agents HQ floor UI that was never
201
+ built — `apps/factory` has zero references to it and it had no other consumer.
202
+ Typing `agents hq` now prints a clear removal notice and exits non-zero instead
203
+ of silently disappearing. Source: `apps/cli/src/index.ts`,
204
+ `apps/cli/src/lib/startup/command-registry.ts` (removed
205
+ `apps/cli/src/commands/hq.ts`, `apps/cli/src/lib/hq/`).
206
+
207
+ - **Removed `agents drive` and the R2/CRDT background session-sync beta.** Both
208
+ predate `agents sessions export`/`import`, which now cover the same ground
209
+ without a daemon: `agents drive` (rsync-based session/config mirroring) and
210
+ the opt-in `session-sync` beta (`agents sessions sync`, the daemon's ~90s R2
211
+ push/pull loop, `agents sync --sessions`) are gone. If you had `session-sync`
212
+ or `drive` enabled, re-enable is no longer possible — use `--host` for live
213
+ cross-machine reads or `agents sessions export --encrypt` /
214
+ `agents sessions import` for portable, encrypted transcript bundles instead.
215
+ The R2 network client and CRDT merge machinery are removed entirely with the
216
+ rest of the background sync. Export/import's own encrypted-bundle path
217
+ survives unchanged: it never talked to R2 over the network — it only reuses
218
+ the `r2.backups` bundle's shared `R2_SYNC_ENC_KEY` for local AES-256-GCM
219
+ encryption, falling back to a printed ephemeral key when that bundle isn't
220
+ configured. Source: `apps/cli/src/commands/drive.ts`,
221
+ `apps/cli/src/commands/sessions-sync.ts`, `apps/cli/src/lib/session/sync/crdt.ts`,
222
+ `apps/cli/src/lib/session/sync/sync.ts`, `apps/cli/src/lib/session/sync/r2.ts`,
223
+ `apps/cli/src/lib/daemon.ts`.
224
+
225
+ - **A routine that fires less often than weekly can now be caught up at all.** Overdue
226
+ detection walked a fixed one-week window for the most recent expected fire, so any cron whose
227
+ gap exceeds that returned nothing and the routine was skipped entirely — never flagged
228
+ overdue on any device, never caught up, no `missed` record, silently. Monthly, semi-monthly,
229
+ quarterly and annual routines were all in that class. Measured on a real schedule
230
+ (`0 9 1,13,25 * *`, 12-day gaps): on **10 of every 28 days** the routine could not be
231
+ evaluated. The lookback now widens (week → month → quarter → year) only when the narrower
232
+ window finds nothing, so a dense schedule never walks more than a week of occurrences.
233
+ - **Catch-up no longer resurrects a retired routine.** `detectOverdueJobs` never checked
234
+ `endAt`, and the scheduler only auto-disables lazily inside a live cron tick — so a routine
235
+ whose `endAt` elapsed while the daemon was down was still enabled on disk, rescheduled on
236
+ restart, and executed by the catch-up pass.
237
+ - **One-shot detection matches the scheduler's.** Overdue used the raw `runOnce` flag while the
238
+ scheduler uses `isOneShotRoutine`, so a one-shot-*like* schedule (a fixed minute/hour/day/
239
+ month) that never carried the flag could be replayed by catch-up.
240
+ - **The creation floor now covers built-in routines.** `routineEffectiveStart` resolved a
241
+ routine's file through a user-layer-only lookup, but `listJobs` reads the system layer too —
242
+ so a built-in shipped in the system repo had neither a `createdAt` stamp nor a resolvable
243
+ path, the floor was skipped, and it read as instantly overdue on first daemon start. Added
244
+ `resolveJobFilePath`, which resolves across every layer the loader reads.
245
+ - **A `createdAt` in the future is clamped to now.** Left unclamped (clock skew, a hand-edited
246
+ year) it sits after every possible expected fire, so the routine could never be flagged
247
+ overdue until wall-clock time caught up.
248
+
249
+ - **A routine now runs on exactly one device, instead of once per device listed.** `devices:`
250
+ was an allowlist where *every* listed device fired independently, so a routine pinned to two
251
+ boxes ran twice on every schedule — two full agent sessions doing identical work and burning
252
+ double the agent quota. On one live fleet seven routines were in that state: `security-sweep`
253
+ ran at 15:30:02 on one box and 15:30:03 on the other, both completing. Ownership is now a pure
254
+ function of the config (the first device in normalized sort order), so every daemon reaches the
255
+ same answer with no lease, no cross-device coordination, and no split brain when the fleet
256
+ partitions. Omitting `devices` still means fleet-wide, which is what `watchdog` and
257
+ `check-updates` want.
258
+ - **`agents routines add --devices a,b` and `devices --set a,b` are now rejected.** A routine
259
+ belongs to one machine; the error names the fix. Routines already on disk with a multi-device
260
+ pin keep running — on their owner only — rather than being dropped.
261
+ - **`agents doctor` lists any routine still carrying a multi-device pin**, with the devices it
262
+ names, the one that now fires, and the command to make it explicit. Also in `doctor --json` as
263
+ `ambiguousDevicePins`. The remediation deliberately offers the candidates rather than
264
+ prescribing the owner: the lowest-sorted name can be a registry alias that matches no live
265
+ machine, and cementing that would keep the routine dead.
266
+
267
+ ## 1.20.93
268
+
269
+ - **`agents send` is a real delivery envelope; `notify` is just `--to owner` (RUSH-2123).** Flag-first form: `--to`, `--text`, `--channel`, `--attach`, `--url`. `--to owner` expands from `notify.owner` in agents.yaml. Positional text still works. Help names the three planes (deliver / record / control) so send is not confused with `feed post`, `activity`, or `message`/`sessions inject`. Source: `apps/cli/src/commands/send.ts`, `apps/cli/src/lib/channels/send.ts`.
270
+
271
+ - **`agents events emit` — record events produced outside the CLI.** In-process code
272
+ calls `emit()` directly, but the producers that most need to record events are not
273
+ agents-cli processes: the Factory VS Code extension host, shell guards, external
274
+ tools. They now pipe JSONL on stdin —
275
+ `… | agents events emit --source factory`. `--source` is stamped as `module`, so
276
+ `agents events --module factory` filters to one producer. Routing is forced by the
277
+ stores rather than chosen: a milestone kind requires a `sessionId` and lands in that
278
+ session's activity log, everything else lands in the operational log. A milestone
279
+ with no `sessionId` is rejected, not quietly written elsewhere. Rejection is per
280
+ line, so one bad line never discards a batch, and the exit code is 1 if any line was
281
+ rejected. `--dry-run` validates without writing.
282
+ Source: `apps/cli/src/lib/events-ingest.ts`, `apps/cli/src/commands/events.ts`,
283
+ `apps/cli/docs/06-observability.md`.
284
+
285
+ - **Four `factory.*` event kinds.** `factory.command`, `factory.action`, `factory.uri`
286
+ and `factory.launch` describe what a user did in the Factory VS Code extension.
287
+ `factory.launch` is a milestone — it carries the `sessionId` and `terminalId` that
288
+ later events join through — and `factory.uri` is audit-level, since an external
289
+ process driving the user's editor is a "who reached in from outside" fact.
290
+ Source: `apps/cli/src/lib/events.ts`, `apps/cli/src/lib/activity.ts`.
291
+
292
+ - **`emit()` accepts a caller-supplied timestamp.** A batched producer records when
293
+ each event *happened* and flushes later; without this, every event in a flush was
294
+ stamped at flush time, collapsing their order and corrupting `--since` boundaries.
295
+ `ts` stays reserved against payload injection — only the explicit override can set
296
+ it. Source: `apps/cli/src/lib/events.ts`.
297
+
298
+ - **Fixed: `agents _internal friction` recorded its own invocation.** The command
299
+ exists precisely because shell guards run before any `agents` process exists and so
300
+ cannot emit in-process, but it still fired the `command.start` / `command.end` audit
301
+ hooks, writing two records on top of every friction record. Recorder commands are now
302
+ exempt. Source: `apps/cli/src/index.ts`.
303
+
304
+ - **Desktop notifications now show the agent on the right, not a second copy of
305
+ the app icon.** macOS draws two images on a banner: the sending app's icon on
306
+ the left and `contentImage` on the right (a YouTube notification uses the slots
307
+ for "YouTube" plus the channel avatar). agents-cli was putting its own lime mark
308
+ in the right slot, so both slots said the same thing. The right slot now carries
309
+ the harness the notification is *about* — a brand-colored tile with a two-letter
310
+ mark (`CL` claude, `CX` codex, `GK` grok, …), two letters because four harnesses
311
+ start with `c` and two with `g`. `agents run --notify` and agent/workflow
312
+ routines pass their harness through; a daemon heal, an overdue sweep, a command
313
+ routine, or a fan-out across several agents has no single agent and leaves the
314
+ right slot empty. Source:
315
+ `apps/cli/menubar/Sources/MenubarHelper/AgentAvatar.swift`,
316
+ `apps/cli/src/lib/menubar/notify-desktop.ts`, `apps/cli/src/lib/run-notify.ts`,
317
+ `apps/cli/src/lib/routine-notify.ts`, `apps/cli/docs/menubar.md`.
318
+
319
+ - **`MenubarHelper --notify` gains `--agent <id>`.** The one-shot notifier accepts
320
+ the harness id that drives the right-hand avatar; omitting it is how a caller
321
+ says "no single agent owns this event".
322
+
323
+ - **`agents projects status --fleet` — per-device workspace drift (beta).**
324
+ Projects are natively multi-device; `--fleet` adds a `fleet` line to the status
325
+ card showing, for each project, whether its workspace repos are present on every
326
+ fleet device, on which branch, ahead/behind their upstream (`↑`/`↓`), and how
327
+ many uncommitted changes they carry — plus a hidden `agents projects probe
328
+ --json <path...>` subcommand that is the peer half of the fan-out. One parallel
329
+ SSH call per device (12s timeout), **no `git fetch`** — drift is measured
330
+ against each peer's last-fetched upstream, and a repo with no upstream reports
331
+ no drift rather than zero. Peers that are unreachable or run an older CLI are
332
+ named once in a trailing note; `probe` itself is not beta-gated so peers answer
333
+ whenever their binary carries it. The schema gains `repos[].path` (home-relative
334
+ local checkout) to opt additional repos into probing beyond the primary `root`,
335
+ and `--json` gains per-project `workspaces[]` with the host-tagged probe rows.
336
+ The card's `live` line also counts agents on every box under `--fleet` via the
337
+ existing sessions fan-out. Source: `apps/cli/src/lib/project-probe.ts`,
338
+ `apps/cli/src/commands/projects.ts`, `apps/cli/src/lib/projects.ts`.
339
+
340
+ - **`agents projects` outcomes on the card — agent×project members, releases,
341
+ Linear counts, and `projects link --linear` (beta).** The status card gains an
342
+ `agents` line under `live` naming WHICH harness is on each project
343
+ (`claude · running · RUSH-2107 @zion`, sorted running-first, capped at 6 with a
344
+ `+N more` tail; under `--fleet` remote agents carry their peer's hostname), a
345
+ latest-release tag on the `ships` line (primary repo only, best-effort
346
+ `gh release list`), and a `linear` line counting the bound Linear project's
347
+ issues by state type (`12/30 done · 5 in progress`) — best-effort with an 8s
348
+ budget, skipped by `--no-remote`, and omitted when the def has no
349
+ `linear.projectId`. The new `agents projects link <name> --linear [query]`
350
+ writes that binding: no query auto-suggests from the def name + repo slug via
351
+ the normalized-key matcher (ported from Factory's `linearProjects.ts`),
352
+ ambiguous/none prints the candidate list and exits 1. `--json` gains
353
+ `members[]`, `latestRelease`, and `linear`. Source:
354
+ `apps/cli/src/lib/project-status.ts`, `apps/cli/src/lib/linear-projects.ts`,
355
+ `apps/cli/src/lib/linear-project-counts.ts`, `apps/cli/src/commands/projects.ts`.
356
+
357
+ - **Project sync no longer spams a warning per file it left alone.** Syncing a
358
+ project whose `.claude/commands/` you wrote yourself printed one wrapped
359
+ `Skipping project resource target …: already exists and is user-owned` line
360
+ per file — six hand-authored commands meant twelve lines of terminal noise in
361
+ the middle of `agents view claude`. Those files are the normal steady state,
362
+ not a warning, so the sync now reports them once, grouped, in plain words:
363
+ `Kept 6 of your own files in .claude/commands: debug.md, doc-gaps.md,
364
+ image-nbp.md, +3 more`. The list also rides out on `SyncResult.projectSkipped`
365
+ for callers that want it. Source: `apps/cli/src/lib/project-resources.ts`.
366
+
367
+ - **`agents secrets` now tracks per-bundle usage and surfaces it.** Every secret
368
+ lifecycle/access event — create, import, export, view, access (a value read for
369
+ injection), unlock — funnels through the one `emitSecretAudit` chokepoint, which
370
+ writes to BOTH the append-only `~/.agents/events.jsonl` audit log AND a derived,
371
+ value-free read-model at `~/.agents/secrets/secrets.db` (never a secret value —
372
+ bundle name, event kind, key count, resolving agent/host, status only).
373
+ `agents secrets view <bundle>` now shows whether the bundle is currently
374
+ **unlocked** (held by the secrets-agent, so reads are prompt-free), a **usage**
375
+ summary ("accessed 42× (last 2h ago) · exported 3× (last 1d ago)"), and
376
+ **per-agent** attribution, and nudges when a bundle has no description (also at
377
+ `create` time). `agents secrets list` gains **`--sort uses`** (most frequently
378
+ accessed) alongside the existing `--sort used`, and the `--json` payloads carry
379
+ `uses`, `usage`, and `heldExpiresAt`. A new **`agents secrets activity [bundle]`**
380
+ prints the recent value-free event timeline (bounded to 90 days). Naming guidance
381
+ is taught in the help and skill: name a website bundle after its domain
382
+ (`stripe.com`, `openai.ai`), a desktop-app bundle after its binary suffix
383
+ (`slack.app`, `photoshop.exe`). Recording is best-effort and
384
+ `AGENTS_NO_USAGE_TRACK=1` disables it. Source:
385
+ `apps/cli/src/lib/secrets/usage-db.ts`, `apps/cli/src/lib/secrets/audit.ts`,
386
+ `apps/cli/src/commands/secrets.ts`, `apps/cli/src/lib/secrets/list-filter.ts`.
387
+
3
388
  ## 1.20.92
4
389
 
5
390
  - **`agents sessions` rows show creation time as well as last activity (RUSH-2107).**
package/README.md CHANGED
@@ -476,6 +476,9 @@ agents devices list # fleet + headroom: load, mem, idle/busy
476
476
  agents devices list --live # force a live probe of every device (alias of --refresh)
477
477
  agents devices list --full # add per-device cores and free/total RAM
478
478
  agents devices list --no-stats # instant: names/addresses only, skip the probe
479
+ agents devices set-interactive zion # the device agents show YOU artifacts on (★ in the list)
480
+ agents devices configure mac-mini --max-agents 4 --scheduler off # per-device config (syncs via devices/<name>/agents.yaml)
481
+ agents devices note mac-mini "runs the releases — don't reboot" # operator notes, repeat to append
479
482
  agents ssh mac-mini # hardened SSH: fails fast if offline,
480
483
  # PowerShell on Windows, password-from-Keychain,
481
484
  # auto-syncs your terminfo (Ghostty/kitty/…) so
package/dist/bin/agents CHANGED
Binary file
@@ -1,9 +1,7 @@
1
1
  import chalk from 'chalk';
2
2
  import { ALL_BETA_FEATURES, getBetaConfigLocation, getEnabledBetaFeatures, setBetaEnabled, } from '../lib/beta.js';
3
3
  const BETA_DESCRIPTIONS = {
4
- drive: 'Google Drive integration for reading and writing files',
5
4
  factory: 'Cloud-based agent dispatch via Rush Factory',
6
- 'session-sync': 'Cross-machine session transcript sync via R2 (daemon push/pull)',
7
5
  projects: 'Named multi-repo projects with a progress rollup (agents projects)',
8
6
  };
9
7
  function parseFeatures(values) {
@@ -19,12 +17,12 @@ function parseFeatures(values) {
19
17
  export function registerBetaCommands(program) {
20
18
  const beta = program
21
19
  .command('beta')
22
- .description('Enable or disable preview features like drive and factory.')
20
+ .description('Enable or disable preview features like factory.')
23
21
  .addHelpText('after', `
24
22
  Examples:
25
23
  agents beta list
26
- agents beta enable drive factory
27
- agents beta disable drive
24
+ agents beta enable factory
25
+ agents beta disable factory
28
26
  `);
29
27
  beta
30
28
  .command('list')
@@ -13,6 +13,7 @@ import { resolveHost } from '../lib/hosts/registry.js';
13
13
  import { sshExecAsync } from '../lib/ssh-exec.js';
14
14
  import { sshTargetFor } from '../lib/hosts/types.js';
15
15
  import { machineId, normalizeHost } from '../lib/session/sync/config.js';
16
+ import { findAmbiguousDevicePins } from '../lib/routines.js';
16
17
  import chalk from 'chalk';
17
18
  import { checkAllClis, collectTeamsDoctorData } from '../lib/teams/agents.js';
18
19
  import { AGENTS, ALL_AGENT_IDS, resolveAgentName, formatAgentError, getAccountInfo } from '../lib/agents.js';
@@ -1298,6 +1299,10 @@ export function registerDoctorCommand(program) {
1298
1299
  const inventory = await collectLocalFleetInventory(cwd);
1299
1300
  const localName = machineId();
1300
1301
  const duplicateHooks = inspectDuplicateVersionHooks(cwd);
1302
+ // A routine belongs to one device. A multi-device pin used to fire it
1303
+ // once per listed device — duplicate agent runs, duplicate spend — so
1304
+ // surface any that are still on disk with the exact fix.
1305
+ const ambiguousPins = findAmbiguousDevicePins(cwd);
1301
1306
  // Legacy account-global sign-in map, kept for `--json` back-compat
1302
1307
  // (ssh.ts RemoteDoctorJson / menubar read `signIn`). File-based, no home.
1303
1308
  const signIn = {};
@@ -1361,6 +1366,9 @@ export function registerDoctorCommand(program) {
1361
1366
  // reading `sync`/`orphans`/`repos` are unaffected.
1362
1367
  health: computeOverviewHealth(syncRows, orphanRows, repoBehindMarkers, duplicateHooks),
1363
1368
  duplicateHooks,
1369
+ // Routines whose `devices` names more than one machine — each used to
1370
+ // fire once per device. `owner` is the one that fires now.
1371
+ ambiguousDevicePins: ambiguousPins,
1364
1372
  // Prioritized RUSH-2069 findings (critical/warning, per-version, with
1365
1373
  // remediation). Additive alongside the legacy fields above.
1366
1374
  findings,
@@ -1404,6 +1412,21 @@ export function registerDoctorCommand(program) {
1404
1412
  if (syncRows.some((r) => r.status !== 'fresh' || (r.unwiredHooks ?? 0) > 0) || repoBehindMarkers.some((m) => m.behind > 0)) {
1405
1413
  console.log(chalk.gray('\nRun `agents status` to review and sync what has drifted.'));
1406
1414
  }
1415
+ // A routine runs on exactly one device. Each of these named several and
1416
+ // used to fire once per device — duplicate agent runs on every schedule.
1417
+ if (ambiguousPins.length > 0) {
1418
+ console.log();
1419
+ console.log(chalk.yellow(`${ambiguousPins.length} routine(s) pin more than one device — a routine runs on exactly one:`));
1420
+ for (const pin of ambiguousPins) {
1421
+ console.log(` ${chalk.cyan(pin.name)} ${chalk.gray(`[${pin.devices.join(', ')}]`)} ` +
1422
+ `${chalk.gray('→ fires only on')} ${pin.owner}`);
1423
+ // Deliberately not prescribing `--set ${pin.owner}`: ownership is the
1424
+ // lowest-sorted name, which can be a registry alias that matches no
1425
+ // live machine (`worker` here), and cementing that keeps the routine
1426
+ // dead. Name the candidates and let the operator pick the real box.
1427
+ console.log(chalk.gray(` fix: agents routines devices ${pin.name} --set <${pin.devices.join('|')}>`));
1428
+ }
1429
+ }
1407
1430
  return;
1408
1431
  }
1409
1432
  const parsed = parseTargetArg(target);
@@ -17,6 +17,8 @@ import chalk from 'chalk';
17
17
  import * as fs from 'fs';
18
18
  import { getLogsPath } from '../lib/events.js';
19
19
  import { readUnifiedEvents } from '../lib/event-stream.js';
20
+ import { ingestBatch } from '../lib/events-ingest.js';
21
+ import { setHelpSections } from '../lib/help.js';
20
22
  /**
21
23
  * Resolve `--limit` into a record cap. `0` means "no cap" — without it there is
22
24
  * no way to read the whole stream, and any aggregation (group-by failure, count
@@ -97,8 +99,93 @@ function renderRow(r) {
97
99
  const agent = r.agent ? chalk.gray(` ${r.agent}`) : '';
98
100
  return `${time} ${originLabel(r).padEnd(24)} ${user.padEnd(22)} ${ev.padEnd(26)}${agent} ${detailFor(r)}`;
99
101
  }
102
+ /** Read all of stdin. Returns '' when nothing is piped. */
103
+ async function readStdin() {
104
+ if (process.stdin.isTTY)
105
+ return '';
106
+ const chunks = [];
107
+ for await (const chunk of process.stdin)
108
+ chunks.push(Buffer.from(chunk));
109
+ return Buffer.concat(chunks).toString('utf-8');
110
+ }
111
+ function registerEmitSubcommand(events) {
112
+ const emitCmd = events
113
+ .command('emit')
114
+ .description('Record events produced outside this process (reads JSONL on stdin)')
115
+ .requiredOption('--source <name>', 'Producer name; stamped as `module` so `events --module <name>` filters to it')
116
+ .option('--dry-run', 'Validate and report without writing')
117
+ .option('--json', 'Report {written, rejected} as JSON')
118
+ .action(async (opts, cmd) => {
119
+ // The parent `events` command also declares `--json` (for its read view).
120
+ // Commander binds a flag declared on BOTH to the parent, so a bare
121
+ // `events emit … --json` lands on parent.opts().json and the child sees
122
+ // undefined — the human string would print where a caller expects JSON.
123
+ // Read both, same as `feed post`.
124
+ const wantJson = Boolean(opts?.json ?? cmd?.parent?.opts?.().json);
125
+ const input = await readStdin();
126
+ if (input.trim() === '') {
127
+ console.error('events emit: nothing on stdin — pipe one JSON object per line.');
128
+ process.exitCode = 1;
129
+ return;
130
+ }
131
+ let result;
132
+ try {
133
+ result = ingestBatch(input, { source: opts.source, dryRun: opts.dryRun });
134
+ }
135
+ catch (err) {
136
+ console.error(`events emit: ${err instanceof Error ? err.message : String(err)}`);
137
+ process.exitCode = 1;
138
+ return;
139
+ }
140
+ if (wantJson) {
141
+ console.log(JSON.stringify({ written: result.written, rejected: result.rejected, routed: result.routed }, null, 2));
142
+ }
143
+ else if (result.rejected.length === 0) {
144
+ console.log(`${result.written} event(s) recorded${opts.dryRun ? ' (dry run)' : ''}.`);
145
+ }
146
+ else {
147
+ console.log(`${result.written} event(s) recorded${opts.dryRun ? ' (dry run)' : ''}.`);
148
+ console.error(`rejected ${result.rejected.length} line(s):`);
149
+ for (const r of result.rejected)
150
+ console.error(` line ${r.line}: ${r.reason}`);
151
+ }
152
+ // Valid siblings are already written; a non-zero exit reports that the
153
+ // batch was not fully accepted without pretending nothing landed.
154
+ if (result.rejected.length > 0)
155
+ process.exitCode = 1;
156
+ });
157
+ setHelpSections(emitCmd, {
158
+ examples: `
159
+ # 1. Produce one JSON object per line (this is the whole input format).
160
+ printf '%s\\n' '{"event":"factory.command","commandId":"agents.newClaude"}' \\
161
+ | agents events emit --source factory
162
+
163
+ # 2. Flush a batch and check what landed.
164
+ agents events emit --source factory --json < batch.jsonl
165
+
166
+ # 3. Read it back. --limit 0 matters whenever you aggregate.
167
+ agents events --module factory --limit 0 --json
168
+ `,
169
+ notes: `
170
+ \`event\` is required and must be a known kind. \`ts\` (ISO-8601) is optional
171
+ and defaults to now — pass it so a batched producer records when each event
172
+ HAPPENED rather than when it flushed.
173
+
174
+ Envelope keys: ts, sessionId, mailboxId, terminalId, launchId, tmuxPane,
175
+ host, runtime, agent, tool, detail, url, project, cwd. Any other key is
176
+ payload and passes through the usual redaction.
177
+
178
+ A milestone kind (e.g. factory.launch) REQUIRES a sessionId — the activity
179
+ log is one file per session. Milestones route there; everything else goes
180
+ to the operational log. \`agents events\` reads both.
181
+
182
+ Rejection is per line: one bad line never discards the batch. Exit is 1 if
183
+ any line was rejected.
184
+ `,
185
+ });
186
+ }
100
187
  export function registerEventsCommand(program) {
101
- program
188
+ const events = program
102
189
  .command('events')
103
190
  .description('Read the unified event stream (operational + agent activity)')
104
191
  .option('--module <name>', 'Only events from this group (e.g. teams, secrets, activity)')
@@ -166,6 +253,9 @@ Examples:
166
253
  if (truncated)
167
254
  console.log(chalk.yellow(capNote));
168
255
  });
256
+ // `events` both reads (its own action, above) and writes (this subcommand) —
257
+ // the same shape as `feed` / `feed post`.
258
+ registerEmitSubcommand(events);
169
259
  }
170
260
  /** commander repeatable-option collector. */
171
261
  function collect(value, previous) {
@@ -23,6 +23,7 @@ import * as os from 'os';
23
23
  import { spawnSync } from 'child_process';
24
24
  import { randomUUID } from 'crypto';
25
25
  import { isSessionTrackedAgent } from '../lib/session/types.js';
26
+ import { applyActiveRulesPresetAtRun } from '../lib/rules/run-sync.js';
26
27
  /** Distinguish a terminal account-picker marker from an explicit @version pin. */
27
28
  export function parseRunAccountPickerRequest(agentSpec) {
28
29
  const requested = agentSpec.endsWith('@');
@@ -1978,6 +1979,18 @@ export function registerRunCommand(program) {
1978
1979
  }
1979
1980
  }
1980
1981
  const defaultVersion = version ?? resolveVersion(agent, cwd);
1982
+ // Re-apply the active rules preset before every launch (issue: preset
1983
+ // changes via `setActiveRulesPreset` only took effect after an explicit
1984
+ // `agents rules switch` / `agents sync`). Version-scoped, skip-fast when
1985
+ // nothing changed — see lib/rules/run-sync.ts. Placed here (immediately
1986
+ // after the resolved version is known, before ACP/loop/fallback branch
1987
+ // off) so every downstream dispatch path for this agent+version sees a
1988
+ // fresh rules file, not just the plain execAgent path. `defaultVersion`
1989
+ // is null when nothing is installed yet — execAgent handles that error
1990
+ // path itself; there's no version home to sync into.
1991
+ if (defaultVersion) {
1992
+ applyActiveRulesPresetAtRun(agent, defaultVersion, getVersionHomePath(agent, defaultVersion));
1993
+ }
1981
1994
  // Login preflight (advisory, warn + continue). On a local INTERACTIVE
1982
1995
  // launch, probe whether this agent's account has a credential and print a
1983
1996
  // one-line warning if it looks logged out — so you find out BEFORE the TUI