@phnx-labs/agents-cli 1.22.40 → 1.22.42

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 (201) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +14 -3
  3. package/dist/bin/agents +0 -0
  4. package/dist/bootstrap.js +0 -1
  5. package/dist/cli/command-registry.d.ts +2 -0
  6. package/dist/cli/command-registry.js +5 -2
  7. package/dist/commands/accounts.d.ts +29 -0
  8. package/dist/commands/accounts.js +220 -29
  9. package/dist/commands/alias.js +1 -1
  10. package/dist/commands/auth.d.ts +9 -0
  11. package/dist/commands/auth.js +104 -0
  12. package/dist/commands/browser-sessions-picker.js +42 -62
  13. package/dist/commands/browser.d.ts +15 -0
  14. package/dist/commands/browser.js +145 -78
  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 +2 -2
  20. package/dist/commands/doctor.d.ts +1 -1
  21. package/dist/commands/doctor.js +3 -3
  22. package/dist/commands/feed.js +4 -12
  23. package/dist/commands/hooks.js +1 -1
  24. package/dist/commands/import.js +1 -1
  25. package/dist/commands/insights.js +163 -85
  26. package/dist/commands/inspect.js +2 -2
  27. package/dist/commands/mine.js +1 -1
  28. package/dist/commands/org.d.ts +6 -0
  29. package/dist/commands/org.js +174 -0
  30. package/dist/commands/packages.js +1 -1
  31. package/dist/commands/prune.js +1 -1
  32. package/dist/commands/routines.js +1 -1
  33. package/dist/commands/run-account-picker.d.ts +19 -0
  34. package/dist/commands/run-account-picker.js +81 -0
  35. package/dist/commands/secrets.js +87 -97
  36. package/dist/commands/sessions-picker-factory.d.ts +40 -0
  37. package/dist/commands/sessions-picker-factory.js +67 -0
  38. package/dist/commands/sessions-picker.d.ts +1 -0
  39. package/dist/commands/sessions-picker.js +27 -5
  40. package/dist/commands/sessions-share.d.ts +25 -0
  41. package/dist/commands/sessions-share.js +166 -0
  42. package/dist/commands/sessions.js +2 -0
  43. package/dist/commands/setup-browser.js +5 -5
  44. package/dist/commands/setup-mine.js +1 -1
  45. package/dist/commands/setup-preferences.js +4 -4
  46. package/dist/commands/setup.js +5 -3
  47. package/dist/commands/sync.js +1 -1
  48. package/dist/commands/versions.js +1 -1
  49. package/dist/commands/view.js +1 -1
  50. package/dist/commands/webhook.js +14 -9
  51. package/dist/lib/account-capabilities.d.ts +10 -0
  52. package/dist/lib/account-capabilities.js +34 -0
  53. package/dist/lib/agent-spec/agents.d.ts +584 -0
  54. package/dist/lib/agent-spec/agents.js +3127 -0
  55. package/dist/lib/agent-spec/provider.js +1 -1
  56. package/dist/lib/agents.d.ts +2 -584
  57. package/dist/lib/agents.js +2 -3127
  58. package/dist/lib/browser/cdp.js +4 -0
  59. package/dist/lib/browser/chrome.js +12 -1
  60. package/dist/lib/browser/drivers/local.d.ts +8 -2
  61. package/dist/lib/browser/drivers/local.js +8 -2
  62. package/dist/lib/browser/drivers/ssh.d.ts +8 -2
  63. package/dist/lib/browser/drivers/ssh.js +11 -4
  64. package/dist/lib/browser/hygiene.d.ts +1 -1
  65. package/dist/lib/browser/hygiene.js +5 -1
  66. package/dist/lib/browser/profiles.d.ts +77 -4
  67. package/dist/lib/browser/profiles.js +121 -12
  68. package/dist/lib/browser/runtime-state.d.ts +11 -5
  69. package/dist/lib/browser/runtime-state.js +13 -9
  70. package/dist/lib/browser/service.d.ts +91 -30
  71. package/dist/lib/browser/service.js +342 -207
  72. package/dist/lib/browser/types.d.ts +96 -4
  73. package/dist/lib/browser/types.js +51 -0
  74. package/dist/lib/catchup.js +1 -1
  75. package/dist/lib/computer/actions.d.ts +1 -1
  76. package/dist/lib/computer/actions.js +1 -1
  77. package/dist/lib/{computer-rpc.js → computer/computer-rpc.js} +5 -5
  78. package/dist/lib/computer/dispatch.d.ts +1 -1
  79. package/dist/lib/computer/download.d.ts +1 -1
  80. package/dist/lib/computer/download.js +3 -3
  81. package/dist/lib/{ssh-tunnel.d.ts → computer/ssh-tunnel.d.ts} +1 -1
  82. package/dist/lib/{ssh-tunnel.js → computer/ssh-tunnel.js} +12 -12
  83. package/dist/lib/daemon/daemon.js +1 -1
  84. package/dist/lib/{runner.d.ts → daemon/runner.d.ts} +10 -10
  85. package/dist/lib/{runner.js → daemon/runner.js} +59 -118
  86. package/dist/lib/daemon-webhooks.d.ts +3 -1
  87. package/dist/lib/daemon-webhooks.js +12 -7
  88. package/dist/lib/device-config.js +1 -1
  89. package/dist/lib/devices/doctor-findings.d.ts +1 -1
  90. package/dist/lib/devices/doctor-findings.js +3 -3
  91. package/dist/lib/devices/fleet-inventory.js +1 -1
  92. package/dist/lib/devices/harness-inventory.js +1 -1
  93. package/dist/lib/devices/registry.js +4 -22
  94. package/dist/lib/doctor-diff.d.ts +1 -1
  95. package/dist/lib/doctor-diff.js +1 -1
  96. package/dist/lib/drift.js +1 -1
  97. package/dist/lib/entitlement.d.ts +31 -0
  98. package/dist/lib/entitlement.js +137 -0
  99. package/dist/lib/exec.js +39 -202
  100. package/dist/lib/feed/feed.js +2 -8
  101. package/dist/lib/fs-atomic.d.ts +7 -0
  102. package/dist/lib/fs-atomic.js +9 -0
  103. package/dist/lib/harness/adapter.d.ts +160 -0
  104. package/dist/lib/harness/adapter.js +47 -0
  105. package/dist/lib/harness/adapters/claude.d.ts +2 -0
  106. package/dist/lib/harness/adapters/claude.js +110 -0
  107. package/dist/lib/harness/adapters/codex.d.ts +2 -0
  108. package/dist/lib/harness/adapters/codex.js +83 -0
  109. package/dist/lib/harness/adapters/copilot.d.ts +2 -0
  110. package/dist/lib/harness/adapters/copilot.js +23 -0
  111. package/dist/lib/harness/adapters/cursor.d.ts +2 -0
  112. package/dist/lib/harness/adapters/cursor.js +38 -0
  113. package/dist/lib/harness/adapters/droid.d.ts +2 -0
  114. package/dist/lib/harness/adapters/droid.js +17 -0
  115. package/dist/lib/harness/adapters/grok.d.ts +2 -0
  116. package/dist/lib/harness/adapters/grok.js +14 -0
  117. package/dist/lib/harness/adapters/kimi.d.ts +2 -0
  118. package/dist/lib/harness/adapters/kimi.js +49 -0
  119. package/dist/lib/harness/adapters/muse.d.ts +2 -0
  120. package/dist/lib/harness/adapters/muse.js +44 -0
  121. package/dist/lib/harness/adapters/opencode.d.ts +2 -0
  122. package/dist/lib/harness/adapters/opencode.js +14 -0
  123. package/dist/lib/harness/exec-config-version.d.ts +8 -0
  124. package/dist/lib/harness/exec-config-version.js +22 -0
  125. package/dist/lib/harness/index.d.ts +1 -0
  126. package/dist/lib/harness/index.js +34 -0
  127. package/dist/lib/{hooks.d.ts → hooks/install.d.ts} +1 -1
  128. package/dist/lib/{hooks.js → hooks/install.js} +11 -11
  129. package/dist/lib/import.js +1 -1
  130. package/dist/lib/installations/index.d.ts +4 -4
  131. package/dist/lib/installations/index.js +3 -4
  132. package/dist/lib/installations/migrate.js +6 -6
  133. package/dist/lib/{shims.d.ts → installations/shims.d.ts} +2 -2
  134. package/dist/lib/{shims.js → installations/shims.js} +12 -114
  135. package/dist/lib/installations/store.d.ts +141 -3
  136. package/dist/lib/installations/store.js +475 -5
  137. package/dist/lib/installations/versions.d.ts +1 -126
  138. package/dist/lib/installations/versions.js +5 -465
  139. package/dist/lib/isolation-boundary-report.d.ts +1 -1
  140. package/dist/lib/isolation-boundary-report.js +1 -1
  141. package/dist/lib/mailbox-gc.js +2 -3
  142. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  143. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  144. package/dist/lib/monitors/dispatch.d.ts +1 -1
  145. package/dist/lib/monitors/dispatch.js +2 -2
  146. package/dist/lib/monitors/state.d.ts +1 -1
  147. package/dist/lib/observe-aliases.d.ts +2 -2
  148. package/dist/lib/observe-aliases.js +2 -11
  149. package/dist/lib/plugins/plugins.js +1 -1
  150. package/dist/lib/plugins/skills.js +1 -1
  151. package/dist/lib/prix-account.d.ts +158 -0
  152. package/dist/lib/prix-account.js +214 -0
  153. package/dist/lib/project-key.js +7 -0
  154. package/dist/lib/project-launch.js +1 -1
  155. package/dist/lib/refresh.js +2 -2
  156. package/dist/lib/resource-inventory.d.ts +1 -1
  157. package/dist/lib/resource-inventory.js +1 -1
  158. package/dist/lib/rules/rules.js +1 -1
  159. package/dist/lib/scheduling/routines.js +1 -1
  160. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  161. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  162. package/dist/lib/secrets/bundles.d.ts +1 -1
  163. package/dist/lib/secrets/bundles.js +1 -1
  164. package/dist/lib/secrets/headless.d.ts +16 -0
  165. package/dist/lib/secrets/headless.js +21 -0
  166. package/dist/lib/secrets/remote.d.ts +9 -7
  167. package/dist/lib/secrets/remote.js +18 -9
  168. package/dist/lib/self-heal/checks/hook-runtime.js +1 -1
  169. package/dist/lib/self-heal/checks/path.js +1 -1
  170. package/dist/lib/self-heal/checks/shadowing.js +1 -1
  171. package/dist/lib/self-heal/checks/shims.js +1 -1
  172. package/dist/lib/session/discover.d.ts +39 -0
  173. package/dist/lib/session/discover.js +3 -3
  174. package/dist/lib/session/parse.js +24 -42
  175. package/dist/lib/session/recovery.js +1 -1
  176. package/dist/lib/session/share-html.d.ts +55 -0
  177. package/dist/lib/session/share-html.js +319 -0
  178. package/dist/lib/settings-manifest.d.ts +2 -0
  179. package/dist/lib/settings-manifest.js +81 -3
  180. package/dist/lib/share/publish.d.ts +38 -0
  181. package/dist/lib/share/publish.js +81 -8
  182. package/dist/lib/staleness/prune.d.ts +1 -1
  183. package/dist/lib/staleness/prune.js +1 -1
  184. package/dist/lib/staleness/writers/hooks.js +1 -1
  185. package/dist/lib/startup/command-registry.d.ts +2 -1
  186. package/dist/lib/startup/command-registry.js +5 -3
  187. package/dist/lib/teams/agents.js +2 -19
  188. package/dist/lib/teams/parsers.js +20 -26
  189. package/dist/lib/teams/registry.js +2 -21
  190. package/dist/lib/triggers/handlers.d.ts +37 -2
  191. package/dist/lib/triggers/handlers.js +57 -6
  192. package/dist/lib/triggers/webhook.d.ts +80 -6
  193. package/dist/lib/triggers/webhook.js +128 -4
  194. package/dist/lib/types.d.ts +1 -1
  195. package/dist/lib/uninstall.js +1 -1
  196. package/dist/lib/version.d.ts +9 -2
  197. package/dist/lib/version.js +11 -9
  198. package/dist/lib/wrap.d.ts +33 -0
  199. package/dist/lib/wrap.js +70 -0
  200. package/package.json +1 -1
  201. /package/dist/lib/{computer-rpc.d.ts → computer/computer-rpc.d.ts} +0 -0
@@ -17,29 +17,29 @@ import { spawn, execFileSync } from 'child_process';
17
17
  import * as fs from 'fs';
18
18
  import * as path from 'path';
19
19
  import * as os from 'os';
20
- import { getCliLaunch, getAgentsBinDir } from './cli-entry.js';
21
- import { resolveJobPrompt, parseTimeout, writeRunMeta, listRuns, getJobRunsDir, getRunDir, checkJobDeviceEligibility, finalizeRunMeta, resolveJobExecutionContext, slotRunId, claimRunSlot, readRunMeta, resolveHostStrategy, } from './scheduling/routines.js';
22
- import { getRunsDir, getUserAgentsDir, readMeta, getDaemonDir } from './state.js';
23
- import { shortCodexHome } from './codex-home.js';
24
- import { prepareJobHome, buildSpawnEnv, getJobHomePath } from './sandbox.js';
25
- import { resolveModel, buildReasoningFlags } from './models.js';
26
- import { createTimer, redactPrompt, emitRoutineEnd } from './feed/events.js';
27
- import { codexEditWritableRoots, codexPolicyArgs } from './codex-policy.js';
28
- import { applyAddDirs } from './add-dir.js';
29
- import { normalizeMode, resolveHeadlessMode, buildExecEnv, detectRateLimit, detectAuthFailure, isAuthFailureFromLog, authFailureReason, } from './exec.js';
30
- import { resolveActor } from './actor.js';
31
- import { loadTask as loadHostTask } from './hosts/tasks.js';
32
- import { reconcileTask as reconcileHostTask } from './hosts/reconcile.js';
33
- import { backgroundSpawnOptions, killTree } from './platform/process.js';
20
+ import { getCliLaunch, getAgentsBinDir } from '../cli-entry.js';
21
+ import { resolveJobPrompt, parseTimeout, writeRunMeta, listRuns, getJobRunsDir, getRunDir, checkJobDeviceEligibility, finalizeRunMeta, resolveJobExecutionContext, slotRunId, claimRunSlot, readRunMeta, resolveHostStrategy, } from '../scheduling/routines.js';
22
+ import { getRunsDir, getUserAgentsDir, readMeta, getDaemonDir } from '../state.js';
23
+ import { shortCodexHome } from '../codex-home.js';
24
+ import { prepareJobHome, buildSpawnEnv, getJobHomePath } from '../sandbox.js';
25
+ import { resolveModel, buildReasoningFlags } from '../models.js';
26
+ import { createTimer, redactPrompt, emitRoutineEnd } from '../feed/events.js';
27
+ import { resolveHarnessAdapter } from '../harness/index.js';
28
+ import { applyAddDirs } from '../add-dir.js';
29
+ import { normalizeMode, resolveHeadlessMode, buildExecEnv, detectRateLimit, detectAuthFailure, isAuthFailureFromLog, authFailureReason, AGENT_COMMANDS, } from '../exec.js';
30
+ import { resolveActor } from '../actor.js';
31
+ import { loadTask as loadHostTask } from '../hosts/tasks.js';
32
+ import { reconcileTask as reconcileHostTask } from '../hosts/reconcile.js';
33
+ import { backgroundSpawnOptions, killTree } from '../platform/process.js';
34
34
  import lockfile from 'proper-lockfile';
35
- import { ensureLockTarget } from './fs-atomic.js';
36
- import { walkForFiles } from './fs-walk.js';
37
- import { getBinaryPath, isVersionInstalled, resolveVersion, getVersionHomePath } from './installations/versions.js';
38
- import { resolveClaudeSetupToken } from './claude-account-token.js';
39
- import { getConfiguredRunStrategy, resolveRunVersion, resolveAccountVersion, rotationFailoverChain, readinessFromCandidate, formatNoHealthyAccountError, } from './accounting/rotate.js';
40
- import { readAuthHealth, isDeadVerdict } from './auth-health.js';
41
- import { machineId } from './machine-id.js';
42
- import { isSelfUpdatingAgent, ROUTINE_AGENT_COMMANDS as AGENT_COMMANDS, isAgentHardDeprecated, hardDeprecationError } from './agents.js';
35
+ import { ensureLockTarget } from '../fs-atomic.js';
36
+ import { walkForFiles } from '../fs-walk.js';
37
+ import { getBinaryPath, isVersionInstalled, resolveVersion, getVersionHomePath } from '../installations/versions.js';
38
+ import { resolveClaudeSetupToken } from '../claude-account-token.js';
39
+ import { getConfiguredRunStrategy, resolveRunVersion, resolveAccountVersion, rotationFailoverChain, readinessFromCandidate, formatNoHealthyAccountError, } from '../accounting/rotate.js';
40
+ import { readAuthHealth, isDeadVerdict } from '../auth-health.js';
41
+ import { machineId } from '../machine-id.js';
42
+ import { isSelfUpdatingAgent, ROUTINE_AGENT_COMMANDS, isAgentHardDeprecated, hardDeprecationError } from '../agents.js';
43
43
  export class RoutineAlreadyRunningError extends Error {
44
44
  constructor(jobName, runId) {
45
45
  super(`Routine '${jobName}' already has a running execution (${runId})`);
@@ -416,6 +416,17 @@ async function runWithAttempt(config, trigger, run, wrapTerminal) {
416
416
  function terminateRoutineTree(pid) {
417
417
  if (!pid)
418
418
  return;
419
+ // Never take THIS process down. Both kills below are unconditional SIGKILLs,
420
+ // so a RunMeta naming the reaper's own pid -- a reused pid, a record written
421
+ // by the process now doing the reaping, or a hand-written fixture -- makes the
422
+ // reaper SIGKILL itself, and via `-pid` its whole process group with it. There
423
+ // is no recovery from that: the run is never finalized, and on the daemon it
424
+ // takes the scheduler down mid-sweep. daemon.ts:457 already refuses to evict an
425
+ // incumbent whose pid is `process.pid` for exactly this reason; the reap path
426
+ // needs the same guard. Skipping the kill still lets the caller finalize the
427
+ // run record, which is the part that matters.
428
+ if (pid === process.pid)
429
+ return;
419
430
  if (process.platform === 'win32') {
420
431
  killTree(pid);
421
432
  return;
@@ -503,94 +514,20 @@ export function buildJobCommand(config, resolvedPrompt, forwardAccount = true) {
503
514
  cmd.push('--account', config.account);
504
515
  return cmd;
505
516
  }
506
- const template = AGENT_COMMANDS[agent];
517
+ const template = ROUTINE_AGENT_COMMANDS[agent];
507
518
  if (!template) {
508
519
  throw new Error(`Unsupported agent for daemon jobs: ${agent}`);
509
520
  }
510
521
  let cmd = template.map((part) => part.replace('{prompt}', resolvedPrompt));
511
522
  // Canonicalize mode (accepts legacy `full` as alias for `skip`).
512
523
  const mode = normalizeMode(config.mode);
513
- if (config.agent === 'claude') {
514
- if (mode === 'edit') {
515
- const planIndex = cmd.indexOf('plan');
516
- if (planIndex !== -1)
517
- cmd[planIndex] = 'acceptEdits';
518
- }
519
- else if (mode === 'auto') {
520
- const planIndex = cmd.indexOf('plan');
521
- if (planIndex !== -1)
522
- cmd[planIndex] = 'auto';
523
- }
524
- else if (mode === 'skip') {
525
- // Replace --permission-mode plan with --dangerously-skip-permissions
526
- const pmIndex = cmd.indexOf('--permission-mode');
527
- if (pmIndex !== -1)
528
- cmd.splice(pmIndex, 2, '--dangerously-skip-permissions');
529
- }
530
- appendModelAndReasoning(cmd, config);
531
- }
532
- if (config.agent === 'codex') {
533
- const policyMode = mode === 'plan' || mode === 'skip' ? mode : 'edit';
534
- const routineRoots = (config.allow?.dirs ?? []).map((dir) => {
535
- if (dir.startsWith('-')) {
536
- throw new Error(`allow.dirs entries must not start with '-': ${JSON.stringify(dir)}`);
537
- }
538
- return dir.replace(/^~/, os.homedir());
539
- });
540
- cmd.push(...codexPolicyArgs(policyMode, [...codexEditWritableRoots(), ...routineRoots]));
541
- appendModelAndReasoning(cmd, config);
542
- }
543
- if (config.agent === 'cursor') {
544
- if (mode === 'plan') {
545
- cmd.push('--plan');
546
- }
547
- else if (mode === 'skip') {
548
- cmd.push('-f');
549
- }
550
- else {
551
- // The configured cwd is the user's workspace trust decision. --trust is
552
- // narrower than --yolo/-f because it does not bypass tool permissions.
553
- cmd.push('--trust');
554
- }
555
- appendModelAndReasoning(cmd, config);
556
- }
557
- if (config.agent === 'kimi') {
558
- // kimi daemon jobs always run headless via `--prompt`, which cannot be
559
- // combined with any startup-mode flag (--plan/--auto/--yolo all abort with
560
- // "Cannot combine --prompt with --X"). edit/auto/skip reduce to kimi's default
561
- // headless auto-run, so emit no flag. plan has no headless read-only
562
- // equivalent, so resolveHeadlessMode downgrades a plan request to auto with a
563
- // stderr warning (kimi's headlessPlan is false) — routines run headless, so
564
- // interactive is always false here. The returned mode carries no flag either.
565
- resolveHeadlessMode('kimi', mode, false);
566
- appendModelAndReasoning(cmd, config);
567
- }
568
- if (config.agent === 'droid') {
569
- // droid exec defaults to read-only (plan). Escalate autonomy per mode.
570
- if (mode === 'edit') {
571
- cmd.push('--auto', 'low');
572
- }
573
- else if (mode === 'auto') {
574
- cmd.push('--auto', 'high');
575
- }
576
- else if (mode === 'skip') {
577
- cmd.push('--skip-permissions-unsafe');
578
- }
579
- appendModelAndReasoning(cmd, config);
580
- }
581
- if (config.agent === 'muse') {
582
- // muse exec: plan ≈ no non-shell writes; auto skips approval prompts but
583
- // keeps the OS sandbox; skip is --yolo (no approval, no sandbox, trust).
584
- if (mode === 'plan') {
585
- cmd.push('--disable-write');
586
- }
587
- else if (mode === 'auto') {
588
- cmd.push('--disable-approval');
589
- }
590
- else if (mode === 'skip') {
591
- cmd.push('--yolo');
592
- }
593
- // edit: default on-request approval + sandbox
524
+ // Routine launch-arg quirks (the harness axis of Move 3): each per-agent arm
525
+ // moves to its harness adapter; runner appends model/reasoning flags after,
526
+ // exactly as every arm did. Agents with no routine quirk have no adapter
527
+ // override, so they skip both — the old behavior for an entry with no arm.
528
+ const routineAdapter = resolveHarnessAdapter(agent);
529
+ if (routineAdapter.routineModeArgs) {
530
+ routineAdapter.routineModeArgs(cmd, { mode, config, resolveHeadlessMode });
594
531
  appendModelAndReasoning(cmd, config);
595
532
  }
596
533
  // allow.dirs → harness-specific grants. Codex is handled in its branch above
@@ -610,7 +547,7 @@ export function buildJobCommand(config, resolvedPrompt, forwardAccount = true) {
610
547
  return cmd;
611
548
  }
612
549
  /**
613
- * Append --model and reasoning flags to a command being assembled.
550
+ * Append the agent's canonical model flag and reasoning flags to a command.
614
551
  *
615
552
  * Pass-through model resolution: validates against the installed (agent, version)
616
553
  * catalog when possible and writes a warning to stderr on miss, but never blocks.
@@ -621,15 +558,19 @@ function appendModelAndReasoning(cmd, config) {
621
558
  const agent = config.agent;
622
559
  const model = config.config?.model;
623
560
  if (model) {
561
+ const modelFlag = AGENT_COMMANDS[agent].modelFlag;
562
+ if (!modelFlag) {
563
+ throw new Error(`Agent ${agent} does not support routine model selection`);
564
+ }
624
565
  if (config.version) {
625
566
  const resolved = resolveModel(agent, config.version, model);
626
567
  if (resolved.warning) {
627
568
  process.stderr.write(`[agents] ${resolved.warning}\n`);
628
569
  }
629
- cmd.push('--model', resolved.forwarded);
570
+ cmd.push(modelFlag, resolved.forwarded);
630
571
  }
631
572
  else {
632
- cmd.push('--model', model);
573
+ cmd.push(modelFlag, model);
633
574
  }
634
575
  }
635
576
  const reasoning = config.config?.reasoning;
@@ -870,7 +811,7 @@ export async function resolveRoutineLaunch(config, cwd = process.cwd(), deps = {
870
811
  // resolveRoutineLaunch is only called for agent jobs (workflow returns above;
871
812
  // command jobs branch out of execute*Job before reaching this).
872
813
  const agent = config.agent;
873
- const { findAccount, findUnifiedAccount, resolveAccountSelection, resolveCredentialAccount } = await import('./account-registry.js');
814
+ const { findAccount, findUnifiedAccount, resolveAccountSelection, resolveCredentialAccount } = await import('../account-registry.js');
874
815
  const meta = (deps.readMeta ?? readMeta)();
875
816
  const explicitCredential = config.account
876
817
  ? (deps.findCredentialAccount?.(config.account) ?? (deps.resolveCredentialAccount !== undefined || findAccount(config.account) !== null))
@@ -1026,7 +967,7 @@ export async function assertRoutineAccountLocalForPlacement(config, mode, deps =
1026
967
  return;
1027
968
  let account = deps.account;
1028
969
  if (account === undefined) {
1029
- const { findUnifiedAccount } = await import('./account-registry.js');
970
+ const { findUnifiedAccount } = await import('../account-registry.js');
1030
971
  account = findUnifiedAccount(config.account, (deps.readMeta ?? readMeta)());
1031
972
  }
1032
973
  if (!account) {
@@ -1246,7 +1187,7 @@ async function executeJobPlaced(config, deps, attempt) {
1246
1187
  // do not apply. Sync callers (manual `routines run`, catchup) follow the
1247
1188
  // remote run to completion when possible.
1248
1189
  {
1249
- const { resolvePlacementTarget } = await import('./routines-placement.js');
1190
+ const { resolvePlacementTarget } = await import('../routines-placement.js');
1250
1191
  const target = await resolvePlacementTarget(config);
1251
1192
  const placed = await dispatchPlacedJob(config, target, attempt);
1252
1193
  if (placed)
@@ -1286,7 +1227,7 @@ async function executeJobPlaced(config, deps, attempt) {
1286
1227
  // (command jobs branched out earlier, so config.agent is set on the non-workflow path.)
1287
1228
  const effectiveAgent = config.workflow ? 'claude' : config.agent;
1288
1229
  if (!dispatchesViaAgentsRun(config)) {
1289
- const { findUnifiedAccount, resolveAccountSelection, resolveCredentialAccount } = await import('./account-registry.js');
1230
+ const { findUnifiedAccount, resolveAccountSelection, resolveCredentialAccount } = await import('../account-registry.js');
1290
1231
  const meta = readMeta();
1291
1232
  const selectedAccount = resolveAccountSelection(config.account, effectiveAgent, meta);
1292
1233
  if (selectedAccount) {
@@ -1359,7 +1300,7 @@ async function executeJobPlaced(config, deps, attempt) {
1359
1300
  .map((d) => d.replace(/^~/, os.homedir())),
1360
1301
  } : {}),
1361
1302
  };
1362
- const { runLoop } = await import('./loop.js');
1303
+ const { runLoop } = await import('../loop.js');
1363
1304
  const loopResult = await runLoop(execOptions, config.loop, {
1364
1305
  runId,
1365
1306
  runDir,
@@ -1515,8 +1456,8 @@ async function executeJobOnCloud(config, opts, attempt) {
1515
1456
  if (!config.agent) {
1516
1457
  throw new Error(`Routine '${config.name}' hostStrategy: cloud requires an agent`);
1517
1458
  }
1518
- const { resolveProvider } = await import('./cloud/registry.js');
1519
- const { insertTask } = await import('./cloud/store.js');
1459
+ const { resolveProvider } = await import('../cloud/registry.js');
1460
+ const { insertTask } = await import('../cloud/store.js');
1520
1461
  const provider = resolveProvider(undefined, config.agent);
1521
1462
  const timer = createTimer('agent.run', {
1522
1463
  agent: config.agent,
@@ -1589,9 +1530,9 @@ async function executeJobOnHost(config, opts, attempt) {
1589
1530
  if (config.command) {
1590
1531
  throw new Error(`Routine '${config.name}' uses 'command:', which can't execute on a host yet — remove 'host:' or 'command:'.`);
1591
1532
  }
1592
- const { resolveHostRunTarget, dispatchPromptToHost } = await import('./hosts/run-target.js');
1533
+ const { resolveHostRunTarget, dispatchPromptToHost } = await import('../hosts/run-target.js');
1593
1534
  const host = await resolveHostRunTarget(config.host);
1594
- const { evaluateHostActivationReadiness } = await import('./routine-readiness.js');
1535
+ const { evaluateHostActivationReadiness } = await import('../routine-readiness.js');
1595
1536
  const readiness = await evaluateHostActivationReadiness(config);
1596
1537
  if (!readiness.ready)
1597
1538
  throw new Error(`${readiness.readiness?.code ?? 'not_ready'}: ${readiness.readiness?.message ?? 'target is not ready'}`);
@@ -1739,7 +1680,7 @@ async function executeJobDetachedClaimed(config, attempt, hooks) {
1739
1680
  // (the monitor tick observes an already-finalized record), so notify-desktop
1740
1681
  // sends the finish notification only for local detached runs below (RUSH-2030).
1741
1682
  {
1742
- const { resolvePlacementTarget } = await import('./routines-placement.js');
1683
+ const { resolvePlacementTarget } = await import('../routines-placement.js');
1743
1684
  const target = await resolvePlacementTarget(config);
1744
1685
  if (target.mode === 'host' || target.mode === 'cloud') {
1745
1686
  await assertRoutineAccountLocalForPlacement(config, target.mode);
@@ -1798,7 +1739,7 @@ async function executeJobDetachedClaimed(config, attempt, hooks) {
1798
1739
  ? buildSpawnEnv(overlayHome, config.env)
1799
1740
  : { ...process.env }, config);
1800
1741
  if (!dispatchesViaAgentsRun(config)) {
1801
- const { findAccount, resolveAccountSelection, resolveCredentialAccount } = await import('./account-registry.js');
1742
+ const { findAccount, resolveAccountSelection, resolveCredentialAccount } = await import('../account-registry.js');
1802
1743
  const selectedAccount = config.account && !findAccount(config.account)
1803
1744
  ? undefined
1804
1745
  : resolveAccountSelection(config.account, config.agent, readMeta());
@@ -23,7 +23,9 @@ export interface HostedReceiverFunnel {
23
23
  }
24
24
  /** One receiver this box hosts. */
25
25
  export interface HostedReceiverConfig {
26
- /** Secrets bundle holding GITHUB_WEBHOOK_SECRET and/or LINEAR_WEBHOOK_SECRET. */
26
+ /** Secrets bundle holding GITHUB_WEBHOOK_SECRET, LINEAR_WEBHOOK_SECRET, and/or
27
+ * SLACK_SIGNING_SECRET (a Slack receiver also carries SLACK_BOT_TOKEN when the
28
+ * agent replies with the Slack Web API rather than `agents send`). */
27
29
  bundle: string;
28
30
  /** Local bind port (default 8787). */
29
31
  port?: number;
@@ -112,8 +112,10 @@ export function resolveReceiverSecrets(bundle) {
112
112
  secrets.github = env.GITHUB_WEBHOOK_SECRET;
113
113
  if (env.LINEAR_WEBHOOK_SECRET)
114
114
  secrets.linear = env.LINEAR_WEBHOOK_SECRET;
115
- if (!secrets.github && !secrets.linear) {
116
- throw new Error(`bundle '${bundle}' has neither GITHUB_WEBHOOK_SECRET nor LINEAR_WEBHOOK_SECRET`);
115
+ if (env.SLACK_SIGNING_SECRET)
116
+ secrets.slack = env.SLACK_SIGNING_SECRET;
117
+ if (!secrets.github && !secrets.linear && !secrets.slack) {
118
+ throw new Error(`bundle '${bundle}' has none of GITHUB_WEBHOOK_SECRET, LINEAR_WEBHOOK_SECRET, or SLACK_SIGNING_SECRET`);
117
119
  }
118
120
  return secrets;
119
121
  }
@@ -176,12 +178,15 @@ export async function startHostedWebhookReceivers(opts) {
176
178
  // Per-port durable delivery dedup so replays survive a daemon restart and
177
179
  // two receivers on distinct ports never share a dedup ledger.
178
180
  deliveryStore: createFileDeliveryStore(path.join(getRuntimeStateDir(), 'webhook', `deliveries-${port}.json`)),
179
- onDelivery: (webhook, fired, handlers) => {
181
+ // Logged at MATCH time, before dispatch — a `run.command` handler can
182
+ // block on a shelled-out agent run for minutes, and that must never
183
+ // delay the log that says a delivery fired (RUSH-2722).
184
+ onMatch: (webhook, matchedJobNames, matchedHandlerNames) => {
180
185
  const parts = [];
181
- if (fired.length)
182
- parts.push(`routines ${fired.map((f) => f.jobName).join(', ')}`);
183
- if (handlers.length)
184
- parts.push(`handlers ${handlers.map((h) => h.handlerName).join(', ')}`);
186
+ if (matchedJobNames.length)
187
+ parts.push(`routines ${matchedJobNames.join(', ')}`);
188
+ if (matchedHandlerNames.length)
189
+ parts.push(`handlers ${matchedHandlerNames.join(', ')}`);
185
190
  log('INFO', `webhook ${webhook.source}:${webhook.event} ${parts.length ? `fired ${parts.join('; ')}` : 'no match'}`);
186
191
  },
187
192
  // The 202 ack means no HTTP status carries a settle failure — the daemon
@@ -96,7 +96,7 @@ export const CONFIG_KEYS = [
96
96
  scope: 'device',
97
97
  visibility: 'machine',
98
98
  type: 'string',
99
- description: 'Browser profile `agents browser start` resolves to without --profile (set via `agents browser profiles set-default`).',
99
+ description: 'Browser profile `agents browser start` resolves to without --profile (set via `agents browser use`).',
100
100
  },
101
101
  {
102
102
  name: 'agents.max-concurrent',
@@ -1,5 +1,5 @@
1
1
  import type { AgentId } from '../types.js';
2
- import type { DuplicateVersionHook } from '../hooks.js';
2
+ import type { DuplicateVersionHook } from '../hooks/install.js';
3
3
  import type { AgentsBinaryShadow } from '../binary-shadow.js';
4
4
  import type { RcSecretFinding } from '../secrets/rc-hygiene.js';
5
5
  import type { OwnerSinkStatus } from '../channels/owner-sink.js';
@@ -41,13 +41,13 @@ import chalk from 'chalk';
41
41
  import { AGENTS, ALL_AGENT_IDS, supportsAccountInspection } from '../agents.js';
42
42
  import { blocksLocalScripts } from '../platform/winpath.js';
43
43
  import { loginHint } from '../signin-badge.js';
44
- import { CONFIG_ENV_ISOLATED_AGENTS } from '../shims.js';
44
+ import { CONFIG_ENV_ISOLATED_AGENTS } from '../installations/shims.js';
45
45
  import { padToWidth, stringWidth } from '../session/width.js';
46
46
  import { windowsSshEnrollmentProblem } from './windows-ssh-enrollment.js';
47
47
  const AGENT_NAMES = Object.fromEntries(ALL_AGENT_IDS.map((id) => [id, AGENTS[id].name]));
48
48
  /** Agents with NO per-version credential isolation: their login is shared across
49
49
  * every installed version, so a "log into THIS version" remediation would be a
50
- * lie. Derived from `CONFIG_ENV_ISOLATED_AGENTS` in `lib/shims.ts` — the shim
50
+ * lie. Derived from `CONFIG_ENV_ISOLATED_AGENTS` in `lib/installations/shims.ts` — the shim
51
51
  * generator is what actually exports the per-version isolation env var, so it is
52
52
  * the source of truth. Do not hand-maintain a second copy: an agent gaining
53
53
  * isolation there must not leave a stale "login is shared" hint here. */
@@ -193,7 +193,7 @@ export function remediationFor(finding) {
193
193
  }
194
194
  // Isolated per-version home: the login must RUN INSIDE that home. A bare
195
195
  // `<cli> login` afterwards would NOT — the native shim resolves to the
196
- // project/default version (`lib/shims.ts:471-474`), so it would log into
196
+ // project/default version (`lib/installations/shims.ts:471-474`), so it would log into
197
197
  // whichever version is default, not the one that is logged out.
198
198
  switch (loginShape(agent)) {
199
199
  case 'subcommand':
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import { getAvailableResources, getVersionHomePath, isVersionIsolated, listInstalledVersions } from '../installations/versions.js';
12
12
  import { supports } from '../capabilities.js';
13
- import { checkVersionHookWiring } from '../hooks.js';
13
+ import { checkVersionHookWiring } from '../hooks/install.js';
14
14
  import { getUserAgentsDir, getSystemAgentsDir, readMeta } from '../state.js';
15
15
  import { listNativeAccounts } from '../account-registry.js';
16
16
  import { readRepoState } from '../git.js';
@@ -16,7 +16,7 @@
16
16
  import chalk from 'chalk';
17
17
  import { ALL_AGENT_IDS, accountDisplayLabel, getAccountInfo, } from '../agents.js';
18
18
  import { deriveUsageStatusFromSnapshot, getUsageInfoByIdentity, getUsageLookupKey, } from '../accounting/usage.js';
19
- import { getVersionHomePath, listInstalledVersions } from '../installations/versions.js';
19
+ import { getVersionHomePath, listInstalledVersions } from '../installations/store.js';
20
20
  import { readMeta } from '../state.js';
21
21
  import { listNativeAccounts } from '../account-registry.js';
22
22
  /**
@@ -16,9 +16,9 @@
16
16
  import * as fs from 'fs/promises';
17
17
  import * as fsSync from 'fs';
18
18
  import * as path from 'path';
19
- import { randomBytes } from 'crypto';
20
19
  import lockfile from 'proper-lockfile';
21
20
  import { getDevicesRegistryPath, getDevicesIgnoredPath } from '../state.js';
21
+ import { atomicWriteJsonSync } from '../fs-atomic.js';
22
22
  /** A device's effective role, defaulting to `worker` when unset. */
23
23
  export function deviceRole(d) {
24
24
  return d.role ?? 'worker';
@@ -94,24 +94,6 @@ export function platformFromOs(os) {
94
94
  export function shellForPlatform(platform) {
95
95
  return platform === 'windows' ? 'powershell' : 'posix';
96
96
  }
97
- /**
98
- * Atomic JSON write: write to a unique sibling tmp file then rename over the
99
- * target. rename(2) is atomic on POSIX, so a crashed write leaves the old file
100
- * untouched instead of producing a half-written registry that loadDevices()
101
- * would reject.
102
- */
103
- async function atomicWriteJson(p, data) {
104
- await fs.mkdir(path.dirname(p), { recursive: true });
105
- const tmp = `${p}.tmp.${process.pid}.${randomBytes(4).toString('hex')}`;
106
- await fs.writeFile(tmp, JSON.stringify(data, null, 2));
107
- try {
108
- await fs.rename(tmp, p);
109
- }
110
- catch (err) {
111
- await fs.unlink(tmp).catch(() => { });
112
- throw err;
113
- }
114
- }
115
97
  /**
116
98
  * Run `fn` while holding an exclusive cross-process lock on the registry file.
117
99
  * proper-lockfile requires the target to exist, so we touch it first. Stale
@@ -187,7 +169,7 @@ export function loadDevicesSync() {
187
169
  }
188
170
  }
189
171
  async function saveDevices(reg) {
190
- await atomicWriteJson(registryPath(), reg);
172
+ atomicWriteJsonSync(registryPath(), reg);
191
173
  }
192
174
  /** Get a single device profile, or null if it is not registered. */
193
175
  export async function getDevice(name) {
@@ -309,7 +291,7 @@ export async function addIgnored(name) {
309
291
  return withRegistryLock(p, async () => {
310
292
  const set = await loadIgnored();
311
293
  set.add(name);
312
- await atomicWriteJson(p, { ignored: [...set].sort(), updatedAt: new Date().toISOString() });
294
+ atomicWriteJsonSync(p, { ignored: [...set].sort(), updatedAt: new Date().toISOString() });
313
295
  return set;
314
296
  });
315
297
  }
@@ -321,7 +303,7 @@ export async function removeIgnored(name) {
321
303
  const set = await loadIgnored();
322
304
  if (!set.delete(name))
323
305
  return false;
324
- await atomicWriteJson(p, { ignored: [...set].sort(), updatedAt: new Date().toISOString() });
306
+ atomicWriteJsonSync(p, { ignored: [...set].sort(), updatedAt: new Date().toISOString() });
325
307
  return true;
326
308
  });
327
309
  }
@@ -21,7 +21,7 @@
21
21
  */
22
22
  import type { AgentId } from './types.js';
23
23
  import type { DiscoveredPlugin } from './types.js';
24
- import { type HookWiringReport } from './hooks.js';
24
+ import { type HookWiringReport } from './hooks/install.js';
25
25
  import { type ResourceInventory } from './resource-inventory.js';
26
26
  export type DoctorKind = 'commands' | 'skills' | 'hooks' | 'rules' | 'mcp' | 'permissions' | 'subagents' | 'plugins' | 'promptcuts';
27
27
  export type DiffStatus = 'ok' | 'diff' | 'missing' | 'extra';
@@ -33,7 +33,7 @@ import { shouldInstallCommandAsSkill, commandSkillMatches, commandSkillName } fr
33
33
  import { gooseCommandMatches, gooseCommandsDir } from './goose-commands.js';
34
34
  import { supports } from './capabilities.js';
35
35
  import { listSkillsInVersionHome, getVersionSkillsDir } from './plugins/skills.js';
36
- import { listHookEntriesFromDir } from './hooks.js';
36
+ import { listHookEntriesFromDir } from './hooks/install.js';
37
37
  import { getResourceInventory } from './resource-inventory.js';
38
38
  const RULES_DOC_FILENAME = 'README.md';
39
39
  const ALL_KINDS = [
package/dist/lib/drift.js CHANGED
@@ -4,7 +4,7 @@ import { loadManifest, isStale } from './staleness/index.js';
4
4
  import { diffVersionResources } from './doctor-diff.js';
5
5
  import { diffVersionCommands, iterCommandsCapableVersions } from './commands.js';
6
6
  import { diffVersionSkills, iterSkillsCapableVersions } from './plugins/skills.js';
7
- import { iterHooksCapableVersions, listUnmanagedHooksInVersionHome, checkVersionHookWiring } from './hooks.js';
7
+ import { iterHooksCapableVersions, listUnmanagedHooksInVersionHome, checkVersionHookWiring } from './hooks/install.js';
8
8
  import { commitsBehindUpstream } from './git.js';
9
9
  import { getUserAgentsDir, getSystemAgentsDir, getEnabledExtraRepos } from './state.js';
10
10
  // Lines naming exactly what's out of sync for a version, plugins prioritized:
@@ -0,0 +1,31 @@
1
+ /** How long a cached tier is trusted before the next call re-fetches. */
2
+ export declare const ENTITLEMENT_CACHE_TTL_MS: number;
3
+ export type EntitlementSource = 'live' | 'cache' | 'offline' | 'no-session';
4
+ export interface EntitlementTier {
5
+ /** The raw tier name from the backend ('free', 'admin', or a paid tier name). */
6
+ tierName: string;
7
+ /** true for 'admin' or any tier name other than 'free'. */
8
+ isPaid: boolean;
9
+ /** Where this value came from — mainly for diagnostics/tests. */
10
+ source: EntitlementSource;
11
+ }
12
+ export declare function setEntitlementUserYamlPathForTest(value: string | null): void;
13
+ export declare function setEntitlementCachePathForTest(value: string | null): void;
14
+ export declare function setEntitlementFetchForTest(fn: typeof globalThis.fetch): void;
15
+ export declare function entitlementCachePath(): string;
16
+ /**
17
+ * The live plan tier, cache-first with a TTL.
18
+ *
19
+ * - No `~/.rush/user.yaml` at all → free, no network call.
20
+ * - A fresh cache entry (within {@link ENTITLEMENT_CACHE_TTL_MS}) is returned
21
+ * with no network call.
22
+ * - A stale or missing cache triggers a live fetch; on success the result is
23
+ * cached and returned.
24
+ * - A failed fetch (offline, timeout, non-2xx) falls back to a stale cache if
25
+ * one exists — never silently drops a known-paid account to free just
26
+ * because the network hiccuped — and only falls back to free when there is
27
+ * no cache at all to fall back to.
28
+ */
29
+ export declare function getTier(): Promise<EntitlementTier>;
30
+ /** Per-harness account cap for a tier: 3 on free, 10 on paid/admin. */
31
+ export declare function accountCapForTier(tier: Pick<EntitlementTier, 'isPaid'>): number;