@phnx-labs/agents-cli 1.22.38 → 1.22.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/dist/bin/agents +0 -0
- package/dist/bootstrap.js +2 -2
- package/dist/commands/config.js +31 -1
- package/dist/commands/exec.js +1 -1
- package/dist/commands/ssh.js +156 -8
- package/dist/lib/config-keys.d.ts +11 -3
- package/dist/lib/config-keys.js +22 -3
- package/dist/lib/config-machine-keys.js +1 -0
- package/dist/lib/device-config.d.ts +34 -0
- package/dist/lib/device-config.js +96 -0
- package/dist/lib/devices/pool.d.ts +56 -0
- package/dist/lib/devices/pool.js +85 -0
- package/dist/lib/exec.d.ts +4 -1
- package/dist/lib/exec.js +8 -2
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/migrate.d.ts +36 -0
- package/dist/lib/migrate.js +107 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/smart-launch.d.ts +33 -3
- package/dist/lib/smart-launch.js +61 -6
- package/package.json +1 -1
package/dist/lib/smart-launch.js
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { queryAffinityRollup } from './session/db.js';
|
|
9
9
|
import { localMachineId } from './session/origin-machine.js';
|
|
10
|
-
import { loadDevicesSync } from './devices/registry.js';
|
|
10
|
+
import { isControlDevice, loadDevicesSync } from './devices/registry.js';
|
|
11
|
+
import { describeAutoPool, filterAutoPool, isAutoPoolMember } from './devices/pool.js';
|
|
11
12
|
import { normalizeHost } from './machine-id.js';
|
|
12
13
|
import { probePoolSignals } from './teams/placement-probe.js';
|
|
13
14
|
import { pickBestDevice } from './teams/scheduler.js';
|
|
@@ -45,7 +46,23 @@ export function sampleWeighted(candidates, rng = Math.random) {
|
|
|
45
46
|
}
|
|
46
47
|
return candidates[candidates.length - 1].key;
|
|
47
48
|
}
|
|
48
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* Online device names from the local registry (+ local), narrowed to the
|
|
51
|
+
* automatic-placement pool.
|
|
52
|
+
*
|
|
53
|
+
* The pool rule lives in `devices/pool.ts` and is an allowlist once any device
|
|
54
|
+
* is marked `role=worker`: this is the single place both automatic-placement
|
|
55
|
+
* paths (`resolveDeviceAuto`, `resolveDeviceAffinity`) get their candidates, so
|
|
56
|
+
* marking workers moves every `--device auto` at once instead of one surface.
|
|
57
|
+
*
|
|
58
|
+
* Paired cockpits (`role: control` in the device registry) are dropped here,
|
|
59
|
+
* where the registry is already being read — they are control surfaces, not
|
|
60
|
+
* compute. It CAN return an empty list — a fleet where every marked worker is
|
|
61
|
+
* offline, or where this box is the only candidate and is marked `personal`. That is a
|
|
62
|
+
* real answer, and both callers fail loud on it rather than falling back to the
|
|
63
|
+
* local machine (which would be the exact box the operator marked personal to
|
|
64
|
+
* keep agents off).
|
|
65
|
+
*/
|
|
49
66
|
export function listOnlineDeviceNames(localName = localMachineId()) {
|
|
50
67
|
const names = new Set([normalizeHost(localName)]);
|
|
51
68
|
try {
|
|
@@ -57,13 +74,27 @@ export function listOnlineDeviceNames(localName = localMachineId()) {
|
|
|
57
74
|
// No tailscale snapshot → treat as candidate (registry-only box).
|
|
58
75
|
if (online === false)
|
|
59
76
|
continue;
|
|
77
|
+
// A paired cockpit (iPhone/iPad) is a control surface, not compute — it
|
|
78
|
+
// is never dialed for a session, so it is never a placement candidate.
|
|
79
|
+
if (isControlDevice(d))
|
|
80
|
+
continue;
|
|
60
81
|
names.add(normalizeHost(name));
|
|
61
82
|
}
|
|
62
83
|
}
|
|
63
84
|
catch {
|
|
64
85
|
/* registry missing — local only */
|
|
65
86
|
}
|
|
66
|
-
return [...names];
|
|
87
|
+
return filterAutoPool([...names]);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The error both automatic-placement resolvers raise when device roles leave no
|
|
91
|
+
* candidate at all. Fail loud: the alternative — quietly running on the local
|
|
92
|
+
* machine — puts the agent on the box the operator marked `personal`.
|
|
93
|
+
*/
|
|
94
|
+
export function formatEmptyAutoPoolError() {
|
|
95
|
+
const marked = describeAutoPool();
|
|
96
|
+
return (`agents: no device is eligible for automatic placement${marked ? ` (${marked})` : ''} — ` +
|
|
97
|
+
'mark one with `agents devices role <name> worker`, or widen the pool with `agents config set auto.pool all`.');
|
|
67
98
|
}
|
|
68
99
|
export function formatNoHealthyDeviceError(pool, signals, agent) {
|
|
69
100
|
const excluded = pool.map((key) => {
|
|
@@ -78,7 +109,11 @@ export function formatNoHealthyDeviceError(pool, signals, agent) {
|
|
|
78
109
|
return `${key} (${reason})`;
|
|
79
110
|
}).join(', ');
|
|
80
111
|
const target = agent ? `can run ${agent}` : "for 'run auto'";
|
|
81
|
-
|
|
112
|
+
// Name the role narrowing when there is one: a fleet where every box but two
|
|
113
|
+
// is filtered out by a worker mark reads as "the fleet is down" without it.
|
|
114
|
+
const marked = describeAutoPool();
|
|
115
|
+
const poolNote = marked ? ` [pool: ${marked}]` : '';
|
|
116
|
+
return `agents: no healthy device ${target}${poolNote} — excluded: ${excluded}; earliest window resets unknown`;
|
|
82
117
|
}
|
|
83
118
|
/**
|
|
84
119
|
* Pick the least-loaded healthy device that can run `agent` when the harness is
|
|
@@ -90,8 +125,13 @@ export function formatNoHealthyDeviceError(pool, signals, agent) {
|
|
|
90
125
|
export async function resolveDeviceAuto(agent, opts = {}) {
|
|
91
126
|
const local = normalizeHost(opts.localMachine ?? localMachineId());
|
|
92
127
|
const pool = [...new Set((opts.eligibleHosts ?? listOnlineDeviceNames(local)).map(normalizeHost))];
|
|
93
|
-
|
|
128
|
+
// The local machine participates in the same probe as every peer — unless a
|
|
129
|
+
// role excludes it. Adding it unconditionally would put agents back on the box
|
|
130
|
+
// the operator marked `personal` precisely to keep them off it.
|
|
131
|
+
if (!pool.includes(local) && isAutoPoolMember(local))
|
|
94
132
|
pool.push(local);
|
|
133
|
+
if (pool.length === 0)
|
|
134
|
+
throw new Error(formatEmptyAutoPoolError());
|
|
95
135
|
const signals = await (opts.probe ?? probePoolSignals)(pool, agent);
|
|
96
136
|
if (!agent && !opts.probe) {
|
|
97
137
|
const { collectFleetHarnesses } = await import('../commands/ssh.js');
|
|
@@ -135,6 +175,12 @@ export async function resolveDeviceAuto(agent, opts = {}) {
|
|
|
135
175
|
}
|
|
136
176
|
/**
|
|
137
177
|
* Resolve host for `--device auto`. Does NOT pick harness or accounts.
|
|
178
|
+
*
|
|
179
|
+
* Draws from the same automatic-placement pool as {@link resolveDeviceAuto}, so
|
|
180
|
+
* `agents ssh auto`, the generic `--host auto` passthrough, and `matchHost`'s
|
|
181
|
+
* `auto` sentinel honour device roles too. Throws when roles leave the pool
|
|
182
|
+
* empty — a `null` host here means "run locally", which for a box marked
|
|
183
|
+
* `personal` is the outcome the mark exists to prevent.
|
|
138
184
|
*/
|
|
139
185
|
export function resolveDeviceAffinity(opts = {}) {
|
|
140
186
|
const local = normalizeHost(opts.localMachine ?? localMachineId());
|
|
@@ -142,9 +188,18 @@ export function resolveDeviceAffinity(opts = {}) {
|
|
|
142
188
|
const rng = opts.rng ?? Math.random;
|
|
143
189
|
const sinceDays = opts.sinceDays ?? 14;
|
|
144
190
|
const sinceMs = Date.now() - sinceDays * 24 * 60 * 60 * 1000;
|
|
191
|
+
// `listOnlineDeviceNames` always contained the local machine before device
|
|
192
|
+
// roles existed, so an empty default list can only mean roles excluded
|
|
193
|
+
// everything — fail loud, exactly as resolveDeviceAuto does. An explicitly
|
|
194
|
+
// empty `eligibleHosts` is the caller's own list and keeps the historical
|
|
195
|
+
// degrade-to-local behavior.
|
|
196
|
+
const usingDefaultPool = opts.eligibleHosts === undefined;
|
|
145
197
|
const eligible = new Set((opts.eligibleHosts ?? listOnlineDeviceNames(local)).map(normalizeHost));
|
|
146
|
-
if (eligible.size === 0)
|
|
198
|
+
if (eligible.size === 0) {
|
|
199
|
+
if (usingDefaultPool)
|
|
200
|
+
throw new Error(formatEmptyAutoPoolError());
|
|
147
201
|
eligible.add(local);
|
|
202
|
+
}
|
|
148
203
|
const deviceRows = opts.deviceAffinity ??
|
|
149
204
|
queryAffinityRollup({
|
|
150
205
|
groupBy: 'machine',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phnx-labs/agents-cli",
|
|
3
|
-
"version": "1.22.
|
|
3
|
+
"version": "1.22.39",
|
|
4
4
|
"description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|