@phnx-labs/agents-cli 1.22.78 → 1.22.79
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 +11 -0
- package/README.md +28 -1
- package/dist/bootstrap.js +40 -12
- package/dist/commands/accounts.d.ts +22 -0
- package/dist/commands/accounts.js +158 -35
- package/dist/commands/config.js +37 -0
- package/dist/commands/exec.js +21 -10
- package/dist/commands/update.js +169 -18
- package/dist/commands/versions.d.ts +11 -0
- package/dist/commands/versions.js +30 -4
- package/dist/commands/view.d.ts +4 -0
- package/dist/commands/view.js +114 -19
- package/dist/index.js +35 -2
- package/dist/lib/account-catalog.d.ts +97 -1
- package/dist/lib/account-catalog.js +134 -6
- package/dist/lib/account-registry.d.ts +43 -0
- package/dist/lib/account-registry.js +97 -2
- package/dist/lib/accounting/rotate.d.ts +2 -2
- package/dist/lib/accounting/rotate.js +5 -5
- package/dist/lib/accounts/auth-operation-lock.d.ts +9 -0
- package/dist/lib/accounts/auth-operation-lock.js +55 -0
- package/dist/lib/accounts/connect.d.ts +170 -0
- package/dist/lib/accounts/connect.js +383 -0
- package/dist/lib/capabilities.js +2 -0
- package/dist/lib/commands.js +2 -0
- package/dist/lib/config-keys.d.ts +11 -2
- package/dist/lib/config-keys.js +21 -1
- package/dist/lib/daemon/daemon.js +5 -0
- package/dist/lib/daemon/harness-update-service.d.ts +110 -0
- package/dist/lib/daemon/harness-update-service.js +216 -0
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +5 -0
- package/dist/lib/device-config.d.ts +0 -1
- package/dist/lib/device-config.js +50 -5
- package/dist/lib/exec.js +26 -2
- package/dist/lib/fs-atomic.d.ts +2 -0
- package/dist/lib/fs-atomic.js +2 -0
- package/dist/lib/hooks/install.js +7 -2
- package/dist/lib/installations/active-check.d.ts +48 -0
- package/dist/lib/installations/active-check.js +84 -0
- package/dist/lib/installations/index.d.ts +5 -1
- package/dist/lib/installations/index.js +4 -0
- package/dist/lib/installations/installation-lock.d.ts +6 -0
- package/dist/lib/installations/installation-lock.js +29 -0
- package/dist/lib/installations/launch-gate.d.ts +69 -0
- package/dist/lib/installations/launch-gate.js +133 -0
- package/dist/lib/installations/native-command.d.ts +5 -0
- package/dist/lib/installations/native-command.js +52 -0
- package/dist/lib/installations/shims.d.ts +8 -2
- package/dist/lib/installations/shims.js +105 -2
- package/dist/lib/installations/store.d.ts +5 -1
- package/dist/lib/installations/store.js +24 -3
- package/dist/lib/installations/strategies.js +55 -35
- package/dist/lib/installations/types.d.ts +17 -0
- package/dist/lib/installations/update-cancellation.d.ts +82 -0
- package/dist/lib/installations/update-cancellation.js +122 -0
- package/dist/lib/installations/update-policy.d.ts +69 -0
- package/dist/lib/installations/update-policy.js +114 -0
- package/dist/lib/installations/update-runtime.d.ts +118 -0
- package/dist/lib/installations/update-runtime.js +321 -0
- package/dist/lib/installations/update.d.ts +25 -0
- package/dist/lib/installations/update.js +141 -2
- package/dist/lib/installations/versions.d.ts +1 -0
- package/dist/lib/installations/versions.js +166 -131
- package/dist/lib/platform/process.d.ts +3 -1
- package/dist/lib/platform/process.js +2 -2
- package/dist/lib/staleness/detectors/commands.d.ts +1 -2
- package/dist/lib/staleness/detectors/hooks.d.ts +1 -2
- package/dist/lib/staleness/detectors/mcp.d.ts +1 -2
- package/dist/lib/staleness/detectors/permissions.d.ts +1 -2
- package/dist/lib/staleness/detectors/plugins.d.ts +1 -7
- package/dist/lib/staleness/detectors/rules.d.ts +1 -2
- package/dist/lib/staleness/detectors/skills.d.ts +1 -2
- package/dist/lib/staleness/detectors/subagents.d.ts +1 -7
- package/dist/lib/staleness/detectors/workflows.d.ts +1 -2
- package/dist/lib/staleness/writers/commands.d.ts +1 -2
- package/dist/lib/staleness/writers/hooks.d.ts +1 -2
- package/dist/lib/staleness/writers/mcp.d.ts +1 -2
- package/dist/lib/staleness/writers/permissions.d.ts +1 -12
- package/dist/lib/staleness/writers/plugins.d.ts +1 -6
- package/dist/lib/staleness/writers/rules.d.ts +1 -2
- package/dist/lib/staleness/writers/skills.d.ts +1 -2
- package/dist/lib/staleness/writers/subagents.d.ts +1 -2
- package/dist/lib/staleness/writers/workflows.d.ts +1 -8
- package/dist/lib/state.d.ts +3 -1
- package/dist/lib/state.js +38 -13
- package/dist/lib/types.d.ts +26 -1
- package/dist/lib/types.js +5 -0
- package/dist/lib/view-types.d.ts +6 -0
- package/package.json +1 -1
package/dist/commands/view.js
CHANGED
|
@@ -21,6 +21,11 @@ import { isCapable } from '../lib/capabilities.js';
|
|
|
21
21
|
import { discoverPlugins, pluginSupportsAgent } from '../lib/plugins/plugins.js';
|
|
22
22
|
import { getAgentsDir, getUserAgentsDir, getEffectivePromptcutsPath, readMergedPromptcuts, readMeta } from '../lib/state.js';
|
|
23
23
|
import { listNativeAccounts } from '../lib/account-registry.js';
|
|
24
|
+
import { buildNativeCatalog } from '../lib/account-catalog.js';
|
|
25
|
+
import { connectSupported } from '../lib/accounts/connect.js';
|
|
26
|
+
import { readInstallation } from '../lib/installations/store.js';
|
|
27
|
+
import { isAutoUpdateEnabledForAgent } from '../lib/installations/update-policy.js';
|
|
28
|
+
import { selectUpdateStrategy } from '../lib/installations/strategies.js';
|
|
24
29
|
import { isGitRepo, getGitSyncStatus } from '../lib/git.js';
|
|
25
30
|
import { getCentralRulesFileName } from '../lib/rules/rules.js';
|
|
26
31
|
import { composeRulesFromState } from '../lib/rules/compose.js';
|
|
@@ -68,6 +73,10 @@ export function namedAccountColumnLabel(agentId, info) {
|
|
|
68
73
|
const saved = listNativeAccounts(readMeta()).find(item => item.agent === agentId && item.identityKey === identityKey);
|
|
69
74
|
return saved ? `${saved.name} · ${display || saved.identityLabel || identityKey}` : display;
|
|
70
75
|
}
|
|
76
|
+
/** Human account identity; release labels belong only in installation diagnostics. */
|
|
77
|
+
export function nativeAccountViewLabel(row) {
|
|
78
|
+
return row.name && row.name !== row.display ? `${row.name} · ${row.display}` : row.display;
|
|
79
|
+
}
|
|
71
80
|
/**
|
|
72
81
|
* Human `agents view` row order: selected default first, then email-bearing
|
|
73
82
|
* accounts alphabetically, then installs whose account has no email. Version
|
|
@@ -368,7 +377,7 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
368
377
|
const cliStates = Object.fromEntries(await Promise.all(agentsToShow
|
|
369
378
|
.filter((agentId) => !hasNonIsolatedVersion(agentId))
|
|
370
379
|
.map(async (agentId) => [agentId, await getUnmanagedCliState(agentId)])));
|
|
371
|
-
const showPaths = !!filterAgentId;
|
|
380
|
+
const showPaths = !!filterAgentId && viewOpts?.versions === true;
|
|
372
381
|
// A filtered native view is about that native harness's installed versions.
|
|
373
382
|
// Custom forks are standalone agent types and only belong in the overview.
|
|
374
383
|
const harnesses = filterAgentId ? [] : getHarnesses();
|
|
@@ -452,7 +461,7 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
452
461
|
allowInteractiveLogin: usageAllowInteractiveLogin(),
|
|
453
462
|
});
|
|
454
463
|
spinner.stop();
|
|
455
|
-
console.log(chalk.bold('Installed Agent CLIs\n'));
|
|
464
|
+
console.log(chalk.bold(viewOpts?.versions ? 'Installed Agent CLIs\n' : 'Agents and accounts\n'));
|
|
456
465
|
const mergeCanonical = (info) => {
|
|
457
466
|
const key = getUsageLookupKey(info);
|
|
458
467
|
if (!key)
|
|
@@ -504,12 +513,13 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
504
513
|
if (live)
|
|
505
514
|
liveVersionByAgent.set(agentId, live);
|
|
506
515
|
}));
|
|
507
|
-
const displayVersion = (agentId, dirVersion) => liveVersionByAgent.get(agentId) ?? dirVersion;
|
|
516
|
+
const displayVersion = (agentId, dirVersion) => liveVersionByAgent.get(agentId) ?? readInstallation(agentId, dirVersion)?.releaseVersion ?? dirVersion;
|
|
508
517
|
// Uncolored row label, shared by the width pass and the render so padding lines
|
|
509
518
|
// up. An isolated copy is never the global default (installing one deliberately
|
|
510
519
|
// records no default), so the two tags can't collide.
|
|
511
520
|
const versionRowLabel = (agentId, version, globalDefault) => {
|
|
512
|
-
const
|
|
521
|
+
const release = displayVersion(agentId, version);
|
|
522
|
+
const shown = release === version ? version : `${version} → ${release}`;
|
|
513
523
|
if (version === globalDefault)
|
|
514
524
|
return `${shown} (default)`;
|
|
515
525
|
if (isVersionIsolated(agentId, version)) {
|
|
@@ -522,7 +532,69 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
522
532
|
return shown;
|
|
523
533
|
};
|
|
524
534
|
// Show version-managed agents
|
|
525
|
-
if (versionManaged.length > 0) {
|
|
535
|
+
if (versionManaged.length > 0 && !viewOpts?.versions) {
|
|
536
|
+
const catalog = buildNativeCatalog(infoResults.map(({ agentId, version, home, info }) => ({
|
|
537
|
+
agent: agentId,
|
|
538
|
+
label: version,
|
|
539
|
+
releaseVersion: displayVersion(agentId, version),
|
|
540
|
+
accountKey: info.accountKey,
|
|
541
|
+
email: info.email,
|
|
542
|
+
signedIn: isLaunchableSignedIn(info.signedIn, credentialPresence(agentId, home)),
|
|
543
|
+
})), readMeta(), (agent) => getGlobalDefault(agent) ?? getIsolatedDefault(agent));
|
|
544
|
+
for (const agentId of versionManaged) {
|
|
545
|
+
const accounts = catalog.filter((row) => row.agent === agentId);
|
|
546
|
+
let updateLabel = 'manual updates';
|
|
547
|
+
try {
|
|
548
|
+
if (selectUpdateStrategy(agentId).transactional) {
|
|
549
|
+
updateLabel = `automatic updates ${isAutoUpdateEnabledForAgent(agentId) ? 'on' : 'off'}`;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
catch { /* unsupported updater: keep the truthful manual label */ }
|
|
553
|
+
console.log(` ${chalk.bold(agentLabel(agentId))}${chalk.gray(` · ${updateLabel}`)}`);
|
|
554
|
+
const width = Math.max(0, ...accounts.map((row) => stringWidth(nativeAccountViewLabel(row))));
|
|
555
|
+
for (const row of accounts) {
|
|
556
|
+
const source = row.installations.find((home) => home.label === row.home && home.signedIn)
|
|
557
|
+
?? row.installations.find((home) => home.signedIn)
|
|
558
|
+
?? row.installations[0];
|
|
559
|
+
const rawInfo = source ? infoMap.get(`${agentId}:${source.label}`) : undefined;
|
|
560
|
+
const info = rawInfo ? mergeCanonical(rawInfo) : undefined;
|
|
561
|
+
const usageKey = getUsageLookupKey(info);
|
|
562
|
+
const usageInfo = usageKey ? usageByKey.get(usageKey) : undefined;
|
|
563
|
+
const usage = formatUsageSummary(info?.plan ?? null, usageInfo?.snapshot ?? null, 3, viewUsageSummaryOptions(agentId, row.state === 'connected', usageInfo, usageWindowCap));
|
|
564
|
+
const connected = row.state === 'connected';
|
|
565
|
+
const model = source ? resolveConfiguredModel(agentId, source.label)?.model : null;
|
|
566
|
+
const pinned = row.installations.some((home) => readInstallation(agentId, home.label)?.updatePolicy === 'pinned');
|
|
567
|
+
const parts = [
|
|
568
|
+
` ${row.isDefault ? chalk.green('*') : ' '} ${chalk.cyan(padToWidth(nativeAccountViewLabel(row), width))}`,
|
|
569
|
+
connected ? chalk.green('connected') : chalk.yellow('not connected here'),
|
|
570
|
+
model ? chalk.yellow(model) : '',
|
|
571
|
+
usage,
|
|
572
|
+
formatUsageStatusBadge(info?.usageStatus),
|
|
573
|
+
pinned ? chalk.gray('release pinned · --versions') : '',
|
|
574
|
+
];
|
|
575
|
+
console.log(joinViewColumns(parts));
|
|
576
|
+
}
|
|
577
|
+
if (accounts.length === 0) {
|
|
578
|
+
const localIdentity = infoResults.some((row) => row.agentId === agentId && row.info.signedIn);
|
|
579
|
+
const hint = connectSupported(agentId) ? `agents accounts connect ${agentId}` : loginHint(agentId);
|
|
580
|
+
console.log(chalk.gray(localIdentity
|
|
581
|
+
? ' Native login detected · account identity unavailable'
|
|
582
|
+
: ` No native login detected · ${hint}`));
|
|
583
|
+
}
|
|
584
|
+
const isolatedCount = listInstalledVersions(agentId).filter((label) => isVersionIsolated(agentId, label)).length;
|
|
585
|
+
if (isolatedCount > 0)
|
|
586
|
+
console.log(chalk.gray(` ${isolatedCount} isolated installation${isolatedCount === 1 ? '' : 's'} · inspect with --versions`));
|
|
587
|
+
const projectVersion = getProjectVersionFromCwd(agentId);
|
|
588
|
+
if (projectVersion)
|
|
589
|
+
console.log(chalk.gray(' Project installation override configured · inspect with --versions'));
|
|
590
|
+
console.log();
|
|
591
|
+
}
|
|
592
|
+
if (filterAgentId) {
|
|
593
|
+
const connect = connectSupported(filterAgentId) ? `Connect: agents accounts connect ${filterAgentId} [name] · ` : '';
|
|
594
|
+
console.log(chalk.gray(` ${connect}Details: agents view ${filterAgentId} --versions\n`));
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
if (versionManaged.length > 0 && viewOpts?.versions) {
|
|
526
598
|
// Calculate column widths across all agents for alignment
|
|
527
599
|
let maxVerLabel = 0;
|
|
528
600
|
let maxEmail = 0;
|
|
@@ -596,7 +668,8 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
596
668
|
const isDefault = version === globalDefault;
|
|
597
669
|
const isolated = !isDefault && isVersionIsolated(agentId, version);
|
|
598
670
|
const isolatedTag = getIsolatedDefault(agentId) === version ? ' (isolated default)' : ' (isolated)';
|
|
599
|
-
const
|
|
671
|
+
const release = displayVersion(agentId, version);
|
|
672
|
+
const shown = release === version ? version : `${version} → ${release}`;
|
|
600
673
|
const base = versionRowLabel(agentId, version, globalDefault);
|
|
601
674
|
const tagPad = ' '.repeat(maxVerLabel - base.length);
|
|
602
675
|
const label = isDefault
|
|
@@ -1253,6 +1326,15 @@ export async function collectAgentsJson(filterAgentId, resourceSections, opts) {
|
|
|
1253
1326
|
}
|
|
1254
1327
|
}
|
|
1255
1328
|
const infoResults = await Promise.all(infoFetches);
|
|
1329
|
+
const globalReleases = new Map();
|
|
1330
|
+
await Promise.all(agentsToShow.filter(isGlobalBinaryAgent).map(async (agent) => {
|
|
1331
|
+
if (!infoResults.some((row) => row.agentId === agent))
|
|
1332
|
+
return;
|
|
1333
|
+
const release = await getLiveVersion(agent);
|
|
1334
|
+
if (release)
|
|
1335
|
+
globalReleases.set(agent, release);
|
|
1336
|
+
}));
|
|
1337
|
+
const actualRelease = (agent, label) => globalReleases.get(agent) ?? readInstallation(agent, label)?.releaseVersion ?? label;
|
|
1256
1338
|
const { canonicalByUsageKey, usageByKey } = await getUsageInfoByIdentity(infoResults.map(({ agentId, home, version, info }) => ({
|
|
1257
1339
|
agentId,
|
|
1258
1340
|
home,
|
|
@@ -1290,6 +1372,7 @@ export async function collectAgentsJson(filterAgentId, resourceSections, opts) {
|
|
|
1290
1372
|
const authHealth = authCache[authCacheKey(host, agentId, version)];
|
|
1291
1373
|
const entry = {
|
|
1292
1374
|
version,
|
|
1375
|
+
releaseVersion: actualRelease(agentId, version),
|
|
1293
1376
|
isDefault: version === globalDefault,
|
|
1294
1377
|
isolated: isVersionIsolated(agentId, version),
|
|
1295
1378
|
isIsolatedDefault: getIsolatedDefault(agentId) === version,
|
|
@@ -1348,7 +1431,16 @@ export async function collectAgentsJson(filterAgentId, resourceSections, opts) {
|
|
|
1348
1431
|
return a.isDefault ? -1 : 1;
|
|
1349
1432
|
return compareVersions(b.version, a.version);
|
|
1350
1433
|
});
|
|
1351
|
-
|
|
1434
|
+
const accounts = buildNativeCatalog(infoResults.filter((row) => row.agentId === agentId).map(({ version, home, info }) => ({
|
|
1435
|
+
agent: agentId,
|
|
1436
|
+
label: version,
|
|
1437
|
+
releaseVersion: actualRelease(agentId, version),
|
|
1438
|
+
accountKey: info.accountKey,
|
|
1439
|
+
email: info.email,
|
|
1440
|
+
signedIn: isLaunchableSignedIn(info.signedIn, credentialPresence(agentId, home)),
|
|
1441
|
+
})), readMeta(), (agent) => getGlobalDefault(agent) ?? getIsolatedDefault(agent))
|
|
1442
|
+
.filter((row) => row.agent === agentId);
|
|
1443
|
+
out.push({ agent: agentId, versions, accounts, harnesses: harnesses.filter((h) => h.agent === agentId) });
|
|
1352
1444
|
}
|
|
1353
1445
|
return out;
|
|
1354
1446
|
}
|
|
@@ -1603,8 +1695,7 @@ export async function viewAction(agentArg, options) {
|
|
|
1603
1695
|
console.log(JSON.stringify(data, null, 2));
|
|
1604
1696
|
return;
|
|
1605
1697
|
}
|
|
1606
|
-
|
|
1607
|
-
await showInstalledVersions(undefined, { forceRefresh });
|
|
1698
|
+
await showInstalledVersions(undefined, { forceRefresh, versions: options?.versions });
|
|
1608
1699
|
return;
|
|
1609
1700
|
}
|
|
1610
1701
|
// Parse agent@version syntax
|
|
@@ -1675,15 +1766,15 @@ export async function viewAction(agentArg, options) {
|
|
|
1675
1766
|
await showAgentResources(agentId, 'default', filter);
|
|
1676
1767
|
}
|
|
1677
1768
|
else {
|
|
1678
|
-
|
|
1679
|
-
await showInstalledVersions(agentId, { forceRefresh });
|
|
1769
|
+
await showInstalledVersions(agentId, { forceRefresh, versions: options?.versions });
|
|
1680
1770
|
}
|
|
1681
1771
|
}
|
|
1682
1772
|
/** Register the `agents view` command. */
|
|
1683
1773
|
export function registerViewCommand(program) {
|
|
1684
1774
|
addHostOption(program.command('view [agent]'))
|
|
1685
|
-
.description('Show
|
|
1686
|
-
.option('--
|
|
1775
|
+
.description('Show your agents, connected accounts, and usage. Use --versions for installation diagnostics.')
|
|
1776
|
+
.option('--versions', 'Show every installation, actual release, account, model, and home path.')
|
|
1777
|
+
.option('--json', 'Emit machine-readable JSON (accounts plus backward-compatible installation list).')
|
|
1687
1778
|
.option('--resources [sections]', 'In --json mode, include each version\'s resources: "all" (default) or a comma list (skills,plugins,mcp,commands,workflows,memory,hooks). Implies --json.')
|
|
1688
1779
|
.option('--detailed', 'Include all resources in --json output (alias for --resources all). Implies --json.')
|
|
1689
1780
|
.option('-r, --refresh', 'Force a live usage refresh, bypassing the cache (slower). Repopulates the S:/W: limit bars for every account whose token is reachable.')
|
|
@@ -1703,12 +1794,15 @@ export function registerViewCommand(program) {
|
|
|
1703
1794
|
.option('--merged', 'Show the merged, first-wins resource surface across all layers (project, user, extras, system) in one table with the winning layer per row.')
|
|
1704
1795
|
.addHelpText('after', `
|
|
1705
1796
|
Examples:
|
|
1706
|
-
# Show
|
|
1797
|
+
# Show installed agents with connected accounts and usage
|
|
1707
1798
|
agents view
|
|
1708
1799
|
|
|
1709
|
-
# Show
|
|
1800
|
+
# Show accounts for one agent
|
|
1710
1801
|
agents view claude
|
|
1711
1802
|
|
|
1803
|
+
# Inspect installation labels, current releases, models, and paths
|
|
1804
|
+
agents view claude --versions
|
|
1805
|
+
|
|
1712
1806
|
# Describe one custom harness (host, model, provider, auth, path)
|
|
1713
1807
|
agents view deepseek-flash
|
|
1714
1808
|
|
|
@@ -1738,16 +1832,17 @@ Examples:
|
|
|
1738
1832
|
agents view --merged
|
|
1739
1833
|
|
|
1740
1834
|
When to use:
|
|
1741
|
-
- Checking which agents
|
|
1742
|
-
- Seeing
|
|
1835
|
+
- Checking which agents and accounts are connected on this device
|
|
1836
|
+
- Seeing the selected default account and each account's usage
|
|
1743
1837
|
- Inspecting commands, skills, hooks, and MCP servers synced to a version
|
|
1744
1838
|
- Verifying a version is installed before running it
|
|
1745
1839
|
- Cleaning up stale versions left behind after upgrading (--prune)
|
|
1746
1840
|
|
|
1747
1841
|
Output:
|
|
1748
|
-
- Without arguments: table of all agents with
|
|
1842
|
+
- Without arguments: table of all agents with accounts, connection state, usage,
|
|
1749
1843
|
then one block per custom harness (see 'agents harness')
|
|
1750
|
-
- With agent name:
|
|
1844
|
+
- With agent name: one row per native identity, showing the default account
|
|
1845
|
+
- With --versions: every retained installation, actual release, model, and home
|
|
1751
1846
|
- With a custom harness name: that harness's host, model, provider, and auth
|
|
1752
1847
|
- With agent@version: detailed breakdown of resources synced to that version
|
|
1753
1848
|
- With --json: structured JSON with version, isDefault, signedIn, authVerdict,
|
package/dist/index.js
CHANGED
|
@@ -24,8 +24,21 @@
|
|
|
24
24
|
* agent.js would pull the whole secrets graph into every invocation.
|
|
25
25
|
*/
|
|
26
26
|
import { SYNC_GET_CMD, SYNC_PING_CMD, SYNC_LOCK_CMD } from './lib/secrets/sync-commands.js';
|
|
27
|
-
// Force exit on Ctrl+C when no interactive prompt is handling it
|
|
28
|
-
|
|
27
|
+
// Force exit on Ctrl+C when no interactive prompt is handling it — UNLESS a
|
|
28
|
+
// guarded harness auto-update pass is mutating this process transactionally
|
|
29
|
+
// (PHNX-3940). A hard exit mid-swap can leave an installation whose directory
|
|
30
|
+
// and metadata disagree, so defer ONLY while that guard is held; the pass sees
|
|
31
|
+
// the same SIGINT cooperatively (lib/installations/update-cancellation.ts) and
|
|
32
|
+
// stops at its next safe boundary. The depth is read through the shared
|
|
33
|
+
// `Symbol.for` registry key so this slim entry shell needs no static import of
|
|
34
|
+
// that module (agent.test.ts pins the single allowed import). Every other SIGINT
|
|
35
|
+
// still force-exits 130, and no other listener is touched.
|
|
36
|
+
process.on('SIGINT', () => {
|
|
37
|
+
const depth = globalThis[Symbol.for('agents.guardedAutoUpdateDepth')] ?? 0;
|
|
38
|
+
if (depth > 0)
|
|
39
|
+
return;
|
|
40
|
+
process.exit(130);
|
|
41
|
+
});
|
|
29
42
|
// Ignore SIGPIPE — prevents exit code 13 crashes in piped environments
|
|
30
43
|
// (e.g. `agents sessions | head`, or when stdout is captured by another process).
|
|
31
44
|
process.on('SIGPIPE', () => { });
|
|
@@ -58,6 +71,15 @@ if (process.argv[2] === SYNC_GET_CMD ||
|
|
|
58
71
|
: await runAgentLockSync(name);
|
|
59
72
|
process.exit(code);
|
|
60
73
|
}
|
|
74
|
+
// Launch-lease delegate: every generated native shim/alias calls
|
|
75
|
+
// `agents __launch-lease <agent> <label> <pid>` right before its final `exec`
|
|
76
|
+
// (PHNX-3940) — on the hot launch path of every managed agent, so it must skip
|
|
77
|
+
// the same update-check/bootstrap machinery `__shim` skips below. See
|
|
78
|
+
// `lib/installations/launch-gate.ts` for what this call actually does.
|
|
79
|
+
if (process.argv[2] === '__launch-lease') {
|
|
80
|
+
const { runLaunchLeaseCli } = await import('./lib/installations/launch-gate.js');
|
|
81
|
+
process.exit(await runLaunchLeaseCli(process.argv.slice(3)));
|
|
82
|
+
}
|
|
61
83
|
// Transparent shim delegate: the generated Windows `.cmd` shims invoke
|
|
62
84
|
// `agents __shim <agent>[@version] <raw args>`. Intercept here, before bootstrap
|
|
63
85
|
// parses anything, so the agent's own flags (`--help`, `--version`, etc.) pass
|
|
@@ -98,6 +120,17 @@ if (process.argv[2] === '__usage-export') {
|
|
|
98
120
|
process.stdout.write(JSON.stringify({ v: 1, rows: exportClaudeUsageCacheRows() }));
|
|
99
121
|
process.exit(0);
|
|
100
122
|
}
|
|
123
|
+
// Harness auto-update child (PHNX-3940): the daemon's harness-update tick spawns
|
|
124
|
+
// `agents __harness-update-run` over a Node IPC channel and cancels it
|
|
125
|
+
// cooperatively by MESSAGE, never a kill (see
|
|
126
|
+
// lib/daemon/harness-update-service.ts + lib/installations/update-cancellation.ts).
|
|
127
|
+
// Above bootstrap for the same reason as __daemon-run: this internal periodic
|
|
128
|
+
// child must not itself fire a self-update check or fork a detached sync, and its
|
|
129
|
+
// stdout carries a clean JSON summary the daemon logs.
|
|
130
|
+
if (process.argv[2] === '__harness-update-run') {
|
|
131
|
+
const { runHarnessUpdateChild } = await import('./lib/installations/update-runtime.js');
|
|
132
|
+
process.exit(await runHarnessUpdateChild());
|
|
133
|
+
}
|
|
101
134
|
if (process.argv[2] === '__daemon-run') {
|
|
102
135
|
const { runDaemon, log: daemonLog } = await import('./lib/daemon/daemon.js');
|
|
103
136
|
// RUSH-2418: the daemon is the one always-on process here, and it ran with no
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type AccountInfo } from './agents.js';
|
|
2
|
+
import type { AgentId, Meta } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Strict "is this home actually connected?" — a live CREDENTIAL in that exact
|
|
5
|
+
* version home, not just a metadata identity claim (PHNX-3940). A `.claude.json`
|
|
6
|
+
* carrying an `oauthAccount` block with no `.credentials.json`/`.oauth_token`
|
|
7
|
+
* beside it is stale/expired, and must read as `reconnect-needed`, never
|
|
8
|
+
* `connected`. Where agents-cli does not know the credential location
|
|
9
|
+
* (`knownLocation` false), it falls back to the metadata `signedIn` since there
|
|
10
|
+
* is nothing stricter to check.
|
|
11
|
+
*/
|
|
12
|
+
export declare function isLaunchableSignedIn(agent: AgentId, versionHome: string, info: Pick<AccountInfo, 'signedIn'>): boolean;
|
|
2
13
|
export interface NativeAccountCatalogEntry {
|
|
3
14
|
kind: 'native';
|
|
4
15
|
id: string;
|
|
@@ -16,3 +27,88 @@ export declare function groupNativeAccountRows(rows: Array<{
|
|
|
16
27
|
}>): NativeAccountCatalogEntry[];
|
|
17
28
|
/** Discover signed-in harness-native identities without copying their auth files. */
|
|
18
29
|
export declare function discoverNativeAccounts(): Promise<NativeAccountCatalogEntry[]>;
|
|
30
|
+
/** One installed home carrying an identity — the secondary "release/home" facts. */
|
|
31
|
+
export interface AccountHome {
|
|
32
|
+
/** The installation label (version-dir basename). */
|
|
33
|
+
label: string;
|
|
34
|
+
/** The vendor release the home currently carries, when the record is present. */
|
|
35
|
+
releaseVersion: string | null;
|
|
36
|
+
/** Whether this exact home currently has a live signed-in credential. */
|
|
37
|
+
signedIn: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A live credential state, derived from the homes rather than the registry:
|
|
41
|
+
* - `connected` — at least one home for this identity is signed in.
|
|
42
|
+
* - `reconnect-needed` — the account is registered (or was created by connect)
|
|
43
|
+
* but no home currently holds a live credential; the label alone is not a
|
|
44
|
+
* connection. `agents accounts connect <name>` re-authenticates it.
|
|
45
|
+
*/
|
|
46
|
+
export type AccountConnectionState = 'connected' | 'reconnect-needed';
|
|
47
|
+
/** Account-first row for a native (harness-owned OAuth) identity. */
|
|
48
|
+
export interface NativeAccountCatalogRow {
|
|
49
|
+
kind: 'native';
|
|
50
|
+
agent: AgentId;
|
|
51
|
+
identityKey: string;
|
|
52
|
+
/** Registered account name/label, or null for an unnamed discovered login. */
|
|
53
|
+
name: string | null;
|
|
54
|
+
/** Registered stable account id, or null when unnamed. */
|
|
55
|
+
id: string | null;
|
|
56
|
+
email: string | null;
|
|
57
|
+
display: string;
|
|
58
|
+
/**
|
|
59
|
+
* The LOCAL installation home for this identity on THIS box: the recorded
|
|
60
|
+
* connect home when installed here, else the local home carrying the login.
|
|
61
|
+
* Null when no local home exists (the identity may be connected on another
|
|
62
|
+
* box). Per-host by construction — never a label another box minted.
|
|
63
|
+
*/
|
|
64
|
+
home: string | null;
|
|
65
|
+
/** Every installed home carrying this identity (secondary diagnostics). */
|
|
66
|
+
installations: AccountHome[];
|
|
67
|
+
/** Whether the harness's configured default account points at this identity. */
|
|
68
|
+
isDefault: boolean;
|
|
69
|
+
state: AccountConnectionState;
|
|
70
|
+
}
|
|
71
|
+
/** Account-first row for a durable provider (API-key / token) account bundle. */
|
|
72
|
+
export interface ProviderAccountCatalogRow {
|
|
73
|
+
kind: 'provider';
|
|
74
|
+
name: string;
|
|
75
|
+
id: string;
|
|
76
|
+
provider: string;
|
|
77
|
+
auth: string;
|
|
78
|
+
baseUrl?: string;
|
|
79
|
+
}
|
|
80
|
+
export type AccountCatalogRow = NativeAccountCatalogRow | ProviderAccountCatalogRow;
|
|
81
|
+
export interface AccountCatalog {
|
|
82
|
+
native: NativeAccountCatalogRow[];
|
|
83
|
+
provider: ProviderAccountCatalogRow[];
|
|
84
|
+
}
|
|
85
|
+
/** One installed home probed for its native identity — the raw input to the fold. */
|
|
86
|
+
export interface NativeHomeRow {
|
|
87
|
+
agent: AgentId;
|
|
88
|
+
label: string;
|
|
89
|
+
releaseVersion: string | null;
|
|
90
|
+
accountKey: string | null;
|
|
91
|
+
email: string | null;
|
|
92
|
+
signedIn: boolean;
|
|
93
|
+
}
|
|
94
|
+
/** Probe every installed home of every inspectable harness for its native identity. */
|
|
95
|
+
export declare function collectNativeHomeRows(): Promise<NativeHomeRow[]>;
|
|
96
|
+
/** The subset of Meta the pure builder reads — the registered native accounts + defaults. */
|
|
97
|
+
type CatalogMeta = Pick<Meta, 'accounts' | 'deviceAccounts'>;
|
|
98
|
+
/**
|
|
99
|
+
* Fold installed homes + the registry into account-first native rows (pure).
|
|
100
|
+
*
|
|
101
|
+
* Every home carrying an identity contributes an `AccountHome`; the identity is
|
|
102
|
+
* the group key. A registered account (name/id/connect home) is merged onto its
|
|
103
|
+
* identity, and — crucially — a registered account whose identity has NO live
|
|
104
|
+
* home still appears, as `reconnect-needed`, so a stale/expired login is never
|
|
105
|
+
* silently dropped from the list nor shown as connected.
|
|
106
|
+
*/
|
|
107
|
+
export declare function buildNativeCatalog(rows: NativeHomeRow[], meta: CatalogMeta, globalDefault?: (agent: AgentId) => string | null): NativeAccountCatalogRow[];
|
|
108
|
+
/**
|
|
109
|
+
* Wire the real collectors to the pure builder. This is the canonical
|
|
110
|
+
* account-first read model `view.ts` renders: native identities (account +
|
|
111
|
+
* connection first, release/home secondary) plus the durable provider bundles.
|
|
112
|
+
*/
|
|
113
|
+
export declare function loadAccountCatalog(): Promise<AccountCatalog>;
|
|
114
|
+
export {};
|
|
@@ -1,5 +1,21 @@
|
|
|
1
|
-
import { ALL_AGENT_IDS, getAccountInfo, supportsAccountInspection } from './agents.js';
|
|
2
|
-
import { getVersionHomePath, listInstalledVersions } from './installations/versions.js';
|
|
1
|
+
import { ALL_AGENT_IDS, credentialPresence, getAccountInfo, supportsAccountInspection } from './agents.js';
|
|
2
|
+
import { getGlobalDefault, getVersionHomePath, listInstalledVersions } from './installations/versions.js';
|
|
3
|
+
import { readInstallation } from './installations/store.js';
|
|
4
|
+
import { readMeta } from './state.js';
|
|
5
|
+
import { listNativeAccounts, readAccountRegistry } from './account-registry.js';
|
|
6
|
+
import { isLaunchableSignedIn as isCredentialLaunchable } from './accounting/rotate.js';
|
|
7
|
+
/**
|
|
8
|
+
* Strict "is this home actually connected?" — a live CREDENTIAL in that exact
|
|
9
|
+
* version home, not just a metadata identity claim (PHNX-3940). A `.claude.json`
|
|
10
|
+
* carrying an `oauthAccount` block with no `.credentials.json`/`.oauth_token`
|
|
11
|
+
* beside it is stale/expired, and must read as `reconnect-needed`, never
|
|
12
|
+
* `connected`. Where agents-cli does not know the credential location
|
|
13
|
+
* (`knownLocation` false), it falls back to the metadata `signedIn` since there
|
|
14
|
+
* is nothing stricter to check.
|
|
15
|
+
*/
|
|
16
|
+
export function isLaunchableSignedIn(agent, versionHome, info) {
|
|
17
|
+
return isCredentialLaunchable(info.signedIn, credentialPresence(agent, versionHome));
|
|
18
|
+
}
|
|
3
19
|
export function groupNativeAccountRows(rows) {
|
|
4
20
|
const grouped = new Map();
|
|
5
21
|
for (const row of rows) {
|
|
@@ -27,12 +43,124 @@ export function groupNativeAccountRows(rows) {
|
|
|
27
43
|
}
|
|
28
44
|
/** Discover signed-in harness-native identities without copying their auth files. */
|
|
29
45
|
export async function discoverNativeAccounts() {
|
|
46
|
+
const rows = await collectNativeHomeRows();
|
|
47
|
+
return groupNativeAccountRows(rows.map(r => ({ agent: r.agent, version: r.label, accountKey: r.accountKey, email: r.email, signedIn: r.signedIn })));
|
|
48
|
+
}
|
|
49
|
+
/** Probe every installed home of every inspectable harness for its native identity. */
|
|
50
|
+
export async function collectNativeHomeRows() {
|
|
30
51
|
const rows = [];
|
|
31
52
|
for (const agent of ALL_AGENT_IDS.filter(supportsAccountInspection)) {
|
|
32
|
-
for (const
|
|
33
|
-
const
|
|
34
|
-
|
|
53
|
+
for (const label of listInstalledVersions(agent)) {
|
|
54
|
+
const home = getVersionHomePath(agent, label);
|
|
55
|
+
const info = await getAccountInfo(agent, home);
|
|
56
|
+
rows.push({
|
|
57
|
+
agent,
|
|
58
|
+
label,
|
|
59
|
+
releaseVersion: readInstallation(agent, label)?.releaseVersion ?? null,
|
|
60
|
+
accountKey: info.accountKey,
|
|
61
|
+
email: info.email,
|
|
62
|
+
// Strict: a live credential in THIS home, not a bare metadata identity.
|
|
63
|
+
signedIn: isLaunchableSignedIn(agent, home, info),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return rows;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Fold installed homes + the registry into account-first native rows (pure).
|
|
71
|
+
*
|
|
72
|
+
* Every home carrying an identity contributes an `AccountHome`; the identity is
|
|
73
|
+
* the group key. A registered account (name/id/connect home) is merged onto its
|
|
74
|
+
* identity, and — crucially — a registered account whose identity has NO live
|
|
75
|
+
* home still appears, as `reconnect-needed`, so a stale/expired login is never
|
|
76
|
+
* silently dropped from the list nor shown as connected.
|
|
77
|
+
*/
|
|
78
|
+
export function buildNativeCatalog(rows, meta, globalDefault = getGlobalDefault) {
|
|
79
|
+
const registered = listNativeAccounts(meta);
|
|
80
|
+
const defaults = meta.accounts?.defaults ?? {};
|
|
81
|
+
const groups = new Map();
|
|
82
|
+
const keyOf = (agent, identity) => `${agent}:${identity}`;
|
|
83
|
+
// Every installed home that carries a resolvable identity seeds a group.
|
|
84
|
+
for (const row of rows) {
|
|
85
|
+
const identity = row.accountKey ?? row.email?.toLowerCase();
|
|
86
|
+
if (!identity)
|
|
87
|
+
continue;
|
|
88
|
+
const key = keyOf(row.agent, identity);
|
|
89
|
+
const group = groups.get(key) ?? { agent: row.agent, identityKey: identity, email: null, homes: [] };
|
|
90
|
+
group.email ??= row.email;
|
|
91
|
+
group.homes.push({ label: row.label, releaseVersion: row.releaseVersion, signedIn: row.signedIn });
|
|
92
|
+
groups.set(key, group);
|
|
93
|
+
}
|
|
94
|
+
// A registered account whose identity has no discovered home still belongs in
|
|
95
|
+
// the catalog — it just has nothing live to connect through yet. Its
|
|
96
|
+
// `identityKey` is the same value a home row groups on (a raw accountKey, or
|
|
97
|
+
// an already-lowercased email), so an exact-key check finds the existing group.
|
|
98
|
+
for (const account of registered) {
|
|
99
|
+
const key = keyOf(account.agent, account.identityKey);
|
|
100
|
+
if (!groups.has(key)) {
|
|
101
|
+
groups.set(key, { agent: account.agent, identityKey: account.identityKey, email: account.identityLabel ?? null, homes: [] });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const out = [];
|
|
105
|
+
for (const group of groups.values()) {
|
|
106
|
+
const account = registered.find(a => a.agent === group.agent && a.identityKey === group.identityKey);
|
|
107
|
+
const email = group.email ?? account?.identityLabel ?? null;
|
|
108
|
+
const homes = [...group.homes].sort((a, b) => a.label.localeCompare(b.label));
|
|
109
|
+
const signedIn = homes.some(h => h.signedIn);
|
|
110
|
+
// The configured per-harness account default is AUTHORITATIVE: when present,
|
|
111
|
+
// only the matching native account is the default — an explicit default that
|
|
112
|
+
// names a provider (or a stale name) never invents a native default. Only
|
|
113
|
+
// when NO account default is configured does the global-default installation
|
|
114
|
+
// home decide (diagnostic fallback).
|
|
115
|
+
const defaultRef = defaults[group.agent];
|
|
116
|
+
let isDefault;
|
|
117
|
+
if (defaultRef !== undefined) {
|
|
118
|
+
isDefault = !!account && (account.name === defaultRef || account.id === defaultRef);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
const gd = globalDefault(group.agent);
|
|
122
|
+
isDefault = !!gd && homes.some(h => h.label === gd);
|
|
35
123
|
}
|
|
124
|
+
// The home is a PER-HOST fact: this box's recorded connect home when it is
|
|
125
|
+
// actually installed here, else the local home carrying the identity. A home
|
|
126
|
+
// label recorded on another box is never assumed to exist locally.
|
|
127
|
+
const recordedHome = account ? (meta.deviceAccounts?.homes?.[account.id] ?? null) : null;
|
|
128
|
+
const home = (recordedHome && homes.some(h => h.label === recordedHome))
|
|
129
|
+
? recordedHome
|
|
130
|
+
: (homes.find(h => h.signedIn)?.label ?? homes[0]?.label ?? null);
|
|
131
|
+
out.push({
|
|
132
|
+
kind: 'native',
|
|
133
|
+
agent: group.agent,
|
|
134
|
+
identityKey: group.identityKey,
|
|
135
|
+
name: account?.name ?? null,
|
|
136
|
+
id: account?.id ?? null,
|
|
137
|
+
email,
|
|
138
|
+
display: email ?? account?.name ?? group.identityKey,
|
|
139
|
+
home,
|
|
140
|
+
installations: homes,
|
|
141
|
+
isDefault,
|
|
142
|
+
state: signedIn ? 'connected' : 'reconnect-needed',
|
|
143
|
+
});
|
|
36
144
|
}
|
|
37
|
-
|
|
145
|
+
// Named accounts first, then default, then a stable display order.
|
|
146
|
+
return out.sort((a, b) => a.agent.localeCompare(b.agent)
|
|
147
|
+
|| Number(!!b.name) - Number(!!a.name)
|
|
148
|
+
|| Number(b.isDefault) - Number(a.isDefault)
|
|
149
|
+
|| a.display.localeCompare(b.display));
|
|
150
|
+
}
|
|
151
|
+
function toProviderRow(account) {
|
|
152
|
+
return { kind: 'provider', name: account.name, id: account.id, provider: account.provider, auth: account.auth, baseUrl: account.baseUrl };
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Wire the real collectors to the pure builder. This is the canonical
|
|
156
|
+
* account-first read model `view.ts` renders: native identities (account +
|
|
157
|
+
* connection first, release/home secondary) plus the durable provider bundles.
|
|
158
|
+
*/
|
|
159
|
+
export async function loadAccountCatalog() {
|
|
160
|
+
const meta = readMeta();
|
|
161
|
+
const native = buildNativeCatalog(await collectNativeHomeRows(), meta);
|
|
162
|
+
const provider = Object.values(readAccountRegistry().accounts)
|
|
163
|
+
.map(toProviderRow)
|
|
164
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
165
|
+
return { native, provider };
|
|
38
166
|
}
|
|
@@ -55,9 +55,52 @@ export declare function listNativeAccounts(meta: Pick<Meta, 'accounts' | 'device
|
|
|
55
55
|
* able to omit it.
|
|
56
56
|
*/
|
|
57
57
|
export declare function findUnifiedAccount(nameOrId: string, meta: Pick<Meta, 'accounts' | 'deviceAccounts'>, doc?: AccountRegistryDocument, preferAgent?: AgentId): UnifiedAccount | null;
|
|
58
|
+
/**
|
|
59
|
+
* Validate a native account NAME (charset + per-harness uniqueness) WITHOUT a
|
|
60
|
+
* known identity — the pre-flight `agents accounts connect` runs before it
|
|
61
|
+
* installs a home and drives a login, so a bad/colliding name fails before any
|
|
62
|
+
* side effect instead of orphaning a freshly-minted home (PHNX-3940).
|
|
63
|
+
*/
|
|
64
|
+
export declare function assertNativeAccountNameAvailable(name: string, agent: AgentId): void;
|
|
58
65
|
export declare function addNativeAccount(name: string, agent: AgentId, identityKey: string, identityLabel: string | undefined, scope: 'version' | 'device'): NativeAccount;
|
|
59
66
|
/** Create or replace the version-independent label for one native identity. */
|
|
60
67
|
export declare function labelNativeAccount(agent: AgentId, identityKey: string, identityLabel: string | undefined, label: string | undefined, scope: 'version' | 'device'): NativeAccount;
|
|
68
|
+
/**
|
|
69
|
+
* Record THIS box's connect home for an account (PHNX-3940). Device-scoped: the
|
|
70
|
+
* home a box minted for an account is not assumed to exist on any other box, so
|
|
71
|
+
* it lives in the device doc keyed by the stable account id, never on the
|
|
72
|
+
* fleet-synced central identity row. Idempotent. A reconnect reads it back via
|
|
73
|
+
* {@link nativeAccountHome} to reuse the exact home even when the credential has
|
|
74
|
+
* expired and local signed-in discovery can no longer find it.
|
|
75
|
+
*/
|
|
76
|
+
export declare function setNativeAccountHome(accountId: string, installationLabel: string): void;
|
|
77
|
+
/** This box's recorded connect home for an account, or null. */
|
|
78
|
+
export declare function nativeAccountHome(accountId: string, meta: Pick<Meta, 'deviceAccounts'>): string | null;
|
|
79
|
+
/**
|
|
80
|
+
* Every installation label THIS box has recorded as SOME account's connect home
|
|
81
|
+
* (PHNX-3940). These are identity-bearing and MUST NEVER be re-minted for a new
|
|
82
|
+
* account — the safe-allocation invariant that stops a new connect from
|
|
83
|
+
* overwriting another account's login.
|
|
84
|
+
*/
|
|
85
|
+
export declare function ownedConnectHomeLabels(meta: Pick<Meta, 'deviceAccounts'>): Set<string>;
|
|
86
|
+
/** This box's in-flight connect slot for `(agent, name)`, or null. */
|
|
87
|
+
export declare function pendingConnectSlot(agent: AgentId, name: string, meta: Pick<Meta, 'deviceAccounts'>): string | null;
|
|
88
|
+
/** Record an in-flight connect slot for `(agent, name)` (device-scoped). */
|
|
89
|
+
export declare function setPendingConnectSlot(agent: AgentId, name: string, slot: string): void;
|
|
90
|
+
/** Clear the in-flight connect slot for `(agent, name)` once the account lands. */
|
|
91
|
+
export declare function clearPendingConnectSlot(agent: AgentId, name: string): void;
|
|
92
|
+
/**
|
|
93
|
+
* Set the per-harness default account by NAME only when none is configured
|
|
94
|
+
* (PHNX-3940). Never overrides an existing choice — a first connect selecting a
|
|
95
|
+
* default is a convenience, not a takeover. Returns whether it set the default.
|
|
96
|
+
*
|
|
97
|
+
* The check-then-write is performed INSIDE the `updateMeta` callback so two
|
|
98
|
+
* concurrent callers (concurrent Promises or back-to-back calls on an async
|
|
99
|
+
* boundary) cannot both observe "no default" and then both set themselves. Only
|
|
100
|
+
* the first write wins; the second callback sees the first's write and returns
|
|
101
|
+
* the current state unchanged.
|
|
102
|
+
*/
|
|
103
|
+
export declare function setDefaultAccountIfAbsent(agent: AgentId, name: string): boolean;
|
|
61
104
|
export declare function bindAccount(nameOrId: string, target: string, preferAgent?: AgentId): UnifiedAccount;
|
|
62
105
|
export declare function unbindAccount(nameOrId: string, target: string, preferAgent?: AgentId): void;
|
|
63
106
|
/** Every target bound to `accountId`: this box's device-doc bindings merged over
|