@phnx-labs/agents-cli 1.20.64 → 1.20.66
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 +51 -3
- package/README.md +156 -3
- package/dist/bin/agents +0 -0
- package/dist/commands/apply.d.ts +12 -0
- package/dist/commands/apply.js +274 -0
- package/dist/commands/browser.js +2 -2
- package/dist/commands/cloud.js +32 -2
- package/dist/commands/doctor.js +4 -1
- package/dist/commands/exec.d.ts +48 -0
- package/dist/commands/exec.js +159 -49
- package/dist/commands/feed.js +25 -11
- package/dist/commands/hosts.js +44 -6
- package/dist/commands/mcp.js +55 -5
- package/dist/commands/monitors.d.ts +12 -0
- package/dist/commands/monitors.js +748 -0
- package/dist/commands/output.js +2 -2
- package/dist/commands/plugins.js +28 -7
- package/dist/commands/routines.js +23 -2
- package/dist/commands/secrets.d.ts +16 -0
- package/dist/commands/secrets.js +215 -64
- package/dist/commands/serve.js +31 -0
- package/dist/commands/sessions-browser.d.ts +82 -0
- package/dist/commands/sessions-browser.js +320 -0
- package/dist/commands/sessions-export.js +8 -3
- package/dist/commands/sessions.d.ts +17 -0
- package/dist/commands/sessions.js +157 -10
- package/dist/commands/share.d.ts +2 -0
- package/dist/commands/share.js +150 -0
- package/dist/commands/ssh.js +54 -3
- package/dist/commands/versions.js +7 -3
- package/dist/commands/view.d.ts +26 -0
- package/dist/commands/view.js +32 -9
- package/dist/commands/webhook.js +10 -2
- package/dist/index.js +35 -14
- package/dist/lib/agents.d.ts +46 -0
- package/dist/lib/agents.js +121 -3
- package/dist/lib/auto-dispatch-provider.js +7 -2
- package/dist/lib/auto-dispatch.d.ts +3 -0
- package/dist/lib/auto-dispatch.js +3 -0
- package/dist/lib/browser/chrome.js +2 -2
- package/dist/lib/cloud/antigravity.js +2 -2
- package/dist/lib/cloud/host.d.ts +59 -0
- package/dist/lib/cloud/host.js +224 -0
- package/dist/lib/cloud/registry.js +4 -0
- package/dist/lib/cloud/types.d.ts +6 -4
- package/dist/lib/computer-rpc.js +3 -1
- package/dist/lib/crabbox/cli.js +5 -1
- package/dist/lib/crabbox/runtimes.js +11 -2
- package/dist/lib/daemon.d.ts +20 -4
- package/dist/lib/daemon.js +62 -19
- package/dist/lib/devices/connect.d.ts +18 -1
- package/dist/lib/devices/connect.js +10 -2
- package/dist/lib/devices/fleet.d.ts +3 -2
- package/dist/lib/devices/fleet.js +9 -0
- package/dist/lib/devices/known-hosts.d.ts +62 -0
- package/dist/lib/devices/known-hosts.js +137 -0
- package/dist/lib/devices/registry.d.ts +15 -0
- package/dist/lib/devices/registry.js +9 -0
- package/dist/lib/exec.d.ts +19 -2
- package/dist/lib/exec.js +41 -13
- package/dist/lib/fleet/apply.d.ts +63 -0
- package/dist/lib/fleet/apply.js +214 -0
- package/dist/lib/fleet/auth-sync.d.ts +67 -0
- package/dist/lib/fleet/auth-sync.js +142 -0
- package/dist/lib/fleet/manifest.d.ts +29 -0
- package/dist/lib/fleet/manifest.js +127 -0
- package/dist/lib/fleet/types.d.ts +129 -0
- package/dist/lib/fleet/types.js +13 -0
- package/dist/lib/git.d.ts +27 -0
- package/dist/lib/git.js +34 -2
- package/dist/lib/hosts/dispatch.d.ts +29 -8
- package/dist/lib/hosts/dispatch.js +66 -20
- package/dist/lib/hosts/passthrough.js +2 -0
- package/dist/lib/hosts/providers/devices.d.ts +27 -0
- package/dist/lib/hosts/providers/devices.js +98 -0
- package/dist/lib/hosts/registry.d.ts +10 -16
- package/dist/lib/hosts/registry.js +17 -50
- package/dist/lib/hosts/remote-cmd.d.ts +23 -0
- package/dist/lib/hosts/remote-cmd.js +79 -4
- package/dist/lib/hosts/run-target.d.ts +84 -0
- package/dist/lib/hosts/run-target.js +99 -0
- package/dist/lib/hosts/types.d.ts +23 -5
- package/dist/lib/hosts/types.js +22 -4
- package/dist/lib/linear-autoclose.d.ts +30 -0
- package/dist/lib/linear-autoclose.js +22 -0
- package/dist/lib/mcp.d.ts +27 -1
- package/dist/lib/mcp.js +126 -12
- package/dist/lib/monitors/config.d.ts +161 -0
- package/dist/lib/monitors/config.js +372 -0
- package/dist/lib/monitors/dispatch.d.ts +28 -0
- package/dist/lib/monitors/dispatch.js +91 -0
- package/dist/lib/monitors/engine.d.ts +61 -0
- package/dist/lib/monitors/engine.js +205 -0
- package/dist/lib/monitors/sources/command.d.ts +11 -0
- package/dist/lib/monitors/sources/command.js +31 -0
- package/dist/lib/monitors/sources/device.d.ts +13 -0
- package/dist/lib/monitors/sources/device.js +45 -0
- package/dist/lib/monitors/sources/file.d.ts +14 -0
- package/dist/lib/monitors/sources/file.js +57 -0
- package/dist/lib/monitors/sources/http.d.ts +10 -0
- package/dist/lib/monitors/sources/http.js +34 -0
- package/dist/lib/monitors/sources/index.d.ts +14 -0
- package/dist/lib/monitors/sources/index.js +31 -0
- package/dist/lib/monitors/sources/poll.d.ts +9 -0
- package/dist/lib/monitors/sources/poll.js +9 -0
- package/dist/lib/monitors/sources/types.d.ts +18 -0
- package/dist/lib/monitors/sources/types.js +9 -0
- package/dist/lib/monitors/sources/webhook.d.ts +23 -0
- package/dist/lib/monitors/sources/webhook.js +47 -0
- package/dist/lib/monitors/sources/ws.d.ts +14 -0
- package/dist/lib/monitors/sources/ws.js +45 -0
- package/dist/lib/monitors/state.d.ts +69 -0
- package/dist/lib/monitors/state.js +144 -0
- package/dist/lib/picker.d.ts +53 -0
- package/dist/lib/picker.js +214 -1
- package/dist/lib/platform/exec.d.ts +16 -0
- package/dist/lib/platform/exec.js +17 -0
- package/dist/lib/plugins.d.ts +31 -1
- package/dist/lib/plugins.js +175 -15
- package/dist/lib/redact.d.ts +14 -1
- package/dist/lib/redact.js +47 -1
- package/dist/lib/remote-agents-json.js +7 -1
- package/dist/lib/rotate.d.ts +6 -3
- package/dist/lib/rotate.js +0 -1
- package/dist/lib/routines.d.ts +16 -0
- package/dist/lib/routines.js +19 -0
- package/dist/lib/runner.d.ts +1 -0
- package/dist/lib/runner.js +102 -9
- package/dist/lib/secrets/agent.d.ts +83 -10
- package/dist/lib/secrets/agent.js +237 -70
- package/dist/lib/secrets/bundles.d.ts +26 -0
- package/dist/lib/secrets/bundles.js +59 -8
- package/dist/lib/secrets/filestore.d.ts +9 -0
- package/dist/lib/secrets/filestore.js +21 -8
- package/dist/lib/secrets/index.d.ts +7 -0
- package/dist/lib/secrets/index.js +26 -6
- package/dist/lib/secrets/mcp.js +4 -2
- package/dist/lib/secrets/remote.d.ts +17 -0
- package/dist/lib/secrets/remote.js +40 -0
- package/dist/lib/self-update.d.ts +20 -0
- package/dist/lib/self-update.js +54 -1
- package/dist/lib/serve/control.d.ts +95 -0
- package/dist/lib/serve/control.js +260 -0
- package/dist/lib/serve/server.d.ts +35 -1
- package/dist/lib/serve/server.js +106 -76
- package/dist/lib/serve/stream.d.ts +43 -0
- package/dist/lib/serve/stream.js +116 -0
- package/dist/lib/serve/token.d.ts +35 -0
- package/dist/lib/serve/token.js +85 -0
- package/dist/lib/session/bundle.d.ts +14 -0
- package/dist/lib/session/bundle.js +12 -1
- package/dist/lib/session/remote-list.js +5 -1
- package/dist/lib/session/state.d.ts +7 -25
- package/dist/lib/session/state.js +16 -6
- package/dist/lib/session/sync/config.js +8 -2
- package/dist/lib/session/types.d.ts +30 -0
- package/dist/lib/share/capture.d.ts +29 -0
- package/dist/lib/share/capture.js +140 -0
- package/dist/lib/share/config.d.ts +35 -0
- package/dist/lib/share/config.js +100 -0
- package/dist/lib/share/og.d.ts +25 -0
- package/dist/lib/share/og.js +84 -0
- package/dist/lib/share/provision.d.ts +10 -0
- package/dist/lib/share/provision.js +91 -0
- package/dist/lib/share/publish.d.ts +57 -0
- package/dist/lib/share/publish.js +145 -0
- package/dist/lib/share/worker-template.d.ts +2 -0
- package/dist/lib/share/worker-template.js +82 -0
- package/dist/lib/shims.d.ts +13 -0
- package/dist/lib/shims.js +42 -2
- package/dist/lib/ssh-exec.d.ts +24 -0
- package/dist/lib/ssh-exec.js +15 -3
- package/dist/lib/ssh-tunnel.d.ts +19 -1
- package/dist/lib/ssh-tunnel.js +86 -7
- package/dist/lib/startup/command-registry.d.ts +3 -0
- package/dist/lib/startup/command-registry.js +6 -0
- package/dist/lib/state.d.ts +5 -0
- package/dist/lib/state.js +12 -0
- package/dist/lib/tmux/session.d.ts +7 -0
- package/dist/lib/tmux/session.js +3 -1
- package/dist/lib/triggers/webhook.d.ts +18 -0
- package/dist/lib/triggers/webhook.js +105 -0
- package/dist/lib/types.d.ts +36 -1
- package/dist/lib/usage.js +7 -5
- package/dist/lib/versions.js +14 -11
- package/dist/lib/workflows.d.ts +20 -0
- package/dist/lib/workflows.js +24 -0
- package/package.json +2 -1
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
* verification, idempotency, source allow-listing, and rate limiting.
|
|
13
13
|
*/
|
|
14
14
|
import * as crypto from 'crypto';
|
|
15
|
+
import * as fs from 'fs';
|
|
15
16
|
import * as http from 'http';
|
|
17
|
+
import * as path from 'path';
|
|
16
18
|
import { jobRunsOnThisDevice, listJobs } from '../routines.js';
|
|
17
19
|
import { executeJobDetached } from '../runner.js';
|
|
18
20
|
/** Read `repository.full_name` (`owner/name`) from a webhook payload, if present. */
|
|
@@ -236,6 +238,88 @@ export function createMemoryDeliveryStore(maxEntries = 1000) {
|
|
|
236
238
|
},
|
|
237
239
|
};
|
|
238
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* A durable, disk-backed delivery store. Unlike `createMemoryDeliveryStore`,
|
|
243
|
+
* seen delivery ids survive a process restart and are bounded by AGE, not by a
|
|
244
|
+
* fixed entry count — so a captured valid delivery cannot re-fire after a
|
|
245
|
+
* restart or after count-based LRU eviction would have dropped it.
|
|
246
|
+
*
|
|
247
|
+
* `retentionMs` doubles as the replay-acceptance window: a delivery whose id is
|
|
248
|
+
* still on record (younger than the window) is rejected as a duplicate; entries
|
|
249
|
+
* older than the window are pruned (keeping the file bounded) since a webhook
|
|
250
|
+
* source will not legitimately retry a delivery that old.
|
|
251
|
+
*/
|
|
252
|
+
export function createFileDeliveryStore(filePath, retentionMs = 14 * 24 * 60 * 60 * 1000) {
|
|
253
|
+
const seen = new Map();
|
|
254
|
+
// Load persisted state (best-effort: a corrupt/missing file starts empty).
|
|
255
|
+
try {
|
|
256
|
+
const raw = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
257
|
+
const loadedAt = Date.now();
|
|
258
|
+
for (const [id, entry] of Object.entries(raw)) {
|
|
259
|
+
if (typeof entry?.updatedAt !== 'number' || loadedAt - entry.updatedAt > retentionMs)
|
|
260
|
+
continue;
|
|
261
|
+
seen.set(id, {
|
|
262
|
+
complete: entry.complete === true,
|
|
263
|
+
jobs: new Set(Array.isArray(entry.jobs) ? entry.jobs : []),
|
|
264
|
+
updatedAt: entry.updatedAt,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
// no prior file / unreadable — start empty
|
|
270
|
+
}
|
|
271
|
+
const persist = () => {
|
|
272
|
+
const snapshot = {};
|
|
273
|
+
for (const [id, entry] of seen) {
|
|
274
|
+
snapshot[id] = { complete: entry.complete, jobs: [...entry.jobs], updatedAt: entry.updatedAt };
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
278
|
+
const tmp = `${filePath}.${process.pid}.tmp`;
|
|
279
|
+
fs.writeFileSync(tmp, JSON.stringify(snapshot), 'utf-8');
|
|
280
|
+
fs.renameSync(tmp, filePath);
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
// best-effort durability; an unwritable dir must not crash ingress
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
const prune = (now) => {
|
|
287
|
+
for (const [id, entry] of seen) {
|
|
288
|
+
if (now - entry.updatedAt > retentionMs)
|
|
289
|
+
seen.delete(id);
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
const touch = (id) => {
|
|
293
|
+
const now = Date.now();
|
|
294
|
+
prune(now);
|
|
295
|
+
let current = seen.get(id);
|
|
296
|
+
if (!current) {
|
|
297
|
+
current = { complete: false, jobs: new Set(), updatedAt: now };
|
|
298
|
+
seen.set(id, current);
|
|
299
|
+
}
|
|
300
|
+
current.updatedAt = now;
|
|
301
|
+
return current;
|
|
302
|
+
};
|
|
303
|
+
return {
|
|
304
|
+
seen: (id) => {
|
|
305
|
+
const entry = seen.get(id);
|
|
306
|
+
if (!entry)
|
|
307
|
+
return false;
|
|
308
|
+
if (Date.now() - entry.updatedAt > retentionMs)
|
|
309
|
+
return false;
|
|
310
|
+
return entry.complete === true;
|
|
311
|
+
},
|
|
312
|
+
mark: (id) => {
|
|
313
|
+
touch(id).complete = true;
|
|
314
|
+
persist();
|
|
315
|
+
},
|
|
316
|
+
completedJobs: (id) => new Set(seen.get(id)?.jobs ?? []),
|
|
317
|
+
markJob: (id, jobName) => {
|
|
318
|
+
touch(id).jobs.add(jobName);
|
|
319
|
+
persist();
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
}
|
|
239
323
|
export function createMemoryRateLimiter(limit, windowMs) {
|
|
240
324
|
const buckets = new Map();
|
|
241
325
|
return {
|
|
@@ -285,6 +369,7 @@ async function readRawBody(req, maxBytes) {
|
|
|
285
369
|
export function startWebhookServer(options) {
|
|
286
370
|
const deliveryStore = options.deliveryStore ?? createMemoryDeliveryStore();
|
|
287
371
|
const rateLimiter = options.rateLimiter ?? createMemoryRateLimiter(options.rateLimitPerMinute ?? 60, 60_000);
|
|
372
|
+
const ipRateLimiter = options.ipRateLimiter ?? createMemoryRateLimiter(options.ipRateLimitPerMinute ?? 120, 60_000);
|
|
288
373
|
const maxBodyBytes = options.maxBodyBytes ?? 1024 * 1024;
|
|
289
374
|
const server = http.createServer((req, res) => {
|
|
290
375
|
void (async () => {
|
|
@@ -305,6 +390,23 @@ export function startWebhookServer(options) {
|
|
|
305
390
|
res.end(JSON.stringify({ ok: false, error: `missing ${source} webhook secret` }));
|
|
306
391
|
return;
|
|
307
392
|
}
|
|
393
|
+
// Per-IP throttle + declared-size cap BEFORE the (expensive) body read +
|
|
394
|
+
// HMAC. A bad-signature flood of 1 MiB POSTs must be rejected without
|
|
395
|
+
// forcing a full body read and an HMAC per request — the signed-delivery
|
|
396
|
+
// rate limit further down runs only after a signature passes, so it can't
|
|
397
|
+
// shed this load on its own.
|
|
398
|
+
const ip = req.socket.remoteAddress ?? 'unknown';
|
|
399
|
+
if (!ipRateLimiter.take(ip)) {
|
|
400
|
+
res.writeHead(429, { 'content-type': 'application/json' });
|
|
401
|
+
res.end(JSON.stringify({ ok: false, error: 'rate limit exceeded' }));
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const declaredLength = Number.parseInt(header(req.headers, 'content-length') ?? '', 10);
|
|
405
|
+
if (Number.isFinite(declaredLength) && declaredLength > maxBodyBytes) {
|
|
406
|
+
res.writeHead(413, { 'content-type': 'application/json' });
|
|
407
|
+
res.end(JSON.stringify({ ok: false, error: `payload exceeds ${maxBodyBytes} bytes` }));
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
308
410
|
try {
|
|
309
411
|
const rawBody = await readRawBody(req, maxBodyBytes);
|
|
310
412
|
const valid = source === 'github'
|
|
@@ -357,6 +459,9 @@ export function startWebhookServer(options) {
|
|
|
357
459
|
}
|
|
358
460
|
})();
|
|
359
461
|
});
|
|
462
|
+
// Connection cap: bound how many concurrent TCP connections the receiver
|
|
463
|
+
// will hold open, so a flood cannot exhaust file descriptors / memory.
|
|
464
|
+
server.maxConnections = options.maxConnections ?? 256;
|
|
360
465
|
server.listen(options.port ?? 0, options.host ?? '127.0.0.1');
|
|
361
466
|
return server;
|
|
362
467
|
}
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -515,6 +515,20 @@ export interface PluginManifest {
|
|
|
515
515
|
userConfig?: PluginUserConfigField[];
|
|
516
516
|
/** Other plugin names this plugin depends on. Missing deps produce a warning. */
|
|
517
517
|
dependencies?: string[];
|
|
518
|
+
/**
|
|
519
|
+
* Inline hook configuration (or a path to a hooks JSON file) declared directly
|
|
520
|
+
* in the manifest, per the official plugin format — an execution surface even
|
|
521
|
+
* when the plugin ships no `hooks/` directory. Untyped because the shape is a
|
|
522
|
+
* path string or an inline event map; capability detection only needs to know
|
|
523
|
+
* whether it is present and non-empty.
|
|
524
|
+
*/
|
|
525
|
+
hooks?: unknown;
|
|
526
|
+
/**
|
|
527
|
+
* Inline MCP-server configuration (or a path to an MCP JSON file) declared
|
|
528
|
+
* directly in the manifest — an execution surface even when the plugin ships
|
|
529
|
+
* no `.mcp.json`. Untyped for the same reason as `hooks`.
|
|
530
|
+
*/
|
|
531
|
+
mcpServers?: unknown;
|
|
518
532
|
}
|
|
519
533
|
/** A plugin found on disk with its parsed manifest and resource inventory. */
|
|
520
534
|
export interface DiscoveredPlugin {
|
|
@@ -637,11 +651,15 @@ export interface Meta {
|
|
|
637
651
|
* bundles without an explicit per-bundle policy: `daily` (the default) asks
|
|
638
652
|
* once per ~7 days, `always` asks every time. `auto` (default on) lets the
|
|
639
653
|
* first real keychain read of a `daily` bundle populate the broker so
|
|
640
|
-
* concurrent runs read silently — set it `false` to force a prompt on every read.
|
|
654
|
+
* concurrent runs read silently — set it `false` to force a prompt on every read.
|
|
655
|
+
* `holdMs` caps how long an unlocked/auto-cached bundle is held before the next
|
|
656
|
+
* read re-prompts (default 7 days; e.g. 86400000 for a 24h cap). Clamped to
|
|
657
|
+
* [1 minute, 30 days]. */
|
|
641
658
|
secrets?: {
|
|
642
659
|
policy?: 'always' | 'daily';
|
|
643
660
|
agent?: {
|
|
644
661
|
auto?: boolean;
|
|
662
|
+
holdMs?: number;
|
|
645
663
|
};
|
|
646
664
|
};
|
|
647
665
|
/** Spend guardrails (issue #346). User-global caps; project agents.yaml overrides. */
|
|
@@ -698,6 +716,23 @@ export interface Meta {
|
|
|
698
716
|
* are never copied. `inline` hosts carry their own address/user.
|
|
699
717
|
*/
|
|
700
718
|
hosts?: Record<string, HostEntry>;
|
|
719
|
+
/**
|
|
720
|
+
* Declarative fleet profile (`agents apply` / `ag apply`). Additive to the
|
|
721
|
+
* schema — project `agents:` version-pins are untouched. Declares which agents
|
|
722
|
+
* every device should have, which config to sync, and how login propagates.
|
|
723
|
+
* Full shape in `lib/fleet/types.ts` (FleetManifest).
|
|
724
|
+
*/
|
|
725
|
+
fleet?: import('./fleet/types.js').FleetManifest;
|
|
726
|
+
/** `agents share` endpoint (Cloudflare R2 + Worker). Set by `agents share
|
|
727
|
+
* setup`/`join`; syncs fleet-wide via `agents repo push/pull`. The write token
|
|
728
|
+
* lives in the `share` secrets bundle, not here. */
|
|
729
|
+
share?: {
|
|
730
|
+
baseUrl?: string;
|
|
731
|
+
accountId?: string;
|
|
732
|
+
workerName?: string;
|
|
733
|
+
bucketName?: string;
|
|
734
|
+
domain?: string;
|
|
735
|
+
};
|
|
701
736
|
}
|
|
702
737
|
/** Persisted agent-host entry in agents.yaml (overlay or inline). */
|
|
703
738
|
export interface HostEntry {
|
package/dist/lib/usage.js
CHANGED
|
@@ -207,12 +207,14 @@ export function formatUsageSummary(plan, snapshot, planWidth = 3) {
|
|
|
207
207
|
parts.push(chalk.gray(plan.padEnd(planWidth)));
|
|
208
208
|
}
|
|
209
209
|
if (snapshot) {
|
|
210
|
-
// Compact rows show
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
//
|
|
210
|
+
// Compact rows show every BLOCKING window — the same set
|
|
211
|
+
// deriveUsageStatusFromSnapshot uses for the rate-limited badge — so an
|
|
212
|
+
// account throttled by its month window (Droid meters on 5h/week/month)
|
|
213
|
+
// shows the bar that explains why. Claude's Sonnet week is a per-model
|
|
214
|
+
// sub-limit, not a blocking window; it renders only in the full
|
|
215
|
+
// per-version usage section.
|
|
214
216
|
const windows = snapshot.windows
|
|
215
|
-
.filter((window) => window.key
|
|
217
|
+
.filter((window) => window.key !== 'sonnet_week')
|
|
216
218
|
.map((window) => `${chalk.gray(`${window.shortLabel}:`)} ${renderCompactUsageBar(window.usedPercent)}`);
|
|
217
219
|
if (windows.length > 0) {
|
|
218
220
|
parts.push(windows.join(' '));
|
package/dist/lib/versions.js
CHANGED
|
@@ -30,7 +30,7 @@ import { listResources } from './resources.js';
|
|
|
30
30
|
import { VERSION_RE, compareVersions } from './agent-spec/primitives.js';
|
|
31
31
|
import { AGENTS, agentConfigDirName, getAccountEmail, resolveAgentName, formatAgentError, findInPath, isSelfUpdatingAgent } from './agents.js';
|
|
32
32
|
import { discoverPermissionGroups, getActivePermissionPresetName, readPermissionPresetRecipe, PERMISSION_PRESET_ENV_VAR } from './permissions.js';
|
|
33
|
-
import { parseMcpServerConfig } from './mcp.js';
|
|
33
|
+
import { parseMcpServerConfig, isProjectMcpTrusted } from './mcp.js';
|
|
34
34
|
import { createVersionedAlias, removeVersionedAlias, getConfigSymlinkVersion, ensureClaudeInsideSymlink } from './shims.js';
|
|
35
35
|
import { importInstallScriptBinary } from './import.js';
|
|
36
36
|
import { IS_WINDOWS, composeWin32CommandLine } from './platform/index.js';
|
|
@@ -2511,20 +2511,23 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2511
2511
|
// For Claude/Codex: uses CLI commands (claude mcp add, codex mcp add)
|
|
2512
2512
|
// For others: edits config files directly
|
|
2513
2513
|
//
|
|
2514
|
-
// Mirror the hooks defense:
|
|
2515
|
-
//
|
|
2516
|
-
//
|
|
2517
|
-
//
|
|
2518
|
-
//
|
|
2519
|
-
//
|
|
2520
|
-
//
|
|
2521
|
-
//
|
|
2522
|
-
const
|
|
2514
|
+
// Mirror the hooks defense: an MCP server is an executable invoked under the
|
|
2515
|
+
// agent's authority, so a cloned public repo's .agents/mcp/foo.yaml could
|
|
2516
|
+
// install an arbitrary command (RUSH-1776). Project-scoped MCPs are UNTRUSTED
|
|
2517
|
+
// by default — drop them before handing the list to installMcpServers unless
|
|
2518
|
+
// the user has explicitly trusted this project (`agents mcp trust`). The
|
|
2519
|
+
// register/spawn choke in lib/mcp.ts (getMcpServersByName) enforces the same
|
|
2520
|
+
// trust gate and drops untrusted project entries before dedup, so a project
|
|
2521
|
+
// entry can no longer shadow a same-named user entry either.
|
|
2522
|
+
const projectMcpTrusted = projectAgentsDir ? isProjectMcpTrusted(projectAgentsDir) : false;
|
|
2523
|
+
const untrustedProjectMcpNames = new Set(projectMcpTrusted
|
|
2524
|
+
? []
|
|
2525
|
+
: getScopedMcpResources(cwd).filter(r => r.scope === 'project').map(r => r.name));
|
|
2523
2526
|
const mcpWriter = getWriter('mcp', agent);
|
|
2524
2527
|
const mcpToSyncAll = selection
|
|
2525
2528
|
? resolveSelection(selection.mcp, available.mcp)
|
|
2526
2529
|
: (mcpWriter ? available.mcp : []);
|
|
2527
|
-
const mcpToSync = mcpToSyncAll.filter(n => !
|
|
2530
|
+
const mcpToSync = mcpToSyncAll.filter(n => !untrustedProjectMcpNames.has(n));
|
|
2528
2531
|
if (mcpToSync.length > 0 && mcpWriter) {
|
|
2529
2532
|
const r = mcpWriter.write({ version, versionHome, selection: mcpToSync, cwd });
|
|
2530
2533
|
result.mcp = r.synced;
|
package/dist/lib/workflows.d.ts
CHANGED
|
@@ -240,6 +240,26 @@ export declare function resolveAllowedSubagents(available: string[], allowedAgen
|
|
|
240
240
|
allowedStems: string[];
|
|
241
241
|
missing: string[];
|
|
242
242
|
};
|
|
243
|
+
/** The Claude tool an orchestrator uses to dispatch subagents. Must stay in a
|
|
244
|
+
* workflow's `--tools` allowlist whenever the workflow ships dispatchable
|
|
245
|
+
* subagents, or the orchestrator has no way to reach them. */
|
|
246
|
+
export declare const SUBAGENT_DISPATCH_TOOL = "Task";
|
|
247
|
+
/**
|
|
248
|
+
* Keep the subagent-dispatch tool (`Task`) in a `tools:`-restricted workflow's
|
|
249
|
+
* allowlist when the workflow actually ships subagents to dispatch.
|
|
250
|
+
*
|
|
251
|
+
* A WORKFLOW.md `tools:` list becomes Claude's `--tools` set, which *restricts*
|
|
252
|
+
* the available built-ins. An orchestrator whose `subagents/` files were just
|
|
253
|
+
* copied into the shared agents dir but whose `tools:` omits `Task` cannot
|
|
254
|
+
* reach any of them — it silently no-ops (observed: the run emits only "I'll
|
|
255
|
+
* wait for the completion notification" and exits). Omitting `Task` while
|
|
256
|
+
* shipping a `subagents/` dir is always an authoring miss, so we re-add it at
|
|
257
|
+
* the source rather than relying on every workflow author to remember.
|
|
258
|
+
*
|
|
259
|
+
* Returns `tools` unchanged when the workflow has no subagents or already lists
|
|
260
|
+
* `Task`; otherwise appends `Task`.
|
|
261
|
+
*/
|
|
262
|
+
export declare function ensureSubagentDispatchTool(tools: string[], hasSubagents: boolean): string[];
|
|
243
263
|
/**
|
|
244
264
|
* Prune stale workflow-managed subagent files from the shared per-agent agents
|
|
245
265
|
* dir before a scoped run writes the permitted set (issue #401, follow-up to
|
package/dist/lib/workflows.js
CHANGED
|
@@ -278,6 +278,30 @@ export function resolveAllowedSubagents(available, allowedAgents) {
|
|
|
278
278
|
missing: allowedAgents.filter(a => !present.has(a)),
|
|
279
279
|
};
|
|
280
280
|
}
|
|
281
|
+
/** The Claude tool an orchestrator uses to dispatch subagents. Must stay in a
|
|
282
|
+
* workflow's `--tools` allowlist whenever the workflow ships dispatchable
|
|
283
|
+
* subagents, or the orchestrator has no way to reach them. */
|
|
284
|
+
export const SUBAGENT_DISPATCH_TOOL = 'Task';
|
|
285
|
+
/**
|
|
286
|
+
* Keep the subagent-dispatch tool (`Task`) in a `tools:`-restricted workflow's
|
|
287
|
+
* allowlist when the workflow actually ships subagents to dispatch.
|
|
288
|
+
*
|
|
289
|
+
* A WORKFLOW.md `tools:` list becomes Claude's `--tools` set, which *restricts*
|
|
290
|
+
* the available built-ins. An orchestrator whose `subagents/` files were just
|
|
291
|
+
* copied into the shared agents dir but whose `tools:` omits `Task` cannot
|
|
292
|
+
* reach any of them — it silently no-ops (observed: the run emits only "I'll
|
|
293
|
+
* wait for the completion notification" and exits). Omitting `Task` while
|
|
294
|
+
* shipping a `subagents/` dir is always an authoring miss, so we re-add it at
|
|
295
|
+
* the source rather than relying on every workflow author to remember.
|
|
296
|
+
*
|
|
297
|
+
* Returns `tools` unchanged when the workflow has no subagents or already lists
|
|
298
|
+
* `Task`; otherwise appends `Task`.
|
|
299
|
+
*/
|
|
300
|
+
export function ensureSubagentDispatchTool(tools, hasSubagents) {
|
|
301
|
+
if (!hasSubagents || tools.includes(SUBAGENT_DISPATCH_TOOL))
|
|
302
|
+
return tools;
|
|
303
|
+
return [...tools, SUBAGENT_DISPATCH_TOOL];
|
|
304
|
+
}
|
|
281
305
|
/**
|
|
282
306
|
* Prune stale workflow-managed subagent files from the shared per-agent agents
|
|
283
307
|
* dir before a scoped run writes the permitted set (issue #401, follow-up to
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phnx-labs/agents-cli",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.66",
|
|
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",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"prepack": "cp ../../README.md README.md && scripts/verify-keychain-helper.sh && scripts/verify-menubar-helper.sh && scripts/verify-cli-binary.sh",
|
|
52
52
|
"postinstall": "node scripts/postinstall.js",
|
|
53
53
|
"dev": "tsx src/index.ts",
|
|
54
|
+
"changelog": "bun scripts/gen-changelog.ts",
|
|
54
55
|
"start": "node dist/index.js",
|
|
55
56
|
"test": "node ./node_modules/vitest/vitest.mjs run",
|
|
56
57
|
"test:remote": "scripts/sandbox.sh 'bun install && bun run build && bun run test'",
|