@phnx-labs/agents-cli 1.20.93 → 1.21.1
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/CHANGELOG.md +290 -0
- package/README.md +4 -1
- package/dist/bin/agents +0 -0
- package/dist/commands/beta.js +3 -5
- package/dist/commands/doctor.js +23 -0
- package/dist/commands/exec.js +13 -0
- package/dist/commands/feed.js +37 -23
- package/dist/commands/mailboxes.js +39 -1
- package/dist/commands/message.js +12 -1
- package/dist/commands/monitors.js +8 -6
- package/dist/commands/packages.js +14 -5
- package/dist/commands/projects.d.ts +28 -0
- package/dist/commands/projects.js +173 -47
- package/dist/commands/routines.js +27 -1
- package/dist/commands/rules.js +6 -0
- package/dist/commands/sessions.d.ts +17 -0
- package/dist/commands/sessions.js +16 -8
- package/dist/commands/setup-browser.js +5 -1
- package/dist/commands/setup-fleet.js +5 -0
- package/dist/commands/setup-preferences.d.ts +53 -0
- package/dist/commands/setup-preferences.js +142 -0
- package/dist/commands/setup.js +8 -9
- package/dist/commands/ssh.js +220 -10
- package/dist/commands/sync.d.ts +2 -2
- package/dist/commands/sync.js +4 -9
- package/dist/commands/watchdog.js +26 -3
- package/dist/index.js +22 -6
- package/dist/lib/activity.d.ts +13 -0
- package/dist/lib/activity.js +22 -2
- package/dist/lib/auto-dispatch.d.ts +6 -1
- package/dist/lib/auto-dispatch.js +7 -2
- package/dist/lib/beta.d.ts +1 -1
- package/dist/lib/beta.js +1 -1
- package/dist/lib/channels/providers/openclaw-telegram.js +1 -1
- package/dist/lib/channels/resolve.d.ts +18 -1
- package/dist/lib/channels/resolve.js +15 -5
- package/dist/lib/daemon.d.ts +13 -0
- package/dist/lib/daemon.js +80 -60
- package/dist/lib/device-config.d.ts +82 -0
- package/dist/lib/device-config.js +296 -0
- package/dist/lib/event-provenance.d.ts +19 -0
- package/dist/lib/event-provenance.js +48 -0
- package/dist/lib/events.d.ts +2 -2
- package/dist/lib/events.js +4 -53
- package/dist/lib/feed-broadcast.d.ts +41 -5
- package/dist/lib/feed-broadcast.js +110 -12
- package/dist/lib/feed-post.d.ts +16 -2
- package/dist/lib/feed-post.js +35 -7
- package/dist/lib/feed.d.ts +1 -1
- package/dist/lib/feed.js +3 -1
- package/dist/lib/git.d.ts +13 -2
- package/dist/lib/git.js +38 -6
- package/dist/lib/hooks.d.ts +16 -1
- package/dist/lib/hooks.js +61 -1
- package/dist/lib/linear-project-counts.d.ts +72 -5
- package/dist/lib/linear-project-counts.js +90 -6
- package/dist/lib/linear-projects.d.ts +12 -0
- package/dist/lib/linear-projects.js +30 -0
- package/dist/lib/mailbox-gc.js +30 -7
- package/dist/lib/mailbox.d.ts +14 -1
- package/dist/lib/mailbox.js +35 -3
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/install-menubar.d.ts +5 -21
- package/dist/lib/menubar/install-menubar.js +35 -44
- package/dist/lib/migrate.js +1 -1
- package/dist/lib/models.js +21 -11
- package/dist/lib/monitors/config.d.ts +1 -1
- package/dist/lib/monitors/dispatch.d.ts +6 -2
- package/dist/lib/monitors/dispatch.js +10 -15
- package/dist/lib/notify.d.ts +42 -6
- package/dist/lib/notify.js +41 -32
- package/dist/lib/overdue.d.ts +1 -1
- package/dist/lib/overdue.js +54 -19
- package/dist/lib/project-import.d.ts +96 -0
- package/dist/lib/project-import.js +171 -0
- package/dist/lib/remote-agents-json.d.ts +14 -1
- package/dist/lib/remote-agents-json.js +21 -3
- package/dist/lib/routines.d.ts +53 -0
- package/dist/lib/routines.js +105 -4
- package/dist/lib/rules/run-sync.d.ts +18 -0
- package/dist/lib/rules/run-sync.js +92 -0
- package/dist/lib/scheduler.js +12 -2
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/index.d.ts +4 -5
- package/dist/lib/secrets/index.js +4 -5
- package/dist/lib/session/active.d.ts +16 -2
- package/dist/lib/session/active.js +8 -4
- package/dist/lib/session/bundle.js +5 -1
- package/dist/lib/session/remote-list.d.ts +1 -15
- package/dist/lib/session/remote-list.js +22 -111
- package/dist/lib/session/sync/config.d.ts +13 -5
- package/dist/lib/session/sync/config.js +21 -13
- package/dist/lib/startup/command-registry.d.ts +0 -2
- package/dist/lib/startup/command-registry.js +1 -5
- package/dist/lib/state.d.ts +7 -2
- package/dist/lib/state.js +85 -10
- package/dist/lib/sync-umbrella.d.ts +11 -23
- package/dist/lib/sync-umbrella.js +14 -45
- package/dist/lib/teams/agents.d.ts +35 -3
- package/dist/lib/teams/agents.js +52 -8
- package/dist/lib/teams/scheduler.d.ts +29 -2
- package/dist/lib/teams/scheduler.js +60 -16
- package/dist/lib/types.d.ts +23 -1
- package/package.json +1 -2
- package/dist/commands/drive.d.ts +0 -10
- package/dist/commands/drive.js +0 -183
- package/dist/commands/hq.d.ts +0 -2
- package/dist/commands/hq.js +0 -58
- package/dist/commands/sessions-sync.d.ts +0 -17
- package/dist/commands/sessions-sync.js +0 -135
- package/dist/commands/sync-provision.d.ts +0 -23
- package/dist/commands/sync-provision.js +0 -107
- package/dist/lib/drive-sync.d.ts +0 -45
- package/dist/lib/drive-sync.js +0 -238
- package/dist/lib/hq/floor.d.ts +0 -87
- package/dist/lib/hq/floor.js +0 -243
- package/dist/lib/session/sync/crdt.d.ts +0 -44
- package/dist/lib/session/sync/crdt.js +0 -119
- package/dist/lib/session/sync/manifest.d.ts +0 -62
- package/dist/lib/session/sync/manifest.js +0 -100
- package/dist/lib/session/sync/provision.d.ts +0 -49
- package/dist/lib/session/sync/provision.js +0 -91
- package/dist/lib/session/sync/r2.d.ts +0 -32
- package/dist/lib/session/sync/r2.js +0 -121
- package/dist/lib/session/sync/sync.d.ts +0 -106
- package/dist/lib/session/sync/sync.js +0 -374
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,295 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.21.1
|
|
4
|
+
|
|
5
|
+
- **Feed posts require a title + body; phone `{message}` ends with a Sent-from footer.** `agents feed post --title "Short subject" "body text"` — title is the phone first line (~4–5 words), body follows after a blank line, then `Sent from <agent>/<session-chunk> on <host>` (like "Sent from my iPhone"). Em/en dashes in title/body are scrubbed to ASCII ` - `. Source: `apps/cli/src/lib/feed-broadcast.ts`, `feed-post.ts`, `commands/feed.ts`.
|
|
6
|
+
|
|
7
|
+
- **Hook `timeout` in agents.yaml now accepts duration strings, not just bare seconds (#1555).**
|
|
8
|
+
A hook can be written `timeout: 5s` / `timeout: 2m` / `timeout: 1h30m` instead of only
|
|
9
|
+
`timeout: 30` — self-documenting at the call site. A bare number still means seconds, so
|
|
10
|
+
every existing manifest keeps working. `parseHookManifest` normalizes the value to a
|
|
11
|
+
seconds number once, so all harness serializers keep consuming a number; an unparseable
|
|
12
|
+
timeout is dropped with a warning rather than silently coerced. Source:
|
|
13
|
+
`apps/cli/src/lib/hooks.ts` (`normalizeHookTimeoutSeconds`, `parseHookManifest`),
|
|
14
|
+
`apps/cli/docs/hooks.md`.
|
|
15
|
+
|
|
16
|
+
- **Owner notifications route through the one channel seam.** The feed urgent-block
|
|
17
|
+
dispatch and the monitor `notify` action now send through the registered channel
|
|
18
|
+
provider (`lookupTransport` → `ChannelProvider.send`) instead of shelling out to
|
|
19
|
+
`openclaw` directly. The recipient comes from `notify.owner` in agents.yaml — the
|
|
20
|
+
hardcoded owner chat id is gone, so changing `notify.owner` is honoured by every
|
|
21
|
+
path. A bare `--notify` on a monitor now targets `notify.owner`; `--notify <channel>`
|
|
22
|
+
overrides the owner channel. The monitor path also gains the provider's missing-binary
|
|
23
|
+
guard (a clean error instead of a raw ENOENT). A channel name that resolves to no
|
|
24
|
+
registered provider (a typo in `notify.owner.channel`, or `--notify <channel>`) fails
|
|
25
|
+
that one send with a clean error — it does not exit the monitor daemon or abort the
|
|
26
|
+
`agents feed --dispatch` loop. Source: `apps/cli/src/lib/notify.ts`,
|
|
27
|
+
`apps/cli/src/lib/monitors/dispatch.ts`, `apps/cli/src/lib/channels/resolve.ts`.
|
|
28
|
+
|
|
29
|
+
## 1.21.0
|
|
30
|
+
|
|
31
|
+
- **A clone of your own DotAgents repo no longer hijacks project-layer rule resolution (RUSH-2037).**
|
|
32
|
+
Cloning `~/.agents` to the canonical `~/src/github.com/<you>/.agents` path (to edit
|
|
33
|
+
rules in an editor) made that checkout eligible as a *project* layer whenever you
|
|
34
|
+
worked from its parent directory. Because project outranks user, a stale clone's
|
|
35
|
+
`rules/subrules/*` then silently shadowed the live user rules by filename, and the
|
|
36
|
+
compile planted an out-of-date `AGENTS.md` in an ancestor dir that every session
|
|
37
|
+
beneath it ingested. Project-layer discovery now identifies a DotAgents repo by
|
|
38
|
+
**repo identity** (git origin), not path: a `.agents/` that is itself a git checkout
|
|
39
|
+
whose origin matches the user's or system's DotAgents repo is skipped, so the live
|
|
40
|
+
user layer wins. Legitimate project `.agents/` layers (a plain subdirectory of a
|
|
41
|
+
project, or a git repo with an unrelated origin) are unaffected.
|
|
42
|
+
Source: `apps/cli/src/lib/state.ts`.
|
|
43
|
+
|
|
44
|
+
- **`agents sessions --local` no longer dials remote-host teammates over ssh,
|
|
45
|
+
in the default listing or `--active` (RUSH-2118).** `--local` is supposed to
|
|
46
|
+
mean this-machine-only, but the underlying `AgentManager` poll still fired a
|
|
47
|
+
real ssh round-trip for every teammate dispatched via
|
|
48
|
+
`agents teams add --device` — even a teammate that had already finished.
|
|
49
|
+
On a box with 30 completed remote-host teammates that measured out to 180
|
|
50
|
+
real `ssh` execve calls (6 per teammate: two ssh calls in
|
|
51
|
+
`syncRemoteMirror`, run three times per poll) and a ~4.3s
|
|
52
|
+
`--active --local` call. A `--local` query now reads a remote-host
|
|
53
|
+
teammate's last-persisted `meta.json` state instead of dialing it, and a
|
|
54
|
+
teammate that has already reached a terminal status (completed/failed/
|
|
55
|
+
stopped) is never re-dialed by ANY `--active` query, local or not — its
|
|
56
|
+
final log bytes and exit code were already captured on the poll that
|
|
57
|
+
resolved it. The same gate now covers every `--local` surface: the bare
|
|
58
|
+
default listing's live-glyph enrichment (`maybeLiveIndex`) and `--preview`
|
|
59
|
+
(`renderSessionPreview`, freely combinable with `--local`) both called the
|
|
60
|
+
local-only `getActiveSessions()` with no `localOnly` threaded through,
|
|
61
|
+
despite the `--local` help text already promising this-machine-only for
|
|
62
|
+
all of them. Source: `apps/cli/src/lib/teams/agents.ts`
|
|
63
|
+
(`syncRemoteMirror`, `readNewEvents`, `updateStatusFromProcess`,
|
|
64
|
+
`AgentManager`), `apps/cli/src/lib/session/active.ts` (`listTeamsActive`,
|
|
65
|
+
`getActiveSessions`), `apps/cli/src/commands/sessions.ts`
|
|
66
|
+
(`gatherActiveSessions`, `maybeLiveIndex`, `renderSessionPreview`).
|
|
67
|
+
|
|
68
|
+
- **A rules preset now applies at `agents run` time, not only after `agents
|
|
69
|
+
rules switch` (RUSH-2128).** `setActiveRulesPreset` used to take effect only on the next
|
|
70
|
+
explicit `agents rules switch`/`agents add`/`agents use` — a preset change
|
|
71
|
+
made any other way left the harness launching against a stale rules file
|
|
72
|
+
until someone remembered to re-sync. `agents run` now re-applies the active
|
|
73
|
+
preset for the resolved agent+version immediately before dispatch, every
|
|
74
|
+
time, with a skip-fast sentinel so an unchanged preset costs no recompose or
|
|
75
|
+
rewrite. Version-scoped only; per-model preset scoping is a follow-up.
|
|
76
|
+
Source: `apps/cli/src/lib/rules/run-sync.ts`, `apps/cli/src/commands/exec.ts`.
|
|
77
|
+
|
|
78
|
+
- **Activity events now carry the same actor and session lineage as operational events.**
|
|
79
|
+
The TypeScript activity writer and the embedded PostToolUse hook stamp actor kind,
|
|
80
|
+
launch id, and parent session id from the shared execution provenance floor, so
|
|
81
|
+
`agents events` no longer invents an agent name as the activity record's OS user.
|
|
82
|
+
Source: `apps/cli/src/lib/event-provenance.ts`, `apps/cli/src/lib/activity.ts`.
|
|
83
|
+
|
|
84
|
+
- **`agents view` no longer re-scans every installed Claude binary on each run.**
|
|
85
|
+
When a Claude model extractor produced zero models (a broken regex, or a
|
|
86
|
+
mid-install CLI), the result was never cached — so `getModelCatalog` re-ran a
|
|
87
|
+
full `readFileSync` scan of the 230-270MB Claude binary for every affected
|
|
88
|
+
installed version, on every invocation (~1.85s each). With 4 affected
|
|
89
|
+
versions installed, that was ~7.5s added to every `agents view`. A 0-model
|
|
90
|
+
extraction is now cached too, stamped with when it was attempted, and served
|
|
91
|
+
for 24 hours before self-healing by retrying; an upgrade/reinstall (a new
|
|
92
|
+
source mtime) still re-extracts immediately, as before. Measured on a real
|
|
93
|
+
install with 7 Claude versions (4 of them hitting the broken extractor): the
|
|
94
|
+
cold first-call cost (~12.5s, unavoidable) drops to ~1-2ms on every
|
|
95
|
+
subsequent call. Source: `apps/cli/src/lib/models.ts`.
|
|
96
|
+
|
|
97
|
+
- **Per-device and fleet-wide config keys now have a home: the `config:` block in
|
|
98
|
+
the two-tier agents.yaml store.** Three new subcommands under `agents devices`
|
|
99
|
+
(no new top-level noun): `agents devices set-interactive <name>` records the one
|
|
100
|
+
device agents show YOU artifacts on (browser opens, dashboards) as
|
|
101
|
+
`config.interactiveHost` in the central, synced agents.yaml — skills no longer
|
|
102
|
+
guess "the online macOS box", and the host is marked `★ interactive` in
|
|
103
|
+
`agents devices list`. `agents devices configure <name> --max-agents N
|
|
104
|
+
--scheduler on|off` and `agents devices note <name> "…"` (repeat
|
|
105
|
+
to append, `--clear` to empty) write device-scope keys under `config:` in
|
|
106
|
+
`~/.agents/devices/<name>/agents.yaml` — targetable for any device from any box
|
|
107
|
+
(the devices/ tree syncs; each machine reads only its own). The default browser
|
|
108
|
+
profile joins the same registry as `browser.profile`, routed to the existing
|
|
109
|
+
device-local `defaultBrowserProfile` field (no duplicate key, resolution order
|
|
110
|
+
unchanged). Unset keys always mean today's behavior; everything is scriptable
|
|
111
|
+
with `--json`, and `devices list --json` now carries each row's `config` and an
|
|
112
|
+
`interactive` flag. agents.yaml files the CLI writes now carry a
|
|
113
|
+
`yaml-language-server` hint pointing at the new
|
|
114
|
+
`apps/cli/schema/agents-yaml.schema.json`.
|
|
115
|
+
|
|
116
|
+
The keys are live inputs, not just stored values. `--scheduler off` stops the
|
|
117
|
+
routines scheduler from starting on that device — `routines add` skips the
|
|
118
|
+
auto-start with the reason, a manual `routines start` refuses, and the daemon
|
|
119
|
+
re-evaluates the gate on every SIGHUP reload (boot it again with
|
|
120
|
+
`agents devices configure <host> --scheduler on` + any reload, no daemon
|
|
121
|
+
restart). `--max-agents` feeds host ranking: Factory auto-launch excludes a
|
|
122
|
+
device at its cap (counting device-wide running agents) and names the cap when
|
|
123
|
+
a pool is exhausted; teams placement excludes it from the least-loaded
|
|
124
|
+
auto-pick (counting the team's own roster, local teammates included) and an
|
|
125
|
+
all-capped pool fails loud instead of over-filling a machine. Setup asks
|
|
126
|
+
instead of guessing: bare `agents setup` ends with a skippable preferences
|
|
127
|
+
step (which machine you sit at → interactive host; which browser agents drive
|
|
128
|
+
here → device default), `agents setup fleet` offers the interactive host after
|
|
129
|
+
a sync, and the `agents setup browser` picker highlights the auto-detect
|
|
130
|
+
winner. Source: `apps/cli/src/lib/device-config.ts`,
|
|
131
|
+
`apps/cli/src/lib/state.ts`, `apps/cli/src/lib/daemon.ts`,
|
|
132
|
+
`apps/cli/src/lib/teams/scheduler.ts`, `apps/cli/src/commands/ssh.ts`,
|
|
133
|
+
`apps/cli/src/commands/setup-preferences.ts`,
|
|
134
|
+
`apps/factory/src/core/launchHost.ts`,
|
|
135
|
+
`apps/cli/schema/agents-yaml.schema.json`.
|
|
136
|
+
|
|
137
|
+
- **Mailbox messages now expire and dead boxes are reaped automatically.** Messages
|
|
138
|
+
enqueued without an explicit TTL used to sit in the spool forever, so pending mail
|
|
139
|
+
would outlive the session that needed it. They now get a 24-hour default TTL
|
|
140
|
+
(`AGENTS_MAILBOX_TTL` overrides the default; `agents message … --ttl 2h` sets it
|
|
141
|
+
per message). When a message expires, a live-but-idle box archives it with a
|
|
142
|
+
`dropped: expired` receipt. The watchdog tick also runs a liveness sweep using the
|
|
143
|
+
same live-session set as `agents sessions --active`, archiving pending mail in dead
|
|
144
|
+
boxes as `dropped: dead` and pruning stale consumed entries. Dropped messages tied
|
|
145
|
+
to a feed block surface a failure receipt (`status: dropped` / `expired`) so the
|
|
146
|
+
sender sees the bounce instead of silence. Run the sweep manually with
|
|
147
|
+
`agents mailboxes gc` (`--json` supported). Source:
|
|
148
|
+
`apps/cli/src/lib/mailbox.ts`, `apps/cli/src/lib/mailbox-gc.ts`,
|
|
149
|
+
`apps/cli/src/commands/message.ts`, `apps/cli/src/commands/mailboxes.ts`,
|
|
150
|
+
`apps/cli/src/commands/watchdog.ts`, `apps/cli/src/lib/feed.ts`.
|
|
151
|
+
|
|
152
|
+
- **The menu-bar helper can no longer leak CLI processes until the machine is unusable.**
|
|
153
|
+
Its poll shelled `agents doctor --json` through an unbounded `Process` +
|
|
154
|
+
`readDataToEndOfFile()`. Two properties composed badly: the call had no deadline
|
|
155
|
+
(`doctor --json` measures **136s on an idle box**, against a 60s poll interval), and a
|
|
156
|
+
helper that died mid-call left the child reparented to launchd (PPID 1) with nothing
|
|
157
|
+
to reap it — along with the `node -e` version probes that child had forked. Both fire
|
|
158
|
+
together, because the helper crashes under exactly the conditions that make the CLI
|
|
159
|
+
slow: `NSApplication.shared` segfaults inside `SLSNewConnection` when WindowServer is
|
|
160
|
+
too starved to hand out a connection, launchd's `KeepAlive` restarts it, and the
|
|
161
|
+
restart spawns a new doctor while the old one keeps burning a core. Observed on a real
|
|
162
|
+
machine: 38 orphaned doctors + 92 orphaned probes, ~13 of 18 cores consumed, load
|
|
163
|
+
average 490, keystrokes visibly lagging.
|
|
164
|
+
The crash itself cannot be prevented from inside the app — it is AppKit dereferencing
|
|
165
|
+
a null connection before any of our code runs — so a crash no longer costs anything
|
|
166
|
+
permanent: every child carries a deadline (30s; 180s for `doctor --json`, above its
|
|
167
|
+
real measured cost); it is spawned as its own process-group leader so a timeout kills
|
|
168
|
+
the whole subtree rather than just the CLI; and each live child is recorded on disk so
|
|
169
|
+
the *next* launch reaps whatever a crash abandoned (no exit handler runs on SIGSEGV).
|
|
170
|
+
The doctor refresh also drops from every 60s to every 15 minutes, and the launchd job
|
|
171
|
+
gains `ThrottleInterval` 30 so a startup crash-loop cannot respawn every 10s.
|
|
172
|
+
A poll that blows its deadline now shows a stale menu instead of taking the machine
|
|
173
|
+
down with it. Source: `apps/cli/menubar/Sources/MenubarHelper/ChildProcess.swift`,
|
|
174
|
+
`AgentsCLI.swift`, `StatusItemController.swift`, `main.swift`,
|
|
175
|
+
`apps/cli/src/lib/menubar/install-menubar.ts`.
|
|
176
|
+
|
|
177
|
+
- **The macOS menu-bar helper is now notarized, ending the "app is damaged"
|
|
178
|
+
dialog and the per-run `no valid code signature; skipping launch` spam
|
|
179
|
+
(RUSH-2134).** The helper shipped Developer-ID signed but *not* notarized, so
|
|
180
|
+
Gatekeeper on macOS 26+ rejected it as damaged and the install path tried to
|
|
181
|
+
heal it by re-signing ad-hoc on every `agents` invocation — which can never
|
|
182
|
+
satisfy Gatekeeper, so the dialog and the noise persisted. The release now
|
|
183
|
+
notarizes + staples the helper (`menubar/scripts/build.sh`, mandatory for any
|
|
184
|
+
Developer-ID build, run under the release's `agents secrets exec apple.com`
|
|
185
|
+
context), the `prepack` gate refuses to pack an un-notarized bundle
|
|
186
|
+
(`scripts/verify-menubar-helper.sh` now requires a stapled ticket), and the
|
|
187
|
+
runtime ad-hoc re-sign band-aid is deleted — a notarized + stapled bundle
|
|
188
|
+
survives npm's tarball round-trip untouched, so the helper launches with no
|
|
189
|
+
per-machine healing. The launch guards now verify Gatekeeper acceptance (not
|
|
190
|
+
just `codesign --verify`) and fail loud pointing at an upgrade rather than
|
|
191
|
+
re-signing over it. Source: `apps/cli/menubar/scripts/build.sh`,
|
|
192
|
+
`apps/cli/scripts/verify-menubar-helper.sh`, `apps/cli/scripts/release.sh`,
|
|
193
|
+
`apps/cli/src/lib/menubar/install-menubar.ts`.
|
|
194
|
+
|
|
195
|
+
- **`agents projects import` gains Linear as a source, and gates the Factory guess.**
|
|
196
|
+
`import --from-linear` turns the workspace's Linear projects into definitions via the
|
|
197
|
+
`linear` CLI, binding a local checkout only on an exact name match so it never
|
|
198
|
+
silently points a project at the wrong repo. `--from-factory` now imports only
|
|
199
|
+
`high`-confidence rows by default (`--min-confidence low|medium|high`, `--all` to
|
|
200
|
+
take everything), and prints why each row was skipped — the auto-detected registry
|
|
201
|
+
used to absorb every stale clone it found. Source: `apps/cli/src/lib/project-import.ts`.
|
|
202
|
+
- **`agents projects list` columns line up again.** Widths are computed from the rows
|
|
203
|
+
being printed instead of a fixed 32-character path pad that every home-relative root
|
|
204
|
+
ran straight through. Source: `apps/cli/src/commands/projects.ts`.
|
|
205
|
+
|
|
206
|
+
- **`agents projects status` shows the next Linear milestone.** A new `next` line names
|
|
207
|
+
the project's earliest unfinished milestone with its progress and a human due date
|
|
208
|
+
(`Beta cut · 3/8 · due in 6 days`, `overdue by 3 days`, `due Aug 21`) — a percentage
|
|
209
|
+
says how far along a project is, the milestone says what it is due to hit next. The
|
|
210
|
+
milestone list comes from the project rather than from issue assignments, so a
|
|
211
|
+
milestone with nothing filed under it yet still shows; it rides along on the first
|
|
212
|
+
page of the existing issue fetch, costing no extra request. Source:
|
|
213
|
+
`apps/cli/src/lib/linear-project-counts.ts`.
|
|
214
|
+
|
|
215
|
+
- **`agents publish --branch <b>` now pushes the index to `<b>`, not just the printed URL (#1061).**
|
|
216
|
+
The flag rewrote the printed `raw.githubusercontent.com/.../<b>/skills-index.json`
|
|
217
|
+
URL, but the commit still landed on the checked-out branch — so `--branch dev` from a
|
|
218
|
+
`main` checkout published the index to `main` while advertising a `dev` URL that didn't
|
|
219
|
+
resolve. `commitAndPush` now takes an optional target branch and pushes
|
|
220
|
+
`<current>:<target>`, reporting back the branch the index actually landed on so the URL
|
|
221
|
+
references it. Omitting `--branch` still publishes to the repo's current branch.
|
|
222
|
+
Source: `apps/cli/src/lib/git.ts` (`commitAndPush`, `pushOrigin`, `getCurrentBranch`),
|
|
223
|
+
`apps/cli/src/commands/packages.ts`.
|
|
224
|
+
|
|
225
|
+
- **Remove the unused `agents hq` command.** `agents hq floor --json` was a
|
|
226
|
+
machine-readable bridge for an interactive Agents HQ floor UI that was never
|
|
227
|
+
built — `apps/factory` has zero references to it and it had no other consumer.
|
|
228
|
+
Typing `agents hq` now prints a clear removal notice and exits non-zero instead
|
|
229
|
+
of silently disappearing. Source: `apps/cli/src/index.ts`,
|
|
230
|
+
`apps/cli/src/lib/startup/command-registry.ts` (removed
|
|
231
|
+
`apps/cli/src/commands/hq.ts`, `apps/cli/src/lib/hq/`).
|
|
232
|
+
|
|
233
|
+
- **Removed `agents drive` and the R2/CRDT background session-sync beta.** Both
|
|
234
|
+
predate `agents sessions export`/`import`, which now cover the same ground
|
|
235
|
+
without a daemon: `agents drive` (rsync-based session/config mirroring) and
|
|
236
|
+
the opt-in `session-sync` beta (`agents sessions sync`, the daemon's ~90s R2
|
|
237
|
+
push/pull loop, `agents sync --sessions`) are gone. If you had `session-sync`
|
|
238
|
+
or `drive` enabled, re-enable is no longer possible — use `--host` for live
|
|
239
|
+
cross-machine reads or `agents sessions export --encrypt` /
|
|
240
|
+
`agents sessions import` for portable, encrypted transcript bundles instead.
|
|
241
|
+
The R2 network client and CRDT merge machinery are removed entirely with the
|
|
242
|
+
rest of the background sync. Export/import's own encrypted-bundle path
|
|
243
|
+
survives unchanged: it never talked to R2 over the network — it only reuses
|
|
244
|
+
the `r2.backups` bundle's shared `R2_SYNC_ENC_KEY` for local AES-256-GCM
|
|
245
|
+
encryption, falling back to a printed ephemeral key when that bundle isn't
|
|
246
|
+
configured. Source: `apps/cli/src/commands/drive.ts`,
|
|
247
|
+
`apps/cli/src/commands/sessions-sync.ts`, `apps/cli/src/lib/session/sync/crdt.ts`,
|
|
248
|
+
`apps/cli/src/lib/session/sync/sync.ts`, `apps/cli/src/lib/session/sync/r2.ts`,
|
|
249
|
+
`apps/cli/src/lib/daemon.ts`.
|
|
250
|
+
|
|
251
|
+
- **A routine that fires less often than weekly can now be caught up at all.** Overdue
|
|
252
|
+
detection walked a fixed one-week window for the most recent expected fire, so any cron whose
|
|
253
|
+
gap exceeds that returned nothing and the routine was skipped entirely — never flagged
|
|
254
|
+
overdue on any device, never caught up, no `missed` record, silently. Monthly, semi-monthly,
|
|
255
|
+
quarterly and annual routines were all in that class. Measured on a real schedule
|
|
256
|
+
(`0 9 1,13,25 * *`, 12-day gaps): on **10 of every 28 days** the routine could not be
|
|
257
|
+
evaluated. The lookback now widens (week → month → quarter → year) only when the narrower
|
|
258
|
+
window finds nothing, so a dense schedule never walks more than a week of occurrences.
|
|
259
|
+
- **Catch-up no longer resurrects a retired routine.** `detectOverdueJobs` never checked
|
|
260
|
+
`endAt`, and the scheduler only auto-disables lazily inside a live cron tick — so a routine
|
|
261
|
+
whose `endAt` elapsed while the daemon was down was still enabled on disk, rescheduled on
|
|
262
|
+
restart, and executed by the catch-up pass.
|
|
263
|
+
- **One-shot detection matches the scheduler's.** Overdue used the raw `runOnce` flag while the
|
|
264
|
+
scheduler uses `isOneShotRoutine`, so a one-shot-*like* schedule (a fixed minute/hour/day/
|
|
265
|
+
month) that never carried the flag could be replayed by catch-up.
|
|
266
|
+
- **The creation floor now covers built-in routines.** `routineEffectiveStart` resolved a
|
|
267
|
+
routine's file through a user-layer-only lookup, but `listJobs` reads the system layer too —
|
|
268
|
+
so a built-in shipped in the system repo had neither a `createdAt` stamp nor a resolvable
|
|
269
|
+
path, the floor was skipped, and it read as instantly overdue on first daemon start. Added
|
|
270
|
+
`resolveJobFilePath`, which resolves across every layer the loader reads.
|
|
271
|
+
- **A `createdAt` in the future is clamped to now.** Left unclamped (clock skew, a hand-edited
|
|
272
|
+
year) it sits after every possible expected fire, so the routine could never be flagged
|
|
273
|
+
overdue until wall-clock time caught up.
|
|
274
|
+
|
|
275
|
+
- **A routine now runs on exactly one device, instead of once per device listed.** `devices:`
|
|
276
|
+
was an allowlist where *every* listed device fired independently, so a routine pinned to two
|
|
277
|
+
boxes ran twice on every schedule — two full agent sessions doing identical work and burning
|
|
278
|
+
double the agent quota. On one live fleet seven routines were in that state: `security-sweep`
|
|
279
|
+
ran at 15:30:02 on one box and 15:30:03 on the other, both completing. Ownership is now a pure
|
|
280
|
+
function of the config (the first device in normalized sort order), so every daemon reaches the
|
|
281
|
+
same answer with no lease, no cross-device coordination, and no split brain when the fleet
|
|
282
|
+
partitions. Omitting `devices` still means fleet-wide, which is what `watchdog` and
|
|
283
|
+
`check-updates` want.
|
|
284
|
+
- **`agents routines add --devices a,b` and `devices --set a,b` are now rejected.** A routine
|
|
285
|
+
belongs to one machine; the error names the fix. Routines already on disk with a multi-device
|
|
286
|
+
pin keep running — on their owner only — rather than being dropped.
|
|
287
|
+
- **`agents doctor` lists any routine still carrying a multi-device pin**, with the devices it
|
|
288
|
+
names, the one that now fires, and the command to make it explicit. Also in `doctor --json` as
|
|
289
|
+
`ambiguousDevicePins`. The remediation deliberately offers the candidates rather than
|
|
290
|
+
prescribing the owner: the lowest-sorted name can be a registry alias that matches no live
|
|
291
|
+
machine, and cementing that would keep the routine dead.
|
|
292
|
+
|
|
3
293
|
## 1.20.93
|
|
4
294
|
|
|
5
295
|
- **`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`.
|
package/README.md
CHANGED
|
@@ -341,7 +341,7 @@ agents feed --flat # one row per agent (legacy)
|
|
|
341
341
|
agents feed --host mac-mini # scope the view to one or more hosts
|
|
342
342
|
agents feed --local # skip the SSH fan-out
|
|
343
343
|
agents feed --json # blocks stamped with their outcome key
|
|
344
|
-
agents feed post "
|
|
344
|
+
agents feed post --title "Halfway done" "CI green, watching merge" # title + body
|
|
345
345
|
```
|
|
346
346
|
|
|
347
347
|
Top-level questions and waiting notifications publish one atomic open-block record per session, including the mailbox id, host, runtime, and every answer option. The default view collapses agents under the **outcome** they serve (Linear ticket, PR, worktree slug, or Unassigned) so a 1,100-agent fleet reads as dozens of deliverables. Answered, resumed, and stopped blocks clear automatically; Task subagents are excluded. The rendered reply command uses the same mailbox id with `agents message`, so the decision routes back to the agent that asked it.
|
|
@@ -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
|
package/dist/commands/beta.js
CHANGED
|
@@ -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
|
|
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
|
|
27
|
-
agents beta disable
|
|
24
|
+
agents beta enable factory
|
|
25
|
+
agents beta disable factory
|
|
28
26
|
`);
|
|
29
27
|
beta
|
|
30
28
|
.command('list')
|
package/dist/commands/doctor.js
CHANGED
|
@@ -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);
|
package/dist/commands/exec.js
CHANGED
|
@@ -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
|
package/dist/commands/feed.js
CHANGED
|
@@ -234,44 +234,44 @@ export function registerFeedCommand(program) {
|
|
|
234
234
|
feed
|
|
235
235
|
.command('post')
|
|
236
236
|
.description('Post a status update to the fleet activity stream (for agents)')
|
|
237
|
-
.argument('<text...>', '
|
|
237
|
+
.argument('<text...>', 'Body: what just happened (after --title)')
|
|
238
|
+
.requiredOption('--title <title>', 'Short subject, ~4-5 words (phone first line)')
|
|
238
239
|
.option('--session <id>', 'Session id escape hatch (default: auto from env / pid registry)')
|
|
239
240
|
.option('--attach <path-or-url...>', 'Attach an artifact (local file or URL); repeatable')
|
|
240
241
|
.option('--level <level>', 'How loudly to broadcast: milestone (default) or important. Configured sinks with minLevel: important only fire on the latter.', 'milestone')
|
|
241
|
-
.option('--blocked', 'You are STUCK and need the user. Opens an answerable block and always broadcasts at important
|
|
242
|
+
.option('--blocked', 'You are STUCK and need the user. Opens an answerable block and always broadcasts at important - do not also pass --level.')
|
|
242
243
|
.option('--option <label...>', 'With --blocked: an answer the user can pick; repeatable')
|
|
243
244
|
.option('--default <answer>', 'With --blocked: a safe default policy may apply if nobody answers in time')
|
|
244
245
|
.option('--json', 'Emit the written event as JSON')
|
|
245
246
|
.addHelpText('after', `
|
|
246
247
|
Examples:
|
|
247
|
-
#
|
|
248
|
-
|
|
249
|
-
agents feed post "
|
|
250
|
-
agents feed post "ready
|
|
248
|
+
# Title (subject) + body. Phone broadcasts put title first, body after a
|
|
249
|
+
# blank line, then a "Sent from agent/session on host" footer.
|
|
250
|
+
agents feed post --title "CHANGELOG pushed" "Watching CI and mac-mini E2E"
|
|
251
|
+
agents feed post --title "Cover ready" "render at ./out/cover.png" --attach ./out/cover.png
|
|
252
|
+
agents feed post --title "Ready for review" "PR opened, waiting on prix-cloud" --json
|
|
251
253
|
|
|
252
|
-
# Worth interrupting someone over
|
|
253
|
-
agents feed post "
|
|
254
|
+
# Worth interrupting someone over - reaches sinks gated on minLevel: important:
|
|
255
|
+
agents feed post --title "npm token expired" "Cannot publish the release" --level important
|
|
254
256
|
|
|
255
|
-
#
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
agents feed post "
|
|
259
|
-
agents feed post "publish to npm or wait for review?" --blocked --option publish --option wait
|
|
260
|
-
agents feed post "delete the stale preview env?" --blocked --default "leave it"
|
|
257
|
+
# Stuck: opens a needs-you block and always broadcasts at important:
|
|
258
|
+
agents feed post --title "Force-push denied" "git-guard blocked PR #1749" --blocked
|
|
259
|
+
agents feed post --title "Publish or wait?" "npm publish now or after review" --blocked --option publish --option wait
|
|
260
|
+
agents feed post --title "Delete preview env?" "stale preview still running" --blocked --default "leave it"
|
|
261
261
|
|
|
262
262
|
# Exhaust self-serve FIRST. A block is for what you genuinely cannot do:
|
|
263
263
|
# a credential only the user holds, a decision only they can make, an
|
|
264
264
|
# approval only they can give. Not "should I do the obvious next step?".
|
|
265
265
|
|
|
266
266
|
# Outside a run, pass the session explicitly:
|
|
267
|
-
agents feed post "
|
|
267
|
+
agents feed post --title "Manual note" "context for the next agent" --session 00998b0e-2d15-4d2f-a58b-974a886c9b47
|
|
268
268
|
|
|
269
|
-
Identity (session, agent, host, runtime, pid, launchId) is stamped automatically
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
269
|
+
Identity (session, agent, host, runtime, pid, launchId) is stamped automatically
|
|
270
|
+
and rides the phone footer of feed.broadcast {message}. Domain facts (tickets,
|
|
271
|
+
PRs) are not CLI flags - the ticket is joined from the session index at post
|
|
272
|
+
time. No em-dashes in title/body - they are scrubbed on the way out.
|
|
273
273
|
|
|
274
|
-
Configure where a post is mirrored under feed.broadcast in agents.yaml
|
|
274
|
+
Configure where a post is mirrored under feed.broadcast in agents.yaml - see
|
|
275
275
|
docs/06-observability.md.
|
|
276
276
|
`)
|
|
277
277
|
.action((textParts, opts, cmd) => {
|
|
@@ -279,6 +279,7 @@ docs/06-observability.md.
|
|
|
279
279
|
// binds the flag on the parent, so a `feed post … --json` lands on
|
|
280
280
|
// parent.opts().json — not the child. Read both.
|
|
281
281
|
const flags = {
|
|
282
|
+
title: opts?.title ?? cmd?.opts?.()?.title,
|
|
282
283
|
session: opts?.session ?? cmd?.opts?.()?.session,
|
|
283
284
|
attach: opts?.attach ?? cmd?.opts?.()?.attach,
|
|
284
285
|
level: opts?.level ?? cmd?.opts?.()?.level,
|
|
@@ -298,8 +299,12 @@ docs/06-observability.md.
|
|
|
298
299
|
if (!flags.blocked && (flags.option?.length || flags.default)) {
|
|
299
300
|
throw new Error('--option/--default only apply with --blocked.');
|
|
300
301
|
}
|
|
302
|
+
if (!flags.title?.trim()) {
|
|
303
|
+
throw new Error('Missing --title. Usage: agents feed post --title "Short subject" "body text"');
|
|
304
|
+
}
|
|
301
305
|
const level = flags.blocked ? 'important' : parseFeedPostLevel(flags.level);
|
|
302
306
|
const { event } = postFeedStatus({
|
|
307
|
+
title: flags.title,
|
|
303
308
|
text: Array.isArray(textParts) ? textParts.join(' ') : String(textParts ?? ''),
|
|
304
309
|
sessionId: flags.session,
|
|
305
310
|
attach: flags.attach,
|
|
@@ -312,12 +317,20 @@ docs/06-observability.md.
|
|
|
312
317
|
let outcomes;
|
|
313
318
|
if (flags.blocked) {
|
|
314
319
|
const block = buildDeclaredBlock(event, {
|
|
315
|
-
|
|
320
|
+
// Prefer title as the front-loaded ask on the phone; body is detail.
|
|
321
|
+
text: event.title
|
|
322
|
+
? (event.detail ? `${event.title}: ${event.detail}` : event.title)
|
|
323
|
+
: (event.detail ?? ''),
|
|
316
324
|
options: flags.option,
|
|
317
325
|
safeDefault: flags.default,
|
|
318
326
|
});
|
|
319
327
|
publishBlock(block);
|
|
320
|
-
outcomes = broadcastBlock(block, {
|
|
328
|
+
outcomes = broadcastBlock(block, {
|
|
329
|
+
project: event.project,
|
|
330
|
+
agent: event.agent,
|
|
331
|
+
title: event.title,
|
|
332
|
+
body: event.detail,
|
|
333
|
+
});
|
|
321
334
|
}
|
|
322
335
|
else {
|
|
323
336
|
outcomes = broadcastPostedEvent(event, level);
|
|
@@ -561,6 +574,7 @@ function broadcastPostedEvent(event, level) {
|
|
|
561
574
|
return [];
|
|
562
575
|
const ticket = getSessionById(event.sessionId)?.ticketId;
|
|
563
576
|
const planned = planFeedBroadcast(config, {
|
|
577
|
+
title: event.title,
|
|
564
578
|
text: event.detail ?? '',
|
|
565
579
|
level,
|
|
566
580
|
ticket,
|
|
@@ -675,7 +689,7 @@ function renderUpdatesView(updates) {
|
|
|
675
689
|
}));
|
|
676
690
|
console.log();
|
|
677
691
|
if (updates.length === 0) {
|
|
678
|
-
console.log(chalk.gray(' No progress updates yet. Agents post them with `agents feed post "…"`.'));
|
|
692
|
+
console.log(chalk.gray(' No progress updates yet. Agents post them with `agents feed post --title "…" "…"`.'));
|
|
679
693
|
return;
|
|
680
694
|
}
|
|
681
695
|
for (const ev of updates) {
|
|
@@ -2,7 +2,9 @@ import * as os from 'os';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { die, humanDuration, relTime, truncate, visibleWidth } from '../lib/format.js';
|
|
5
|
+
import { setHelpSections } from '../lib/help.js';
|
|
5
6
|
import { listBoxes, readBox, mailboxDir, isValidMailboxId, watchMessages, } from '../lib/mailbox.js';
|
|
7
|
+
import { gcMailbox } from '../lib/mailbox-gc.js';
|
|
6
8
|
import { GLYPH, masthead, sparkline, aggregate, hourlyCounts, graphEdges, } from '../lib/comms-render.js';
|
|
7
9
|
import { getMailboxRootDir } from '../lib/state.js';
|
|
8
10
|
import { getActiveSessions } from '../lib/session/active.js';
|
|
@@ -262,6 +264,21 @@ function renderBetween(boxes, a, b, json) {
|
|
|
262
264
|
console.log(` ${truncate(m.text.replace(/\s+/g, ' ').trim(), 100)}`);
|
|
263
265
|
}
|
|
264
266
|
}
|
|
267
|
+
async function runGcCommand(opts) {
|
|
268
|
+
const sessions = await getActiveSessions();
|
|
269
|
+
const activeBoxIds = new Set(sessions.map(mailboxIdForActiveSession).filter((id) => !!id));
|
|
270
|
+
const result = gcMailbox(activeBoxIds);
|
|
271
|
+
if (opts.json) {
|
|
272
|
+
console.log(JSON.stringify(result, null, 2));
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (result.messagesDroppedDead === 0 && result.messagesDroppedExpired === 0) {
|
|
276
|
+
console.log(chalk.dim(`gc: ${result.boxesScanned} boxes scanned, no pending messages dropped.`));
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
console.log(chalk.yellow(`gc: ${result.messagesDroppedDead} dead-box messages, ${result.messagesDroppedExpired} expired messages dropped ` +
|
|
280
|
+
`(${result.boxesScanned} boxes scanned, ${result.deadBoxes} dead boxes).`));
|
|
281
|
+
}
|
|
265
282
|
function renderGraph(boxes, filters, json) {
|
|
266
283
|
const nonEmpty = boxes.filter((b) => b.messages.length > 0);
|
|
267
284
|
const msgs = aggregate(nonEmpty).filter((m) => matchesFilters(m, m.toLabel, m.box, filters));
|
|
@@ -313,7 +330,7 @@ function newestMessage(box) {
|
|
|
313
330
|
return box.messages.reduce((a, b) => (a.ts >= b.ts ? a : b));
|
|
314
331
|
}
|
|
315
332
|
export function registerMailboxesCommand(program) {
|
|
316
|
-
program
|
|
333
|
+
const cmd = program
|
|
317
334
|
.command('mailboxes')
|
|
318
335
|
.alias('mailbox')
|
|
319
336
|
.argument('[id]', 'A mailbox id (session UUID / teams agentId) to inspect in full')
|
|
@@ -387,4 +404,25 @@ export function registerMailboxesCommand(program) {
|
|
|
387
404
|
}
|
|
388
405
|
renderOverview(boxes, limit, filters);
|
|
389
406
|
});
|
|
407
|
+
const gcCmd = cmd
|
|
408
|
+
.command('gc')
|
|
409
|
+
.description('Run a liveness sweep: archive pending messages in dead boxes and prune stale consumed mail.')
|
|
410
|
+
.option('--json', 'Emit the GC result as JSON')
|
|
411
|
+
.action(async (_opts, command) => runGcCommand({ json: command.optsWithGlobals().json === true }));
|
|
412
|
+
setHelpSections(gcCmd, {
|
|
413
|
+
examples: `
|
|
414
|
+
# One-shot sweep using the live session set as the liveness source
|
|
415
|
+
agents mailboxes gc
|
|
416
|
+
|
|
417
|
+
# Machine-readable summary (for scripts / monitors)
|
|
418
|
+
agents mailboxes gc --json
|
|
419
|
+
`,
|
|
420
|
+
notes: `
|
|
421
|
+
A box is considered dead when no live session (the same source
|
|
422
|
+
\`agents sessions --active\` uses) owns it. Pending messages in dead boxes
|
|
423
|
+
are archived as \`dropped: dead\`; expired messages in live boxes are
|
|
424
|
+
archived as \`dropped: expired\'. Both surfaces a failure receipt back to
|
|
425
|
+
the feed store when the message carried a blockId.
|
|
426
|
+
`,
|
|
427
|
+
});
|
|
390
428
|
}
|