@phnx-labs/agents-cli 1.22.84 → 1.22.86
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 +96 -0
- package/README.md +80 -53
- package/dist/bootstrap.d.ts +7 -6
- package/dist/bootstrap.js +20 -27
- package/dist/cli/command-registry.js +5 -1
- package/dist/commands/accounts.d.ts +6 -13
- package/dist/commands/accounts.js +330 -143
- package/dist/commands/alias.js +7 -3
- package/dist/commands/apply.js +1 -1
- package/dist/commands/auth-mint.d.ts +7 -3
- package/dist/commands/auth-mint.js +21 -9
- package/dist/commands/auth.js +2 -3
- package/dist/commands/browser.js +10 -10
- package/dist/commands/daemon.d.ts +7 -3
- package/dist/commands/daemon.js +40 -25
- package/dist/commands/doctor.js +28 -2
- package/dist/commands/exec.js +87 -39
- package/dist/commands/fleet-capture.js +21 -2
- package/dist/commands/harness-wizard.js +2 -2
- package/dist/commands/lease.js +7 -12
- package/dist/commands/profiles.d.ts +2 -2
- package/dist/commands/profiles.js +20 -13
- package/dist/commands/repo.js +3 -3
- package/dist/commands/run-account-picker.d.ts +3 -3
- package/dist/commands/run-account-picker.js +29 -30
- package/dist/commands/secrets-passthrough.d.ts +20 -0
- package/dist/commands/secrets-passthrough.js +43 -0
- package/dist/commands/setup-accounts.d.ts +3 -2
- package/dist/commands/setup-accounts.js +4 -4
- package/dist/commands/setup-secrets.d.ts +16 -21
- package/dist/commands/setup-secrets.js +43 -224
- package/dist/commands/ssh.js +2 -2
- package/dist/commands/sync.js +1 -1
- package/dist/commands/update.d.ts +10 -0
- package/dist/commands/update.js +43 -40
- package/dist/commands/versions.d.ts +11 -11
- package/dist/commands/versions.js +131 -83
- package/dist/commands/view.d.ts +1 -9
- package/dist/commands/view.js +41 -55
- package/dist/commands/webhook.js +5 -5
- package/dist/commands/workflows.js +2 -1
- package/dist/index.d.ts +12 -12
- package/dist/index.js +13 -42
- package/dist/lib/account-capabilities.d.ts +1 -1
- package/dist/lib/account-capabilities.js +1 -1
- package/dist/lib/account-catalog.d.ts +121 -6
- package/dist/lib/account-catalog.js +421 -7
- package/dist/lib/account-registry.d.ts +8 -30
- package/dist/lib/account-registry.js +36 -79
- package/dist/lib/account-schema.d.ts +1 -1
- package/dist/lib/account-schema.js +1 -1
- package/dist/lib/accounting/account-pool-collect.js +16 -4
- package/dist/lib/accounting/rotate.d.ts +20 -7
- package/dist/lib/accounting/rotate.js +89 -13
- package/dist/lib/accounting/usage.js +11 -16
- package/dist/lib/accounts/add.d.ts +138 -0
- package/dist/lib/accounts/add.js +651 -0
- package/dist/lib/accounts/migrate.d.ts +117 -0
- package/dist/lib/accounts/migrate.js +536 -0
- package/dist/lib/accounts/slots.d.ts +13 -0
- package/dist/lib/accounts/slots.js +100 -0
- package/dist/lib/agent-spec/agents.d.ts +16 -0
- package/dist/lib/agent-spec/agents.js +38 -4
- package/dist/lib/app-bundle-install.js +5 -4
- package/dist/lib/auth-health.d.ts +3 -0
- package/dist/lib/auth-health.js +11 -0
- package/dist/lib/auth-mint.d.ts +52 -16
- package/dist/lib/auth-mint.js +117 -39
- package/dist/lib/browser/chrome.d.ts +1 -1
- package/dist/lib/browser/chrome.js +5 -5
- package/dist/lib/byok-usage.js +3 -3
- package/dist/lib/claude-account-token.d.ts +47 -3
- package/dist/lib/claude-account-token.js +172 -50
- package/dist/lib/cloud/antigravity.js +4 -4
- package/dist/lib/cloud/cursor.js +4 -4
- package/dist/lib/crabbox/cli.d.ts +1 -1
- package/dist/lib/crabbox/cli.js +6 -6
- package/dist/lib/crabbox/runtimes.d.ts +3 -3
- package/dist/lib/crabbox/runtimes.js +5 -5
- package/dist/lib/daemon/account-state-daemon-service.d.ts +37 -1
- package/dist/lib/daemon/account-state-daemon-service.js +199 -3
- package/dist/lib/daemon/auth-sync-service.js +32 -1
- package/dist/lib/daemon/daemon.d.ts +6 -16
- package/dist/lib/daemon/daemon.js +52 -97
- package/dist/lib/daemon/daemon.test-fixture.d.ts +7 -9
- package/dist/lib/daemon/daemon.test-fixture.js +22 -40
- package/dist/lib/daemon/harness-update-service.d.ts +1 -1
- package/dist/lib/daemon/harness-update-service.js +1 -1
- package/dist/lib/daemon/runner.d.ts +15 -3
- package/dist/lib/daemon/runner.js +49 -24
- package/dist/lib/daemon-health.d.ts +1 -2
- package/dist/lib/daemon-health.js +7 -6
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +1 -11
- package/dist/lib/daemon-webhooks.d.ts +8 -7
- package/dist/lib/daemon-webhooks.js +10 -9
- package/dist/lib/device-config.js +1 -1
- package/dist/lib/devices/doctor-findings.d.ts +1 -1
- package/dist/lib/devices/harness-inventory.d.ts +39 -0
- package/dist/lib/devices/harness-inventory.js +126 -4
- package/dist/lib/doctor-diff.js +2 -1
- package/dist/lib/exec-account-home.d.ts +38 -0
- package/dist/lib/exec-account-home.js +164 -0
- package/dist/lib/exec.d.ts +30 -0
- package/dist/lib/exec.js +101 -31
- package/dist/lib/fleet/apply.d.ts +2 -2
- package/dist/lib/fleet/apply.js +4 -4
- package/dist/lib/fleet/auth-sync.js +4 -3
- package/dist/lib/fleet-shared-repo-sync.js +6 -9
- package/dist/lib/harness/adapter.d.ts +7 -7
- package/dist/lib/harness/adapter.js +10 -6
- package/dist/lib/harness/adapters/grok.js +8 -3
- package/dist/lib/harness/adapters/muse.js +1 -1
- package/dist/lib/harness/adapters/opencode.js +12 -3
- package/dist/lib/harness-auth-capabilities.d.ts +61 -0
- package/dist/lib/harness-auth-capabilities.js +52 -0
- package/dist/lib/helper-versions.d.ts +6 -4
- package/dist/lib/helper-versions.js +5 -4
- package/dist/lib/hosts/credential-transport.d.ts +10 -0
- package/dist/lib/hosts/credential-transport.js +37 -0
- package/dist/lib/hosts/dispatch.d.ts +7 -0
- package/dist/lib/hosts/dispatch.js +13 -6
- package/dist/lib/identity/client.d.ts +3 -3
- package/dist/lib/identity/client.js +3 -3
- package/dist/lib/installations/index.d.ts +1 -1
- package/dist/lib/installations/index.js +1 -1
- package/dist/lib/installations/migrate.js +9 -0
- package/dist/lib/installations/resolve.js +1 -1
- package/dist/lib/installations/shims.d.ts +21 -4
- package/dist/lib/installations/shims.js +86 -5
- package/dist/lib/installations/store.d.ts +43 -0
- package/dist/lib/installations/store.js +75 -7
- package/dist/lib/installations/versions.js +1 -1
- package/dist/lib/menubar/install-menubar.d.ts +3 -3
- package/dist/lib/menubar/install-menubar.js +3 -3
- package/dist/lib/native-accounts.d.ts +35 -0
- package/dist/lib/native-accounts.js +41 -0
- package/dist/lib/net-close.d.ts +11 -0
- package/dist/lib/net-close.js +25 -0
- package/dist/lib/openclaw-keychain.js +27 -1
- package/dist/lib/profiles.js +22 -9
- package/dist/lib/project-resources.js +1 -1
- package/dist/lib/reserved-stores.d.ts +53 -0
- package/dist/lib/reserved-stores.js +120 -0
- package/dist/lib/secrets-client.d.ts +199 -0
- package/dist/lib/secrets-client.js +718 -0
- package/dist/lib/secrets-policy.d.ts +235 -0
- package/dist/lib/secrets-policy.js +505 -0
- package/dist/lib/secrets-types.d.ts +188 -0
- package/dist/lib/secrets-types.js +24 -0
- package/dist/lib/self-heal/checks/shims.js +5 -3
- package/dist/lib/service-manifest.js +3 -4
- package/dist/lib/session/db.d.ts +18 -0
- package/dist/lib/session/db.js +47 -14
- package/dist/lib/session/sync/config.js +2 -2
- package/dist/lib/sha256-asset.d.ts +11 -16
- package/dist/lib/sha256-asset.js +11 -16
- package/dist/lib/share/config.js +14 -5
- package/dist/lib/signin-badge.d.ts +26 -0
- package/dist/lib/signin-badge.js +42 -0
- package/dist/lib/staleness/detectors/workflows.js +3 -105
- package/dist/lib/staleness/writers/workflows.js +2 -1
- package/dist/lib/state.d.ts +7 -4
- package/dist/lib/state.js +18 -7
- package/dist/lib/sync-umbrella.js +2 -2
- package/dist/lib/teams/agents.js +1 -1
- package/dist/lib/types.d.ts +74 -33
- package/dist/lib/view-types.d.ts +6 -3
- package/dist/lib/workflows-registry.d.ts +71 -0
- package/dist/lib/workflows-registry.js +280 -0
- package/dist/lib/workflows.d.ts +21 -21
- package/dist/lib/workflows.js +12 -327
- package/package.json +2 -3
- package/scripts/postinstall.js +35 -30
- package/dist/commands/secrets-import.d.ts +0 -18
- package/dist/commands/secrets-import.js +0 -74
- package/dist/commands/secrets-migrate.d.ts +0 -25
- package/dist/commands/secrets-migrate.js +0 -334
- package/dist/commands/secrets-rotate-passphrase.d.ts +0 -17
- package/dist/commands/secrets-rotate-passphrase.js +0 -96
- package/dist/commands/secrets-sync.d.ts +0 -11
- package/dist/commands/secrets-sync.js +0 -153
- package/dist/commands/secrets-vault.d.ts +0 -10
- package/dist/commands/secrets-vault.js +0 -130
- package/dist/commands/secrets.d.ts +0 -212
- package/dist/commands/secrets.js +0 -3030
- package/dist/lib/accounts/connect.d.ts +0 -176
- package/dist/lib/accounts/connect.js +0 -453
- package/dist/lib/daemon/keychain-reap-service.d.ts +0 -17
- package/dist/lib/daemon/keychain-reap-service.js +0 -32
- package/dist/lib/daemon/secrets-broker-service.d.ts +0 -21
- package/dist/lib/daemon/secrets-broker-service.js +0 -51
- package/dist/lib/secrets/agent.d.ts +0 -358
- package/dist/lib/secrets/agent.js +0 -1291
- package/dist/lib/secrets/audit.d.ts +0 -46
- package/dist/lib/secrets/audit.js +0 -101
- package/dist/lib/secrets/bundles.d.ts +0 -290
- package/dist/lib/secrets/bundles.js +0 -1547
- package/dist/lib/secrets/download-keychain.d.ts +0 -47
- package/dist/lib/secrets/download-keychain.js +0 -70
- package/dist/lib/secrets/drivers/rush.d.ts +0 -14
- package/dist/lib/secrets/drivers/rush.js +0 -90
- package/dist/lib/secrets/fallback.d.ts +0 -48
- package/dist/lib/secrets/fallback.js +0 -48
- package/dist/lib/secrets/filestore.d.ts +0 -222
- package/dist/lib/secrets/filestore.js +0 -1099
- package/dist/lib/secrets/headless.d.ts +0 -43
- package/dist/lib/secrets/headless.js +0 -56
- package/dist/lib/secrets/icloud-import.d.ts +0 -79
- package/dist/lib/secrets/icloud-import.js +0 -206
- package/dist/lib/secrets/index.d.ts +0 -451
- package/dist/lib/secrets/index.js +0 -1568
- package/dist/lib/secrets/install-helper.d.ts +0 -72
- package/dist/lib/secrets/install-helper.js +0 -245
- package/dist/lib/secrets/lease.d.ts +0 -25
- package/dist/lib/secrets/lease.js +0 -44
- package/dist/lib/secrets/linux.d.ts +0 -77
- package/dist/lib/secrets/linux.js +0 -393
- package/dist/lib/secrets/list-filter.d.ts +0 -109
- package/dist/lib/secrets/list-filter.js +0 -261
- package/dist/lib/secrets/mcp.d.ts +0 -93
- package/dist/lib/secrets/mcp.js +0 -211
- package/dist/lib/secrets/profiles.d.ts +0 -10
- package/dist/lib/secrets/profiles.js +0 -13
- package/dist/lib/secrets/push.d.ts +0 -133
- package/dist/lib/secrets/push.js +0 -273
- package/dist/lib/secrets/rc-hygiene.d.ts +0 -63
- package/dist/lib/secrets/rc-hygiene.js +0 -143
- package/dist/lib/secrets/read-backoff.d.ts +0 -27
- package/dist/lib/secrets/read-backoff.js +0 -64
- package/dist/lib/secrets/reaper.d.ts +0 -90
- package/dist/lib/secrets/reaper.js +0 -243
- package/dist/lib/secrets/remote.d.ts +0 -152
- package/dist/lib/secrets/remote.js +0 -344
- package/dist/lib/secrets/reserved-sync.d.ts +0 -66
- package/dist/lib/secrets/reserved-sync.js +0 -147
- package/dist/lib/secrets/scope.d.ts +0 -26
- package/dist/lib/secrets/scope.js +0 -29
- package/dist/lib/secrets/session-store.d.ts +0 -107
- package/dist/lib/secrets/session-store.js +0 -342
- package/dist/lib/secrets/sync-backend.d.ts +0 -48
- package/dist/lib/secrets/sync-backend.js +0 -13
- package/dist/lib/secrets/sync-commands.d.ts +0 -21
- package/dist/lib/secrets/sync-commands.js +0 -21
- package/dist/lib/secrets/sync.d.ts +0 -48
- package/dist/lib/secrets/sync.js +0 -237
- package/dist/lib/secrets/unlock-hints.d.ts +0 -27
- package/dist/lib/secrets/unlock-hints.js +0 -36
- package/dist/lib/secrets/usage-db.d.ts +0 -46
- package/dist/lib/secrets/usage-db.js +0 -96
- package/dist/lib/secrets/vault-age-helper.d.ts +0 -1
- package/dist/lib/secrets/vault-age-helper.js +0 -34
- package/dist/lib/secrets/vault.d.ts +0 -49
- package/dist/lib/secrets/vault.js +0 -399
- package/dist/lib/secrets/windows.d.ts +0 -81
- package/dist/lib/secrets/windows.js +0 -556
- package/scripts/install-helper.js +0 -97
- /package/dist/lib/{secrets/sync-passphrase.d.ts → sync-passphrase.d.ts} +0 -0
- /package/dist/lib/{secrets/sync-passphrase.js → sync-passphrase.js} +0 -0
|
@@ -3,7 +3,23 @@ import { getGlobalDefault, getVersionHomePath, listInstalledVersions } from './i
|
|
|
3
3
|
import { readInstallation } from './installations/store.js';
|
|
4
4
|
import { readMeta } from './state.js';
|
|
5
5
|
import { listNativeAccounts, readAccountRegistry } from './account-registry.js';
|
|
6
|
+
import { providerAuthenticatesHarness } from './account-provider-registry.js';
|
|
7
|
+
import { readSlots } from './accounts/slots.js';
|
|
8
|
+
import { harnessWorkerIsPerDevice } from './harness-auth-capabilities.js';
|
|
9
|
+
import { hasKeychainTokenSync, isSecretsTransportError } from './secrets-client.js';
|
|
6
10
|
import { isLaunchableSignedIn as isCredentialLaunchable } from './accounting/rotate.js';
|
|
11
|
+
import { authCacheKey, readAuthHealthCache } from './auth-health.js';
|
|
12
|
+
import { readFleetSharedDeviceStates } from './fleet-shared-state.js';
|
|
13
|
+
import { machineId } from './machine-id.js';
|
|
14
|
+
import { isHeadedDeviceRole, selfConfiguredDeviceRole } from './device-config.js';
|
|
15
|
+
import { applyUsageHonesty, collectLocalHarnessInventory, } from './devices/harness-inventory.js';
|
|
16
|
+
import { fixFor, } from './signin-badge.js';
|
|
17
|
+
import { renderBar } from './accounting/usage.js';
|
|
18
|
+
import { padToWidth, stringWidth } from './text/width.js';
|
|
19
|
+
export { applyUsageHonesty };
|
|
20
|
+
import chalk from 'chalk';
|
|
21
|
+
/** Compact usage gauge in the USAGE column — same length as the pre-T3 inline bar. */
|
|
22
|
+
const LISTING_USAGE_BAR_LEN = 5;
|
|
7
23
|
/**
|
|
8
24
|
* Strict "is this home actually connected?" — a live CREDENTIAL in that exact
|
|
9
25
|
* version home, not just a metadata identity claim (PHNX-3940). A `.claude.json`
|
|
@@ -46,6 +62,28 @@ export async function discoverNativeAccounts() {
|
|
|
46
62
|
const rows = await collectNativeHomeRows();
|
|
47
63
|
return groupNativeAccountRows(rows.map(r => ({ agent: r.agent, version: r.label, accountKey: r.accountKey, email: r.email, signedIn: r.signedIn })));
|
|
48
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* The provider (durable-credential) accounts read through `secrets`, tolerant of
|
|
67
|
+
* an unreachable standalone. Native OAuth logins never touch `secrets`, so a
|
|
68
|
+
* missing/broken standalone must NOT take down the whole account view — the
|
|
69
|
+
* provider section is simply reported unavailable. Only a TRANSPORT-level
|
|
70
|
+
* failure (missing binary, timeout, non-JSON, spawn error) degrades; a genuine
|
|
71
|
+
* data error still throws so it is never silently swallowed. DIST-1 holds: there
|
|
72
|
+
* is no fallback to an embedded engine, the standalone just could not answer.
|
|
73
|
+
*/
|
|
74
|
+
function readProviderRowsTolerant(meta) {
|
|
75
|
+
try {
|
|
76
|
+
const rows = Object.values(readAccountRegistry().accounts)
|
|
77
|
+
.map((account) => toProviderRow(account, meta))
|
|
78
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
79
|
+
return { rows, error: null };
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
if (isSecretsTransportError(err))
|
|
83
|
+
return { rows: [], error: err };
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
49
87
|
/** Probe every installed home of every inspectable harness for its native identity. */
|
|
50
88
|
export async function collectNativeHomeRows() {
|
|
51
89
|
const rows = [];
|
|
@@ -140,6 +178,19 @@ export function buildNativeCatalog(rows, meta, globalDefault = getGlobalDefault)
|
|
|
140
178
|
installations: homes,
|
|
141
179
|
isDefault,
|
|
142
180
|
state: signedIn ? 'connected' : 'reconnect-needed',
|
|
181
|
+
identityLabel: email ?? account?.name ?? group.identityKey,
|
|
182
|
+
provisioning: provisioningFor(group.agent),
|
|
183
|
+
verdict: signedIn ? 'unverified' : 'missing',
|
|
184
|
+
checkedAt: null,
|
|
185
|
+
devices: [],
|
|
186
|
+
usage: null,
|
|
187
|
+
fix: fixFor({
|
|
188
|
+
agent: group.agent,
|
|
189
|
+
verdict: signedIn ? 'unverified' : 'missing',
|
|
190
|
+
name: account?.name,
|
|
191
|
+
version: home,
|
|
192
|
+
provisioning: provisioningFor(group.agent),
|
|
193
|
+
}),
|
|
143
194
|
});
|
|
144
195
|
}
|
|
145
196
|
// Named accounts first, then default, then a stable display order.
|
|
@@ -148,19 +199,382 @@ export function buildNativeCatalog(rows, meta, globalDefault = getGlobalDefault)
|
|
|
148
199
|
|| Number(b.isDefault) - Number(a.isDefault)
|
|
149
200
|
|| a.display.localeCompare(b.display));
|
|
150
201
|
}
|
|
151
|
-
function
|
|
152
|
-
return
|
|
202
|
+
function provisioningFor(agent) {
|
|
203
|
+
return harnessWorkerIsPerDevice(agent) ? 'per-device' : 'portable';
|
|
204
|
+
}
|
|
205
|
+
export function toProviderRow(account, meta) {
|
|
206
|
+
const harnesses = ALL_AGENT_IDS.filter((agent) => providerAuthenticatesHarness(account.provider, account.auth, agent));
|
|
207
|
+
const defaults = meta.accounts?.defaults ?? {};
|
|
208
|
+
const defaultFor = ALL_AGENT_IDS.filter((agent) => {
|
|
209
|
+
const ref = defaults[agent];
|
|
210
|
+
return ref === account.name || ref === account.id;
|
|
211
|
+
});
|
|
212
|
+
const secretPresent = hasKeychainTokenSync(account.secretRef);
|
|
213
|
+
return {
|
|
214
|
+
kind: 'provider',
|
|
215
|
+
name: account.name,
|
|
216
|
+
id: account.id,
|
|
217
|
+
provider: account.provider,
|
|
218
|
+
auth: account.auth,
|
|
219
|
+
...(account.baseUrl ? { baseUrl: account.baseUrl } : {}),
|
|
220
|
+
harnesses,
|
|
221
|
+
defaultFor,
|
|
222
|
+
identityLabel: account.provider,
|
|
223
|
+
verdict: secretPresent ? 'ready' : 'missing',
|
|
224
|
+
fix: secretPresent ? null : `agents accounts set-key ${account.name}`,
|
|
225
|
+
};
|
|
153
226
|
}
|
|
154
227
|
/**
|
|
155
228
|
* Wire the real collectors to the pure builder. This is the canonical
|
|
156
229
|
* account-first read model `view.ts` renders: native identities (account +
|
|
157
|
-
* connection first, release/home secondary) plus
|
|
230
|
+
* connection first, release/home secondary) plus durable provider credentials.
|
|
158
231
|
*/
|
|
159
232
|
export async function loadAccountCatalog() {
|
|
160
233
|
const meta = readMeta();
|
|
161
234
|
const native = buildNativeCatalog(await collectNativeHomeRows(), meta);
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
235
|
+
const host = machineId();
|
|
236
|
+
const auth = readAuthHealthCache();
|
|
237
|
+
const inventory = await collectLocalHarnessInventory();
|
|
238
|
+
const inventoryByHome = new Map(inventory.map((row) => [`${row.agent}:${row.version}`, row]));
|
|
239
|
+
const shared = readSharedAccountVerdicts();
|
|
240
|
+
const headed = isHeadedDeviceRole(selfConfiguredDeviceRole());
|
|
241
|
+
const localSlots = readSlots(meta);
|
|
242
|
+
for (const row of native) {
|
|
243
|
+
const registered = row.id ? listNativeAccounts(meta).find((account) => account.id === row.id) : undefined;
|
|
244
|
+
row.provisioning = registered?.provisioning ?? provisioningFor(row.agent);
|
|
245
|
+
const localHome = row.installations.find((home) => home.label === row.home)
|
|
246
|
+
?? row.installations.find((home) => home.signedIn)
|
|
247
|
+
?? row.installations[0];
|
|
248
|
+
const cached = localHome ? auth[authCacheKey(host, row.agent, localHome.label)] : undefined;
|
|
249
|
+
const slot = row.id ? localSlots[row.id] : undefined;
|
|
250
|
+
const observation = resolveLocalAccountObservation(slot, cached, localHome?.signedIn === true);
|
|
251
|
+
const local = {
|
|
252
|
+
device: host,
|
|
253
|
+
authMode: observation.authMode
|
|
254
|
+
?? (row.provisioning === 'per-device' ? 'per-device' : (headed ? 'native' : 'durable')),
|
|
255
|
+
verdict: observation.verdict,
|
|
256
|
+
...(observation.checkedAt ? { checkedAt: observation.checkedAt } : {}),
|
|
257
|
+
};
|
|
258
|
+
// Registered accounts join fleet state on their stable id ONLY — two
|
|
259
|
+
// accounts may share one email label, so a label join would cross-attribute
|
|
260
|
+
// one account's revoked verdict to the other. The label index exists solely
|
|
261
|
+
// for unnamed legacy logins, which have no registry id to key on.
|
|
262
|
+
const fromFleet = row.id
|
|
263
|
+
? (shared.get(`${row.agent}:${row.id}`) ?? [])
|
|
264
|
+
: (shared.get(`label:${row.agent}:${row.identityLabel}`) ?? []);
|
|
265
|
+
row.devices = mergeDeviceVerdicts(fromFleet, local);
|
|
266
|
+
row.verdict = aggregateAccountVerdict(row.provisioning, row.devices);
|
|
267
|
+
row.checkedAt = newestCheckedAt(row.devices);
|
|
268
|
+
const honest = applyUsageHonesty(row.verdict, localHome ? (inventoryByHome.get(`${row.agent}:${localHome.label}`)?.quota ?? null) : null);
|
|
269
|
+
row.verdict = honest.verdict;
|
|
270
|
+
row.usage = honest.usage;
|
|
271
|
+
row.fix = fixFor({
|
|
272
|
+
agent: row.agent,
|
|
273
|
+
verdict: row.verdict,
|
|
274
|
+
name: row.name,
|
|
275
|
+
version: localHome?.label,
|
|
276
|
+
provisioning: row.provisioning,
|
|
277
|
+
hasSlot: slot != null,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
const { rows: provider, error } = readProviderRowsTolerant(meta);
|
|
281
|
+
return error
|
|
282
|
+
? { native, provider, secretsUnavailable: { code: error.code, message: error.message } }
|
|
283
|
+
: { native, provider };
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* The one clear line a read-only surface prints when the provider section could
|
|
287
|
+
* not be read (PHNX-3989) — so a missing/broken standalone is stated plainly
|
|
288
|
+
* while the native rows and the rest of the output still render. Null when the
|
|
289
|
+
* catalog is complete. Printed to stderr by the command so it never corrupts
|
|
290
|
+
* `--json` stdout.
|
|
291
|
+
*/
|
|
292
|
+
export function secretsUnavailableNote(catalog) {
|
|
293
|
+
const err = catalog.secretsUnavailable;
|
|
294
|
+
if (!err)
|
|
295
|
+
return null;
|
|
296
|
+
return `secrets unavailable (${err.code}) — provider accounts not shown; native logins and the rest are current.`;
|
|
297
|
+
}
|
|
298
|
+
function providerListEntry(row, harness) {
|
|
299
|
+
return {
|
|
300
|
+
kind: 'provider',
|
|
301
|
+
id: row.id,
|
|
302
|
+
harness,
|
|
303
|
+
name: row.name,
|
|
304
|
+
identityLabel: row.identityLabel,
|
|
305
|
+
isDefault: harness ? row.defaultFor.includes(harness) : false,
|
|
306
|
+
provisioning: 'portable',
|
|
307
|
+
verdict: row.verdict,
|
|
308
|
+
checkedAt: null,
|
|
309
|
+
devices: [],
|
|
310
|
+
usage: null,
|
|
311
|
+
fix: row.fix,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Unfiltered JSON emits one entry per harness the credential authenticates
|
|
316
|
+
* (same `id`, `kind: 'provider'`, each with its own `harness`), plus one
|
|
317
|
+
* `harness: null` entry for an orphan. A harness filter emits only that
|
|
318
|
+
* harness's entry. The v2 field set is otherwise unchanged.
|
|
319
|
+
*/
|
|
320
|
+
function providerJsonEntries(row, harness) {
|
|
321
|
+
if (harness) {
|
|
322
|
+
return row.harnesses.includes(harness) ? [providerListEntry(row, harness)] : [];
|
|
323
|
+
}
|
|
324
|
+
if (row.harnesses.length === 0)
|
|
325
|
+
return [providerListEntry(row, null)];
|
|
326
|
+
return row.harnesses.map((agent) => providerListEntry(row, agent));
|
|
327
|
+
}
|
|
328
|
+
export function accountListJson(native, providers = [], harness) {
|
|
329
|
+
return {
|
|
330
|
+
version: 2,
|
|
331
|
+
accounts: [
|
|
332
|
+
...native.map((row) => ({
|
|
333
|
+
kind: 'native',
|
|
334
|
+
id: row.id ?? row.identityKey,
|
|
335
|
+
harness: row.agent,
|
|
336
|
+
name: row.name,
|
|
337
|
+
identityLabel: row.identityLabel,
|
|
338
|
+
isDefault: row.isDefault,
|
|
339
|
+
provisioning: row.provisioning,
|
|
340
|
+
verdict: row.verdict,
|
|
341
|
+
checkedAt: row.checkedAt,
|
|
342
|
+
devices: row.devices.map(({ device, authMode, verdict }) => ({ device, authMode, verdict })),
|
|
343
|
+
usage: row.usage,
|
|
344
|
+
fix: row.fix,
|
|
345
|
+
})),
|
|
346
|
+
...providers.flatMap((row) => providerJsonEntries(row, harness)),
|
|
347
|
+
],
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
function verdictText(verdict) {
|
|
351
|
+
if (verdict === 'rate_limited')
|
|
352
|
+
return 'LIMITED';
|
|
353
|
+
return verdict.toUpperCase();
|
|
354
|
+
}
|
|
355
|
+
function whereText(row, localDevice) {
|
|
356
|
+
const names = [...new Set(row.devices.map((device) => device.device))];
|
|
357
|
+
// Peers on an older release do not publish slot verdicts, so the only
|
|
358
|
+
// observation is this box — say so rather than a coverage count of +1.
|
|
359
|
+
if (names.length === 1 && names[0] === localDevice)
|
|
360
|
+
return 'this box';
|
|
361
|
+
const live = row.devices.filter((device) => device.verdict === 'live').length;
|
|
362
|
+
if (live > 0)
|
|
363
|
+
return `+${live}`;
|
|
364
|
+
if (row.provisioning === 'per-device') {
|
|
365
|
+
const present = row.devices
|
|
366
|
+
.filter((device) => device.verdict !== 'missing')
|
|
367
|
+
.map((device) => device.device);
|
|
368
|
+
return present.join(', ') || 'this box';
|
|
369
|
+
}
|
|
370
|
+
return '—';
|
|
371
|
+
}
|
|
372
|
+
function usageText(row) {
|
|
373
|
+
if (row.usage?.status === 'rate_limited' && (row.usage.usedPercent === null || row.usage.usedPercent === undefined)) {
|
|
374
|
+
return 'limited';
|
|
375
|
+
}
|
|
376
|
+
if (row.usage?.status === 'out_of_credits')
|
|
377
|
+
return 'no credits';
|
|
378
|
+
if (row.usage?.usedPercent === null || row.usage?.usedPercent === undefined)
|
|
379
|
+
return '';
|
|
380
|
+
const percent = `${row.usage.usedPercent}%${row.usage.stale ? '*' : ''}`;
|
|
381
|
+
return `${renderBar(row.usage.usedPercent, LISTING_USAGE_BAR_LEN)} ${percent}`;
|
|
382
|
+
}
|
|
383
|
+
function nativeLine(row, localDevice) {
|
|
384
|
+
return {
|
|
385
|
+
name: row.name ?? 'unnamed',
|
|
386
|
+
identityLabel: row.identityLabel,
|
|
387
|
+
verdict: row.verdict,
|
|
388
|
+
where: whereText(row, localDevice),
|
|
389
|
+
fix: row.fix,
|
|
390
|
+
usage: usageText(row),
|
|
391
|
+
isDefault: row.isDefault,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
function providerLine(row, harness) {
|
|
395
|
+
return {
|
|
396
|
+
name: row.name,
|
|
397
|
+
identityLabel: row.identityLabel,
|
|
398
|
+
verdict: row.verdict,
|
|
399
|
+
where: '—',
|
|
400
|
+
fix: row.fix,
|
|
401
|
+
usage: '',
|
|
402
|
+
isDefault: harness ? row.defaultFor.includes(harness) : false,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
function formatListingLine(line, nameW, identityW, stateW, whereW, usageW) {
|
|
406
|
+
const marker = line.isDefault ? '*' : ' ';
|
|
407
|
+
const fix = line.fix ? `fix: ${line.fix}` : '';
|
|
408
|
+
return (` ${chalk.green(marker)} ${chalk.cyan(line.name.padEnd(nameW))} `
|
|
409
|
+
+ `${line.identityLabel.padEnd(identityW)} `
|
|
410
|
+
+ `${verdictText(line.verdict).padEnd(stateW)} `
|
|
411
|
+
+ `${line.where.padEnd(whereW)} `
|
|
412
|
+
+ `${padToWidth(line.usage, usageW)} `
|
|
413
|
+
+ `${fix ? chalk.gray(fix) : ''}`).trimEnd();
|
|
414
|
+
}
|
|
415
|
+
function pushGroup(out, title, lines, widths, harnessHeadings) {
|
|
416
|
+
if (lines.length === 0)
|
|
417
|
+
return;
|
|
418
|
+
if (harnessHeadings)
|
|
419
|
+
out.push(chalk.bold(title));
|
|
420
|
+
out.push(chalk.gray(` ${'ACCOUNT'.padEnd(widths.nameW + 2)}${'IDENTITY'.padEnd(widths.identityW + 2)}${'STATE'.padEnd(widths.stateW + 2)}${'WHERE'.padEnd(widths.whereW + 2)}${'USAGE'.padEnd(widths.usageW + 2)}FIX`));
|
|
421
|
+
for (const line of lines) {
|
|
422
|
+
out.push(formatListingLine(line, widths.nameW, widths.identityW, widths.stateW, widths.whereW, widths.usageW));
|
|
423
|
+
}
|
|
424
|
+
out.push('');
|
|
425
|
+
}
|
|
426
|
+
/** Shared text renderer used by both `accounts list` and account-first `view`. */
|
|
427
|
+
export function renderAccountRows(rows, opts) {
|
|
428
|
+
const heading = opts.heading !== false;
|
|
429
|
+
const footer = opts.footer !== false;
|
|
430
|
+
const harnessHeadings = opts.harnessHeadings !== false;
|
|
431
|
+
const providers = opts.providers ?? [];
|
|
432
|
+
const harnessFilter = opts.harness;
|
|
433
|
+
const localDevice = opts.localDevice;
|
|
434
|
+
const out = [];
|
|
435
|
+
if (heading)
|
|
436
|
+
out.push(`${chalk.bold('Accounts')} ${chalk.gray('run: agents run <h>#<name>')}`, '');
|
|
437
|
+
const visibleProviders = harnessFilter
|
|
438
|
+
? providers.filter((row) => row.harnesses.includes(harnessFilter))
|
|
439
|
+
: providers;
|
|
440
|
+
const visibleNative = harnessFilter
|
|
441
|
+
? rows.filter((row) => row.agent === harnessFilter)
|
|
442
|
+
: rows;
|
|
443
|
+
if (visibleNative.length === 0 && visibleProviders.length === 0) {
|
|
444
|
+
out.push(chalk.gray('No accounts found. Add one: agents accounts add <harness> [name]'));
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
const harnesses = new Set();
|
|
448
|
+
for (const row of visibleNative)
|
|
449
|
+
harnesses.add(row.agent);
|
|
450
|
+
for (const row of visibleProviders) {
|
|
451
|
+
for (const agent of row.harnesses) {
|
|
452
|
+
if (!harnessFilter || agent === harnessFilter)
|
|
453
|
+
harnesses.add(agent);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
const grouped = new Map();
|
|
457
|
+
for (const harness of [...harnesses].sort((a, b) => a.localeCompare(b))) {
|
|
458
|
+
const lines = [
|
|
459
|
+
...visibleNative.filter((row) => row.agent === harness).map((row) => nativeLine(row, localDevice)),
|
|
460
|
+
...visibleProviders.filter((row) => row.harnesses.includes(harness)).map((row) => providerLine(row, harness)),
|
|
461
|
+
];
|
|
462
|
+
if (lines.length === 0)
|
|
463
|
+
continue;
|
|
464
|
+
grouped.set(harness, lines);
|
|
465
|
+
}
|
|
466
|
+
const orphans = harnessFilter
|
|
467
|
+
? []
|
|
468
|
+
: visibleProviders.filter((row) => row.harnesses.length === 0).map((row) => providerLine(row));
|
|
469
|
+
const allLines = [...grouped.values()].flat().concat(orphans);
|
|
470
|
+
const widths = {
|
|
471
|
+
nameW: Math.max(7, ...allLines.map((line) => line.name.length)),
|
|
472
|
+
identityW: Math.max(8, ...allLines.map((line) => line.identityLabel.length)),
|
|
473
|
+
stateW: Math.max(5, ...allLines.map((line) => verdictText(line.verdict).length)),
|
|
474
|
+
whereW: Math.max(5, ...allLines.map((line) => line.where.length)),
|
|
475
|
+
usageW: Math.max(5, ...allLines.map((line) => stringWidth(line.usage))),
|
|
476
|
+
};
|
|
477
|
+
for (const [harness, lines] of grouped) {
|
|
478
|
+
pushGroup(out, harness, lines, widths, harnessHeadings);
|
|
479
|
+
}
|
|
480
|
+
if (orphans.length > 0)
|
|
481
|
+
pushGroup(out, 'Other accounts', orphans, widths, true);
|
|
482
|
+
}
|
|
483
|
+
if (footer) {
|
|
484
|
+
// "Need you" is an actionable repair, not an unread usage probe. Unverified
|
|
485
|
+
// (a worker whose token lacks the usage scope) has no fix and must not
|
|
486
|
+
// inflate the count.
|
|
487
|
+
const count = visibleNative.filter((row) => !!row.fix).length
|
|
488
|
+
+ visibleProviders.filter((row) => !!row.fix).length;
|
|
489
|
+
out.push(chalk.gray(`${count} accounts need you · add: agents accounts add <harness>`));
|
|
490
|
+
}
|
|
491
|
+
return out.join('\n').trimEnd();
|
|
492
|
+
}
|
|
493
|
+
export function readSharedAccountVerdicts(userAgentsDir) {
|
|
494
|
+
const out = new Map();
|
|
495
|
+
for (const state of readFleetSharedDeviceStates(userAgentsDir).states) {
|
|
496
|
+
for (const row of state.accounts?.rows ?? []) {
|
|
497
|
+
if (!ALL_AGENT_IDS.includes(row.harness))
|
|
498
|
+
continue;
|
|
499
|
+
if (!['native', 'durable', 'per-device'].includes(row.authMode))
|
|
500
|
+
continue;
|
|
501
|
+
if (!['live', 'expired', 'revoked', 'rate_limited', 'unverified', 'missing'].includes(row.verdict))
|
|
502
|
+
continue;
|
|
503
|
+
const key = `${row.harness}:${row.accountId}`;
|
|
504
|
+
const values = out.get(key) ?? [];
|
|
505
|
+
values.push({
|
|
506
|
+
device: state.device,
|
|
507
|
+
authMode: row.authMode,
|
|
508
|
+
verdict: row.verdict,
|
|
509
|
+
...(row.checkedAt ? { checkedAt: row.checkedAt } : {}),
|
|
510
|
+
});
|
|
511
|
+
out.set(key, values);
|
|
512
|
+
if (row.identityLabel)
|
|
513
|
+
out.set(`label:${row.harness}:${row.identityLabel}`, values);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return out;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Devices whose daemon-state file exists but carries no account verdicts —
|
|
520
|
+
* typically an older release that has not started publishing the T3 envelope.
|
|
521
|
+
* The fleet matrix lists them in a coverage note rather than as blank columns.
|
|
522
|
+
*/
|
|
523
|
+
export function listDevicesWithoutAccountVerdicts(userAgentsDir) {
|
|
524
|
+
const out = [];
|
|
525
|
+
for (const state of readFleetSharedDeviceStates(userAgentsDir).states) {
|
|
526
|
+
if (!state.accounts?.rows?.length)
|
|
527
|
+
out.push(state.device);
|
|
528
|
+
}
|
|
529
|
+
return out.sort((a, b) => a.localeCompare(b));
|
|
530
|
+
}
|
|
531
|
+
function normalizeAuthVerdict(verdict, signedIn) {
|
|
532
|
+
if (verdict === 'unconfigured')
|
|
533
|
+
return 'missing';
|
|
534
|
+
if (verdict === 'error')
|
|
535
|
+
return signedIn ? 'unverified' : 'missing';
|
|
536
|
+
return verdict ?? (signedIn ? 'unverified' : 'missing');
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Merge the two writers of the same local truth — the T1 slot store and the
|
|
540
|
+
* daemon's auth-health cache — by NEWEST observation. A naive `??` chain lets
|
|
541
|
+
* an older slot `live` permanently mask a newer daemon `revoked`, so a revoked
|
|
542
|
+
* account would keep rendering LIVE on its own device. Pure.
|
|
543
|
+
*/
|
|
544
|
+
export function resolveLocalAccountObservation(slot, cached, signedIn) {
|
|
545
|
+
const slotAt = slot?.checkedAt ? Date.parse(slot.checkedAt) : null;
|
|
546
|
+
const cachedAt = cached?.checkedAt ?? null;
|
|
547
|
+
const preferSlot = !!slot && (!cached || (slotAt !== null && (cachedAt === null || slotAt >= cachedAt)));
|
|
548
|
+
return {
|
|
549
|
+
verdict: normalizeAuthVerdict((preferSlot ? slot : cached)?.verdict, signedIn),
|
|
550
|
+
...(preferSlot && slot?.authMode ? { authMode: slot.authMode } : {}),
|
|
551
|
+
...(preferSlot && slot?.checkedAt
|
|
552
|
+
? { checkedAt: slot.checkedAt }
|
|
553
|
+
: !preferSlot && cached ? { checkedAt: new Date(cached.checkedAt).toISOString() } : {}),
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
function mergeDeviceVerdicts(fleet, local) {
|
|
557
|
+
const byDevice = new Map(fleet.map((row) => [row.device, row]));
|
|
558
|
+
byDevice.set(local.device, local);
|
|
559
|
+
return [...byDevice.values()].sort((a, b) => a.device.localeCompare(b.device));
|
|
560
|
+
}
|
|
561
|
+
function aggregateAccountVerdict(provisioning, devices) {
|
|
562
|
+
const verdicts = devices.map((row) => row.verdict);
|
|
563
|
+
if (verdicts.includes('revoked'))
|
|
564
|
+
return 'revoked';
|
|
565
|
+
if (verdicts.includes('expired'))
|
|
566
|
+
return 'expired';
|
|
567
|
+
if (verdicts.includes('rate_limited'))
|
|
568
|
+
return 'rate_limited';
|
|
569
|
+
if (verdicts.includes('live'))
|
|
570
|
+
return 'live';
|
|
571
|
+
if (verdicts.includes('unverified'))
|
|
572
|
+
return 'unverified';
|
|
573
|
+
return provisioning === 'per-device' ? 'per-device' : 'missing';
|
|
574
|
+
}
|
|
575
|
+
function newestCheckedAt(devices) {
|
|
576
|
+
return devices
|
|
577
|
+
.map((row) => row.checkedAt)
|
|
578
|
+
.filter((value) => !!value)
|
|
579
|
+
.sort((a, b) => Date.parse(b) - Date.parse(a))[0] ?? null;
|
|
166
580
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AgentId, type Meta } from './types.js';
|
|
1
|
+
import { type AgentId, type Meta, type NativeAccountRecord } from './types.js';
|
|
2
2
|
import { type AccountAuthKind } from './account-provider-registry.js';
|
|
3
3
|
export interface CredentialAccount {
|
|
4
4
|
id: string;
|
|
@@ -19,15 +19,11 @@ export interface ResolvedCredentialAccount {
|
|
|
19
19
|
auth: AccountAuthKind;
|
|
20
20
|
env: Record<string, string>;
|
|
21
21
|
}
|
|
22
|
-
export interface NativeAccount {
|
|
23
|
-
id: string;
|
|
24
|
-
name: string;
|
|
22
|
+
export interface NativeAccount extends NativeAccountRecord {
|
|
25
23
|
kind: 'native';
|
|
26
|
-
agent: AgentId;
|
|
27
|
-
identityKey: string;
|
|
28
|
-
identityLabel?: string;
|
|
29
|
-
scope: 'version' | 'device';
|
|
30
24
|
}
|
|
25
|
+
export { recordSlot, readSlots } from './accounts/slots.js';
|
|
26
|
+
export { registeredNativeAccountForEmail, parseNativeIdentityKey } from './native-accounts.js';
|
|
31
27
|
export type UnifiedAccount = (CredentialAccount & {
|
|
32
28
|
kind: 'provider';
|
|
33
29
|
}) | NativeAccount;
|
|
@@ -86,7 +82,7 @@ export declare function parseAccountSelector(input: string): {
|
|
|
86
82
|
export declare function assertUnambiguousNativeAccount(meta: Pick<Meta, 'accounts' | 'deviceAccounts'>, name: string, agent?: AgentId): void;
|
|
87
83
|
/**
|
|
88
84
|
* Validate a native account NAME (charset + per-harness uniqueness) WITHOUT a
|
|
89
|
-
* known identity — the pre-flight `agents accounts
|
|
85
|
+
* known identity — the pre-flight `agents accounts add` runs before it
|
|
90
86
|
* installs a home and drives a login, so a bad/colliding name fails before any
|
|
91
87
|
* side effect instead of orphaning a freshly-minted home (PHNX-3940).
|
|
92
88
|
*/
|
|
@@ -95,29 +91,11 @@ export declare function addNativeAccount(name: string, agent: AgentId, identityK
|
|
|
95
91
|
/** Create or replace the version-independent label for one native identity. */
|
|
96
92
|
export declare function labelNativeAccount(agent: AgentId, identityKey: string, identityLabel: string | undefined, label: string | undefined, scope: 'version' | 'device'): NativeAccount;
|
|
97
93
|
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
* fleet-synced central identity row. Idempotent. A reconnect reads it back via
|
|
102
|
-
* {@link nativeAccountHome} to reuse the exact home even when the credential has
|
|
103
|
-
* expired and local signed-in discovery can no longer find it.
|
|
94
|
+
* This box's recorded home LABEL for an account, or null.
|
|
95
|
+
* Reads the leftover device-scoped `homes` map so legacy `acct-*` installation
|
|
96
|
+
* labels still resolve (T5/T7); spawn-time HOME is {@link readSlots}[id].slotDir.
|
|
104
97
|
*/
|
|
105
|
-
export declare function setNativeAccountHome(accountId: string, installationLabel: string): void;
|
|
106
|
-
/** This box's recorded connect home for an account, or null. */
|
|
107
98
|
export declare function nativeAccountHome(accountId: string, meta: Pick<Meta, 'deviceAccounts'>): string | null;
|
|
108
|
-
/**
|
|
109
|
-
* Every installation label THIS box has recorded as SOME account's connect home
|
|
110
|
-
* (PHNX-3940). These are identity-bearing and MUST NEVER be re-minted for a new
|
|
111
|
-
* account — the safe-allocation invariant that stops a new connect from
|
|
112
|
-
* overwriting another account's login.
|
|
113
|
-
*/
|
|
114
|
-
export declare function ownedConnectHomeLabels(meta: Pick<Meta, 'deviceAccounts'>): Set<string>;
|
|
115
|
-
/** This box's in-flight connect slot for `(agent, name)`, or null. */
|
|
116
|
-
export declare function pendingConnectSlot(agent: AgentId, name: string, meta: Pick<Meta, 'deviceAccounts'>): string | null;
|
|
117
|
-
/** Record an in-flight connect slot for `(agent, name)` (device-scoped). */
|
|
118
|
-
export declare function setPendingConnectSlot(agent: AgentId, name: string, slot: string): void;
|
|
119
|
-
/** Clear the in-flight connect slot for `(agent, name)` once the account lands. */
|
|
120
|
-
export declare function clearPendingConnectSlot(agent: AgentId, name: string): void;
|
|
121
99
|
/**
|
|
122
100
|
* Set the per-harness default account by NAME only when none is configured
|
|
123
101
|
* (PHNX-3940). Never overrides an existing choice — a first connect selecting a
|