@phnx-labs/agents-cli 1.22.41 → 1.22.43

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 (253) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +10 -4
  3. package/dist/bin/agents +0 -0
  4. package/dist/bootstrap.js +25 -7
  5. package/dist/cli/command-registry.d.ts +2 -3
  6. package/dist/cli/command-registry.js +4 -8
  7. package/dist/commands/accounts.d.ts +29 -0
  8. package/dist/commands/accounts.js +220 -29
  9. package/dist/commands/alias.d.ts +7 -4
  10. package/dist/commands/alias.js +34 -9
  11. package/dist/commands/auth.d.ts +9 -0
  12. package/dist/commands/auth.js +108 -0
  13. package/dist/commands/browser-sessions-picker.js +42 -62
  14. package/dist/commands/browser.js +30 -38
  15. package/dist/commands/computer-actions.d.ts +1 -1
  16. package/dist/commands/computer-actions.js +1 -1
  17. package/dist/commands/computer-sessions-picker.js +32 -55
  18. package/dist/commands/computer.d.ts +1 -1
  19. package/dist/commands/computer.js +22 -8
  20. package/dist/commands/daemon.js +1 -1
  21. package/dist/commands/doctor.d.ts +5 -3
  22. package/dist/commands/doctor.js +23 -8
  23. package/dist/commands/exec.js +3 -2
  24. package/dist/commands/feed.js +2 -31
  25. package/dist/commands/harness.js +3 -3
  26. package/dist/commands/hooks.js +1 -1
  27. package/dist/commands/import.js +1 -1
  28. package/dist/commands/insights.js +163 -85
  29. package/dist/commands/inspect.js +2 -2
  30. package/dist/commands/mine.js +1 -1
  31. package/dist/commands/monitors.js +1 -1
  32. package/dist/commands/org.d.ts +6 -0
  33. package/dist/commands/org.js +175 -0
  34. package/dist/commands/packages.js +1 -1
  35. package/dist/commands/profiles.js +2 -2
  36. package/dist/commands/prune.js +1 -1
  37. package/dist/commands/routines.js +44 -26
  38. package/dist/commands/run-account-picker.d.ts +19 -0
  39. package/dist/commands/run-account-picker.js +81 -0
  40. package/dist/commands/sessions-picker-factory.d.ts +40 -0
  41. package/dist/commands/sessions-picker-factory.js +67 -0
  42. package/dist/commands/sessions-picker.js +124 -122
  43. package/dist/commands/setup-browser.js +4 -4
  44. package/dist/commands/setup-mine.js +1 -1
  45. package/dist/commands/setup-preferences.js +2 -2
  46. package/dist/commands/setup.js +10 -4
  47. package/dist/commands/snapshot.d.ts +1 -1
  48. package/dist/commands/snapshot.js +4 -4
  49. package/dist/commands/status.d.ts +4 -2
  50. package/dist/commands/status.js +10 -7
  51. package/dist/commands/sync.d.ts +1 -0
  52. package/dist/commands/sync.js +30 -3
  53. package/dist/commands/versions.js +1 -1
  54. package/dist/commands/view.js +7 -1
  55. package/dist/commands/webhook.js +1 -1
  56. package/dist/lib/account-capabilities.d.ts +10 -0
  57. package/dist/lib/account-capabilities.js +34 -0
  58. package/dist/lib/accounting/rotate.d.ts +5 -3
  59. package/dist/lib/accounting/rotate.js +36 -7
  60. package/dist/lib/accounting/usage.d.ts +18 -4
  61. package/dist/lib/accounting/usage.js +138 -27
  62. package/dist/lib/agent-spec/agents.d.ts +584 -0
  63. package/dist/lib/agent-spec/agents.js +3127 -0
  64. package/dist/lib/agent-spec/provider.js +1 -1
  65. package/dist/lib/agents.d.ts +2 -584
  66. package/dist/lib/agents.js +2 -3127
  67. package/dist/lib/browser/drivers/local.d.ts +8 -2
  68. package/dist/lib/browser/drivers/local.js +8 -2
  69. package/dist/lib/browser/drivers/ssh.d.ts +8 -2
  70. package/dist/lib/browser/drivers/ssh.js +10 -4
  71. package/dist/lib/browser/hygiene.d.ts +1 -1
  72. package/dist/lib/browser/hygiene.js +5 -1
  73. package/dist/lib/browser/profiles.d.ts +74 -1
  74. package/dist/lib/browser/profiles.js +114 -5
  75. package/dist/lib/browser/runtime-state.d.ts +11 -5
  76. package/dist/lib/browser/runtime-state.js +13 -9
  77. package/dist/lib/browser/service.d.ts +68 -30
  78. package/dist/lib/browser/service.js +305 -194
  79. package/dist/lib/browser/types.d.ts +95 -3
  80. package/dist/lib/browser/types.js +51 -0
  81. package/dist/lib/catchup.js +1 -1
  82. package/dist/lib/cloud/rush.d.ts +0 -15
  83. package/dist/lib/cloud/rush.js +0 -35
  84. package/dist/lib/computer/actions.d.ts +1 -1
  85. package/dist/lib/computer/actions.js +1 -1
  86. package/dist/lib/{computer-rpc.js → computer/computer-rpc.js} +5 -5
  87. package/dist/lib/computer/dispatch.d.ts +1 -1
  88. package/dist/lib/computer/download.d.ts +1 -1
  89. package/dist/lib/computer/download.js +3 -3
  90. package/dist/lib/{ssh-tunnel.d.ts → computer/ssh-tunnel.d.ts} +1 -1
  91. package/dist/lib/{ssh-tunnel.js → computer/ssh-tunnel.js} +12 -12
  92. package/dist/lib/crabbox/runtimes.d.ts +6 -2
  93. package/dist/lib/crabbox/runtimes.js +21 -5
  94. package/dist/lib/daemon/daemon.js +87 -59
  95. package/dist/lib/{runner.d.ts → daemon/runner.d.ts} +28 -11
  96. package/dist/lib/{runner.js → daemon/runner.js} +143 -123
  97. package/dist/lib/devices/doctor-findings.d.ts +1 -1
  98. package/dist/lib/devices/doctor-findings.js +4 -4
  99. package/dist/lib/devices/fleet-inventory.js +1 -1
  100. package/dist/lib/devices/harness-inventory.js +1 -1
  101. package/dist/lib/devices/health-report.js +1 -1
  102. package/dist/lib/devices/registry.js +4 -22
  103. package/dist/lib/doctor-diff.d.ts +1 -1
  104. package/dist/lib/doctor-diff.js +1 -1
  105. package/dist/lib/drift-sync.d.ts +1 -1
  106. package/dist/lib/drift-sync.js +2 -2
  107. package/dist/lib/drift.d.ts +1 -1
  108. package/dist/lib/drift.js +1 -1
  109. package/dist/lib/entitlement.d.ts +31 -0
  110. package/dist/lib/entitlement.js +137 -0
  111. package/dist/lib/exec.js +52 -205
  112. package/dist/lib/feed/feed.js +2 -8
  113. package/dist/lib/format.d.ts +9 -0
  114. package/dist/lib/format.js +16 -0
  115. package/dist/lib/fs-atomic.d.ts +7 -0
  116. package/dist/lib/fs-atomic.js +9 -0
  117. package/dist/lib/github/pr-mergeable.d.ts +49 -0
  118. package/dist/lib/github/pr-mergeable.js +157 -0
  119. package/dist/lib/github/pr-verdict.d.ts +67 -0
  120. package/dist/lib/github/pr-verdict.js +72 -0
  121. package/dist/lib/harness/adapter.d.ts +161 -0
  122. package/dist/lib/harness/adapter.js +47 -0
  123. package/dist/lib/harness/adapters/claude.d.ts +2 -0
  124. package/dist/lib/harness/adapters/claude.js +110 -0
  125. package/dist/lib/harness/adapters/codex.d.ts +2 -0
  126. package/dist/lib/harness/adapters/codex.js +83 -0
  127. package/dist/lib/harness/adapters/copilot.d.ts +2 -0
  128. package/dist/lib/harness/adapters/copilot.js +23 -0
  129. package/dist/lib/harness/adapters/cursor.d.ts +2 -0
  130. package/dist/lib/harness/adapters/cursor.js +38 -0
  131. package/dist/lib/harness/adapters/droid.d.ts +2 -0
  132. package/dist/lib/harness/adapters/droid.js +17 -0
  133. package/dist/lib/harness/adapters/grok.d.ts +2 -0
  134. package/dist/lib/harness/adapters/grok.js +14 -0
  135. package/dist/lib/harness/adapters/kimi.d.ts +2 -0
  136. package/dist/lib/harness/adapters/kimi.js +49 -0
  137. package/dist/lib/harness/adapters/muse.d.ts +2 -0
  138. package/dist/lib/harness/adapters/muse.js +44 -0
  139. package/dist/lib/harness/adapters/opencode.d.ts +2 -0
  140. package/dist/lib/harness/adapters/opencode.js +14 -0
  141. package/dist/lib/harness/exec-config-version.d.ts +8 -0
  142. package/dist/lib/harness/exec-config-version.js +22 -0
  143. package/dist/lib/harness/index.d.ts +1 -0
  144. package/dist/lib/harness/index.js +34 -0
  145. package/dist/lib/{hooks.d.ts → hooks/install.d.ts} +1 -1
  146. package/dist/lib/{hooks.js → hooks/install.js} +11 -11
  147. package/dist/lib/hosts/passthrough.d.ts +10 -0
  148. package/dist/lib/hosts/passthrough.js +28 -6
  149. package/dist/lib/hosts/session-index.js +1 -1
  150. package/dist/lib/import.js +1 -1
  151. package/dist/lib/installations/index.d.ts +4 -4
  152. package/dist/lib/installations/index.js +3 -4
  153. package/dist/lib/installations/migrate.js +6 -6
  154. package/dist/lib/{shims.d.ts → installations/shims.d.ts} +2 -2
  155. package/dist/lib/{shims.js → installations/shims.js} +13 -115
  156. package/dist/lib/installations/store.d.ts +141 -3
  157. package/dist/lib/installations/store.js +475 -5
  158. package/dist/lib/installations/versions.d.ts +40 -125
  159. package/dist/lib/installations/versions.js +57 -462
  160. package/dist/lib/isolation-boundary-report.d.ts +1 -1
  161. package/dist/lib/isolation-boundary-report.js +1 -1
  162. package/dist/lib/mailbox-gc.js +2 -3
  163. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  164. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  165. package/dist/lib/menubar/install-menubar.js +19 -8
  166. package/dist/lib/monitors/config.d.ts +2 -2
  167. package/dist/lib/monitors/config.js +3 -2
  168. package/dist/lib/monitors/dispatch.d.ts +1 -1
  169. package/dist/lib/monitors/dispatch.js +2 -2
  170. package/dist/lib/monitors/state.d.ts +1 -1
  171. package/dist/lib/origin-machine.d.ts +18 -0
  172. package/dist/lib/origin-machine.js +34 -0
  173. package/dist/lib/perf/db.js +1 -1
  174. package/dist/lib/perf/spool.js +1 -1
  175. package/dist/lib/plugins/plugins.js +1 -1
  176. package/dist/lib/plugins/skills.js +1 -1
  177. package/dist/lib/prix-account.d.ts +158 -0
  178. package/dist/lib/prix-account.js +214 -0
  179. package/dist/lib/profiles.d.ts +7 -0
  180. package/dist/lib/profiles.js +26 -1
  181. package/dist/lib/project-key.js +2 -2
  182. package/dist/lib/project-launch.js +1 -1
  183. package/dist/lib/refresh.js +2 -2
  184. package/dist/lib/resource-inventory.d.ts +1 -1
  185. package/dist/lib/resource-inventory.js +1 -1
  186. package/dist/lib/rules/rules.js +1 -1
  187. package/dist/lib/sandbox.d.ts +24 -0
  188. package/dist/lib/sandbox.js +101 -0
  189. package/dist/lib/scheduling/routines.d.ts +18 -9
  190. package/dist/lib/scheduling/routines.js +17 -9
  191. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  192. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  193. package/dist/lib/secrets/agent.js +9 -1
  194. package/dist/lib/secrets/index.js +1 -1
  195. package/dist/lib/secrets/list-filter.js +1 -1
  196. package/dist/lib/self-heal/checks/hook-runtime.js +1 -1
  197. package/dist/lib/self-heal/checks/path.js +1 -1
  198. package/dist/lib/self-heal/checks/shadowing.js +1 -1
  199. package/dist/lib/self-heal/checks/shims.js +1 -1
  200. package/dist/lib/service-manifest.d.ts +20 -0
  201. package/dist/lib/service-manifest.js +34 -0
  202. package/dist/lib/session/cloud.js +1 -1
  203. package/dist/lib/session/db.js +1 -1
  204. package/dist/lib/session/discover.d.ts +39 -0
  205. package/dist/lib/session/discover.js +3 -3
  206. package/dist/lib/session/fork.js +1 -1
  207. package/dist/lib/session/live-metadata.js +1 -1
  208. package/dist/lib/session/origin-machine.d.ts +1 -18
  209. package/dist/lib/session/origin-machine.js +4 -34
  210. package/dist/lib/session/parse.js +24 -42
  211. package/dist/lib/session/recovery.js +1 -1
  212. package/dist/lib/session/relative-time.d.ts +1 -44
  213. package/dist/lib/session/relative-time.js +4 -120
  214. package/dist/lib/session/remote-list.js +1 -1
  215. package/dist/lib/session/remote.js +2 -2
  216. package/dist/lib/session/short-id.d.ts +1 -17
  217. package/dist/lib/session/short-id.js +4 -20
  218. package/dist/lib/session/width.d.ts +1 -29
  219. package/dist/lib/session/width.js +4 -101
  220. package/dist/lib/snapshot.d.ts +2 -2
  221. package/dist/lib/snapshot.js +1 -1
  222. package/dist/lib/staleness/prune.d.ts +1 -1
  223. package/dist/lib/staleness/prune.js +1 -1
  224. package/dist/lib/staleness/writers/hooks.js +1 -1
  225. package/dist/lib/startup/command-registry.d.ts +4 -1
  226. package/dist/lib/startup/command-registry.js +12 -5
  227. package/dist/lib/teams/agents.js +2 -19
  228. package/dist/lib/teams/parsers.js +20 -26
  229. package/dist/lib/teams/placement-probe.js +1 -1
  230. package/dist/lib/teams/registry.js +2 -21
  231. package/dist/lib/teams/scheduler.js +1 -1
  232. package/dist/lib/text/relative-time.d.ts +44 -0
  233. package/dist/lib/text/relative-time.js +120 -0
  234. package/dist/lib/text/short-id.d.ts +17 -0
  235. package/dist/lib/text/short-id.js +20 -0
  236. package/dist/lib/text/width.d.ts +29 -0
  237. package/dist/lib/text/width.js +101 -0
  238. package/dist/lib/triggers/handlers.d.ts +7 -0
  239. package/dist/lib/triggers/handlers.js +9 -1
  240. package/dist/lib/triggers/webhook.d.ts +1 -0
  241. package/dist/lib/triggers/webhook.js +3 -2
  242. package/dist/lib/uninstall.js +1 -1
  243. package/dist/lib/version.d.ts +9 -2
  244. package/dist/lib/version.js +11 -9
  245. package/dist/lib/view-types.d.ts +4 -0
  246. package/package.json +1 -1
  247. package/dist/commands/tickets.d.ts +0 -2
  248. package/dist/commands/tickets.js +0 -43
  249. package/dist/lib/observe-aliases.d.ts +0 -30
  250. package/dist/lib/observe-aliases.js +0 -38
  251. package/dist/lib/tickets/list.d.ts +0 -53
  252. package/dist/lib/tickets/list.js +0 -153
  253. /package/dist/lib/{computer-rpc.d.ts → computer/computer-rpc.d.ts} +0 -0
@@ -7,36 +7,16 @@
7
7
  * Reuses the same `itemPicker` + `buildPreview` primitives as the ordinary
8
8
  * session picker (`sessions-picker.ts`) — same search/filter/quit help, and the
9
9
  * preview pane for a linked task IS the canonical session digest, not a
10
- * second renderer.
10
+ * second renderer. Interactive routing and the browse loop live in
11
+ * `sessions-picker-factory.ts` (shared with the computer twin).
11
12
  */
12
13
  import chalk from 'chalk';
13
14
  import { itemPicker } from '../lib/picker.js';
14
- import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
15
+ import { isPromptCancelled } from './utils.js';
15
16
  import { buildPreview } from './sessions-picker.js';
17
+ import { createSessionsPickerCommand } from './sessions-picker-factory.js';
16
18
  import { runBrowserSessions, buildBrowserSessionRows, matchesBrowserSessionRow, openArtifact, formatBytes, } from '../lib/browser/sessions-list.js';
17
19
  import { formatRelativeTime } from '../lib/session/relative-time.js';
18
- /** True when the interactive picker should open instead of the printed table:
19
- * a real TTY, no `--json`/`--open`, and `--no-interactive` not set. */
20
- export function shouldOpenInteractiveBrowserSessions(opts, isTTY) {
21
- return opts.interactive !== false && !opts.json && opts.open === undefined && isTTY;
22
- }
23
- /**
24
- * Shared entry point for `agents browser sessions` and `agents sessions
25
- * --browser`. Replaces direct calls to `runBrowserSessions` at both call
26
- * sites so the interactive routing decision lives in one place.
27
- */
28
- export async function runBrowserSessionsCommand(opts) {
29
- if (!shouldOpenInteractiveBrowserSessions(opts, isInteractiveTerminal())) {
30
- runBrowserSessions({ profile: opts.profile, open: opts.open, json: opts.json });
31
- return;
32
- }
33
- const rows = buildBrowserSessionRows(opts.profile);
34
- if (rows.length === 0) {
35
- console.log(`No browser captures found${opts.profile ? ` for profile "${opts.profile}"` : ''}.`);
36
- return;
37
- }
38
- await browseAndOpen(rows);
39
- }
40
20
  const KIND_LABEL = {
41
21
  screenshot: 'shots',
42
22
  pdf: 'pdf',
@@ -116,44 +96,6 @@ function openAndReport(a) {
116
96
  if (!openArtifact(a.path))
117
97
  console.error(`Could not open ${a.path}`);
118
98
  }
119
- /** Task-level picker with a captures preview, then an artifact drill-down when
120
- * a task holds more than one capture. Loops until the user quits (esc) so a
121
- * capture-heavy task can be browsed and opened repeatedly in one session. */
122
- async function browseAndOpen(rows) {
123
- for (;;) {
124
- let picked;
125
- try {
126
- picked = await itemPicker({
127
- message: 'Browser sessions:',
128
- items: rows,
129
- filter: (query) => (query.trim() ? rows.filter((r) => matchesBrowserSessionRow(r, query)) : rows),
130
- labelFor: (row) => formatRowLabel(row),
131
- buildPreview: (row) => buildRowPreview(row),
132
- emptyMessage: 'No browser sessions match.',
133
- enterHint: 'open capture',
134
- });
135
- }
136
- catch (err) {
137
- if (isPromptCancelled(err))
138
- return;
139
- throw err;
140
- }
141
- if (!picked)
142
- return;
143
- const row = picked.item;
144
- if (row.artifacts.length === 0) {
145
- console.log('No captures in this task yet.');
146
- continue;
147
- }
148
- if (row.artifacts.length === 1) {
149
- openAndReport(row.artifacts[0]);
150
- continue;
151
- }
152
- const artifact = await pickArtifact(row);
153
- if (artifact)
154
- openAndReport(artifact);
155
- }
156
- }
157
99
  /** Second-level picker over one row's captures, newest first. Enter opens the
158
100
  * highlighted capture; esc returns to the task list. */
159
101
  async function pickArtifact(row) {
@@ -177,3 +119,41 @@ async function pickArtifact(row) {
177
119
  throw err;
178
120
  }
179
121
  }
122
+ const browserSessionsPicker = createSessionsPickerCommand({
123
+ requireOpenUndefined: true,
124
+ runFlat: (opts) => runBrowserSessions({ profile: opts.profile, open: opts.open, json: opts.json }),
125
+ buildRows: (opts) => buildBrowserSessionRows(opts.profile),
126
+ emptyMessage: (opts) => `No browser captures found${opts.profile ? ` for profile "${opts.profile}"` : ''}.`,
127
+ message: 'Browser sessions:',
128
+ matches: matchesBrowserSessionRow,
129
+ labelFor: formatRowLabel,
130
+ buildPreview: buildRowPreview,
131
+ emptyFilterMessage: 'No browser sessions match.',
132
+ enterHint: 'open capture',
133
+ onOpen: async (row) => {
134
+ if (row.artifacts.length === 0) {
135
+ console.log('No captures in this task yet.');
136
+ return;
137
+ }
138
+ if (row.artifacts.length === 1) {
139
+ openAndReport(row.artifacts[0]);
140
+ return;
141
+ }
142
+ const artifact = await pickArtifact(row);
143
+ if (artifact)
144
+ openAndReport(artifact);
145
+ },
146
+ });
147
+ /** True when the interactive picker should open instead of the printed table:
148
+ * a real TTY, no `--json`/`--open`, and `--no-interactive` not set. */
149
+ export function shouldOpenInteractiveBrowserSessions(opts, isTTY) {
150
+ return browserSessionsPicker.shouldOpen(opts, isTTY);
151
+ }
152
+ /**
153
+ * Shared entry point for `agents browser sessions` and `agents sessions
154
+ * --browser`. Replaces direct calls to `runBrowserSessions` at both call
155
+ * sites so the interactive routing decision lives in one place.
156
+ */
157
+ export async function runBrowserSessionsCommand(opts) {
158
+ await browserSessionsPicker.run(opts);
159
+ }
@@ -1,7 +1,7 @@
1
1
  import chalk from 'chalk';
2
2
  import * as fs from 'fs';
3
3
  import * as path from 'path';
4
- import { listProfiles, getProfile, createProfile, deleteProfile, ensureDefaultBrowserProfile, getConfiguredDefaultProfileName, DEFAULT_BROWSER_PROFILE_NAME, getProfileRuntimeDir, extractConfiguredPort, findFreeProfilePort, getEndpointPresets, listProfilesWithScope, formatProfilesTable, } from '../lib/browser/profiles.js';
4
+ import { listProfiles, getProfile, createProfile, deleteProfile, getConfiguredDefaultProfileName, resolveProfileRef, resolveProfileRefForStart, getProfileRuntimeDir, extractConfiguredPort, findFreeProfilePort, getEndpointPresets, listProfilesWithScope, formatProfilesTable, } from '../lib/browser/profiles.js';
5
5
  import { resolveActor } from '../lib/actor.js';
6
6
  import { loginsForProfile, profilesLoggedInto, serviceForUrl, loginsWithAccountsForProfile, accountsForProfile, credKeysForService, AUTH_SIGNATURES, } from '../lib/browser/login-detection.js';
7
7
  import { parseSecretRef } from '../lib/browser/secret-ref.js';
@@ -576,12 +576,12 @@ function registerProfilesCommands(browser) {
576
576
  other machine — pass --fleet only when the profile really is fleet config.
577
577
 
578
578
  In \`list\`, the \`*\` marker means "this machine's configured default"
579
- (\`agents browser start\` with no --profile). It is NOT the same thing as the
580
- profile NAMED \`default\`, which is the auto-detected one; they are often
581
- different profiles.
579
+ (\`agents browser start\` with no --profile). The auto-detected profile is
580
+ named \`auto-chrome\`; \`default\` is only an ALIAS for whichever profile
581
+ this machine is configured to use, resolved the same way by every command.
582
582
 
583
583
  \`prune\` never removes a profile that is in use, the configured default, the
584
- auto \`default\`, or (without --fleet) a fleet-synced one.
584
+ auto-detected \`auto-chrome\`, or (without --fleet) a fleet-synced one.
585
585
  `,
586
586
  });
587
587
  profiles
@@ -846,33 +846,20 @@ function registerTaskCommands(browser) {
846
846
  console.error(err instanceof Error ? err.message : String(err));
847
847
  process.exit(1);
848
848
  }
849
- let profileName = opts.profile;
850
- if (!profileName) {
851
- try {
852
- const detected = await ensureDefaultBrowserProfile();
853
- profileName = detected.name;
854
- }
855
- catch (err) {
856
- console.error(err instanceof Error ? err.message : String(err));
857
- process.exit(1);
858
- }
849
+ // One resolution order for every command (RUSH-2709): `--profile default`
850
+ // means the same profile here as it does in stop / status / navigate.
851
+ // `start` is the one command that LAUNCHES, so its implicit path goes
852
+ // through ensureDefaultBrowserProfile, which additionally verifies the
853
+ // resolved default can launch on THIS machine and regenerates it when it
854
+ // can't (the "Custom binary not found" repair) — a filter-only command
855
+ // must not warn or rewrite a profile, so that check lives only here.
856
+ let profileName;
857
+ try {
858
+ profileName = await resolveProfileRefForStart(opts.profile);
859
859
  }
860
- else if (profileName === DEFAULT_BROWSER_PROFILE_NAME) {
861
- // Explicit `--profile default` honors the configured default too, so an
862
- // agent that hardcodes the reserved `default` name still lands on the
863
- // user's chosen profile (e.g. their logged-in Comet) rather than a
864
- // literal `default` (which auto-detects Chrome). Narrowly scoped: only
865
- // the reserved name, only here in `start`.
866
- const configured = getConfiguredDefaultProfileName();
867
- if (configured && configured !== DEFAULT_BROWSER_PROFILE_NAME) {
868
- const target = await getProfile(configured);
869
- if (target) {
870
- profileName = target.name;
871
- }
872
- else {
873
- console.error(`warning: configured default profile "${configured}" not found; using "${DEFAULT_BROWSER_PROFILE_NAME}".`);
874
- }
875
- }
860
+ catch (err) {
861
+ console.error(err instanceof Error ? err.message : String(err));
862
+ process.exit(1);
876
863
  }
877
864
  // Pre-check the profile locally so we fail fast with a helpful error
878
865
  // instead of round-tripping a generic "Profile not found" through the daemon.
@@ -1001,15 +988,16 @@ function registerTaskCommands(browser) {
1001
988
  .option('-p, --profile <name>', 'Detach the whole profile (incl. composite "name@endpoint") instead of stopping a single task')
1002
989
  .action(async (opts) => {
1003
990
  if (opts.profile) {
991
+ const profile = (await resolveProfileRef(opts.profile)) ?? opts.profile;
1004
992
  const response = await sendIPCRequest({
1005
993
  action: 'stop',
1006
- profile: opts.profile,
994
+ profile,
1007
995
  });
1008
996
  if (!response.ok) {
1009
997
  console.error(response.error);
1010
998
  process.exit(1);
1011
999
  }
1012
- console.log(`Stopped profile: ${opts.profile}`);
1000
+ console.log(`Stopped profile: ${profile}`);
1013
1001
  return;
1014
1002
  }
1015
1003
  const task = resolveTaskName(opts);
@@ -1083,7 +1071,7 @@ function registerTaskCommands(browser) {
1083
1071
  action: 'navigate',
1084
1072
  task,
1085
1073
  url,
1086
- profile: opts.profile,
1074
+ profile: opts.profile ? await resolveProfileRef(opts.profile) : undefined,
1087
1075
  });
1088
1076
  if (!response.ok) {
1089
1077
  console.error(response.error);
@@ -1108,7 +1096,7 @@ function registerTaskCommands(browser) {
1108
1096
  action: 'tab-add',
1109
1097
  task,
1110
1098
  url: opts.url,
1111
- profile: opts.profile,
1099
+ profile: opts.profile ? await resolveProfileRef(opts.profile) : undefined,
1112
1100
  });
1113
1101
  if (!response.ok) {
1114
1102
  console.error(response.error);
@@ -1369,7 +1357,7 @@ function registerTaskCommands(browser) {
1369
1357
  try {
1370
1358
  response = await sendIPCRequest({
1371
1359
  action: 'status',
1372
- profile: opts.profile,
1360
+ profile: opts.profile ? await resolveProfileRef(opts.profile) : undefined,
1373
1361
  }, { autoStartDaemon: false });
1374
1362
  }
1375
1363
  catch (err) {
@@ -1458,7 +1446,11 @@ function registerTaskCommands(browser) {
1458
1446
  // pid 0 means the daemon attached to a browser we didn't launch — no
1459
1447
  // tracked pid. Render it as "attached" rather than the literal 0.
1460
1448
  const pidLabel = profile.pid ? `pid ${profile.pid}` : 'attached';
1461
- console.log(`\n${profile.name} (${portLabel}, ${pidLabel})`);
1449
+ // `profile.name` is the BARE profile; the endpoint is its own field,
1450
+ // so the row reads `comet-local (endpoint: endpoint-0, port …)`
1451
+ // instead of the raw `comet-local@endpoint-0` key (RUSH-2709).
1452
+ const endpointLabel = profile.endpoint ? `endpoint: ${profile.endpoint}, ` : '';
1453
+ console.log(`\n${profile.name} (${endpointLabel}${portLabel}, ${pidLabel})`);
1462
1454
  if (profile.tasks.length === 0) {
1463
1455
  console.log(' No active tasks');
1464
1456
  }
@@ -1493,7 +1485,7 @@ function registerTaskCommands(browser) {
1493
1485
  .action(async (opts) => {
1494
1486
  const response = await sendIPCRequest({
1495
1487
  action: 'status',
1496
- profile: opts.profile,
1488
+ profile: opts.profile ? await resolveProfileRef(opts.profile) : undefined,
1497
1489
  });
1498
1490
  if (!response.ok) {
1499
1491
  if (opts.json) {
@@ -1,5 +1,5 @@
1
1
  import { Command } from 'commander';
2
- import { type ComputerClient, type RPCResponse } from '../lib/computer-rpc.js';
2
+ import { type ComputerClient, type RPCResponse } from '../lib/computer/computer-rpc.js';
3
3
  export { emitComputerAction, pickTarget, resolveTargetPidDecision, type AppInfo } from '../lib/computer/actions.js';
4
4
  export declare function parseXY(s: string, flag: string): {
5
5
  x: number;
@@ -9,7 +9,7 @@
9
9
  import { execFileSync } from 'child_process';
10
10
  import * as fs from 'fs';
11
11
  import * as path from 'path';
12
- import { openComputerClient, describeTransport, } from '../lib/computer-rpc.js';
12
+ import { openComputerClient, describeTransport, } from '../lib/computer/computer-rpc.js';
13
13
  import { emitComputerAction, resolveTargetPidDecision } from '../lib/computer/actions.js';
14
14
  export { emitComputerAction, pickTarget, resolveTargetPidDecision } from '../lib/computer/actions.js';
15
15
  // Pure target picker — exercised by unit tests. Precedence: explicit --pid,
@@ -13,39 +13,14 @@
13
13
  * computer run has no on-disk artifact of its own to open (see
14
14
  * `lib/computer/sessions-list.ts`'s module docblock), so `enter` prints the
15
15
  * run's full action list rather than opening a file, and the picker keeps
16
- * browsing afterward instead of exiting.
16
+ * browsing afterward instead of exiting. Interactive routing and the browse
17
+ * loop live in `sessions-picker-factory.ts` (shared with the browser twin).
17
18
  */
18
19
  import chalk from 'chalk';
19
- import { itemPicker } from '../lib/picker.js';
20
- import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
21
20
  import { buildPreview } from './sessions-picker.js';
21
+ import { createSessionsPickerCommand } from './sessions-picker-factory.js';
22
22
  import { runComputerSessions, printComputerSessionRows, buildComputerSessionRows, matchesComputerSessionRow, formatRowActions, } from '../lib/computer/sessions-list.js';
23
23
  import { formatRelativeTime } from '../lib/session/relative-time.js';
24
- /** True when the interactive picker should open instead of the printed table:
25
- * a real TTY, no `--json`, and `--no-interactive` not set. */
26
- export function shouldOpenInteractiveComputerSessions(opts, isTTY) {
27
- return opts.interactive !== false && !opts.json && isTTY;
28
- }
29
- /**
30
- * Shared entry point for `agents computer sessions` and `agents sessions
31
- * --computer`. Mirrors `runBrowserSessionsCommand`'s interactive-routing
32
- * split so both call sites stay in lockstep.
33
- */
34
- export async function runComputerSessionsCommand(opts) {
35
- if (!shouldOpenInteractiveComputerSessions(opts, isInteractiveTerminal())) {
36
- if (opts.rows)
37
- printComputerSessionRows(opts.rows, { limit: opts.limit, json: opts.json });
38
- else
39
- runComputerSessions({ machine: opts.machine, limit: opts.limit, json: opts.json });
40
- return;
41
- }
42
- const rows = opts.rows ?? buildComputerSessionRows({ machine: opts.machine });
43
- if (rows.length === 0) {
44
- console.log(`No computer actions recorded${opts.machine ? ` for machine "${opts.machine}"` : ''}.`);
45
- return;
46
- }
47
- await browseComputerSessions(rows);
48
- }
49
24
  function rowLinkSummary(row) {
50
25
  if (row.linkStatus === 'linked' && row.linkedSession) {
51
26
  const s = row.linkedSession;
@@ -111,31 +86,33 @@ function printRunDetail(row) {
111
86
  console.log('(no driving actions recorded)');
112
87
  console.log('');
113
88
  }
114
- /** Run-level picker with an action-list preview. Loops until the user quits
115
- * (esc) so a session's whole run history can be browsed repeatedly in one
116
- * sitting — mirroring `browseAndOpen`'s loop, minus the artifact drill-down
117
- * a computer run has none of. */
118
- async function browseComputerSessions(rows) {
119
- for (;;) {
120
- let picked;
121
- try {
122
- picked = await itemPicker({
123
- message: 'Computer sessions:',
124
- items: rows,
125
- filter: (query) => (query.trim() ? rows.filter((r) => matchesComputerSessionRow(r, query)) : rows),
126
- labelFor: (row) => formatRowLabel(row),
127
- buildPreview: (row) => buildRowPreview(row),
128
- emptyMessage: 'No computer sessions match.',
129
- enterHint: 'view actions',
130
- });
131
- }
132
- catch (err) {
133
- if (isPromptCancelled(err))
134
- return;
135
- throw err;
136
- }
137
- if (!picked)
138
- return;
139
- printRunDetail(picked.item);
140
- }
89
+ const computerSessionsPicker = createSessionsPickerCommand({
90
+ runFlat: (opts) => {
91
+ if (opts.rows)
92
+ printComputerSessionRows(opts.rows, { limit: opts.limit, json: opts.json });
93
+ else
94
+ runComputerSessions({ machine: opts.machine, limit: opts.limit, json: opts.json });
95
+ },
96
+ buildRows: (opts) => opts.rows ?? buildComputerSessionRows({ machine: opts.machine }),
97
+ emptyMessage: (opts) => `No computer actions recorded${opts.machine ? ` for machine "${opts.machine}"` : ''}.`,
98
+ message: 'Computer sessions:',
99
+ matches: matchesComputerSessionRow,
100
+ labelFor: formatRowLabel,
101
+ buildPreview: buildRowPreview,
102
+ emptyFilterMessage: 'No computer sessions match.',
103
+ enterHint: 'view actions',
104
+ onOpen: printRunDetail,
105
+ });
106
+ /** True when the interactive picker should open instead of the printed table:
107
+ * a real TTY, no `--json`, and `--no-interactive` not set. */
108
+ export function shouldOpenInteractiveComputerSessions(opts, isTTY) {
109
+ return computerSessionsPicker.shouldOpen(opts, isTTY);
110
+ }
111
+ /**
112
+ * Shared entry point for `agents computer sessions` and `agents sessions
113
+ * --computer`. Mirrors `runBrowserSessionsCommand`'s interactive-routing
114
+ * split so both call sites stay in lockstep.
115
+ */
116
+ export async function runComputerSessionsCommand(opts) {
117
+ await computerSessionsPicker.run(opts);
141
118
  }
@@ -1,5 +1,5 @@
1
1
  import { Command } from 'commander';
2
- import { resolveHelperExec, resolveSocketPath } from '../lib/computer-rpc.js';
2
+ import { resolveHelperExec, resolveSocketPath } from '../lib/computer/computer-rpc.js';
3
3
  /**
4
4
  * Pure platform gate. The computer subsystem is macOS-only for LOCAL driving
5
5
  * (Accessibility / launchctl). It is NOT blocked off macOS when a remote daemon
@@ -3,8 +3,8 @@ import * as fs from 'fs';
3
3
  import * as os from 'os';
4
4
  import * as path from 'path';
5
5
  import { registerCommandGroups } from '../lib/help.js';
6
- import { openComputerClient, resolveHelperApp, resolveHelperExec, resolveSocketPath, resolveLogPath, resolvePolicyPath, resolvePeersPath, resolveTcpEndpoint, loadComputerAllowList, loadDefaultPeers, writeComputerPolicy, writeComputerPeers, } from '../lib/computer-rpc.js';
7
- import { setupRemoteHelper, startRemoteTunnel, stopRemoteHelper, hydrateRemoteEnvFromState, readRemoteState, resolveRemoteDevice, REMOTE_TASK_NAME, WIN_HELPER_EXE, } from '../lib/ssh-tunnel.js';
6
+ import { openComputerClient, resolveHelperApp, resolveHelperExec, resolveSocketPath, resolveLogPath, resolvePolicyPath, resolvePeersPath, resolveTcpEndpoint, loadComputerAllowList, loadDefaultPeers, writeComputerPolicy, writeComputerPeers, } from '../lib/computer/computer-rpc.js';
7
+ import { setupRemoteHelper, startRemoteTunnel, stopRemoteHelper, hydrateRemoteEnvFromState, readRemoteState, resolveRemoteDevice, REMOTE_TASK_NAME, WIN_HELPER_EXE, } from '../lib/computer/ssh-tunnel.js';
8
8
  import { sshExec } from '../lib/ssh-exec.js';
9
9
  import { encodePowershell } from '../lib/hosts/remote-cmd.js';
10
10
  import { registerActionCommands, withClient, unwrap, pickTarget, emitComputerAction } from './computer-actions.js';
@@ -14,7 +14,7 @@ import { makeClaudeResponder, resolveApiKey, DEFAULT_CLAUDE_MODEL, DEFAULT_CLAUD
14
14
  import { TASK_PREVIEW_MAX_CHARS } from '../lib/computer/sessions-list.js';
15
15
  import { runComputerSessionsCommand } from './computer-sessions-picker.js';
16
16
  import { truncate } from '../lib/feed/events.js';
17
- import { namespacedServiceLabel, serviceManifestHomeEnv } from '../lib/service-manifest.js';
17
+ import { namespacedServiceLabel, serviceManifestHomeEnv, serviceManagerRegistrationAllowed } from '../lib/service-manifest.js';
18
18
  // Help groups — mirror `agents browser` so the mental model carries over.
19
19
  const COMPUTER_HELP_GROUPS = [
20
20
  { title: 'Installation', names: ['setup'] },
@@ -564,6 +564,10 @@ export async function activateComputerHelperMacLocal() {
564
564
  const uid = process.getuid?.();
565
565
  if (typeof uid !== 'number')
566
566
  throw new Error('cannot resolve uid');
567
+ const reg = serviceManagerRegistrationAllowed();
568
+ if (!reg.allowed) {
569
+ throw new Error(reg.reason);
570
+ }
567
571
  const domain = `gui/${uid}`;
568
572
  // Render the policy file BEFORE bootstrap so the daemon reads a fresh allow
569
573
  // list at startup (fail-safe: missing/unparseable → everything denied).
@@ -843,11 +847,21 @@ function registerStopCommand(program) {
843
847
  process.exit(1);
844
848
  }
845
849
  const domain = `gui/${uid}`;
846
- try {
847
- execFileSync('/bin/launchctl', ['bootout', domain, plistPath], { stdio: 'pipe' });
848
- }
849
- catch {
850
- // already gone — fine
850
+ // Same invariant as the start path: a redirected-HOME process must never
851
+ // talk to the real launchd (RUSH-2968). Stop has a local fallback the
852
+ // plist/socket cleanup below — so skip the bootout with the reason
853
+ // rather than throwing like start does.
854
+ const reg = serviceManagerRegistrationAllowed();
855
+ if (!reg.allowed) {
856
+ console.warn(`skipping launchctl bootout: ${reg.reason}`);
857
+ }
858
+ else {
859
+ try {
860
+ execFileSync('/bin/launchctl', ['bootout', domain, plistPath], { stdio: 'pipe' });
861
+ }
862
+ catch {
863
+ // already gone — fine
864
+ }
851
865
  }
852
866
  // launchd unlinks the socket when the daemon exits; helper also has an
853
867
  // atexit unlink. Best-effort cleanup if either path didn't fire.
@@ -761,7 +761,7 @@ function runWebhooksList(json) {
761
761
  const port = hostedReceiverPort(r);
762
762
  const funnel = r.funnel ? chalk.cyan(` public :${r.funnel.publicPort}`) : chalk.gray(' localhost only');
763
763
  console.log(` 127.0.0.1:${String(port).padEnd(6)} ${chalk.gray(`bundle ${r.bundle}`)}${funnel}`);
764
- console.log(chalk.gray(` endpoints: /hooks/github, /hooks/linear · ${r.rateLimit ?? DEFAULT_WEBHOOK_RATE_LIMIT}/min per source`));
764
+ console.log(chalk.gray(` endpoints: /hooks/github, /hooks/linear, /hooks/slack · ${r.rateLimit ?? DEFAULT_WEBHOOK_RATE_LIMIT}/min per source`));
765
765
  }
766
766
  console.log(chalk.gray(`\nConfig: ${getDaemonWebhooksConfigPath()}`));
767
767
  console.log(chalk.gray('Changes take effect on the next daemon restart.'));
@@ -25,7 +25,7 @@ import type { Command } from 'commander';
25
25
  import { type FleetInventory } from '../lib/devices/fleet-divergence.js';
26
26
  import { type DoctorFinding } from '../lib/devices/doctor-findings.js';
27
27
  import { type VersionResourceReport } from '../lib/doctor-diff.js';
28
- import { type DuplicateVersionHook } from '../lib/hooks.js';
28
+ import { type DuplicateVersionHook } from '../lib/hooks/install.js';
29
29
  import { type SyncStatusRow, type OrphanRow } from '../lib/drift.js';
30
30
  import { type FetchStatusMarker } from '../lib/auto-pull.js';
31
31
  export declare function wrapLine(prefix: string, text: string, width?: number): string[];
@@ -78,9 +78,11 @@ export type IssueSeverity = 'critical' | 'warning' | 'info';
78
78
  *
79
79
  * Severity model (agent-agnostic — applies to every agent doctor inspects):
80
80
  * - critical (silent breakage): an unwired hook, a missing/unparseable
81
- * settings.json, a MISSING resource.
81
+ * settings.json, a MISSING hook or plugin.
82
82
  * - warning (stale / drift): a source layer behind origin, a DIVERGENT resource,
83
- * a stale / never-synced version.
83
+ * a stale / never-synced version, a MISSING resource of any other kind
84
+ * (see `missingResourceSeverity`, which reads this split from
85
+ * `FINDING_SEVERITY` — RUSH-2947).
84
86
  * - info (orphan): an EXTRA resource → `agents prune cleanup`.
85
87
  */
86
88
  export interface VerdictIssue {
@@ -1,4 +1,4 @@
1
- import { IsolationBoundaryError } from '../lib/shims.js';
1
+ import { IsolationBoundaryError } from '../lib/installations/shims.js';
2
2
  import { explainIsolationBoundary } from '../lib/isolation-boundary-report.js';
3
3
  import { addHostOption } from '../lib/hosts/option.js';
4
4
  import { buildRemoteAgentsInvocation } from '../lib/hosts/remote-cmd.js';
@@ -23,7 +23,7 @@ import { getVersionHomePath, listInstalledVersions, } from '../lib/installations
23
23
  import { resolveAgentTargets, AgentSpecError } from '../lib/agent-spec/index.js';
24
24
  import { loadManifest, isStale } from '../lib/staleness/index.js';
25
25
  import { diffVersionResources, DOCTOR_ALL_KINDS, } from '../lib/doctor-diff.js';
26
- import { checkVersionHookWiring, inspectDuplicateVersionHooks, registerHooksToSettings, repairManagedHookRuntimeArtifacts } from '../lib/hooks.js';
26
+ import { checkVersionHookWiring, inspectDuplicateVersionHooks, registerHooksToSettings, repairManagedHookRuntimeArtifacts } from '../lib/hooks/install.js';
27
27
  import { isVersionIsolated } from '../lib/installations/versions.js';
28
28
  import { computeDrift, checkSyncStatus, countOrphans, computeSourceBehind } from '../lib/drift.js';
29
29
  import { readAuthHealthCache, summarizeHostAuth } from '../lib/auth-health.js';
@@ -669,6 +669,19 @@ const AUTO_FIXABLE_CATEGORIES = new Set([
669
669
  export function verdictIsAutoFixable(v) {
670
670
  return v.issues.some((i) => AUTO_FIXABLE_CATEGORIES.has(i.category));
671
671
  }
672
+ /**
673
+ * Missing-resource severity for one `DoctorKind`, read from `FINDING_SEVERITY`
674
+ * (the fleet-mode rubric) so target mode agrees with it kind by kind instead of
675
+ * re-hardcoding its own. Hooks and plugins map to their dedicated critical
676
+ * finding kinds; every other kind maps to the generic 'missing-resource' warning.
677
+ */
678
+ function missingResourceSeverity(kind) {
679
+ if (kind === 'hooks')
680
+ return FINDING_SEVERITY['missing-hook'];
681
+ if (kind === 'plugins')
682
+ return FINDING_SEVERITY['missing-plugin'];
683
+ return FINDING_SEVERITY['missing-resource'];
684
+ }
672
685
  /**
673
686
  * Fold a version report's divergences into a triaged verdict — one severity-tagged
674
687
  * finding per unwired hook, missing/divergent/extra resource, and behind-origin
@@ -718,16 +731,18 @@ export function computeVerdict(report) {
718
731
  });
719
732
  }
720
733
  }
721
- // ── critical: missing resources (declared in sources, absent from home) ──
734
+ // ── missing resources (declared in sources, absent from home) — severity
735
+ // follows FINDING_SEVERITY: hooks/plugins are critical, everything else warns ──
722
736
  for (const kind of DOCTOR_ALL_KINDS) {
723
737
  for (const r of report.kinds[kind]) {
724
738
  if (r.status !== 'missing')
725
739
  continue;
740
+ const severity = missingResourceSeverity(kind);
726
741
  issues.push({
727
- severity: 'critical', category: 'missing', subject: r.name,
742
+ severity, category: 'missing', subject: r.name,
728
743
  impact: `declared in sources but absent from the version home (${kind})`,
729
744
  fix: fixCmd,
730
- text: `${r.name} missing`, color: 'red',
745
+ text: `${r.name} missing`, color: severity === 'critical' ? 'red' : 'yellow',
731
746
  });
732
747
  }
733
748
  }
@@ -1523,7 +1538,7 @@ export function registerDoctorCommand(program) {
1523
1538
  console.error(chalk.red('--adopt and --release are mutually exclusive; pass only one.'));
1524
1539
  process.exit(1);
1525
1540
  }
1526
- const { adoptShadowingLauncher, releaseAdoptedLauncher } = await import('../lib/shims.js');
1541
+ const { adoptShadowingLauncher, releaseAdoptedLauncher } = await import('../lib/installations/shims.js');
1527
1542
  const raw = (opts.adopt || opts.release);
1528
1543
  const agent = resolveAgentName(raw);
1529
1544
  if (!agent) {
@@ -1739,10 +1754,10 @@ export function registerDoctorCommand(program) {
1739
1754
  console.log(line);
1740
1755
  }
1741
1756
  // Point at the interactive reconcile when anything is out of sync — each
1742
- // finding carries its own fix, but `agents status` is the one place that
1757
+ // finding carries its own fix, but `agents sync status` is the one place that
1743
1758
  // reviews and applies them together (opt-in, never auto-fires here).
1744
1759
  if (syncRows.some((r) => r.status !== 'fresh' || (r.unwiredHooks ?? 0) > 0) || repoBehindMarkers.some((m) => m.behind > 0)) {
1745
- console.log(chalk.gray('\nRun `agents status` to review and sync what has drifted.'));
1760
+ console.log(chalk.gray('\nRun `agents sync status` to review and sync what has drifted.'));
1746
1761
  }
1747
1762
  // A routine runs on exactly one device. Each of these named several and
1748
1763
  // used to fire once per device — duplicate agent runs on every schedule.
@@ -3015,8 +3015,9 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
3015
3015
  let exitCode;
3016
3016
  let ranAgent = agent;
3017
3017
  let ranVersion = defaultVersion;
3018
- if (fallback.length > 0) {
3019
- // fallback requires a prompt enforced above, narrow the type here.
3018
+ if (fallback.length > 0 || (rotationResult !== null && prompt !== undefined && !options.interactive)) {
3019
+ // Fallback and balanced runs need captured output so a clean-exit
3020
+ // session-limit refusal can update account availability.
3020
3021
  // The sink reports which chain entry actually executed (may differ from
3021
3022
  // the primary after a rate-limit handoff) so the audit record is honest.
3022
3023
  const sink = {};
@@ -312,7 +312,7 @@ export async function loadSessionMetasForFeedEnrichment(load) {
312
312
  export function registerFeedCommand(program) {
313
313
  const feed = program
314
314
  .command('feed')
315
- .description('Operator inbox + agent status posts (alias: inbox = needs-you). Agent progress = --filter updates')
315
+ .description('Operator inbox + agent status posts. Default is needs-you; agent progress = --filter updates')
316
316
  .option('--json', 'Output as JSON (each block stamped with its outcome + ask class)')
317
317
  .option('--filter <view>', 'What to show: needs (default) · updates · all', 'needs')
318
318
  .option('--flat', 'List one block per agent instead of grouping by outcome')
@@ -449,7 +449,7 @@ export function registerFeedCommand(program) {
449
449
  setupWarnings.push(activityInstall.error);
450
450
  if (!hookInstall.error || !activityInstall.error) {
451
451
  const [{ iterHooksCapableVersions, parseHookManifest, registerHooksToSettings }, { getVersionHomePath }] = await Promise.all([
452
- import('../lib/hooks.js'),
452
+ import('../lib/hooks/install.js'),
453
453
  import('../lib/installations/versions.js'),
454
454
  ]);
455
455
  const manifest = parseHookManifest({ warn: false });
@@ -642,35 +642,6 @@ export function registerFeedCommand(program) {
642
642
  renderOutcomeGroup(g, self);
643
643
  await renderTrailingActivity();
644
644
  });
645
- // Observe-umbrella aliases (Phase 3): intentional second names, not deprecations.
646
- // Re-parse into `feed` so flags/help stay single-sourced. See lib/observe-aliases.ts.
647
- registerFeedObserveAliases(program);
648
- }
649
- /**
650
- * `inbox` → feed. Loaded with the feed module so the lazy COMMAND_LOADERS entry
651
- * for `inbox` also gets the real `feed` command registered for re-parse.
652
- */
653
- function registerFeedObserveAliases(program) {
654
- const reparse = async (alias) => {
655
- const { expandObserveAlias } = await import('../lib/observe-aliases.js');
656
- const rest = process.argv.slice(3);
657
- const expanded = expandObserveAlias(alias, rest);
658
- if (!expanded) {
659
- console.error(chalk.red(`Unknown observe alias: ${alias}`));
660
- process.exit(1);
661
- }
662
- if (process.stderr.isTTY)
663
- process.stderr.write(chalk.gray(`${expanded.note}\n`));
664
- await program.parseAsync(['node', 'agents', ...expanded.argv]);
665
- };
666
- program
667
- .command('inbox')
668
- .description('Needs-you inbox (alias of `agents feed`). Open blocks waiting on you.')
669
- .allowUnknownOption()
670
- .allowExcessArguments()
671
- .action(async () => {
672
- await reparse('inbox');
673
- });
674
645
  }
675
646
  /**
676
647
  * Mirror a written post to the configured sinks (`feed.broadcast` in