@phnx-labs/agents-cli 1.22.41 → 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 (167) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +9 -3
  3. package/dist/bin/agents +0 -0
  4. package/dist/cli/command-registry.d.ts +2 -0
  5. package/dist/cli/command-registry.js +4 -0
  6. package/dist/commands/accounts.d.ts +29 -0
  7. package/dist/commands/accounts.js +220 -29
  8. package/dist/commands/alias.js +1 -1
  9. package/dist/commands/auth.d.ts +9 -0
  10. package/dist/commands/auth.js +104 -0
  11. package/dist/commands/browser-sessions-picker.js +42 -62
  12. package/dist/commands/browser.js +30 -38
  13. package/dist/commands/computer-actions.d.ts +1 -1
  14. package/dist/commands/computer-actions.js +1 -1
  15. package/dist/commands/computer-sessions-picker.js +32 -55
  16. package/dist/commands/computer.d.ts +1 -1
  17. package/dist/commands/computer.js +2 -2
  18. package/dist/commands/doctor.d.ts +1 -1
  19. package/dist/commands/doctor.js +3 -3
  20. package/dist/commands/feed.js +1 -1
  21. package/dist/commands/hooks.js +1 -1
  22. package/dist/commands/import.js +1 -1
  23. package/dist/commands/insights.js +163 -85
  24. package/dist/commands/inspect.js +2 -2
  25. package/dist/commands/mine.js +1 -1
  26. package/dist/commands/org.d.ts +6 -0
  27. package/dist/commands/org.js +174 -0
  28. package/dist/commands/packages.js +1 -1
  29. package/dist/commands/prune.js +1 -1
  30. package/dist/commands/routines.js +1 -1
  31. package/dist/commands/run-account-picker.d.ts +19 -0
  32. package/dist/commands/run-account-picker.js +81 -0
  33. package/dist/commands/sessions-picker-factory.d.ts +40 -0
  34. package/dist/commands/sessions-picker-factory.js +67 -0
  35. package/dist/commands/setup-browser.js +4 -4
  36. package/dist/commands/setup-mine.js +1 -1
  37. package/dist/commands/setup-preferences.js +2 -2
  38. package/dist/commands/setup.js +5 -3
  39. package/dist/commands/sync.js +1 -1
  40. package/dist/commands/versions.js +1 -1
  41. package/dist/commands/view.js +1 -1
  42. package/dist/lib/account-capabilities.d.ts +10 -0
  43. package/dist/lib/account-capabilities.js +34 -0
  44. package/dist/lib/agent-spec/agents.d.ts +584 -0
  45. package/dist/lib/agent-spec/agents.js +3127 -0
  46. package/dist/lib/agent-spec/provider.js +1 -1
  47. package/dist/lib/agents.d.ts +2 -584
  48. package/dist/lib/agents.js +2 -3127
  49. package/dist/lib/browser/drivers/local.d.ts +8 -2
  50. package/dist/lib/browser/drivers/local.js +8 -2
  51. package/dist/lib/browser/drivers/ssh.d.ts +8 -2
  52. package/dist/lib/browser/drivers/ssh.js +10 -4
  53. package/dist/lib/browser/hygiene.d.ts +1 -1
  54. package/dist/lib/browser/hygiene.js +5 -1
  55. package/dist/lib/browser/profiles.d.ts +74 -1
  56. package/dist/lib/browser/profiles.js +114 -5
  57. package/dist/lib/browser/runtime-state.d.ts +11 -5
  58. package/dist/lib/browser/runtime-state.js +13 -9
  59. package/dist/lib/browser/service.d.ts +68 -30
  60. package/dist/lib/browser/service.js +305 -194
  61. package/dist/lib/browser/types.d.ts +95 -3
  62. package/dist/lib/browser/types.js +51 -0
  63. package/dist/lib/catchup.js +1 -1
  64. package/dist/lib/computer/actions.d.ts +1 -1
  65. package/dist/lib/computer/actions.js +1 -1
  66. package/dist/lib/{computer-rpc.js → computer/computer-rpc.js} +5 -5
  67. package/dist/lib/computer/dispatch.d.ts +1 -1
  68. package/dist/lib/computer/download.d.ts +1 -1
  69. package/dist/lib/computer/download.js +3 -3
  70. package/dist/lib/{ssh-tunnel.d.ts → computer/ssh-tunnel.d.ts} +1 -1
  71. package/dist/lib/{ssh-tunnel.js → computer/ssh-tunnel.js} +12 -12
  72. package/dist/lib/daemon/daemon.js +1 -1
  73. package/dist/lib/{runner.d.ts → daemon/runner.d.ts} +10 -10
  74. package/dist/lib/{runner.js → daemon/runner.js} +40 -114
  75. package/dist/lib/devices/doctor-findings.d.ts +1 -1
  76. package/dist/lib/devices/doctor-findings.js +3 -3
  77. package/dist/lib/devices/fleet-inventory.js +1 -1
  78. package/dist/lib/devices/harness-inventory.js +1 -1
  79. package/dist/lib/devices/registry.js +4 -22
  80. package/dist/lib/doctor-diff.d.ts +1 -1
  81. package/dist/lib/doctor-diff.js +1 -1
  82. package/dist/lib/drift.js +1 -1
  83. package/dist/lib/entitlement.d.ts +31 -0
  84. package/dist/lib/entitlement.js +137 -0
  85. package/dist/lib/exec.js +39 -202
  86. package/dist/lib/feed/feed.js +2 -8
  87. package/dist/lib/fs-atomic.d.ts +7 -0
  88. package/dist/lib/fs-atomic.js +9 -0
  89. package/dist/lib/harness/adapter.d.ts +160 -0
  90. package/dist/lib/harness/adapter.js +47 -0
  91. package/dist/lib/harness/adapters/claude.d.ts +2 -0
  92. package/dist/lib/harness/adapters/claude.js +110 -0
  93. package/dist/lib/harness/adapters/codex.d.ts +2 -0
  94. package/dist/lib/harness/adapters/codex.js +83 -0
  95. package/dist/lib/harness/adapters/copilot.d.ts +2 -0
  96. package/dist/lib/harness/adapters/copilot.js +23 -0
  97. package/dist/lib/harness/adapters/cursor.d.ts +2 -0
  98. package/dist/lib/harness/adapters/cursor.js +38 -0
  99. package/dist/lib/harness/adapters/droid.d.ts +2 -0
  100. package/dist/lib/harness/adapters/droid.js +17 -0
  101. package/dist/lib/harness/adapters/grok.d.ts +2 -0
  102. package/dist/lib/harness/adapters/grok.js +14 -0
  103. package/dist/lib/harness/adapters/kimi.d.ts +2 -0
  104. package/dist/lib/harness/adapters/kimi.js +49 -0
  105. package/dist/lib/harness/adapters/muse.d.ts +2 -0
  106. package/dist/lib/harness/adapters/muse.js +44 -0
  107. package/dist/lib/harness/adapters/opencode.d.ts +2 -0
  108. package/dist/lib/harness/adapters/opencode.js +14 -0
  109. package/dist/lib/harness/exec-config-version.d.ts +8 -0
  110. package/dist/lib/harness/exec-config-version.js +22 -0
  111. package/dist/lib/harness/index.d.ts +1 -0
  112. package/dist/lib/harness/index.js +34 -0
  113. package/dist/lib/{hooks.d.ts → hooks/install.d.ts} +1 -1
  114. package/dist/lib/{hooks.js → hooks/install.js} +11 -11
  115. package/dist/lib/import.js +1 -1
  116. package/dist/lib/installations/index.d.ts +4 -4
  117. package/dist/lib/installations/index.js +3 -4
  118. package/dist/lib/installations/migrate.js +6 -6
  119. package/dist/lib/{shims.d.ts → installations/shims.d.ts} +2 -2
  120. package/dist/lib/{shims.js → installations/shims.js} +12 -114
  121. package/dist/lib/installations/store.d.ts +141 -3
  122. package/dist/lib/installations/store.js +475 -5
  123. package/dist/lib/installations/versions.d.ts +1 -126
  124. package/dist/lib/installations/versions.js +5 -465
  125. package/dist/lib/isolation-boundary-report.d.ts +1 -1
  126. package/dist/lib/isolation-boundary-report.js +1 -1
  127. package/dist/lib/mailbox-gc.js +2 -3
  128. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  129. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  130. package/dist/lib/monitors/dispatch.d.ts +1 -1
  131. package/dist/lib/monitors/dispatch.js +2 -2
  132. package/dist/lib/monitors/state.d.ts +1 -1
  133. package/dist/lib/plugins/plugins.js +1 -1
  134. package/dist/lib/plugins/skills.js +1 -1
  135. package/dist/lib/prix-account.d.ts +158 -0
  136. package/dist/lib/prix-account.js +214 -0
  137. package/dist/lib/project-key.js +2 -2
  138. package/dist/lib/project-launch.js +1 -1
  139. package/dist/lib/refresh.js +2 -2
  140. package/dist/lib/resource-inventory.d.ts +1 -1
  141. package/dist/lib/resource-inventory.js +1 -1
  142. package/dist/lib/rules/rules.js +1 -1
  143. package/dist/lib/scheduling/routines.js +1 -1
  144. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  145. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  146. package/dist/lib/self-heal/checks/hook-runtime.js +1 -1
  147. package/dist/lib/self-heal/checks/path.js +1 -1
  148. package/dist/lib/self-heal/checks/shadowing.js +1 -1
  149. package/dist/lib/self-heal/checks/shims.js +1 -1
  150. package/dist/lib/session/discover.d.ts +39 -0
  151. package/dist/lib/session/discover.js +3 -3
  152. package/dist/lib/session/parse.js +24 -42
  153. package/dist/lib/session/recovery.js +1 -1
  154. package/dist/lib/staleness/prune.d.ts +1 -1
  155. package/dist/lib/staleness/prune.js +1 -1
  156. package/dist/lib/staleness/writers/hooks.js +1 -1
  157. package/dist/lib/startup/command-registry.js +1 -1
  158. package/dist/lib/teams/agents.js +2 -19
  159. package/dist/lib/teams/parsers.js +20 -26
  160. package/dist/lib/teams/registry.js +2 -21
  161. package/dist/lib/triggers/handlers.js +1 -1
  162. package/dist/lib/triggers/webhook.js +1 -1
  163. package/dist/lib/uninstall.js +1 -1
  164. package/dist/lib/version.d.ts +9 -2
  165. package/dist/lib/version.js +11 -9
  166. package/package.json +1 -1
  167. /package/dist/lib/{computer-rpc.d.ts → computer/computer-rpc.d.ts} +0 -0
@@ -1,4 +1,66 @@
1
1
  export type BrowserType = 'chrome' | 'comet' | 'chromium' | 'brave' | 'edge' | 'arc' | 'custom';
2
+ /**
3
+ * The user-facing name of a profile — what `agents browser profiles list`
4
+ * prints and what `--profile <name>` takes. ALWAYS bare: it never carries an
5
+ * `@<endpoint>` suffix or a `.<fork>` suffix. `BrowserProfile.name` is one.
6
+ */
7
+ export type ProfileName = string;
8
+ /**
9
+ * The key of an endpoint preset within a profile (`endpoint-0`, `local`, …) —
10
+ * see {@link EndpointPreset} and `getEndpointPresets`.
11
+ */
12
+ export type EndpointName = string;
13
+ /**
14
+ * The RUNTIME key a live browser is registered under: `<profile>@<endpoint>`,
15
+ * plus an optional `.<n>` fork suffix for Electron forks. It keys
16
+ * `BrowserService.connections`, the profile's runtime dir, its `tasks.json`,
17
+ * and its capture dirs — so one YAML profile can run at several endpoints
18
+ * concurrently without colliding on disk.
19
+ *
20
+ * It is BRANDED on purpose. Before RUSH-2709 both this and a {@link ProfileName}
21
+ * were bare `string`s and `start()` overwrote `BrowserProfile.name` with the
22
+ * composite, so a connection key leaked into every user-facing listing and six
23
+ * consumers each invented their own rule for turning one back into the other.
24
+ * The brand makes `connections.get(someProfileName)` — the exact miss behind
25
+ * `status --profile comet-local` returning EMPTY for a live
26
+ * `comet-local@endpoint-0` — a compile error. Build one ONLY with
27
+ * {@link connectionKey} (deriving) or {@link asConnectionKey} (adopting a name
28
+ * that is already a runtime key, e.g. a directory read off disk).
29
+ */
30
+ export type ConnectionKey = string & {
31
+ readonly __connectionKey: unique symbol;
32
+ };
33
+ /** The single site that derives a {@link ConnectionKey} from its two parts. */
34
+ export declare function connectionKey(profile: ProfileName, endpoint: EndpointName): ConnectionKey;
35
+ /**
36
+ * Adopt a string that IS already a runtime key — a runtime directory name, a
37
+ * `tasks.json` `profile` field, a fork key. Never use it to launder a bare
38
+ * profile name into a key; use {@link connectionKey} for that.
39
+ */
40
+ export declare function asConnectionKey(raw: string): ConnectionKey;
41
+ /**
42
+ * Split a {@link ConnectionKey} back into its parts. The inverse of
43
+ * {@link connectionKey}, and the ONE rule every consumer uses to decide whether
44
+ * a runtime key belongs to a profile.
45
+ *
46
+ * Tolerates the legacy shape still on disk: a pre-composite key that is just
47
+ * the bare profile name (`comet-local`).
48
+ *
49
+ * A key with no `@` is read as a WHOLE profile name, never as `<name>.<fork>`,
50
+ * because nothing constrains a profile's name (`createProfile` checks only for
51
+ * a duplicate name and a port collision). Reading the trailing digits as a fork
52
+ * would let `stop --profile chrome` claim the runtime dirs of a separate
53
+ * profile named `chrome.2` and kill its browser. A fork is only ever created
54
+ * from a composite base (`forkElectronProfile` appends to a `<p>@<e>` key), so
55
+ * the `@`-bearing branch below is the one that must understand forks.
56
+ */
57
+ export declare function parseConnectionKey(key: string): {
58
+ profile: ProfileName;
59
+ endpoint?: EndpointName;
60
+ fork?: number;
61
+ };
62
+ /** True when `key` is a runtime key of `profile`. The only reconciliation rule. */
63
+ export declare function keyBelongsToProfile(key: string, profile: ProfileName): boolean;
2
64
  /**
3
65
  * A single named endpoint preset within a profile. Lets one profile cover
4
66
  * the local + remote variants of the same app (e.g. an Electron app on this
@@ -24,7 +86,12 @@ export interface EndpointPreset {
24
86
  targetFilter?: string;
25
87
  }
26
88
  export interface BrowserProfile {
27
- name: string;
89
+ /**
90
+ * The user-facing profile name, ALWAYS bare — never `name@endpoint`. The
91
+ * runtime key that carries the endpoint is a separate value
92
+ * ({@link ConnectionKey}); nothing may write one here.
93
+ */
94
+ name: ProfileName;
28
95
  description?: string;
29
96
  browser: BrowserType;
30
97
  binary?: string;
@@ -84,8 +151,24 @@ export interface Task {
84
151
  * short machine id can stay addressable while status stays readable.
85
152
  */
86
153
  label?: string;
87
- profile: string;
154
+ /**
155
+ * The RUNTIME key of the browser this task lives on (`<profile>@<endpoint>`),
156
+ * not the bare profile name — it addresses the runtime dir this task's
157
+ * `tasks.json` and captures are written under. Use
158
+ * {@link parseConnectionKey} to get the user-facing name out of it.
159
+ */
160
+ profile: ConnectionKey;
88
161
  tabs: Record<string, string>;
162
+ /**
163
+ * Tabs this task DRIVES but did not create, by shortId — a tab that already
164
+ * existed in the browser and was reused because the browser cannot open new
165
+ * ones (Arc: `Target.createTarget` crashes it, #2778/#2786). Every close path
166
+ * skips these: the task never opened the tab, so closing it on `done` would
167
+ * take away something that was there first. Same rule `adoptTabShowing`
168
+ * states for unowned pages, kept when reuse is unavoidable rather than
169
+ * optional.
170
+ */
171
+ borrowedTabs?: string[];
89
172
  currentTabId?: string;
90
173
  createdAt: number;
91
174
  /**
@@ -142,7 +225,16 @@ export interface TabInfo {
142
225
  task: string;
143
226
  }
144
227
  export interface ProfileStatus {
145
- name: string;
228
+ /**
229
+ * The BARE profile name — what the user passed to `--profile` and what
230
+ * `status` prints. The endpoint is reported separately in {@link endpoint};
231
+ * the raw runtime key (if a caller genuinely needs it) is {@link key}.
232
+ */
233
+ name: ProfileName;
234
+ /** Endpoint preset this browser is running at, when the key carries one. */
235
+ endpoint?: EndpointName;
236
+ /** The runtime key backing this status: `<profile>@<endpoint>[.<fork>]`. */
237
+ key?: ConnectionKey;
146
238
  running: boolean;
147
239
  port?: number;
148
240
  pid?: number;
@@ -1,3 +1,54 @@
1
+ /** The single site that derives a {@link ConnectionKey} from its two parts. */
2
+ export function connectionKey(profile, endpoint) {
3
+ return `${profile}@${endpoint}`;
4
+ }
5
+ /**
6
+ * Adopt a string that IS already a runtime key — a runtime directory name, a
7
+ * `tasks.json` `profile` field, a fork key. Never use it to launder a bare
8
+ * profile name into a key; use {@link connectionKey} for that.
9
+ */
10
+ export function asConnectionKey(raw) {
11
+ return raw;
12
+ }
13
+ /**
14
+ * Split a {@link ConnectionKey} back into its parts. The inverse of
15
+ * {@link connectionKey}, and the ONE rule every consumer uses to decide whether
16
+ * a runtime key belongs to a profile.
17
+ *
18
+ * Tolerates the legacy shape still on disk: a pre-composite key that is just
19
+ * the bare profile name (`comet-local`).
20
+ *
21
+ * A key with no `@` is read as a WHOLE profile name, never as `<name>.<fork>`,
22
+ * because nothing constrains a profile's name (`createProfile` checks only for
23
+ * a duplicate name and a port collision). Reading the trailing digits as a fork
24
+ * would let `stop --profile chrome` claim the runtime dirs of a separate
25
+ * profile named `chrome.2` and kill its browser. A fork is only ever created
26
+ * from a composite base (`forkElectronProfile` appends to a `<p>@<e>` key), so
27
+ * the `@`-bearing branch below is the one that must understand forks.
28
+ */
29
+ export function parseConnectionKey(key) {
30
+ // LAST `@`, not the first: a profile name may itself contain one (`me@work`),
31
+ // and splitting on the first would report its profile as `me` — so
32
+ // `status --profile me@work` would find nothing for a running browser.
33
+ const at = key.lastIndexOf('@');
34
+ if (at === -1)
35
+ return { profile: key };
36
+ const profile = key.slice(0, at);
37
+ const rest = key.slice(at + 1);
38
+ const dot = rest.indexOf('.');
39
+ if (dot === -1)
40
+ return { profile, endpoint: rest };
41
+ const fork = Number(rest.slice(dot + 1));
42
+ return {
43
+ profile,
44
+ endpoint: rest.slice(0, dot),
45
+ fork: Number.isFinite(fork) ? fork : undefined,
46
+ };
47
+ }
48
+ /** True when `key` is a runtime key of `profile`. The only reconciliation rule. */
49
+ export function keyBelongsToProfile(key, profile) {
50
+ return parseConnectionKey(key).profile === profile;
51
+ }
1
52
  export const TASK_ID_REGEX = /^[a-z0-9][a-z0-9-]*$/;
2
53
  export function isValidTaskId(id) {
3
54
  return TASK_ID_REGEX.test(id) && id.length <= 64;
@@ -38,7 +38,7 @@ import * as fs from 'fs';
38
38
  import * as path from 'path';
39
39
  import { readJob, writeRunMeta, getRunDir, } from './scheduling/routines.js';
40
40
  import { detectOverdueJobs } from './overdue.js';
41
- import { executeJobDetached } from './runner.js';
41
+ import { executeJobDetached } from './daemon/runner.js';
42
42
  /**
43
43
  * Is this routine allowed to run late? Default true — a routine you scheduled
44
44
  * is one you expect to have run, so losing a fire silently is never the helpful
@@ -1,4 +1,4 @@
1
- import type { ComputerClient } from '../computer-rpc.js';
1
+ import type { ComputerClient } from './computer-rpc.js';
2
2
  export interface AppInfo {
3
3
  pid: number;
4
4
  name: string;
@@ -1,7 +1,7 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { randomUUID } from 'node:crypto';
4
- import { resolvePolicyPath } from '../computer-rpc.js';
4
+ import { resolvePolicyPath } from './computer-rpc.js';
5
5
  import { COMPUTER_INPUT_GATED_VERBS, formatComputerPermissionGrantHint } from '../permissions.js';
6
6
  import { emit as emitEvent } from '../feed/events.js';
7
7
  import { recordComputerSession } from '../session/db.js';
@@ -16,7 +16,7 @@ import { createConnection } from 'net';
16
16
  import * as fs from 'fs';
17
17
  import * as path from 'path';
18
18
  import { fileURLToPath } from 'url';
19
- import { getHelpersDir, getLogsDir, getUserPermissionsDir, getPermissionsDir } from './state.js';
19
+ import { getHelpersDir, getLogsDir, getUserPermissionsDir, getPermissionsDir } from '../state.js';
20
20
  // Resolve the socket path used by the launchd-managed daemon. Internal
21
21
  // scratch — lives under getHelpersDir() (~/.agents/.cache/helpers/),
22
22
  // matching browser.sock.
@@ -174,10 +174,10 @@ export function writeComputerPeers(allowedExecPaths) {
174
174
  export function resolveHelperExec() {
175
175
  const here = path.dirname(fileURLToPath(import.meta.url));
176
176
  const candidates = [
177
- // Local build (running from the agents-cli checkout). apps/cli/dist/lib -> repo root (4 up) -> native/computer-mac.
178
- path.resolve(here, '..', '..', '..', '..', 'native', 'computer-mac', 'dist', 'ComputerHelper.app', 'Contents', 'MacOS', 'ComputerHelper'),
179
- // Bundled with the npm package.
180
- path.resolve(here, '..', 'computer-helper', 'ComputerHelper.app', 'Contents', 'MacOS', 'ComputerHelper'),
177
+ // Local build (running from the agents-cli checkout). apps/cli/dist/lib/computer -> repo root (5 up) -> native/computer-mac.
178
+ path.resolve(here, '..', '..', '..', '..', '..', 'native', 'computer-mac', 'dist', 'ComputerHelper.app', 'Contents', 'MacOS', 'ComputerHelper'),
179
+ // Bundled with the npm package (dist/lib/computer -> dist/).
180
+ path.resolve(here, '..', '..', 'computer-helper', 'ComputerHelper.app', 'Contents', 'MacOS', 'ComputerHelper'),
181
181
  ];
182
182
  for (const c of candidates) {
183
183
  if (fs.existsSync(c))
@@ -1,4 +1,4 @@
1
- import type { ComputerClient } from '../computer-rpc.js';
1
+ import type { ComputerClient } from './computer-rpc.js';
2
2
  import { type AppInfo } from './actions.js';
3
3
  import type { VerbDispatcher } from './loop.js';
4
4
  declare function rpcMethodFor(verb: string): string;
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * The helper is a signed + notarized universal `.app` bundle published as a
6
6
  * GitHub release asset per tagged CLI version — the same distribution model as
7
- * the Windows helper (see `lib/ssh-tunnel.ts`). A fresh `npm i -g` machine has
7
+ * the Windows helper (see `lib/computer/ssh-tunnel.ts`). A fresh `npm i -g` machine has
8
8
  * no local build, so `agents computer setup` / `agents setup computer` fetch the
9
9
  * asset for the running CLI version, verify its sha256 against the published
10
10
  * `.sha256`, then verify the code signature (Developer ID Team + notarization)
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * The helper is a signed + notarized universal `.app` bundle published as a
6
6
  * GitHub release asset per tagged CLI version — the same distribution model as
7
- * the Windows helper (see `lib/ssh-tunnel.ts`). A fresh `npm i -g` machine has
7
+ * the Windows helper (see `lib/computer/ssh-tunnel.ts`). A fresh `npm i -g` machine has
8
8
  * no local build, so `agents computer setup` / `agents setup computer` fetch the
9
9
  * asset for the running CLI version, verify its sha256 against the published
10
10
  * `.sha256`, then verify the code signature (Developer ID Team + notarization)
@@ -21,8 +21,8 @@ import { Readable } from 'node:stream';
21
21
  import { pipeline } from 'node:stream/promises';
22
22
  import { getCacheDir } from '../state.js';
23
23
  import { getCliVersion } from '../version.js';
24
- import { parseSha256Asset, sha256File } from '../ssh-tunnel.js';
25
- import { resolveHelperApp } from '../computer-rpc.js';
24
+ import { parseSha256Asset, sha256File } from './ssh-tunnel.js';
25
+ import { resolveHelperApp } from './computer-rpc.js';
26
26
  /** GitHub repo whose `v<version>` releases carry the helper asset. */
27
27
  export const HELPER_RELEASE_REPO = 'phnx-labs/agents-cli';
28
28
  /** The zipped `.app` release asset name. */
@@ -17,7 +17,7 @@
17
17
  * `devices/connect` primitives — no parallel SSH implementation.
18
18
  */
19
19
  import { type ChildProcess } from 'child_process';
20
- import { type DeviceProfile } from './devices/registry.js';
20
+ import { type DeviceProfile } from '../devices/registry.js';
21
21
  export interface StartTunnelOptions {
22
22
  /**
23
23
  * Detach the tunnel so it OUTLIVES this CLI process. Used by
@@ -24,14 +24,14 @@ import { fileURLToPath } from 'url';
24
24
  import { randomBytes, createHash } from 'crypto';
25
25
  import { Readable } from 'stream';
26
26
  import { pipeline } from 'stream/promises';
27
- import { sshExec, SSH_OPTS, assertValidSshTarget } from './ssh-exec.js';
28
- import { backgroundSpawnOptions } from './platform/process.js';
29
- import { encodePowerShell } from './browser/drivers/ssh.js';
30
- import { getDevice } from './devices/registry.js';
31
- import { deviceIdentityArgs, sshTargetFor } from './devices/connect.js';
32
- import { hostNameFor } from './devices/ssh-config.js';
33
- import { getCacheDir } from './state.js';
34
- import { getCliVersion } from './version.js';
27
+ import { sshExec, SSH_OPTS, assertValidSshTarget } from '../ssh-exec.js';
28
+ import { backgroundSpawnOptions } from '../platform/process.js';
29
+ import { encodePowerShell } from '../browser/drivers/ssh.js';
30
+ import { getDevice } from '../devices/registry.js';
31
+ import { deviceIdentityArgs, sshTargetFor } from '../devices/connect.js';
32
+ import { hostNameFor } from '../devices/ssh-config.js';
33
+ import { getCacheDir } from '../state.js';
34
+ import { getCliVersion } from '../version.js';
35
35
  import { openComputerClient, resolveTcpEndpoint } from './computer-rpc.js';
36
36
  /** Build the ssh argv (after the `ssh` program name) for an `-L` tunnel. Pure.
37
37
  *
@@ -115,10 +115,10 @@ export const WIN_HELPER_TOKEN_FILE = 'helper-token';
115
115
  export function resolveWinHelperExe() {
116
116
  const here = path.dirname(fileURLToPath(import.meta.url));
117
117
  const candidates = [
118
- // Running from the agents-cli checkout. apps/cli/dist/lib -> repo root (4 up) -> native/computer-win.
119
- path.resolve(here, '..', '..', '..', '..', 'native', 'computer-win', 'dist', WIN_HELPER_EXE),
120
- // Bundled with the npm package (dist/lib -> package root).
121
- path.resolve(here, '..', 'computer-helper-win', WIN_HELPER_EXE),
118
+ // Running from the agents-cli checkout. apps/cli/dist/lib/computer -> repo root (5 up) -> native/computer-win.
119
+ path.resolve(here, '..', '..', '..', '..', '..', 'native', 'computer-win', 'dist', WIN_HELPER_EXE),
120
+ // Bundled with the npm package (dist/lib/computer -> dist/).
121
+ path.resolve(here, '..', '..', 'computer-helper-win', WIN_HELPER_EXE),
122
122
  ];
123
123
  for (const c of candidates) {
124
124
  if (fs.existsSync(c))
@@ -17,7 +17,7 @@ import { listJobs as listAllJobs } from '../scheduling/routines.js';
17
17
  import { syncAllProjectRoutines } from '../routines-project.js';
18
18
  import { JobScheduler } from '../scheduler.js';
19
19
  import { MonitorEngine } from '../monitors/engine.js';
20
- import { executeJobDetached, monitorRunningJobs, listLiveRoutineChildren } from '../runner.js';
20
+ import { executeJobDetached, monitorRunningJobs, listLiveRoutineChildren } from './runner.js';
21
21
  import { detectOverdueJobs, notifyOverdue } from '../overdue.js';
22
22
  import { runCatchup } from '../catchup.js';
23
23
  import { notifyRoutineStart, notifyRoutineFinish, notifyRoutineStartFailed } from '../routine-notify.js';
@@ -13,12 +13,12 @@
13
13
  * limit is detected mid-run (foreground `executeJob` only — detached daemon
14
14
  * fires once with the pre-flight pick).
15
15
  */
16
- import type { JobConfig, RunMeta } from './scheduling/routines.js';
17
- import { readMeta } from './state.js';
18
- import type { AgentId } from './types.js';
19
- import { type FallbackEntry } from './exec.js';
20
- import type { LoopDeps } from './loop.js';
21
- import { resolveRunVersion, resolveAccountVersion, type RotateResult } from './accounting/rotate.js';
16
+ import type { JobConfig, RunMeta } from '../scheduling/routines.js';
17
+ import { readMeta } from '../state.js';
18
+ import type { AgentId } from '../types.js';
19
+ import { type FallbackEntry } from '../exec.js';
20
+ import type { LoopDeps } from '../loop.js';
21
+ import { resolveRunVersion, resolveAccountVersion, type RotateResult } from '../accounting/rotate.js';
22
22
  /** Result of a completed job execution, including metadata and optional report. */
23
23
  export interface RunResult {
24
24
  meta: RunMeta;
@@ -142,11 +142,11 @@ export declare function pinJobBinary(cmd: string[], agent: AgentId, version: str
142
142
  * registry or a real dispatch.
143
143
  */
144
144
  export declare function assertRoutineAccountLocalForPlacement(config: Pick<JobConfig, 'name' | 'account'>, mode: 'host' | 'cloud', deps?: {
145
- account?: import('./account-registry.js').UnifiedAccount | null;
145
+ account?: import('../account-registry.js').UnifiedAccount | null;
146
146
  readMeta?: typeof readMeta;
147
147
  }): Promise<void>;
148
- export declare function dispatchPlacedJob(config: JobConfig, target: import('./routines-placement.js').PlacementTarget, attempt: RoutineAttempt, deps?: {
149
- account?: import('./account-registry.js').UnifiedAccount | null;
148
+ export declare function dispatchPlacedJob(config: JobConfig, target: import('../routines-placement.js').PlacementTarget, attempt: RoutineAttempt, deps?: {
149
+ account?: import('../account-registry.js').UnifiedAccount | null;
150
150
  host?: typeof executeJobOnHost;
151
151
  cloud?: typeof executeJobOnCloud;
152
152
  }): Promise<RunResult | undefined>;
@@ -160,7 +160,7 @@ export declare function buildHostDispatchOptions(config: JobConfig, ctx: {
160
160
  remoteCwd: string | undefined;
161
161
  runDir: string;
162
162
  detached: boolean;
163
- }): import('./hosts/run-target.js').HostPromptRun;
163
+ }): import('../hosts/run-target.js').HostPromptRun;
164
164
  export declare function dispatchesViaAgentsRun(config: Pick<JobConfig, 'workflow' | 'resume'>): boolean;
165
165
  /**
166
166
  * Merge sandbox/base env with the canonical per-version exec env
@@ -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, 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';
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, 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})`);
@@ -521,87 +521,13 @@ export function buildJobCommand(config, resolvedPrompt, forwardAccount = true) {
521
521
  let cmd = template.map((part) => part.replace('{prompt}', resolvedPrompt));
522
522
  // Canonicalize mode (accepts legacy `full` as alias for `skip`).
523
523
  const mode = normalizeMode(config.mode);
524
- if (config.agent === 'claude') {
525
- if (mode === 'edit') {
526
- const planIndex = cmd.indexOf('plan');
527
- if (planIndex !== -1)
528
- cmd[planIndex] = 'acceptEdits';
529
- }
530
- else if (mode === 'auto') {
531
- const planIndex = cmd.indexOf('plan');
532
- if (planIndex !== -1)
533
- cmd[planIndex] = 'auto';
534
- }
535
- else if (mode === 'skip') {
536
- // Replace --permission-mode plan with --dangerously-skip-permissions
537
- const pmIndex = cmd.indexOf('--permission-mode');
538
- if (pmIndex !== -1)
539
- cmd.splice(pmIndex, 2, '--dangerously-skip-permissions');
540
- }
541
- appendModelAndReasoning(cmd, config);
542
- }
543
- if (config.agent === 'codex') {
544
- const policyMode = mode === 'plan' || mode === 'skip' ? mode : 'edit';
545
- const routineRoots = (config.allow?.dirs ?? []).map((dir) => {
546
- if (dir.startsWith('-')) {
547
- throw new Error(`allow.dirs entries must not start with '-': ${JSON.stringify(dir)}`);
548
- }
549
- return dir.replace(/^~/, os.homedir());
550
- });
551
- cmd.push(...codexPolicyArgs(policyMode, [...codexEditWritableRoots(), ...routineRoots]));
552
- appendModelAndReasoning(cmd, config);
553
- }
554
- if (config.agent === 'cursor') {
555
- if (mode === 'plan') {
556
- cmd.push('--plan');
557
- }
558
- else if (mode === 'skip') {
559
- cmd.push('-f');
560
- }
561
- else {
562
- // The configured cwd is the user's workspace trust decision. --trust is
563
- // narrower than --yolo/-f because it does not bypass tool permissions.
564
- cmd.push('--trust');
565
- }
566
- appendModelAndReasoning(cmd, config);
567
- }
568
- if (config.agent === 'kimi') {
569
- // kimi daemon jobs always run headless via `--prompt`, which cannot be
570
- // combined with any startup-mode flag (--plan/--auto/--yolo all abort with
571
- // "Cannot combine --prompt with --X"). edit/auto/skip reduce to kimi's default
572
- // headless auto-run, so emit no flag. plan has no headless read-only
573
- // equivalent, so resolveHeadlessMode downgrades a plan request to auto with a
574
- // stderr warning (kimi's headlessPlan is false) — routines run headless, so
575
- // interactive is always false here. The returned mode carries no flag either.
576
- resolveHeadlessMode('kimi', mode, false);
577
- appendModelAndReasoning(cmd, config);
578
- }
579
- if (config.agent === 'droid') {
580
- // droid exec defaults to read-only (plan). Escalate autonomy per mode.
581
- if (mode === 'edit') {
582
- cmd.push('--auto', 'low');
583
- }
584
- else if (mode === 'auto') {
585
- cmd.push('--auto', 'high');
586
- }
587
- else if (mode === 'skip') {
588
- cmd.push('--skip-permissions-unsafe');
589
- }
590
- appendModelAndReasoning(cmd, config);
591
- }
592
- if (config.agent === 'muse') {
593
- // muse exec: plan ≈ no non-shell writes; auto skips approval prompts but
594
- // keeps the OS sandbox; skip is --yolo (no approval, no sandbox, trust).
595
- if (mode === 'plan') {
596
- cmd.push('--disable-write');
597
- }
598
- else if (mode === 'auto') {
599
- cmd.push('--disable-approval');
600
- }
601
- else if (mode === 'skip') {
602
- cmd.push('--yolo');
603
- }
604
- // 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 });
605
531
  appendModelAndReasoning(cmd, config);
606
532
  }
607
533
  // allow.dirs → harness-specific grants. Codex is handled in its branch above
@@ -885,7 +811,7 @@ export async function resolveRoutineLaunch(config, cwd = process.cwd(), deps = {
885
811
  // resolveRoutineLaunch is only called for agent jobs (workflow returns above;
886
812
  // command jobs branch out of execute*Job before reaching this).
887
813
  const agent = config.agent;
888
- const { findAccount, findUnifiedAccount, resolveAccountSelection, resolveCredentialAccount } = await import('./account-registry.js');
814
+ const { findAccount, findUnifiedAccount, resolveAccountSelection, resolveCredentialAccount } = await import('../account-registry.js');
889
815
  const meta = (deps.readMeta ?? readMeta)();
890
816
  const explicitCredential = config.account
891
817
  ? (deps.findCredentialAccount?.(config.account) ?? (deps.resolveCredentialAccount !== undefined || findAccount(config.account) !== null))
@@ -1041,7 +967,7 @@ export async function assertRoutineAccountLocalForPlacement(config, mode, deps =
1041
967
  return;
1042
968
  let account = deps.account;
1043
969
  if (account === undefined) {
1044
- const { findUnifiedAccount } = await import('./account-registry.js');
970
+ const { findUnifiedAccount } = await import('../account-registry.js');
1045
971
  account = findUnifiedAccount(config.account, (deps.readMeta ?? readMeta)());
1046
972
  }
1047
973
  if (!account) {
@@ -1261,7 +1187,7 @@ async function executeJobPlaced(config, deps, attempt) {
1261
1187
  // do not apply. Sync callers (manual `routines run`, catchup) follow the
1262
1188
  // remote run to completion when possible.
1263
1189
  {
1264
- const { resolvePlacementTarget } = await import('./routines-placement.js');
1190
+ const { resolvePlacementTarget } = await import('../routines-placement.js');
1265
1191
  const target = await resolvePlacementTarget(config);
1266
1192
  const placed = await dispatchPlacedJob(config, target, attempt);
1267
1193
  if (placed)
@@ -1301,7 +1227,7 @@ async function executeJobPlaced(config, deps, attempt) {
1301
1227
  // (command jobs branched out earlier, so config.agent is set on the non-workflow path.)
1302
1228
  const effectiveAgent = config.workflow ? 'claude' : config.agent;
1303
1229
  if (!dispatchesViaAgentsRun(config)) {
1304
- const { findUnifiedAccount, resolveAccountSelection, resolveCredentialAccount } = await import('./account-registry.js');
1230
+ const { findUnifiedAccount, resolveAccountSelection, resolveCredentialAccount } = await import('../account-registry.js');
1305
1231
  const meta = readMeta();
1306
1232
  const selectedAccount = resolveAccountSelection(config.account, effectiveAgent, meta);
1307
1233
  if (selectedAccount) {
@@ -1374,7 +1300,7 @@ async function executeJobPlaced(config, deps, attempt) {
1374
1300
  .map((d) => d.replace(/^~/, os.homedir())),
1375
1301
  } : {}),
1376
1302
  };
1377
- const { runLoop } = await import('./loop.js');
1303
+ const { runLoop } = await import('../loop.js');
1378
1304
  const loopResult = await runLoop(execOptions, config.loop, {
1379
1305
  runId,
1380
1306
  runDir,
@@ -1530,8 +1456,8 @@ async function executeJobOnCloud(config, opts, attempt) {
1530
1456
  if (!config.agent) {
1531
1457
  throw new Error(`Routine '${config.name}' hostStrategy: cloud requires an agent`);
1532
1458
  }
1533
- const { resolveProvider } = await import('./cloud/registry.js');
1534
- const { insertTask } = await import('./cloud/store.js');
1459
+ const { resolveProvider } = await import('../cloud/registry.js');
1460
+ const { insertTask } = await import('../cloud/store.js');
1535
1461
  const provider = resolveProvider(undefined, config.agent);
1536
1462
  const timer = createTimer('agent.run', {
1537
1463
  agent: config.agent,
@@ -1604,9 +1530,9 @@ async function executeJobOnHost(config, opts, attempt) {
1604
1530
  if (config.command) {
1605
1531
  throw new Error(`Routine '${config.name}' uses 'command:', which can't execute on a host yet — remove 'host:' or 'command:'.`);
1606
1532
  }
1607
- const { resolveHostRunTarget, dispatchPromptToHost } = await import('./hosts/run-target.js');
1533
+ const { resolveHostRunTarget, dispatchPromptToHost } = await import('../hosts/run-target.js');
1608
1534
  const host = await resolveHostRunTarget(config.host);
1609
- const { evaluateHostActivationReadiness } = await import('./routine-readiness.js');
1535
+ const { evaluateHostActivationReadiness } = await import('../routine-readiness.js');
1610
1536
  const readiness = await evaluateHostActivationReadiness(config);
1611
1537
  if (!readiness.ready)
1612
1538
  throw new Error(`${readiness.readiness?.code ?? 'not_ready'}: ${readiness.readiness?.message ?? 'target is not ready'}`);
@@ -1754,7 +1680,7 @@ async function executeJobDetachedClaimed(config, attempt, hooks) {
1754
1680
  // (the monitor tick observes an already-finalized record), so notify-desktop
1755
1681
  // sends the finish notification only for local detached runs below (RUSH-2030).
1756
1682
  {
1757
- const { resolvePlacementTarget } = await import('./routines-placement.js');
1683
+ const { resolvePlacementTarget } = await import('../routines-placement.js');
1758
1684
  const target = await resolvePlacementTarget(config);
1759
1685
  if (target.mode === 'host' || target.mode === 'cloud') {
1760
1686
  await assertRoutineAccountLocalForPlacement(config, target.mode);
@@ -1813,7 +1739,7 @@ async function executeJobDetachedClaimed(config, attempt, hooks) {
1813
1739
  ? buildSpawnEnv(overlayHome, config.env)
1814
1740
  : { ...process.env }, config);
1815
1741
  if (!dispatchesViaAgentsRun(config)) {
1816
- const { findAccount, resolveAccountSelection, resolveCredentialAccount } = await import('./account-registry.js');
1742
+ const { findAccount, resolveAccountSelection, resolveCredentialAccount } = await import('../account-registry.js');
1817
1743
  const selectedAccount = config.account && !findAccount(config.account)
1818
1744
  ? undefined
1819
1745
  : resolveAccountSelection(config.account, config.agent, readMeta());
@@ -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';