@phnx-labs/agents-cli 1.20.78 → 1.20.83
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 +595 -0
- package/README.md +26 -26
- package/dist/bin/agents +0 -0
- package/dist/commands/activity.d.ts +9 -0
- package/dist/commands/activity.js +153 -24
- package/dist/commands/apply.js +11 -4
- package/dist/commands/cli.js +1 -1
- package/dist/commands/commands.d.ts +1 -1
- package/dist/commands/commands.js +7 -6
- package/dist/commands/defaults.js +3 -11
- package/dist/commands/doctor.d.ts +66 -0
- package/dist/commands/doctor.js +596 -33
- package/dist/commands/events.js +1 -1
- package/dist/commands/exec.js +24 -5
- package/dist/commands/feed.d.ts +4 -0
- package/dist/commands/feed.js +87 -11
- package/dist/commands/harness.js +2 -2
- package/dist/commands/hooks.js +1 -1
- package/dist/commands/import.js +8 -4
- package/dist/commands/logs.js +6 -0
- package/dist/commands/menubar.js +12 -0
- package/dist/commands/models.js +1 -1
- package/dist/commands/packages.js +6 -6
- package/dist/commands/permissions.js +1 -1
- package/dist/commands/profiles.js +1 -1
- package/dist/commands/pull.d.ts +1 -1
- package/dist/commands/pull.js +4 -4
- package/dist/commands/repo.d.ts +78 -0
- package/dist/commands/repo.js +187 -11
- package/dist/commands/routines.d.ts +8 -1
- package/dist/commands/routines.js +241 -77
- package/dist/commands/rules.js +13 -12
- package/dist/commands/secrets.d.ts +3 -1
- package/dist/commands/secrets.js +100 -32
- package/dist/commands/sessions-inject.d.ts +7 -4
- package/dist/commands/sessions-inject.js +18 -18
- package/dist/commands/sessions-picker.js +32 -3
- package/dist/commands/sessions-resume.d.ts +16 -0
- package/dist/commands/sessions-resume.js +14 -13
- package/dist/commands/sessions.d.ts +115 -0
- package/dist/commands/sessions.js +189 -35
- package/dist/commands/set.d.ts +15 -0
- package/dist/commands/set.js +79 -0
- package/dist/commands/setup-fleet.d.ts +21 -0
- package/dist/commands/setup-fleet.js +201 -0
- package/dist/commands/setup-secrets.d.ts +20 -0
- package/dist/commands/setup-secrets.js +223 -0
- package/dist/commands/setup.js +17 -3
- package/dist/commands/skills.js +1 -1
- package/dist/commands/sync.js +7 -2
- package/dist/commands/teams-picker.js +0 -1
- package/dist/commands/teams.d.ts +33 -1
- package/dist/commands/teams.js +208 -44
- package/dist/commands/versions.js +10 -5
- package/dist/commands/view.d.ts +1 -0
- package/dist/commands/view.js +11 -0
- package/dist/index.js +52 -8
- package/dist/lib/acp/harnesses.js +0 -7
- package/dist/lib/activity.d.ts +156 -0
- package/dist/lib/activity.js +282 -0
- package/dist/lib/agents.d.ts +9 -2
- package/dist/lib/agents.js +72 -63
- package/dist/lib/browser/service.js +3 -0
- package/dist/lib/browser/types.d.ts +7 -0
- package/dist/lib/capabilities.js +6 -2
- package/dist/lib/crabbox/lease.js +2 -2
- package/dist/lib/crabbox/setup-copy.d.ts +4 -4
- package/dist/lib/crabbox/setup-copy.js +4 -4
- package/dist/lib/daemon.d.ts +18 -0
- package/dist/lib/daemon.js +47 -6
- package/dist/lib/devices/fleet.d.ts +1 -1
- package/dist/lib/devices/fleet.js +1 -1
- package/dist/lib/doctor-diff.d.ts +20 -0
- package/dist/lib/doctor-diff.js +6 -1
- package/dist/lib/drift.d.ts +31 -11
- package/dist/lib/drift.js +58 -7
- package/dist/lib/events.d.ts +8 -1
- package/dist/lib/events.js +10 -1
- package/dist/lib/exec.d.ts +7 -0
- package/dist/lib/exec.js +69 -11
- package/dist/lib/feed-post.d.ts +28 -1
- package/dist/lib/feed-post.js +110 -2
- package/dist/lib/fleet/apply.js +8 -1
- package/dist/lib/fleet/auth-sync.d.ts +18 -1
- package/dist/lib/fleet/auth-sync.js +25 -11
- package/dist/lib/fleet/remote-login.js +5 -0
- package/dist/lib/git.d.ts +14 -14
- package/dist/lib/git.js +41 -44
- package/dist/lib/hooks.d.ts +71 -0
- package/dist/lib/hooks.js +245 -95
- package/dist/lib/hosts/dispatch.d.ts +28 -1
- package/dist/lib/hosts/dispatch.js +33 -5
- package/dist/lib/hosts/option.js +2 -2
- package/dist/lib/hosts/passthrough.d.ts +22 -1
- package/dist/lib/hosts/passthrough.js +238 -13
- package/dist/lib/hosts/run-target.d.ts +2 -0
- package/dist/lib/hosts/run-target.js +1 -0
- package/dist/lib/hq/floor.d.ts +1 -1
- package/dist/lib/hq/floor.js +6 -1
- package/dist/lib/mcp.js +3 -71
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/install-menubar.d.ts +24 -0
- package/dist/lib/menubar/install-menubar.js +52 -2
- package/dist/lib/menubar/notify-desktop.d.ts +15 -0
- package/dist/lib/menubar/notify-desktop.js +41 -5
- package/dist/lib/merged-resources.d.ts +11 -0
- package/dist/{commands/resources.js → lib/merged-resources.js} +11 -19
- package/dist/lib/permissions.d.ts +0 -32
- package/dist/lib/permissions.js +4 -157
- package/dist/lib/picker.js +1 -1
- package/dist/lib/platform/process.d.ts +16 -5
- package/dist/lib/platform/process.js +54 -0
- package/dist/lib/plugins.d.ts +0 -8
- package/dist/lib/plugins.js +4 -110
- package/dist/lib/project-resources.js +4 -1
- package/dist/lib/refresh.d.ts +5 -3
- package/dist/lib/refresh.js +7 -5
- package/dist/lib/resources/commands.js +4 -1
- package/dist/lib/resources/mcp.js +0 -4
- package/dist/lib/resources/permissions.d.ts +1 -1
- package/dist/lib/resources/permissions.js +1 -5
- package/dist/lib/resources/rules.js +4 -1
- package/dist/lib/resources/skills.js +4 -1
- package/dist/lib/resources/subagents.js +4 -0
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/routines.d.ts +33 -0
- package/dist/lib/routines.js +122 -0
- package/dist/lib/run-defaults.d.ts +1 -0
- package/dist/lib/run-defaults.js +9 -0
- package/dist/lib/runner.d.ts +0 -12
- package/dist/lib/runner.js +49 -10
- package/dist/lib/scheduler.js +16 -4
- 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/secrets/agent.d.ts +16 -0
- package/dist/lib/secrets/agent.js +105 -3
- package/dist/lib/secrets/audit.d.ts +46 -0
- package/dist/lib/secrets/audit.js +56 -0
- package/dist/lib/secrets/bundles.d.ts +11 -8
- package/dist/lib/secrets/bundles.js +31 -23
- package/dist/lib/secrets/index.d.ts +2 -1
- package/dist/lib/secrets/index.js +8 -2
- package/dist/lib/secrets/remote.d.ts +75 -2
- package/dist/lib/secrets/remote.js +139 -5
- package/dist/lib/secrets/sync.js +3 -3
- package/dist/lib/session/active.d.ts +128 -26
- package/dist/lib/session/active.js +192 -53
- package/dist/lib/session/actor-sidecar.d.ts +23 -0
- package/dist/lib/session/actor-sidecar.js +101 -0
- package/dist/lib/session/db.d.ts +4 -1
- package/dist/lib/session/db.js +65 -8
- package/dist/lib/session/discover.d.ts +4 -0
- package/dist/lib/session/discover.js +13 -0
- package/dist/lib/session/hook-sessions.d.ts +9 -0
- package/dist/lib/session/hook-sessions.js +57 -8
- package/dist/lib/session/pid-registry.d.ts +13 -0
- package/dist/lib/session/render.d.ts +2 -0
- package/dist/lib/session/render.js +1 -1
- package/dist/lib/session/types.d.ts +16 -0
- package/dist/lib/staleness/detectors/commands.js +2 -2
- package/dist/lib/staleness/detectors/permissions.js +1 -45
- package/dist/lib/staleness/writers/commands.js +4 -5
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/startup/command-registry.d.ts +1 -2
- package/dist/lib/startup/command-registry.js +2 -4
- package/dist/lib/state.js +57 -1
- package/dist/lib/subagents-registry.js +5 -12
- package/dist/lib/subagents.d.ts +0 -10
- package/dist/lib/subagents.js +0 -12
- package/dist/lib/teams/agents.d.ts +0 -2
- package/dist/lib/teams/agents.js +10 -27
- package/dist/lib/teams/index.d.ts +1 -1
- package/dist/lib/teams/index.js +1 -1
- package/dist/lib/types.d.ts +17 -9
- package/dist/lib/usage.d.ts +28 -5
- package/dist/lib/usage.js +271 -8
- package/dist/lib/versions.js +7 -1
- package/dist/lib/watchdog/watchdog.d.ts +1 -1
- package/dist/lib/watchdog/watchdog.js +25 -13
- package/package.json +1 -2
- package/dist/commands/check.d.ts +0 -15
- package/dist/commands/check.js +0 -180
- package/dist/commands/resources.d.ts +0 -5
|
@@ -206,6 +206,22 @@ type BrokerConnectionHandler = (conn: net.Socket) => void;
|
|
|
206
206
|
* rotation on broker restart is picked up without rebuilding the handler.
|
|
207
207
|
*/
|
|
208
208
|
export declare function makeConnectionHandler(handle: (req: Request) => Response, token: () => string | null): BrokerConnectionHandler;
|
|
209
|
+
/**
|
|
210
|
+
* Whether a live process still owns the broker pid file.
|
|
211
|
+
*
|
|
212
|
+
* A single missed `agentPing` is NOT proof the owner is dead — the broker is
|
|
213
|
+
* single-threaded, so a big `get` or the rehydrate at startup can blow the
|
|
214
|
+
* 700ms ping budget while the process is perfectly healthy. Treating that as
|
|
215
|
+
* death let a starting broker unlink a live socket and rebind, leaving the old
|
|
216
|
+
* process alive holding every unlocked bundle in RAM that no client could reach
|
|
217
|
+
* any more (observed live: two brokers, one socket path, two kernel sockets).
|
|
218
|
+
* The pid file is the second, independent liveness signal that makes the reclaim
|
|
219
|
+
* safe.
|
|
220
|
+
*/
|
|
221
|
+
/** Drop our ownership record, but only if it is still ours — never clobber a
|
|
222
|
+
* successor that already claimed the socket. */
|
|
223
|
+
export declare function releaseBrokerPid(): void;
|
|
224
|
+
export declare function brokerPidAlive(): boolean;
|
|
209
225
|
/**
|
|
210
226
|
* Run the broker in the foreground. Spawned detached by ensureAgentRunning via
|
|
211
227
|
* `agents secrets _agent-run`. Holds the store in memory, serves the socket,
|
|
@@ -30,7 +30,7 @@ import * as path from 'path';
|
|
|
30
30
|
import { randomBytes } from 'crypto';
|
|
31
31
|
import { spawn, spawnSync, execFileSync } from 'child_process';
|
|
32
32
|
import { getHelpersDir, readMeta } from '../state.js';
|
|
33
|
-
import { isAlive } from '../platform/process.js';
|
|
33
|
+
import { isAlive, waitForExit } from '../platform/process.js';
|
|
34
34
|
import { getKeychainHelperPath } from './install-helper.js';
|
|
35
35
|
import { getCliVersion, getCliVersionFresh } from '../version.js';
|
|
36
36
|
import { getCliLaunch } from '../cli-entry.js';
|
|
@@ -65,6 +65,19 @@ export const META_CACHE_PREFIX = '!meta:';
|
|
|
65
65
|
const IDLE_EXIT_MS = 5 * 60 * 1000; // 5m
|
|
66
66
|
/** How often the broker sweeps expired entries. */
|
|
67
67
|
const SWEEP_INTERVAL_MS = 30 * 1000;
|
|
68
|
+
/** How many times a starting broker probes an in-use socket before treating its
|
|
69
|
+
* owner as gone. One probe is not enough: the broker is single-threaded, so a
|
|
70
|
+
* large read or the startup rehydrate can outlast a single ping budget while the
|
|
71
|
+
* process is healthy. */
|
|
72
|
+
const BIND_PROBE_ATTEMPTS = 3;
|
|
73
|
+
/** How long a broker teardown waits for the old process to actually exit before
|
|
74
|
+
* clearing its socket + ownership record. */
|
|
75
|
+
const BROKER_STOP_GRACE_MS = 3000;
|
|
76
|
+
/** Pause between those probes. */
|
|
77
|
+
const BIND_PROBE_INTERVAL_MS = 250;
|
|
78
|
+
function delay(ms) {
|
|
79
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
80
|
+
}
|
|
68
81
|
/**
|
|
69
82
|
* Timeouts for the three synchronous broker clients, split across the process
|
|
70
83
|
* boundary they now straddle.
|
|
@@ -170,6 +183,20 @@ function pidPath() {
|
|
|
170
183
|
function tokenPath() {
|
|
171
184
|
return path.join(agentDir(), 'agent.token');
|
|
172
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Path of the CURRENT SOCKET OWNER's pid.
|
|
188
|
+
*
|
|
189
|
+
* Deliberately NOT `agent.pid`: that file is the standalone service's O_EXCL
|
|
190
|
+
* single-instance claim, and a standalone that loses the socket race stays alive
|
|
191
|
+
* and quiescent while holding it (so launchd's KeepAlive doesn't restart-loop
|
|
192
|
+
* it), ready to take over if the hosted broker stops. Overloading it as the
|
|
193
|
+
* socket-ownership record made the standalone see a live holder and exit
|
|
194
|
+
* immediately — the exact restart loop that guard exists to prevent. This file
|
|
195
|
+
* answers a different question: which pid is serving the socket right now.
|
|
196
|
+
*/
|
|
197
|
+
function ownerPath() {
|
|
198
|
+
return path.join(agentDir(), 'agent.owner');
|
|
199
|
+
}
|
|
173
200
|
/**
|
|
174
201
|
* Read the current broker capability token, or null if none is present. Clients
|
|
175
202
|
* read it fresh per request and attach it to every non-ping command; a broker
|
|
@@ -425,11 +452,45 @@ export function makeConnectionHandler(handle, token) {
|
|
|
425
452
|
conn.on('error', () => { });
|
|
426
453
|
};
|
|
427
454
|
}
|
|
455
|
+
/**
|
|
456
|
+
* Whether a live process still owns the broker pid file.
|
|
457
|
+
*
|
|
458
|
+
* A single missed `agentPing` is NOT proof the owner is dead — the broker is
|
|
459
|
+
* single-threaded, so a big `get` or the rehydrate at startup can blow the
|
|
460
|
+
* 700ms ping budget while the process is perfectly healthy. Treating that as
|
|
461
|
+
* death let a starting broker unlink a live socket and rebind, leaving the old
|
|
462
|
+
* process alive holding every unlocked bundle in RAM that no client could reach
|
|
463
|
+
* any more (observed live: two brokers, one socket path, two kernel sockets).
|
|
464
|
+
* The pid file is the second, independent liveness signal that makes the reclaim
|
|
465
|
+
* safe.
|
|
466
|
+
*/
|
|
467
|
+
/** Drop our ownership record, but only if it is still ours — never clobber a
|
|
468
|
+
* successor that already claimed the socket. */
|
|
469
|
+
export function releaseBrokerPid() {
|
|
470
|
+
try {
|
|
471
|
+
if (parseInt(fs.readFileSync(ownerPath(), 'utf-8').trim(), 10) === process.pid) {
|
|
472
|
+
fs.unlinkSync(ownerPath());
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
catch { /* absent or unreadable — nothing to release */ }
|
|
476
|
+
}
|
|
477
|
+
export function brokerPidAlive() {
|
|
478
|
+
try {
|
|
479
|
+
const holder = parseInt(fs.readFileSync(ownerPath(), 'utf-8').trim(), 10);
|
|
480
|
+
return !isNaN(holder) && holder !== process.pid && isAlive(holder);
|
|
481
|
+
}
|
|
482
|
+
catch {
|
|
483
|
+
return false; // no pid file → nothing claims ownership
|
|
484
|
+
}
|
|
485
|
+
}
|
|
428
486
|
/**
|
|
429
487
|
* Bind the shared broker socket without stealing it from another live owner.
|
|
430
488
|
* Both the standalone service and daemon-hosted broker use this single path so
|
|
431
489
|
* either startup order is safe: a reachable owner wins, while an unreachable
|
|
432
490
|
* stale socket is reclaimed once.
|
|
491
|
+
*
|
|
492
|
+
* "Unreachable" is established with retries plus a pid-file liveness check, never
|
|
493
|
+
* a single ping — see {@link brokerPidAlive}.
|
|
433
494
|
*/
|
|
434
495
|
async function bindBrokerSocket(sock, onConnection) {
|
|
435
496
|
const listenOnce = () => new Promise((resolve, reject) => {
|
|
@@ -453,14 +514,38 @@ async function bindBrokerSocket(sock, onConnection) {
|
|
|
453
514
|
writeAgentToken();
|
|
454
515
|
}
|
|
455
516
|
catch { /* dir 0700 gates the socket regardless */ }
|
|
517
|
+
// Record ownership at the same moment, for the same reason. The
|
|
518
|
+
// daemon-hosted broker deliberately skips the O_EXCL pid-file GUARD (the
|
|
519
|
+
// daemon owns its instance), but without an ownership RECORD
|
|
520
|
+
// brokerPidAlive() has nothing to read — so a later starter saw an
|
|
521
|
+
// ownerless socket and reclaimed the live hosted broker anyway, which is
|
|
522
|
+
// the primary configuration and the one that actually orphaned here.
|
|
523
|
+
// Writing it from the confirmed owner covers both broker flavours.
|
|
524
|
+
try {
|
|
525
|
+
fs.writeFileSync(ownerPath(), String(process.pid));
|
|
526
|
+
}
|
|
527
|
+
catch { /* dir 0700 gates it */ }
|
|
456
528
|
resolve(server);
|
|
457
529
|
});
|
|
458
530
|
});
|
|
459
531
|
let bound = await listenOnce();
|
|
460
532
|
if (bound !== 'inuse')
|
|
461
533
|
return bound;
|
|
462
|
-
|
|
463
|
-
|
|
534
|
+
// Give a busy owner several chances before concluding it is gone. One slow
|
|
535
|
+
// ping used to be enough to evict a healthy broker and orphan its RAM.
|
|
536
|
+
for (let attempt = 0; attempt < BIND_PROBE_ATTEMPTS; attempt++) {
|
|
537
|
+
if ((await agentPing()).reachable)
|
|
538
|
+
return null;
|
|
539
|
+
if (attempt < BIND_PROBE_ATTEMPTS - 1)
|
|
540
|
+
await delay(BIND_PROBE_INTERVAL_MS);
|
|
541
|
+
}
|
|
542
|
+
// Silent but alive is still alive: refuse to steal the socket from a process
|
|
543
|
+
// that still owns the pid file, and let the caller surface the wedge instead
|
|
544
|
+
// of quietly starting a second broker on the same path.
|
|
545
|
+
if (brokerPidAlive()) {
|
|
546
|
+
throw new Error(`Secrets broker socket is held by a live process that is not answering: ${sock}. ` +
|
|
547
|
+
`Run 'agents secrets lock --all' then retry, or stop the stuck broker.`);
|
|
548
|
+
}
|
|
464
549
|
try {
|
|
465
550
|
fs.unlinkSync(sock);
|
|
466
551
|
}
|
|
@@ -637,6 +722,7 @@ export async function runSecretsAgent(opts = {}) {
|
|
|
637
722
|
fs.unlinkSync(sock);
|
|
638
723
|
}
|
|
639
724
|
catch { /* gone */ }
|
|
725
|
+
releaseBrokerPid();
|
|
640
726
|
releasePid();
|
|
641
727
|
};
|
|
642
728
|
sweepTimer = setInterval(sweep, SWEEP_INTERVAL_MS);
|
|
@@ -746,6 +832,7 @@ export async function startHostedBroker() {
|
|
|
746
832
|
fs.unlinkSync(sock);
|
|
747
833
|
}
|
|
748
834
|
catch { /* gone */ }
|
|
835
|
+
releaseBrokerPid();
|
|
749
836
|
},
|
|
750
837
|
};
|
|
751
838
|
}
|
|
@@ -1248,6 +1335,12 @@ export async function ensureAgentRunning(timeoutMs = 5000) {
|
|
|
1248
1335
|
process.kill(stalePid, 'SIGTERM');
|
|
1249
1336
|
}
|
|
1250
1337
|
catch { /* already dead */ }
|
|
1338
|
+
// Wait for it to actually go before clearing its socket and ownership
|
|
1339
|
+
// record. Unlinking them under a live broker is what leaves an orphan
|
|
1340
|
+
// holding every unlocked bundle in RAM with no way to reach it — and it also
|
|
1341
|
+
// destroys the very record brokerPidAlive() reads, so the successor sees an
|
|
1342
|
+
// ownerless socket and reclaims it regardless.
|
|
1343
|
+
waitForExit(stalePid, BROKER_STOP_GRACE_MS);
|
|
1251
1344
|
}
|
|
1252
1345
|
try {
|
|
1253
1346
|
fs.unlinkSync(socketPath());
|
|
@@ -1257,6 +1350,10 @@ export async function ensureAgentRunning(timeoutMs = 5000) {
|
|
|
1257
1350
|
fs.unlinkSync(pidPath());
|
|
1258
1351
|
}
|
|
1259
1352
|
catch { /* gone */ }
|
|
1353
|
+
try {
|
|
1354
|
+
fs.unlinkSync(ownerPath());
|
|
1355
|
+
}
|
|
1356
|
+
catch { /* gone */ }
|
|
1260
1357
|
const { cmd, args } = brokerSpawn();
|
|
1261
1358
|
spawn(cmd, args, { stdio: 'ignore', detached: true }).unref();
|
|
1262
1359
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -1286,6 +1383,7 @@ async function teardownStaleBroker() {
|
|
|
1286
1383
|
process.kill(pid, 'SIGTERM');
|
|
1287
1384
|
}
|
|
1288
1385
|
catch { /* gone */ }
|
|
1386
|
+
waitForExit(pid, BROKER_STOP_GRACE_MS); // same reason as ensureAgentRunning
|
|
1289
1387
|
}
|
|
1290
1388
|
try {
|
|
1291
1389
|
fs.unlinkSync(socketPath());
|
|
@@ -1295,4 +1393,8 @@ async function teardownStaleBroker() {
|
|
|
1295
1393
|
fs.unlinkSync(pidPath());
|
|
1296
1394
|
}
|
|
1297
1395
|
catch { /* gone */ }
|
|
1396
|
+
try {
|
|
1397
|
+
fs.unlinkSync(ownerPath());
|
|
1398
|
+
}
|
|
1399
|
+
catch { /* gone */ }
|
|
1298
1400
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export type SecretAuditEvent = 'secrets.get' | 'secrets.unlocked';
|
|
2
|
+
export interface SecretAuditParams {
|
|
3
|
+
/** Which audit event this is — a read (`secrets.get`) or an unlock grant. */
|
|
4
|
+
event: SecretAuditEvent;
|
|
5
|
+
/** Bundle name (never a value). */
|
|
6
|
+
bundle?: string;
|
|
7
|
+
/** Raw keychain item/service name for `secrets get <item>` (never a value). */
|
|
8
|
+
item?: string;
|
|
9
|
+
/** Free-form operation/caller label, e.g. 'export to shell', 'unlock'. */
|
|
10
|
+
operation?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Where the read/grant originated: agent | session | reveal | raw-item |
|
|
13
|
+
* remote | sync-push | broker | broker+durable.
|
|
14
|
+
*/
|
|
15
|
+
source?: string;
|
|
16
|
+
status?: 'success' | 'error';
|
|
17
|
+
/** Env/key NAMES exposed — names only, never values. */
|
|
18
|
+
keys?: string[];
|
|
19
|
+
keyCount?: number;
|
|
20
|
+
/** Subset of `keys` backed by the OS keychain — names only. */
|
|
21
|
+
keychainKeys?: string[];
|
|
22
|
+
/** Count of resolved values by kind (literal / keychain / env / ...). */
|
|
23
|
+
kindCounts?: Record<string, number>;
|
|
24
|
+
/**
|
|
25
|
+
* Resolving agent/harness identity the access was scoped to (`*` = a global
|
|
26
|
+
* grant readable by every harness). Falls back to `$AGENTS_AGENT_NAME`.
|
|
27
|
+
*/
|
|
28
|
+
agent?: string;
|
|
29
|
+
/** Remote host the value was pulled from, when applicable. */
|
|
30
|
+
host?: string;
|
|
31
|
+
/** Grant lifetime in ms (`secrets.unlocked` only). */
|
|
32
|
+
ttlMs?: number;
|
|
33
|
+
error?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The agent/harness identity to attribute a secret access to. Explicit callers
|
|
37
|
+
* (the bundle reader knows the scope it resolved under) win; otherwise fall back
|
|
38
|
+
* to the ambient `$AGENTS_AGENT_NAME` set on every agent launch.
|
|
39
|
+
*/
|
|
40
|
+
export declare function resolveAuditAgent(explicit?: string): string | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Emit one value-free secret audit event to the raw/operational event stream and
|
|
43
|
+
* the persisted audit log. Only ever pass metadata — the secret value must never
|
|
44
|
+
* reach this function.
|
|
45
|
+
*/
|
|
46
|
+
export declare function emitSecretAudit(p: SecretAuditParams): void;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical audit emitter for `agents secrets` value access and unlock grants.
|
|
3
|
+
*
|
|
4
|
+
* Every path that reads a secret VALUE or grants an unlock funnels its audit
|
|
5
|
+
* through here, so the operational event stream — `agents events`, backed by the
|
|
6
|
+
* append-only `~/.agents/events.jsonl` audit log — carries a uniform, value-free
|
|
7
|
+
* provenance record: bundle, key NAMES, the resolving agent/harness identity,
|
|
8
|
+
* operation, source, status. The ts / host / session / caller fields are filled
|
|
9
|
+
* in by `emit()` itself. The secret VALUE is never part of the payload; this
|
|
10
|
+
* helper only ever receives metadata, and `emit()`'s `sanitizePayload` is a
|
|
11
|
+
* second redaction layer.
|
|
12
|
+
*
|
|
13
|
+
* Two event types, both audit-level and non-milestone (so they surface in
|
|
14
|
+
* `agents events` and the persisted audit trail, but are NOT required in the
|
|
15
|
+
* curated `agents activity` / `agents feed` surfaces):
|
|
16
|
+
* - `secrets.get` — a value was READ (exec inject, export, `view --reveal`,
|
|
17
|
+
* raw `get <item>`, remote resolve, sync push,
|
|
18
|
+
* `run --secrets`, and every other bundle read).
|
|
19
|
+
* - `secrets.unlocked` — a bundle was GRANTED into the secrets broker / durable
|
|
20
|
+
* session by `agents secrets unlock`, then readable
|
|
21
|
+
* prompt-free for the grant TTL.
|
|
22
|
+
*/
|
|
23
|
+
import { emit } from '../events.js';
|
|
24
|
+
/**
|
|
25
|
+
* The agent/harness identity to attribute a secret access to. Explicit callers
|
|
26
|
+
* (the bundle reader knows the scope it resolved under) win; otherwise fall back
|
|
27
|
+
* to the ambient `$AGENTS_AGENT_NAME` set on every agent launch.
|
|
28
|
+
*/
|
|
29
|
+
export function resolveAuditAgent(explicit) {
|
|
30
|
+
const a = explicit || process.env.AGENTS_AGENT_NAME;
|
|
31
|
+
return a && a.length > 0 ? a : undefined;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Emit one value-free secret audit event to the raw/operational event stream and
|
|
35
|
+
* the persisted audit log. Only ever pass metadata — the secret value must never
|
|
36
|
+
* reach this function.
|
|
37
|
+
*/
|
|
38
|
+
export function emitSecretAudit(p) {
|
|
39
|
+
const agent = resolveAuditAgent(p.agent);
|
|
40
|
+
emit(p.event, {
|
|
41
|
+
module: 'secrets',
|
|
42
|
+
...(p.bundle !== undefined ? { bundle: p.bundle } : {}),
|
|
43
|
+
...(p.item !== undefined ? { item: p.item } : {}),
|
|
44
|
+
...(p.operation !== undefined ? { operation: p.operation } : {}),
|
|
45
|
+
...(p.source !== undefined ? { source: p.source } : {}),
|
|
46
|
+
...(p.status !== undefined ? { status: p.status } : {}),
|
|
47
|
+
...(p.keys !== undefined ? { keys: p.keys } : {}),
|
|
48
|
+
...(p.keyCount !== undefined ? { keyCount: p.keyCount } : {}),
|
|
49
|
+
...(p.keychainKeys !== undefined ? { keychainKeys: p.keychainKeys } : {}),
|
|
50
|
+
...(p.kindCounts !== undefined ? { kindCounts: p.kindCounts } : {}),
|
|
51
|
+
...(agent !== undefined ? { agent } : {}),
|
|
52
|
+
...(p.host !== undefined ? { host: p.host } : {}),
|
|
53
|
+
...(p.ttlMs !== undefined ? { ttlMs: p.ttlMs } : {}),
|
|
54
|
+
...(p.error !== undefined ? { error: p.error } : {}),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -43,7 +43,9 @@ export interface VarMeta {
|
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
45
|
* A bundle's prompt policy — how often macOS asks for Touch ID to read it:
|
|
46
|
-
* - `
|
|
46
|
+
* - `hold` (default): ask once, then serve it silently for the configured hold
|
|
47
|
+
* duration (`secrets.agent.holdMs`, 7d by default). Named for what it does —
|
|
48
|
+
* it was called `daily`, which stated a period it never had.
|
|
47
49
|
* (Historical name — the window is now a rolling ~1 week, not one calendar day.)
|
|
48
50
|
* Eligible for the secrets-agent — the first real keychain read auto-loads it
|
|
49
51
|
* (auto-cache is on by default) so concurrent runs read it silently, or `unlock`
|
|
@@ -61,12 +63,12 @@ export interface VarMeta {
|
|
|
61
63
|
* it loudly rather than silently downgrading to `always`.
|
|
62
64
|
*
|
|
63
65
|
* The default is configurable via `secrets.policy` in agents.yaml. Stored on disk
|
|
64
|
-
* under the legacy `tier` key (`session` == `
|
|
66
|
+
* under the legacy `tier` key (`session` == `hold`, `biometry` == explicit
|
|
65
67
|
* `always`, `none` == `never`, absent == inherit the default) so bundles stay
|
|
66
68
|
* readable across mixed CLI versions on synced machines. The user-facing
|
|
67
|
-
* vocabulary is `policy`/`always`/`
|
|
69
|
+
* vocabulary is `policy`/`always`/`hold`/`never`.
|
|
68
70
|
*/
|
|
69
|
-
export type SecretsPolicy = 'always' | '
|
|
71
|
+
export type SecretsPolicy = 'always' | 'hold' | 'never';
|
|
70
72
|
/** A named set of environment variable definitions backed by various secret providers. */
|
|
71
73
|
export interface SecretsBundle {
|
|
72
74
|
name: string;
|
|
@@ -74,7 +76,7 @@ export interface SecretsBundle {
|
|
|
74
76
|
allow_exec?: boolean;
|
|
75
77
|
/** Which store carries this bundle's items. Absent ⇒ `keychain` (the default). */
|
|
76
78
|
backend?: SecretsBackend;
|
|
77
|
-
/** Prompt policy. Absent ⇒ the configured default (`
|
|
79
|
+
/** Prompt policy. Absent ⇒ the configured default (`hold`). Serialized under
|
|
78
80
|
* the legacy `tier` key — see SecretsPolicy. */
|
|
79
81
|
policy?: SecretsPolicy;
|
|
80
82
|
/** ISO 8601 UTC timestamp. Set once on the first writeBundle() for a bundle. */
|
|
@@ -142,9 +144,10 @@ export declare class BundleUndecryptableError extends Error {
|
|
|
142
144
|
export declare function readBundleIfDecryptable(name: string): SecretsBundle | null;
|
|
143
145
|
export declare function readBundle(name: string): SecretsBundle;
|
|
144
146
|
/** The default prompt policy applied to bundles without an explicit per-bundle
|
|
145
|
-
* policy. Configurable via `secrets.policy` in agents.yaml; `
|
|
146
|
-
*
|
|
147
|
-
* `always`. Best-effort: an
|
|
147
|
+
* policy. Configurable via `secrets.policy` in agents.yaml; `hold` (one Touch ID
|
|
148
|
+
* per hold window — `secrets.agent.holdMs`, 7d by default) unless the user
|
|
149
|
+
* explicitly opts back into prompt-every-time with `always`. Best-effort: an
|
|
150
|
+
* unreadable config falls back to the `hold` default. */
|
|
148
151
|
export declare function secretsDefaultPolicy(): SecretsPolicy;
|
|
149
152
|
/** The effective prompt policy of a bundle (absent ⇒ the configured default). */
|
|
150
153
|
export declare function bundlePolicy(bundle: SecretsBundle): SecretsPolicy;
|
|
@@ -28,6 +28,7 @@ import { deleteKeychainToken, getKeychainToken, getKeychainTokens, hasKeychainTo
|
|
|
28
28
|
import { fileStore } from './filestore.js';
|
|
29
29
|
import { getVaultSession, vaultDeleteItem, vaultExists, vaultGetItems, vaultGetItem, vaultHasItem, vaultListItems, vaultSetItems, vaultSetItem, } from './vault.js';
|
|
30
30
|
import { emit } from '../events.js';
|
|
31
|
+
import { emitSecretAudit } from './audit.js';
|
|
31
32
|
import { readMeta, getHelpersDir } from '../state.js';
|
|
32
33
|
import { assertNameActiveInResourceProfile, filterNamesForActiveResourceProfile } from '../resource-profiles.js';
|
|
33
34
|
import { agentGetSync, agentAutoLoadSync, agentGetMetaSync, agentAutoLoadMetaSync, agentEvictSync, secretsAgentAutoEnabled, secretsHoldMs } from './agent.js';
|
|
@@ -324,7 +325,7 @@ export function readBundle(name) {
|
|
|
324
325
|
// Absent ⇒ keychain; only set when non-keychain so a keychain bundle
|
|
325
326
|
// round-trips byte-for-byte.
|
|
326
327
|
backend: backend === 'keychain' ? undefined : backend,
|
|
327
|
-
// Legacy wire key: the policy is persisted under `tier` (`session` == `
|
|
328
|
+
// Legacy wire key: the policy is persisted under `tier` (`session` == `hold`).
|
|
328
329
|
policy: parsePolicy(parsed.tier),
|
|
329
330
|
vars: parsed.vars && typeof parsed.vars === 'object' ? parsed.vars : {},
|
|
330
331
|
};
|
|
@@ -343,14 +344,14 @@ export function readBundle(name) {
|
|
|
343
344
|
return bundle;
|
|
344
345
|
}
|
|
345
346
|
/** Normalize the persisted prompt policy. The on-disk `tier` key uses legacy
|
|
346
|
-
* tokens for cross-version compatibility: `session` ⇒ `
|
|
347
|
+
* tokens for cross-version compatibility: `session` ⇒ `hold`, `biometry` ⇒ an
|
|
347
348
|
* explicit `always`. An absent token ⇒ undefined, which resolves to the
|
|
348
|
-
* configured default policy (`
|
|
349
|
-
* legacy `biometry` token keeps older CLIs correct — they don't know `
|
|
349
|
+
* configured default policy (`hold`). Persisting an explicit `always` as the
|
|
350
|
+
* legacy `biometry` token keeps older CLIs correct — they don't know `hold`,
|
|
350
351
|
* read `biometry` as undefined, and fall back to their own always default. */
|
|
351
352
|
function parsePolicy(raw) {
|
|
352
|
-
if (raw === 'daily' || raw === 'session')
|
|
353
|
-
return '
|
|
353
|
+
if (raw === 'hold' || raw === 'daily' || raw === 'session')
|
|
354
|
+
return 'hold';
|
|
354
355
|
if (raw === 'always' || raw === 'biometry')
|
|
355
356
|
return 'always';
|
|
356
357
|
if (raw === 'never' || raw === 'none')
|
|
@@ -358,15 +359,16 @@ function parsePolicy(raw) {
|
|
|
358
359
|
return undefined;
|
|
359
360
|
}
|
|
360
361
|
/** The default prompt policy applied to bundles without an explicit per-bundle
|
|
361
|
-
* policy. Configurable via `secrets.policy` in agents.yaml; `
|
|
362
|
-
*
|
|
363
|
-
* `always`. Best-effort: an
|
|
362
|
+
* policy. Configurable via `secrets.policy` in agents.yaml; `hold` (one Touch ID
|
|
363
|
+
* per hold window — `secrets.agent.holdMs`, 7d by default) unless the user
|
|
364
|
+
* explicitly opts back into prompt-every-time with `always`. Best-effort: an
|
|
365
|
+
* unreadable config falls back to the `hold` default. */
|
|
364
366
|
export function secretsDefaultPolicy() {
|
|
365
367
|
try {
|
|
366
|
-
return readMeta().secrets?.policy === 'always' ? 'always' : '
|
|
368
|
+
return readMeta().secrets?.policy === 'always' ? 'always' : 'hold';
|
|
367
369
|
}
|
|
368
370
|
catch {
|
|
369
|
-
return '
|
|
371
|
+
return 'hold';
|
|
370
372
|
}
|
|
371
373
|
}
|
|
372
374
|
/** The effective prompt policy of a bundle (absent ⇒ the configured default). */
|
|
@@ -434,12 +436,12 @@ function prepareBundleWrite(bundle) {
|
|
|
434
436
|
allow_exec: bundle.allow_exec ? true : undefined,
|
|
435
437
|
backend: backend === 'keychain' ? undefined : backend,
|
|
436
438
|
// Wire format: persist the policy under the legacy `tier` token so older CLI
|
|
437
|
-
// versions on other synced machines keep reading it — `
|
|
439
|
+
// versions on other synced machines keep reading it — `hold`⇒`session`,
|
|
438
440
|
// explicit `always`⇒`biometry`, `never`⇒`none`. An absent policy omits the
|
|
439
|
-
// token entirely and resolves to the configured default (`
|
|
441
|
+
// token entirely and resolves to the configured default (`hold`) on read.
|
|
440
442
|
// An older CLI that doesn't know `none` reads it as undefined and falls back
|
|
441
443
|
// to its own default — safe, since it also lacks the no-ACL write path.
|
|
442
|
-
tier: bundle.policy === '
|
|
444
|
+
tier: bundle.policy === 'hold' ? 'session'
|
|
443
445
|
: bundle.policy === 'always' ? 'biometry'
|
|
444
446
|
: bundle.policy === 'never' ? 'none'
|
|
445
447
|
: undefined,
|
|
@@ -546,7 +548,7 @@ function parseBundleMeta(nameHint, json, backend) {
|
|
|
546
548
|
description: parsed.description,
|
|
547
549
|
allow_exec: Boolean(parsed.allow_exec),
|
|
548
550
|
backend: backend === 'keychain' ? undefined : backend,
|
|
549
|
-
// Legacy wire key: the policy is persisted under `tier` (`session` == `
|
|
551
|
+
// Legacy wire key: the policy is persisted under `tier` (`session` == `hold`).
|
|
550
552
|
policy: parsePolicy(parsed.tier),
|
|
551
553
|
vars: parsed.vars && typeof parsed.vars === 'object' ? parsed.vars : {},
|
|
552
554
|
};
|
|
@@ -1111,13 +1113,14 @@ export function readAndResolveBundleEnv(name, opts = {}) {
|
|
|
1111
1113
|
// the first cache-populating run.
|
|
1112
1114
|
const filtered = filterAgentHitBySubsetAndExpiry(hit, opts);
|
|
1113
1115
|
stampLastUsed(filtered.bundle);
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
+
emitSecretAudit({
|
|
1117
|
+
event: 'secrets.get',
|
|
1116
1118
|
bundle: name,
|
|
1117
1119
|
operation: opts.caller,
|
|
1118
1120
|
status: 'success',
|
|
1119
1121
|
source: 'agent',
|
|
1120
1122
|
keyCount: Object.keys(filtered.env).length,
|
|
1123
|
+
agent: harness,
|
|
1121
1124
|
});
|
|
1122
1125
|
return filtered;
|
|
1123
1126
|
}
|
|
@@ -1137,13 +1140,14 @@ export function readAndResolveBundleEnv(name, opts = {}) {
|
|
|
1137
1140
|
// MADE in (resolved.harness), never the asking scope — re-warming a global
|
|
1138
1141
|
// grant as `claude` would silently narrow it for every other harness.
|
|
1139
1142
|
agentAutoLoadSync(name, session.bundle, session.env, Math.max(1, session.expiresAt - Date.now()), resolved.harness);
|
|
1140
|
-
|
|
1141
|
-
|
|
1143
|
+
emitSecretAudit({
|
|
1144
|
+
event: 'secrets.get',
|
|
1142
1145
|
bundle: name,
|
|
1143
1146
|
operation: opts.caller,
|
|
1144
1147
|
status: 'success',
|
|
1145
1148
|
source: 'session',
|
|
1146
1149
|
keyCount: Object.keys(filtered.env).length,
|
|
1150
|
+
agent: harness,
|
|
1147
1151
|
});
|
|
1148
1152
|
return filtered;
|
|
1149
1153
|
}
|
|
@@ -1198,6 +1202,9 @@ export function readAndResolveBundleEnv(name, opts = {}) {
|
|
|
1198
1202
|
? getKeychainTokens([...new Set([metaItem, ...secretItems])], {
|
|
1199
1203
|
agent: opts.agent || process.env.AGENTS_AGENT_NAME || 'Agents CLI',
|
|
1200
1204
|
bundle: name,
|
|
1205
|
+
// The session that triggered the read, so a Touch ID prompt is
|
|
1206
|
+
// attributable when several agents run at once. Exported by exec.ts.
|
|
1207
|
+
sessionId: process.env.AGENT_SESSION_ID || process.env.AGENTS_SESSION_ID,
|
|
1201
1208
|
reason: opts.caller ? `to ${opts.caller}` : reason,
|
|
1202
1209
|
duration: opts.duration || humanUnlockDuration(secretsHoldMs()),
|
|
1203
1210
|
defaultPolicy: secretsDefaultPolicy(),
|
|
@@ -1233,7 +1240,7 @@ export function readAndResolveBundleEnv(name, opts = {}) {
|
|
|
1233
1240
|
description: parsed.description,
|
|
1234
1241
|
allow_exec: Boolean(parsed.allow_exec),
|
|
1235
1242
|
backend: backend === 'keychain' ? undefined : backend,
|
|
1236
|
-
// Legacy wire key: the policy is persisted under `tier` (`session` == `
|
|
1243
|
+
// Legacy wire key: the policy is persisted under `tier` (`session` == `hold`).
|
|
1237
1244
|
policy: parsePolicy(parsed.tier),
|
|
1238
1245
|
vars: parsed.vars && typeof parsed.vars === 'object' ? parsed.vars : {},
|
|
1239
1246
|
};
|
|
@@ -1269,8 +1276,8 @@ export function readAndResolveBundleEnv(name, opts = {}) {
|
|
|
1269
1276
|
const keys = [...selectedKeys].sort();
|
|
1270
1277
|
keychainKeys.sort();
|
|
1271
1278
|
const emitReadAudit = (status, err) => {
|
|
1272
|
-
|
|
1273
|
-
|
|
1279
|
+
emitSecretAudit({
|
|
1280
|
+
event: 'secrets.get',
|
|
1274
1281
|
bundle: bundle.name,
|
|
1275
1282
|
operation: opts.caller,
|
|
1276
1283
|
status,
|
|
@@ -1278,6 +1285,7 @@ export function readAndResolveBundleEnv(name, opts = {}) {
|
|
|
1278
1285
|
keys,
|
|
1279
1286
|
keychainKeys,
|
|
1280
1287
|
kindCounts,
|
|
1288
|
+
agent: opts.agent,
|
|
1281
1289
|
error: err instanceof Error ? err.message : (err ? String(err) : undefined),
|
|
1282
1290
|
});
|
|
1283
1291
|
};
|
|
@@ -1328,7 +1336,7 @@ export function readAndResolveBundleEnv(name, opts = {}) {
|
|
|
1328
1336
|
if (backend === 'keychain' &&
|
|
1329
1337
|
!opts.noAgent &&
|
|
1330
1338
|
process.env.AGENTS_SECRETS_NO_AGENT !== '1' &&
|
|
1331
|
-
bundlePolicy(bundle) === '
|
|
1339
|
+
bundlePolicy(bundle) === 'hold' &&
|
|
1332
1340
|
secretsAgentAutoEnabled() &&
|
|
1333
1341
|
canCacheResolvedEnv(bundle, selectedKeys, opts.keyMode)) {
|
|
1334
1342
|
agentAutoLoadSync(name, bundle, env, secretsHoldMs(), opts.agent || process.env.AGENTS_AGENT_NAME || GLOBAL_HARNESS);
|
|
@@ -213,9 +213,10 @@ export declare function hasKeychainToken(item: string): boolean;
|
|
|
213
213
|
export interface KeychainReadContext {
|
|
214
214
|
agent?: string;
|
|
215
215
|
bundle?: string;
|
|
216
|
+
sessionId?: string;
|
|
216
217
|
reason?: string;
|
|
217
218
|
duration?: string;
|
|
218
|
-
defaultPolicy?: '
|
|
219
|
+
defaultPolicy?: 'hold' | 'always' | 'never';
|
|
219
220
|
forceDuration?: boolean;
|
|
220
221
|
}
|
|
221
222
|
export declare function keychainOperationPrompt(context?: KeychainReadContext): string;
|
|
@@ -30,6 +30,7 @@ import * as path from 'path';
|
|
|
30
30
|
import { linuxBackend, usesFileFallback as linuxUsesFileFallback, importNativeSecretToolItems } from './linux.js';
|
|
31
31
|
import { windowsBackend, usesFileFallback as windowsUsesFileFallback, importNativeCredManItems } from './windows.js';
|
|
32
32
|
import { getKeychainHelperPath } from './install-helper.js';
|
|
33
|
+
import { deriveShortId } from '../session/short-id.js';
|
|
33
34
|
const SERVICE_PREFIX = 'agents-cli';
|
|
34
35
|
export const SECRETS_ITEM_PREFIX = `${SERVICE_PREFIX}.secrets.`;
|
|
35
36
|
const BUNDLES_ITEM_PREFIX = `${SERVICE_PREFIX}.bundles.`;
|
|
@@ -710,9 +711,12 @@ export function hasKeychainToken(item) {
|
|
|
710
711
|
export function keychainOperationPrompt(context = {}) {
|
|
711
712
|
const agent = context.agent || 'Agents CLI';
|
|
712
713
|
const bundle = context.bundle ? ` the '${context.bundle}' bundle` : ' secrets';
|
|
714
|
+
// Which session triggered the read — the short-id disambiguates an unexpected
|
|
715
|
+
// prompt when several agents run at once (interactive + headless + exec).
|
|
716
|
+
const session = context.sessionId ? ` (session ${deriveShortId(context.sessionId)})` : '';
|
|
713
717
|
const duration = context.duration ? ` for ${context.duration}` : '';
|
|
714
718
|
const reason = context.reason ? ` ${context.reason}` : '';
|
|
715
|
-
return `${agent} is requesting to unlock${bundle}${duration}${reason}.`;
|
|
719
|
+
return `${agent} is requesting to unlock${bundle}${session}${duration}${reason}.`;
|
|
716
720
|
}
|
|
717
721
|
export function getKeychainToken(item, context = {}) {
|
|
718
722
|
// Errors keep the requested (human-readable) name; the storage name may be
|
|
@@ -820,7 +824,9 @@ export function getKeychainTokens(items, context = {}) {
|
|
|
820
824
|
...process.env,
|
|
821
825
|
AGENTS_KEYCHAIN_PROMPT: keychainOperationPrompt(context),
|
|
822
826
|
AGENTS_KEYCHAIN_PROMPT_BASE: keychainOperationPrompt({ ...context, duration: undefined }),
|
|
823
|
-
|
|
827
|
+
// The signed helper's own vocabulary is unchanged (it predates the rename
|
|
828
|
+
// and ships as a separately-versioned binary), so map to its legacy token.
|
|
829
|
+
AGENTS_KEYCHAIN_DEFAULT_POLICY: (context.defaultPolicy ?? 'hold') === 'hold' ? 'daily' : context.defaultPolicy,
|
|
824
830
|
AGENTS_KEYCHAIN_FORCE_DURATION: context.forceDuration ? '1' : '0',
|
|
825
831
|
},
|
|
826
832
|
stdio: ['ignore', 'pipe', 'pipe'],
|