@phnx-labs/agents-cli 1.22.22 → 1.22.23

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 (91) hide show
  1. package/CHANGELOG.md +209 -0
  2. package/README.md +8 -2
  3. package/dist/commands/doctor.js +15 -7
  4. package/dist/commands/exec.js +20 -6
  5. package/dist/commands/focus.d.ts +76 -4
  6. package/dist/commands/focus.js +219 -40
  7. package/dist/commands/fork.d.ts +20 -2
  8. package/dist/commands/fork.js +91 -64
  9. package/dist/commands/go.d.ts +25 -0
  10. package/dist/commands/go.js +63 -2
  11. package/dist/commands/harness-wizard.d.ts +206 -0
  12. package/dist/commands/harness-wizard.js +403 -0
  13. package/dist/commands/harness.d.ts +12 -0
  14. package/dist/commands/harness.js +97 -101
  15. package/dist/commands/resume.js +10 -3
  16. package/dist/commands/secrets.js +25 -30
  17. package/dist/commands/sessions-resume.d.ts +21 -3
  18. package/dist/commands/sessions-resume.js +50 -11
  19. package/dist/commands/sessions.d.ts +81 -5
  20. package/dist/commands/sessions.js +325 -66
  21. package/dist/commands/watchdog.js +13 -2
  22. package/dist/lib/agents.d.ts +1 -1
  23. package/dist/lib/agents.js +132 -0
  24. package/dist/lib/codex-policy.d.ts +17 -0
  25. package/dist/lib/codex-policy.js +48 -0
  26. package/dist/lib/crabbox/lease.d.ts +25 -0
  27. package/dist/lib/crabbox/lease.js +62 -0
  28. package/dist/lib/daemon.js +70 -0
  29. package/dist/lib/exec.d.ts +4 -0
  30. package/dist/lib/exec.js +88 -54
  31. package/dist/lib/feed-broadcast.d.ts +1 -20
  32. package/dist/lib/feed-broadcast.js +31 -1
  33. package/dist/lib/hooks.js +12 -2
  34. package/dist/lib/mcp.js +44 -0
  35. package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +1 -5
  36. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  37. package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +2 -15
  38. package/dist/lib/models.d.ts +0 -5
  39. package/dist/lib/models.js +48 -0
  40. package/dist/lib/plugin-marketplace.js +9 -0
  41. package/dist/lib/pricing/prices.json +119 -92
  42. package/dist/lib/pricing/table.js +13 -0
  43. package/dist/lib/remote-agents-json.d.ts +29 -1
  44. package/dist/lib/remote-agents-json.js +47 -10
  45. package/dist/lib/resources/mcp.js +2 -0
  46. package/dist/lib/resources/permissions.js +3 -0
  47. package/dist/lib/resources/types.d.ts +2 -1
  48. package/dist/lib/runner.js +28 -19
  49. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  50. package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -2
  51. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  52. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -13
  53. package/dist/lib/secrets/index.d.ts +15 -1
  54. package/dist/lib/secrets/index.js +118 -49
  55. package/dist/lib/secrets/reaper.d.ts +87 -0
  56. package/dist/lib/secrets/reaper.js +184 -0
  57. package/dist/lib/secrets/remote.d.ts +29 -0
  58. package/dist/lib/secrets/remote.js +37 -1
  59. package/dist/lib/session/active.d.ts +36 -1
  60. package/dist/lib/session/active.js +60 -19
  61. package/dist/lib/session/actor-sidecar.d.ts +14 -0
  62. package/dist/lib/session/actor-sidecar.js +67 -4
  63. package/dist/lib/session/db.d.ts +1 -1
  64. package/dist/lib/session/db.js +32 -1
  65. package/dist/lib/session/discover.js +168 -0
  66. package/dist/lib/session/parse.d.ts +10 -0
  67. package/dist/lib/session/parse.js +98 -0
  68. package/dist/lib/session/remote-list.d.ts +10 -1
  69. package/dist/lib/session/remote-list.js +2 -8
  70. package/dist/lib/session/remote.d.ts +57 -3
  71. package/dist/lib/session/remote.js +90 -26
  72. package/dist/lib/session/resume-command.d.ts +6 -0
  73. package/dist/lib/session/resume-command.js +8 -0
  74. package/dist/lib/session/session-cache.d.ts +173 -0
  75. package/dist/lib/session/session-cache.js +399 -0
  76. package/dist/lib/session/types.d.ts +1 -1
  77. package/dist/lib/session/types.js +1 -1
  78. package/dist/lib/session/width.d.ts +1 -1
  79. package/dist/lib/session/width.js +12 -2
  80. package/dist/lib/shims.d.ts +2 -2
  81. package/dist/lib/shims.js +40 -5
  82. package/dist/lib/staleness/writers/hooks.js +1 -1
  83. package/dist/lib/types.d.ts +1 -1
  84. package/dist/lib/usage.d.ts +13 -0
  85. package/dist/lib/usage.js +215 -0
  86. package/dist/lib/versions.js +13 -2
  87. package/package.json +1 -1
  88. package/dist/bin/agents +0 -0
  89. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  90. package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
  91. package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
@@ -1,153 +1,180 @@
1
1
  {
2
- "version": "2026-08-03",
2
+ "version": "2026-08-05",
3
3
  "models": {
4
4
  "claude-opus-4": {
5
+ "inputPerToken": 5e-06,
6
+ "outputPerToken": 2.5e-05,
7
+ "cacheReadPerToken": 5e-07,
8
+ "cacheWritePerToken": 6.25e-06
9
+ },
10
+ "claude-sonnet-4": {
11
+ "inputPerToken": 3e-06,
12
+ "outputPerToken": 1.5e-05,
13
+ "cacheReadPerToken": 3e-07,
14
+ "cacheWritePerToken": 3.75e-06
15
+ },
16
+ "claude-haiku-4": {
17
+ "inputPerToken": 1e-06,
18
+ "outputPerToken": 5e-06,
19
+ "cacheReadPerToken": 1e-07,
20
+ "cacheWritePerToken": 1.25e-06
21
+ },
22
+ "claude-opus-5": {
5
23
  "inputPerToken": 0.000005,
6
24
  "outputPerToken": 0.000025,
7
25
  "cacheReadPerToken": 0.0000005,
8
26
  "cacheWritePerToken": 0.00000625
9
27
  },
10
- "claude-sonnet-4": {
11
- "inputPerToken": 0.000003,
12
- "outputPerToken": 0.000015,
13
- "cacheReadPerToken": 0.0000003,
14
- "cacheWritePerToken": 0.00000375
15
- },
16
- "claude-haiku-4": {
17
- "inputPerToken": 0.000001,
18
- "outputPerToken": 0.000005,
19
- "cacheReadPerToken": 0.0000001,
20
- "cacheWritePerToken": 0.00000125
28
+ "claude-sonnet-5": {
29
+ "inputPerToken": 0.000002,
30
+ "outputPerToken": 0.00001,
31
+ "cacheReadPerToken": 0.0000002,
32
+ "cacheWritePerToken": 0.0000025
21
33
  },
22
34
  "claude-fable-5": {
23
- "inputPerToken": 0.00001,
24
- "outputPerToken": 0.00005,
25
- "cacheReadPerToken": 0.000001,
26
- "cacheWritePerToken": 0.0000125
35
+ "inputPerToken": 1e-05,
36
+ "outputPerToken": 5e-05,
37
+ "cacheReadPerToken": 1e-06,
38
+ "cacheWritePerToken": 1.25e-05
27
39
  },
28
40
  "claude-mythos-5": {
29
- "inputPerToken": 0.00001,
30
- "outputPerToken": 0.00005,
31
- "cacheReadPerToken": 0.000001,
32
- "cacheWritePerToken": 0.0000125
41
+ "inputPerToken": 1e-05,
42
+ "outputPerToken": 5e-05,
43
+ "cacheReadPerToken": 1e-06,
44
+ "cacheWritePerToken": 1.25e-05
33
45
  },
34
46
  "claude-3-5-sonnet": {
35
- "inputPerToken": 0.000003,
36
- "outputPerToken": 0.000015,
37
- "cacheReadPerToken": 0.0000003,
38
- "cacheWritePerToken": 0.00000375
47
+ "inputPerToken": 3e-06,
48
+ "outputPerToken": 1.5e-05,
49
+ "cacheReadPerToken": 3e-07,
50
+ "cacheWritePerToken": 3.75e-06
39
51
  },
40
52
  "claude-3-5-haiku": {
41
- "inputPerToken": 0.0000008,
42
- "outputPerToken": 0.000004,
43
- "cacheReadPerToken": 0.00000008,
44
- "cacheWritePerToken": 0.000001
53
+ "inputPerToken": 8e-07,
54
+ "outputPerToken": 4e-06,
55
+ "cacheReadPerToken": 8e-08,
56
+ "cacheWritePerToken": 1e-06
45
57
  },
46
58
  "claude-3-opus": {
47
- "inputPerToken": 0.000015,
48
- "outputPerToken": 0.000075,
49
- "cacheReadPerToken": 0.0000015,
50
- "cacheWritePerToken": 0.00001875
59
+ "inputPerToken": 1.5e-05,
60
+ "outputPerToken": 7.5e-05,
61
+ "cacheReadPerToken": 1.5e-06,
62
+ "cacheWritePerToken": 1.875e-05
51
63
  },
52
64
  "gpt-5.6-sol": {
53
- "inputPerToken": 0.000005,
54
- "outputPerToken": 0.00003,
55
- "cacheReadPerToken": 0.0000005
65
+ "inputPerToken": 5e-06,
66
+ "outputPerToken": 3e-05,
67
+ "cacheReadPerToken": 5e-07
56
68
  },
57
69
  "gpt-5.6-terra": {
58
- "inputPerToken": 0.0000025,
59
- "outputPerToken": 0.000015,
60
- "cacheReadPerToken": 0.00000025
70
+ "inputPerToken": 2.5e-06,
71
+ "outputPerToken": 1.5e-05,
72
+ "cacheReadPerToken": 2.5e-07
61
73
  },
62
74
  "gpt-5.6-luna": {
63
- "inputPerToken": 0.000001,
64
- "outputPerToken": 0.000006,
65
- "cacheReadPerToken": 0.0000001
75
+ "inputPerToken": 1e-06,
76
+ "outputPerToken": 6e-06,
77
+ "cacheReadPerToken": 1e-07
66
78
  },
67
79
  "gpt-5.5": {
68
- "inputPerToken": 0.000005,
69
- "outputPerToken": 0.00003,
70
- "cacheReadPerToken": 0.0000005
80
+ "inputPerToken": 5e-06,
81
+ "outputPerToken": 3e-05,
82
+ "cacheReadPerToken": 5e-07
71
83
  },
72
84
  "gpt-5.4": {
73
- "inputPerToken": 0.0000025,
74
- "outputPerToken": 0.000015,
75
- "cacheReadPerToken": 0.00000025
85
+ "inputPerToken": 2.5e-06,
86
+ "outputPerToken": 1.5e-05,
87
+ "cacheReadPerToken": 2.5e-07
76
88
  },
77
89
  "gpt-5.4-mini": {
78
- "inputPerToken": 0.00000075,
79
- "outputPerToken": 0.0000045,
80
- "cacheReadPerToken": 0.000000075
90
+ "inputPerToken": 7.5e-07,
91
+ "outputPerToken": 4.5e-06,
92
+ "cacheReadPerToken": 7.5e-08
81
93
  },
82
94
  "gpt-5.4-nano": {
83
- "inputPerToken": 0.0000002,
84
- "outputPerToken": 0.00000125,
85
- "cacheReadPerToken": 0.00000002
95
+ "inputPerToken": 2e-07,
96
+ "outputPerToken": 1.25e-06,
97
+ "cacheReadPerToken": 2e-08
86
98
  },
87
99
  "gpt-5": {
88
- "inputPerToken": 0.00000125,
89
- "outputPerToken": 0.00001,
90
- "cacheReadPerToken": 0.000000125
100
+ "inputPerToken": 1.25e-06,
101
+ "outputPerToken": 1e-05,
102
+ "cacheReadPerToken": 1.25e-07
91
103
  },
92
104
  "gpt-4.1": {
93
- "inputPerToken": 0.000002,
94
- "outputPerToken": 0.000008,
95
- "cacheReadPerToken": 0.0000005
105
+ "inputPerToken": 2e-06,
106
+ "outputPerToken": 8e-06,
107
+ "cacheReadPerToken": 5e-07
96
108
  },
97
109
  "gpt-4.1-mini": {
98
- "inputPerToken": 0.0000004,
99
- "outputPerToken": 0.0000016,
100
- "cacheReadPerToken": 0.0000001
110
+ "inputPerToken": 4e-07,
111
+ "outputPerToken": 1.6e-06,
112
+ "cacheReadPerToken": 1e-07
101
113
  },
102
114
  "gpt-4.1-nano": {
103
- "inputPerToken": 0.0000001,
104
- "outputPerToken": 0.0000004,
105
- "cacheReadPerToken": 0.000000025
115
+ "inputPerToken": 1e-07,
116
+ "outputPerToken": 4e-07,
117
+ "cacheReadPerToken": 2.5e-08
106
118
  },
107
119
  "gpt-4o": {
108
- "inputPerToken": 0.0000025,
109
- "outputPerToken": 0.00001,
110
- "cacheReadPerToken": 0.00000125
120
+ "inputPerToken": 2.5e-06,
121
+ "outputPerToken": 1e-05,
122
+ "cacheReadPerToken": 1.25e-06
111
123
  },
112
124
  "gpt-4o-mini": {
113
- "inputPerToken": 0.00000015,
114
- "outputPerToken": 0.0000006,
115
- "cacheReadPerToken": 0.000000075
125
+ "inputPerToken": 1.5e-07,
126
+ "outputPerToken": 6e-07,
127
+ "cacheReadPerToken": 7.5e-08
116
128
  },
117
129
  "o3": {
118
- "inputPerToken": 0.000002,
119
- "outputPerToken": 0.000008,
120
- "cacheReadPerToken": 0.0000005
130
+ "inputPerToken": 2e-06,
131
+ "outputPerToken": 8e-06,
132
+ "cacheReadPerToken": 5e-07
121
133
  },
122
134
  "o4-mini": {
123
- "inputPerToken": 0.0000011,
124
- "outputPerToken": 0.0000044,
125
- "cacheReadPerToken": 0.000000275
135
+ "inputPerToken": 1.1e-06,
136
+ "outputPerToken": 4.4e-06,
137
+ "cacheReadPerToken": 2.75e-07
126
138
  },
127
139
  "gemini-2.5-pro": {
128
- "inputPerToken": 0.00000125,
129
- "outputPerToken": 0.00001,
130
- "cacheReadPerToken": 0.0000003125
140
+ "inputPerToken": 1.25e-06,
141
+ "outputPerToken": 1e-05,
142
+ "cacheReadPerToken": 3.125e-07
131
143
  },
132
144
  "gemini-2.5-flash": {
133
- "inputPerToken": 0.0000003,
134
- "outputPerToken": 0.0000025,
135
- "cacheReadPerToken": 0.000000075
145
+ "inputPerToken": 3e-07,
146
+ "outputPerToken": 2.5e-06,
147
+ "cacheReadPerToken": 7.5e-08
136
148
  },
137
149
  "gemini-2.5-flash-lite": {
138
- "inputPerToken": 0.0000001,
139
- "outputPerToken": 0.0000004,
140
- "cacheReadPerToken": 0.000000025
150
+ "inputPerToken": 1e-07,
151
+ "outputPerToken": 4e-07,
152
+ "cacheReadPerToken": 2.5e-08
141
153
  },
142
154
  "gemini-1.5-pro": {
143
- "inputPerToken": 0.00000125,
144
- "outputPerToken": 0.000005,
145
- "cacheReadPerToken": 0.0000003125
155
+ "inputPerToken": 1.25e-06,
156
+ "outputPerToken": 5e-06,
157
+ "cacheReadPerToken": 3.125e-07
146
158
  },
147
159
  "gemini-1.5-flash": {
148
- "inputPerToken": 0.000000075,
149
- "outputPerToken": 0.0000003,
150
- "cacheReadPerToken": 0.00000001875
160
+ "inputPerToken": 7.5e-08,
161
+ "outputPerToken": 3e-07,
162
+ "cacheReadPerToken": 1.875e-08
163
+ },
164
+ "muse-spark-1.2": {
165
+ "inputPerToken": 1.25e-06,
166
+ "outputPerToken": 4.25e-06,
167
+ "cacheReadPerToken": 1.5e-07
168
+ },
169
+ "muse-spark-1.1": {
170
+ "inputPerToken": 1.25e-06,
171
+ "outputPerToken": 4.25e-06,
172
+ "cacheReadPerToken": 1.5e-07
173
+ },
174
+ "muse-spark-1.2-contributor": {
175
+ "inputPerToken": 1e-07,
176
+ "outputPerToken": 2e-07,
177
+ "cacheReadPerToken": 2e-09
151
178
  }
152
179
  }
153
180
  }
@@ -5,6 +5,19 @@
5
5
  * and is imported with a `type: json` attribute so it survives `tsc` emit AND
6
6
  * Node ESM's import-attribute requirement at runtime (the package is ESM).
7
7
  *
8
+ * ## Keeping it current
9
+ *
10
+ * A model missing from `prices.json` prices to NOTHING — `getModelPricing` returns
11
+ * null and the session contributes $0, silently. That is how 478 `claude-opus-5`
12
+ * sessions read as free: matching is dash-bounded, so `claude-opus-5` cannot fall back
13
+ * to the `claude-opus-4` entry. When a new model ships, add it here in the same change.
14
+ *
15
+ * **Dated rate: Claude Sonnet 5 is priced at its introductory $2/$10 per MTok, which
16
+ * ends 2026-08-31.** From 2026-09-01 the standard rate is $3/$15 (cache write $3.75,
17
+ * cache read $0.30). This table has no notion of an effective date — every row is a
18
+ * single current rate — so that entry MUST be updated then, or Sonnet 5 spend will
19
+ * read ~33% low. Source: https://platform.claude.com/docs/en/about-claude/pricing
20
+ *
8
21
  * `getModelPricing` is prefix/suffix-tolerant: real model identifiers carry
9
22
  * vendor prefixes (`us.anthropic.`), version dashes (`claude-opus-4-8`), and
10
23
  * date suffixes (`-20250514`), none of which appear in the canonical keys. We
@@ -12,6 +12,34 @@ export interface RemoteAgentsJsonOptions<T> {
12
12
  quiet?: boolean;
13
13
  /** Per-peer deadline. Long-running maintenance commands override 12 seconds. */
14
14
  timeoutMs?: number;
15
+ /**
16
+ * Opt-in early-exit for a globally-unique lookup (a full session UUID). When a
17
+ * peer returns an item that satisfies {@link isDefinitive}, the fan-out
18
+ * resolves immediately and SIGTERMs every still-outstanding peer instead of
19
+ * waiting for the slowest one to hit {@link REMOTE_TIMEOUT_MS}.
20
+ *
21
+ * OMITTED BY DEFAULT — tool-search, program-count, and the default session
22
+ * listing keep the all-settle behavior (ambiguity, or a label/prefix conflict,
23
+ * is only known once every peer has answered). Only the full-UUID resolve path
24
+ * opts in, since only a UUID guarantees the first hit is the only hit.
25
+ */
26
+ earlyExit?: {
27
+ isDefinitive: (item: T, machine: string) => boolean;
28
+ };
29
+ }
30
+ /**
31
+ * The SSH boundary as a swappable dependency so the fan-out logic is testable
32
+ * without a live tailnet. Production wires this to the real {@link sshCapture}.
33
+ */
34
+ export type SshCaptureFn = (target: string, remoteCmd: string, opts: {
35
+ timeoutMs: number;
36
+ signal?: AbortSignal;
37
+ }) => Promise<{
38
+ code: number | null;
39
+ stdout: string;
40
+ }>;
41
+ export interface GatherRemoteAgentsJsonDeps {
42
+ capture?: SshCaptureFn;
15
43
  }
16
44
  export interface RemoteAgentsJsonParseResult<T> {
17
45
  items: T[];
@@ -35,4 +63,4 @@ export declare function parseRemoteAgentsJsonPayload<T>(stdout: string, machine:
35
63
  /** Build the command one peer runs, with a guard that prevents recursive fan-out. */
36
64
  export declare function remoteAgentsJsonCommand(args: string[], noFanoutEnv: string, os?: string): string;
37
65
  /** Query explicit hosts, or every registered online peer when hosts is omitted. */
38
- export declare function gatherRemoteAgentsJson<T>(options: RemoteAgentsJsonOptions<T>): Promise<RemoteAgentsJsonResult<T>>;
66
+ export declare function gatherRemoteAgentsJson<T>(options: RemoteAgentsJsonOptions<T>, deps?: GatherRemoteAgentsJsonDeps): Promise<RemoteAgentsJsonResult<T>>;
@@ -7,6 +7,7 @@
7
7
  * healthy results from the rest of the fleet.
8
8
  */
9
9
  import { spawn } from 'child_process';
10
+ import { setMaxListeners } from 'node:events';
10
11
  import chalk from 'chalk';
11
12
  import { SSH_OPTS, controlOpts, assertValidSshTarget, shellQuote } from './ssh-exec.js';
12
13
  import { sshTargetFor } from './devices/connect.js';
@@ -32,9 +33,13 @@ export function remoteAgentsJsonCommand(args, noFanoutEnv, os) {
32
33
  const inner = `${noFanoutEnv}=1 agents ${args.map(shellQuote).join(' ')}`;
33
34
  return `bash -lc ${shellQuote(inner)}`;
34
35
  }
35
- function sshCapture(target, remoteCmd, timeoutMs) {
36
+ const sshCapture = (target, remoteCmd, { timeoutMs, signal }) => {
36
37
  assertValidSshTarget(target);
37
38
  return new Promise((resolve) => {
39
+ if (signal?.aborted) {
40
+ resolve({ code: null, stdout: '' });
41
+ return;
42
+ }
38
43
  const args = [...SSH_OPTS, ...controlOpts(), target, remoteCmd];
39
44
  const child = spawn('ssh', args, { stdio: ['ignore', 'pipe', 'ignore'] });
40
45
  let stdout = '';
@@ -44,19 +49,25 @@ function sshCapture(target, remoteCmd, timeoutMs) {
44
49
  return;
45
50
  settled = true;
46
51
  clearTimeout(timer);
52
+ signal?.removeEventListener('abort', onAbort);
47
53
  resolve({ code, stdout });
48
54
  };
49
55
  const timer = setTimeout(() => {
50
56
  child.kill('SIGKILL');
51
57
  done(null);
52
58
  }, timeoutMs);
59
+ // A definitive hit on a fast peer cancels the rest: SIGTERM the child so an
60
+ // unreachable peer is not left running to its full timeout.
61
+ const onAbort = () => { child.kill('SIGTERM'); done(null); };
62
+ signal?.addEventListener('abort', onAbort, { once: true });
53
63
  child.stdout.on('data', (data) => { stdout += data.toString(); });
54
64
  child.on('error', () => done(null));
55
65
  child.on('close', (code) => done(code));
56
66
  });
57
- }
67
+ };
58
68
  /** Query explicit hosts, or every registered online peer when hosts is omitted. */
59
- export async function gatherRemoteAgentsJson(options) {
69
+ export async function gatherRemoteAgentsJson(options, deps = {}) {
70
+ const capture = deps.capture ?? sshCapture;
60
71
  const self = machineId();
61
72
  const targets = [];
62
73
  if (options.hosts && options.hosts.length > 0) {
@@ -100,24 +111,50 @@ export async function gatherRemoteAgentsJson(options) {
100
111
  }
101
112
  const skipped = [];
102
113
  const parseFailed = [];
114
+ // The controller exists ONLY for early-exit: the first definitive hit aborts
115
+ // every still-outstanding peer. When earlyExit is not requested there is no
116
+ // controller and no per-peer abort listener, so the default path is a plain
117
+ // all-settle Promise.all, byte-identical to before (tool-search, program-count).
118
+ const controller = options.earlyExit ? new AbortController() : undefined;
119
+ // A fleet can hold more peers than Node's default 10-listener cap; each peer's
120
+ // capture adds one abort listener, so lift the cap to avoid a spurious warning.
121
+ if (controller)
122
+ setMaxListeners(targets.length + 1, controller.signal);
123
+ let earlyResolved = false;
103
124
  const results = await Promise.all(targets.map(async (target) => {
104
125
  const command = remoteAgentsJsonCommand(options.args, options.noFanoutEnv, target.os);
105
- const result = await sshCapture(target.target, command, options.timeoutMs ?? REMOTE_TIMEOUT_MS);
126
+ const result = await capture(target.target, command, {
127
+ timeoutMs: options.timeoutMs ?? REMOTE_TIMEOUT_MS,
128
+ signal: controller?.signal,
129
+ });
130
+ // A peer we deliberately cancelled is neither a hit nor a failure — it never
131
+ // got to answer, so it must not pollute skipped/parseFailed (which drive the
132
+ // "unreachable" listing and the fail-closed partial-resolution gate).
133
+ const cancelled = controller?.signal.aborted ?? false;
106
134
  if (result.code !== 0) {
107
- skipped.push(target.name);
108
- if (!options.quiet) {
109
- process.stderr.write(chalk.gray(` ${target.name}: unreachable or no agents CLI — skipped\n`));
135
+ if (!cancelled) {
136
+ skipped.push(target.name);
137
+ if (!options.quiet) {
138
+ process.stderr.write(chalk.gray(` ${target.name}: unreachable or no agents CLI — skipped\n`));
139
+ }
110
140
  }
111
141
  return [];
112
142
  }
113
143
  const parsed = parseRemoteAgentsJsonPayload(result.stdout, target.machine, options.parse);
114
144
  if (parsed.parseFailed) {
115
- parseFailed.push(target.name);
116
- if (!options.quiet) {
117
- process.stderr.write(chalk.gray(` ${target.name}: unreachable or no agents CLI — skipped\n`));
145
+ if (!cancelled) {
146
+ parseFailed.push(target.name);
147
+ if (!options.quiet) {
148
+ process.stderr.write(chalk.gray(` ${target.name}: unreachable or no agents CLI — skipped\n`));
149
+ }
118
150
  }
119
151
  return [];
120
152
  }
153
+ if (controller && options.earlyExit && !earlyResolved
154
+ && parsed.items.some((item) => options.earlyExit.isDefinitive(item, target.machine))) {
155
+ earlyResolved = true;
156
+ controller.abort(); // SIGTERM the remaining peers; they settle fast below.
157
+ }
121
158
  return parsed.items;
122
159
  }));
123
160
  return { items: results.flat(), deviceCount: targets.length, skipped, parseFailed, discoveryFailed: false };
@@ -127,6 +127,8 @@ export function getMcpConfigPath(agent, versionHome) {
127
127
  case 'pi':
128
128
  // omp reads user-scope MCP from ~/.omp/agent/.mcp.json (Claude schema).
129
129
  return path.join(versionHome, '.omp', 'agent', '.mcp.json');
130
+ case 'muse':
131
+ return path.join(versionHome, '.config', 'muse', 'settings.json');
130
132
  default:
131
133
  return null;
132
134
  }
@@ -81,6 +81,9 @@ function getAgentConfigPath(agent, versionHome) {
81
81
  return path.join(versionHome, '.hermes', 'config.yaml');
82
82
  case 'goose':
83
83
  return path.join(versionHome, '.config', 'goose', 'permission.yaml');
84
+ case 'muse':
85
+ // Muse approval defaults live in settings.json; no separate permissions file.
86
+ return path.join(versionHome, '.config', 'muse', 'settings.json');
84
87
  default:
85
88
  return null;
86
89
  }
@@ -5,7 +5,8 @@
5
5
  * - Union: All resources from all layers are combined
6
6
  * - Override on name conflict: Higher layer wins (project > user > system)
7
7
  */
8
- export type AgentId = 'claude' | 'codex' | 'gemini' | 'cursor' | 'opencode' | 'openclaw' | 'copilot' | 'kiro' | 'goose' | 'antigravity' | 'grok' | 'kimi' | 'droid' | 'hermes' | 'pi';
8
+ import type { AgentId } from '../types.js';
9
+ export type { AgentId };
9
10
  /** Resource origin. Precedence (highest first): project > user > plugin > system. */
10
11
  export type Layer = 'system' | 'user' | 'project' | 'plugin';
11
12
  export type ResourceKind = 'command' | 'hook' | 'skill' | 'rule' | 'mcp' | 'permission' | 'subagent' | 'workflow' | 'memory';
@@ -22,6 +22,7 @@ import { getRunsDir } from './state.js';
22
22
  import { prepareJobHome, buildSpawnEnv, getJobHomePath } from './sandbox.js';
23
23
  import { resolveModel, buildReasoningFlags } from './models.js';
24
24
  import { createTimer, redactPrompt } from './events.js';
25
+ import { codexEditWritableRoots, codexPolicyArgs } from './codex-policy.js';
25
26
  import { normalizeMode, resolveHeadlessMode, buildExecEnv, detectRateLimit, detectAuthFailure, isAuthFailureFromLog, authFailureReason, } from './exec.js';
26
27
  import { resolveActor } from './actor.js';
27
28
  import { loadTask as loadHostTask } from './hosts/tasks.js';
@@ -99,11 +100,12 @@ function terminateRoutineTree(pid) {
99
100
  /** CLI command templates per agent, with {prompt} as a placeholder. */
100
101
  const AGENT_COMMANDS = {
101
102
  claude: ['claude', '-p', '--verbose', '{prompt}', '--output-format', 'stream-json', '--permission-mode', 'plan'],
102
- codex: ['codex', 'exec', '--sandbox', 'workspace-write', '{prompt}', '--json'],
103
+ codex: ['codex', 'exec', '{prompt}', '--json'],
103
104
  gemini: ['gemini', '{prompt}', '--output-format', 'stream-json'],
104
105
  cursor: ['cursor-agent', '-p', '{prompt}', '--output-format', 'stream-json'],
105
106
  kimi: ['kimi', '--prompt', '{prompt}', '--output-format', 'stream-json'],
106
107
  droid: ['droid', 'exec', '{prompt}', '-o', 'stream-json'],
108
+ muse: ['muse', 'exec', '{prompt}', '--json'],
107
109
  };
108
110
  /** Agents the daemon can actually run, derived from the command table above
109
111
  * so the `--agent` help and any validation can never drift from it. */
@@ -137,6 +139,8 @@ const ROUTINE_TRANSCRIPT_SPECS = {
137
139
  { root: ['.kimi-code', 'sessions'], ext: '.jsonl' },
138
140
  ],
139
141
  grok: [{ root: ['.grok', 'sessions'], ext: '.json' }],
142
+ // Muse: ~/.local/share/muse/sessions/YYYY/MM/DD/<uuid>/session.jsonl
143
+ muse: [{ root: ['.local', 'share', 'muse', 'sessions'], ext: '.jsonl' }],
140
144
  };
141
145
  /** Stable working directory for routine children, independent of the daemon's launch cwd. */
142
146
  export function routineSpawnCwd(config, configuredRoot = getProjectRoot()) {
@@ -210,24 +214,14 @@ export function buildJobCommand(config, resolvedPrompt) {
210
214
  appendModelAndReasoning(cmd, config);
211
215
  }
212
216
  if (config.agent === 'codex') {
213
- if (mode === 'plan') {
214
- // The template defaults to workspace-write; plan means read-only.
215
- const sbIndex = cmd.indexOf('--sandbox');
216
- if (sbIndex !== -1)
217
- cmd[sbIndex + 1] = 'read-only';
218
- }
219
- else if (mode === 'edit' || mode === 'auto') {
220
- // Keep the workspace-write sandbox — no approval bypass; only skip drops
221
- // the guardrails. Re-enable network, which workspace-write turns off.
222
- cmd.push('-c', 'sandbox_workspace_write.network_access=true');
223
- }
224
- else if (mode === 'skip') {
225
- // Remove sandbox restriction, just --dangerously-bypass-approvals-and-sandbox
226
- const sbIndex = cmd.indexOf('--sandbox');
227
- if (sbIndex !== -1)
228
- cmd.splice(sbIndex, 2);
229
- cmd.push('--dangerously-bypass-approvals-and-sandbox');
230
- }
217
+ const policyMode = mode === 'plan' || mode === 'skip' ? mode : 'edit';
218
+ const routineRoots = (config.allow?.dirs ?? []).map((dir) => {
219
+ if (dir.startsWith('-')) {
220
+ throw new Error(`allow.dirs entries must not start with '-': ${JSON.stringify(dir)}`);
221
+ }
222
+ return dir.replace(/^~/, os.homedir());
223
+ });
224
+ cmd.push(...codexPolicyArgs(policyMode, [...codexEditWritableRoots(), ...routineRoots]));
231
225
  appendModelAndReasoning(cmd, config);
232
226
  }
233
227
  if (config.agent === 'gemini') {
@@ -277,6 +271,21 @@ export function buildJobCommand(config, resolvedPrompt) {
277
271
  }
278
272
  appendModelAndReasoning(cmd, config);
279
273
  }
274
+ if (config.agent === 'muse') {
275
+ // muse exec: plan ≈ no non-shell writes; auto skips approval prompts but
276
+ // keeps the OS sandbox; skip is --yolo (no approval, no sandbox, trust).
277
+ if (mode === 'plan') {
278
+ cmd.push('--disable-write');
279
+ }
280
+ else if (mode === 'auto') {
281
+ cmd.push('--disable-approval');
282
+ }
283
+ else if (mode === 'skip') {
284
+ cmd.push('--yolo');
285
+ }
286
+ // edit: default on-request approval + sandbox
287
+ appendModelAndReasoning(cmd, config);
288
+ }
280
289
  return cmd;
281
290
  }
282
291
  /**
@@ -8,8 +8,6 @@
8
8
  <string>Agents CLI</string>
9
9
  <key>CFBundleDisplayName</key>
10
10
  <string>Agents CLI</string>
11
- <key>CFBundleIconFile</key>
12
- <string>AppIcon</string>
13
11
  <key>CFBundleExecutable</key>
14
12
  <string>Agents CLI</string>
15
13
  <key>CFBundlePackageType</key>
@@ -3,21 +3,9 @@
3
3
  <plist version="1.0">
4
4
  <dict>
5
5
  <key>files</key>
6
- <dict>
7
- <key>Resources/AppIcon.icns</key>
8
- <data>
9
- DFq5H08EkhgWIC3UvGMR9B58BZw=
10
- </data>
11
- </dict>
6
+ <dict/>
12
7
  <key>files2</key>
13
8
  <dict>
14
- <key>Resources/AppIcon.icns</key>
15
- <dict>
16
- <key>hash2</key>
17
- <data>
18
- mBSjM6jlvN7J1jQowsvqTl7CHM0pur0e6qsHMuzk5k0=
19
- </data>
20
- </dict>
21
9
  <key>embedded.provisionprofile</key>
22
10
  <dict>
23
11
  <key>hash2</key>
@@ -22,10 +22,24 @@
22
22
  * through the explicit export/import flow in src/lib/secrets/sync.ts
23
23
  * rather than the system's cloud-keychain path.
24
24
  */
25
- import { type SpawnSyncOptions } from 'child_process';
25
+ import { type SpawnSyncOptions, type SpawnSyncReturns } from 'child_process';
26
26
  import type { NativeImportReport } from './fallback.js';
27
27
  export type { NativeImportReport, NativeImportResult, NativeImportStatus } from './fallback.js';
28
28
  export declare const SECRETS_ITEM_PREFIX = "agents-cli.secrets.";
29
+ /**
30
+ * Thrown when a keychain helper / security spawnSync is killed because it
31
+ * exceeded its timeout. A wedged coreauthd / LocalAuthentication dialog can hang
32
+ * the parent forever; this makes the failure explicit and arms the read back-off.
33
+ */
34
+ export declare class KeychainHelperTimeoutError extends Error {
35
+ readonly bin: string;
36
+ readonly args: string[];
37
+ constructor(bin: string, args: string[]);
38
+ }
39
+ /** Test seam: suppress the side-effect daemon boot in unit tests. */
40
+ export declare function setKeychainDaemonBootForTest(enabled: boolean): void;
41
+ /** Test seam: exercise the timeout wrapper with an arbitrary binary. */
42
+ export declare function spawnKeychainHelperForTest(bin: string, args: string[], opts: SpawnSyncOptions, timeoutMs: number): SpawnSyncReturns<Buffer>;
29
43
  /** Supported secret resolution backends. */
30
44
  export type SecretProvider = 'keychain' | 'env' | 'file' | 'exec';
31
45
  /** A typed reference to a secret, consisting of a provider and a provider-specific value. */