@phnx-labs/agents-cli 1.22.117 → 1.22.118
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 +235 -0
- package/dist/bootstrap.js +4 -11
- package/dist/commands/browser.d.ts +52 -70
- package/dist/commands/browser.js +192 -3562
- package/dist/commands/daemon.d.ts +39 -2
- package/dist/commands/daemon.js +72 -73
- package/dist/commands/events.js +1 -1
- package/dist/commands/exec.js +53 -1
- package/dist/commands/repo.js +13 -49
- package/dist/commands/route.js +5 -5
- package/dist/commands/routines.js +39 -12
- package/dist/commands/sessions-render.d.ts +1 -1
- package/dist/commands/sessions-render.js +0 -1
- package/dist/commands/sessions.test-fixture.d.ts +0 -1
- package/dist/commands/sessions.test-fixture.js +0 -37
- package/dist/commands/setup-browser.d.ts +17 -12
- package/dist/commands/setup-browser.js +52 -87
- package/dist/commands/setup-preferences.d.ts +0 -17
- package/dist/commands/setup-preferences.js +18 -49
- package/dist/commands/setup.js +9 -9
- package/dist/commands/ssh.js +10 -1
- package/dist/commands/sync.js +6 -44
- package/dist/commands/teams.js +0 -1
- package/dist/commands/update.d.ts +19 -0
- package/dist/commands/update.js +34 -6
- package/dist/commands/view.d.ts +12 -1
- package/dist/commands/view.js +47 -2
- package/dist/index.js +107 -26
- package/dist/lib/account-capabilities.js +0 -2
- package/dist/lib/account-catalog.d.ts +38 -0
- package/dist/lib/account-catalog.js +110 -26
- package/dist/lib/account-provider-registry.js +1 -1
- package/dist/lib/account-registry.d.ts +1 -1
- package/dist/lib/account-registry.js +36 -2
- package/dist/lib/accounting/rotate.js +22 -8
- package/dist/lib/accounting/usage-ingest.d.ts +6 -1
- package/dist/lib/accounting/usage-ingest.js +115 -45
- package/dist/lib/accounting/usage-sync.d.ts +134 -31
- package/dist/lib/accounting/usage-sync.js +227 -66
- package/dist/lib/accounting/usage.d.ts +16 -3
- package/dist/lib/accounting/usage.js +26 -8
- package/dist/lib/accounts/slots.d.ts +8 -0
- package/dist/lib/accounts/slots.js +17 -0
- package/dist/lib/add-dir.js +0 -1
- package/dist/lib/agent-cli-commands.js +0 -1
- package/dist/lib/agent-spec/agents.d.ts +6 -7
- package/dist/lib/agent-spec/agents.js +6 -60
- package/dist/lib/auth-health.d.ts +69 -1
- package/dist/lib/auth-health.js +133 -4
- package/dist/lib/browser/context.d.ts +82 -0
- package/dist/lib/browser/context.js +88 -0
- package/dist/lib/browser/paths.d.ts +25 -0
- package/dist/lib/browser/paths.js +51 -0
- package/dist/lib/browser/record.d.ts +38 -0
- package/dist/lib/browser/record.js +57 -0
- package/dist/lib/browser/sessions-list.js +1 -2
- package/dist/lib/browser-client.d.ts +158 -0
- package/dist/lib/browser-client.js +234 -0
- package/dist/lib/channels/owner-sink.d.ts +1 -18
- package/dist/lib/channels/owner-sink.js +29 -82
- package/dist/lib/channels/providers/rush.d.ts +7 -7
- package/dist/lib/channels/providers/rush.js +127 -57
- package/dist/lib/claude-statusline.d.ts +1 -1
- package/dist/lib/claude-statusline.js +15 -3
- package/dist/lib/commands.js +1 -1
- package/dist/lib/crabbox/lease.js +1 -1
- package/dist/lib/crabbox/runtimes.js +1 -3
- package/dist/lib/daemon/account-state-daemon-service.js +1 -0
- package/dist/lib/daemon/auth-sync-service.d.ts +21 -18
- package/dist/lib/daemon/auth-sync-service.js +37 -43
- package/dist/lib/daemon/daemon.d.ts +25 -14
- package/dist/lib/daemon/daemon.js +92 -154
- package/dist/lib/daemon/runner.d.ts +2 -2
- package/dist/lib/daemon/runner.js +32 -9
- package/dist/lib/daemon/self-update-service.d.ts +35 -34
- package/dist/lib/daemon/self-update-service.js +40 -36
- package/dist/lib/daemon/service.d.ts +12 -8
- package/dist/lib/daemon/supervisor.d.ts +65 -54
- package/dist/lib/daemon/supervisor.js +136 -171
- package/dist/lib/daemon/testdata/process-view-start.js +5 -2
- package/dist/lib/daemon/usage-sync-service.d.ts +21 -30
- package/dist/lib/daemon/usage-sync-service.js +46 -78
- package/dist/lib/daemon-health.d.ts +22 -5
- package/dist/lib/daemon-health.js +47 -3
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +0 -10
- package/dist/lib/daemon-ticks.js +4 -2
- package/dist/lib/devices/doctor-findings.js +8 -11
- package/dist/lib/devices/harness-inventory.js +14 -5
- package/dist/lib/exec-account-home.js +5 -0
- package/dist/lib/exec.js +0 -11
- package/dist/lib/feed/events.js +0 -1
- package/dist/lib/feed/tool-activity.js +5 -2
- package/dist/lib/fleet-shared-state.d.ts +45 -0
- package/dist/lib/fleet-shared-state.js +78 -9
- package/dist/lib/fs-atomic.js +25 -0
- package/dist/lib/harness-auth-capabilities.js +0 -1
- package/dist/lib/helper-versions.js +1 -1
- package/dist/lib/hooks/cache.d.ts +7 -0
- package/dist/lib/hooks/cache.js +27 -2
- package/dist/lib/hooks/install.js +7 -1
- package/dist/lib/hosts/ready.d.ts +38 -8
- package/dist/lib/hosts/ready.js +62 -14
- package/dist/lib/installations/active-check.d.ts +27 -0
- package/dist/lib/installations/active-check.js +61 -0
- package/dist/lib/installations/migrate.js +6 -3
- package/dist/lib/installations/shims.js +9 -7
- package/dist/lib/installations/update.js +21 -15
- package/dist/lib/installations/versions.js +12 -1
- package/dist/lib/manifest.js +1 -1
- package/dist/lib/models.js +0 -2
- package/dist/lib/open-url.js +28 -54
- package/dist/lib/profiles.js +0 -2
- package/dist/lib/refresh.js +1 -1
- package/dist/lib/routine-readiness.d.ts +35 -0
- package/dist/lib/routine-readiness.js +74 -16
- package/dist/lib/rules/compile.js +6 -0
- package/dist/lib/sandbox.js +0 -2
- package/dist/lib/secrets-policy.d.ts +89 -11
- package/dist/lib/secrets-policy.js +175 -28
- package/dist/lib/session/db.d.ts +1 -1
- package/dist/lib/session/db.js +65 -18
- package/dist/lib/session/discover.js +1 -283
- package/dist/lib/session/mirror.d.ts +4 -3
- package/dist/lib/session/mirror.js +4 -3
- package/dist/lib/session/parse.d.ts +0 -2
- package/dist/lib/session/parse.js +1 -138
- package/dist/lib/session/throughput.d.ts +1 -3
- package/dist/lib/session/throughput.js +0 -20
- package/dist/lib/session/types.d.ts +2 -2
- package/dist/lib/session/types.js +2 -2
- package/dist/lib/sessions-client.d.ts +60 -1
- package/dist/lib/sessions-client.js +227 -4
- package/dist/lib/setup-tool-status.js +5 -10
- package/dist/lib/signin-badge.d.ts +3 -2
- package/dist/lib/signin-badge.js +6 -3
- package/dist/lib/smart-launch.js +4 -1
- package/dist/lib/staleness/detectors/commands.d.ts +1 -1
- package/dist/lib/staleness/detectors/hooks.d.ts +1 -1
- package/dist/lib/staleness/detectors/mcp.d.ts +1 -1
- package/dist/lib/staleness/detectors/permissions.d.ts +1 -1
- package/dist/lib/staleness/detectors/plugins.d.ts +1 -1
- package/dist/lib/staleness/detectors/rules.d.ts +1 -1
- package/dist/lib/staleness/detectors/skills.d.ts +1 -1
- package/dist/lib/staleness/detectors/subagents.d.ts +1 -1
- package/dist/lib/staleness/detectors/workflows.d.ts +1 -1
- package/dist/lib/staleness/writers/commands.d.ts +1 -1
- package/dist/lib/staleness/writers/hooks.d.ts +1 -1
- package/dist/lib/staleness/writers/mcp.d.ts +1 -1
- package/dist/lib/staleness/writers/permissions.d.ts +1 -1
- package/dist/lib/staleness/writers/plugins.d.ts +1 -1
- package/dist/lib/staleness/writers/rules.d.ts +1 -1
- package/dist/lib/staleness/writers/skills.d.ts +1 -1
- package/dist/lib/staleness/writers/sources.js +5 -0
- package/dist/lib/staleness/writers/subagents.d.ts +1 -1
- package/dist/lib/staleness/writers/workflows.d.ts +1 -1
- package/dist/lib/state.d.ts +6 -4
- package/dist/lib/state.js +7 -17
- package/dist/lib/teams/parsers.d.ts +1 -1
- package/dist/lib/teams/parsers.js +1 -136
- package/dist/lib/teams/placement-probe.js +50 -15
- package/dist/lib/teams/scheduler.d.ts +7 -0
- package/dist/lib/types.d.ts +10 -1
- package/dist/lib/types.js +1 -1
- package/dist/lib/view-types.d.ts +32 -0
- package/dist/lib/watchdog/read.js +0 -1
- package/dist/lib/watchdog/rotate.js +1 -2
- package/dist/lib/watchdog/runner.js +2 -2
- package/dist/lib/watchdog/watchdog.d.ts +1 -1
- package/dist/lib/watchdog/watchdogTail.js +0 -31
- package/package.json +1 -1
- package/scripts/postinstall.js +8 -8
- package/dist/browser.d.ts +0 -2
- package/dist/browser.js +0 -17
- package/dist/commands/browser-picker.d.ts +0 -18
- package/dist/commands/browser-picker.js +0 -97
- package/dist/lib/browser/arc-discovery.d.ts +0 -52
- package/dist/lib/browser/arc-discovery.js +0 -197
- package/dist/lib/browser/arc-dom.d.ts +0 -14
- package/dist/lib/browser/arc-dom.js +0 -121
- package/dist/lib/browser/caller-identity.d.ts +0 -40
- package/dist/lib/browser/caller-identity.js +0 -175
- package/dist/lib/browser/cdp.d.ts +0 -51
- package/dist/lib/browser/cdp.js +0 -254
- package/dist/lib/browser/chrome.d.ts +0 -141
- package/dist/lib/browser/chrome.js +0 -704
- package/dist/lib/browser/chromium-discovery.d.ts +0 -28
- package/dist/lib/browser/chromium-discovery.js +0 -96
- package/dist/lib/browser/devices.d.ts +0 -34
- package/dist/lib/browser/devices.js +0 -61
- package/dist/lib/browser/domain-skills.d.ts +0 -71
- package/dist/lib/browser/domain-skills.js +0 -195
- package/dist/lib/browser/drivers/arc.d.ts +0 -40
- package/dist/lib/browser/drivers/arc.js +0 -276
- package/dist/lib/browser/drivers/firefox.d.ts +0 -99
- package/dist/lib/browser/drivers/firefox.js +0 -377
- package/dist/lib/browser/drivers/local.d.ts +0 -55
- package/dist/lib/browser/drivers/local.js +0 -266
- package/dist/lib/browser/drivers/ssh.d.ts +0 -120
- package/dist/lib/browser/drivers/ssh.js +0 -467
- package/dist/lib/browser/editor.d.ts +0 -3
- package/dist/lib/browser/editor.js +0 -50
- package/dist/lib/browser/ffmpeg.d.ts +0 -12
- package/dist/lib/browser/ffmpeg.js +0 -184
- package/dist/lib/browser/firefox-discovery.d.ts +0 -69
- package/dist/lib/browser/firefox-discovery.js +0 -162
- package/dist/lib/browser/har.d.ts +0 -85
- package/dist/lib/browser/har.js +0 -77
- package/dist/lib/browser/hygiene.d.ts +0 -97
- package/dist/lib/browser/hygiene.js +0 -153
- package/dist/lib/browser/index.d.ts +0 -5
- package/dist/lib/browser/index.js +0 -5
- package/dist/lib/browser/input.d.ts +0 -7
- package/dist/lib/browser/input.js +0 -92
- package/dist/lib/browser/ipc.d.ts +0 -204
- package/dist/lib/browser/ipc.js +0 -1453
- package/dist/lib/browser/login-detection.d.ts +0 -87
- package/dist/lib/browser/login-detection.js +0 -274
- package/dist/lib/browser/profiles.d.ts +0 -422
- package/dist/lib/browser/profiles.js +0 -1157
- package/dist/lib/browser/refs.d.ts +0 -89
- package/dist/lib/browser/refs.js +0 -191
- package/dist/lib/browser/registry.d.ts +0 -70
- package/dist/lib/browser/registry.js +0 -252
- package/dist/lib/browser/remote-control.d.ts +0 -59
- package/dist/lib/browser/remote-control.js +0 -85
- package/dist/lib/browser/resolve-target.d.ts +0 -66
- package/dist/lib/browser/resolve-target.js +0 -264
- package/dist/lib/browser/runtime-state.d.ts +0 -291
- package/dist/lib/browser/runtime-state.js +0 -584
- package/dist/lib/browser/secret-ref.d.ts +0 -10
- package/dist/lib/browser/secret-ref.js +0 -14
- package/dist/lib/browser/service.d.ts +0 -812
- package/dist/lib/browser/service.js +0 -4693
- package/dist/lib/browser/stream.d.ts +0 -17
- package/dist/lib/browser/stream.js +0 -72
- package/dist/lib/browser/task-index.d.ts +0 -78
- package/dist/lib/browser/task-index.js +0 -199
- package/dist/lib/browser/types.d.ts +0 -647
- package/dist/lib/browser/types.js +0 -61
- package/dist/lib/browser/upload.d.ts +0 -29
- package/dist/lib/browser/upload.js +0 -298
- package/dist/lib/daemon/browser-ipc-service.d.ts +0 -20
- package/dist/lib/daemon/browser-ipc-service.js +0 -46
- package/dist/lib/daemon/browser-task-reap-service.d.ts +0 -14
- package/dist/lib/daemon/browser-task-reap-service.js +0 -26
- package/dist/lib/fleet-shared-repo-sync.d.ts +0 -55
- package/dist/lib/fleet-shared-repo-sync.js +0 -490
|
@@ -9,6 +9,7 @@ import { mergeClaudeUsageCacheWindows } from './accounting/usage.js';
|
|
|
9
9
|
import { loadReminders, pickReminderForSession } from './reminders.js';
|
|
10
10
|
import { readMeta } from './state.js';
|
|
11
11
|
import { machineId } from './machine-id.js';
|
|
12
|
+
import { recordRunAuthOutcome } from './auth-health.js';
|
|
12
13
|
export const CLAUDE_STATUSLINE_COMMAND = 'agents __claude-statusline';
|
|
13
14
|
const DELEGATE_FILE = path.join('.agents', 'claude-statusline-delegate');
|
|
14
15
|
// The private subcommand this feature runs. It is only ever invoked internally,
|
|
@@ -64,7 +65,7 @@ function windowFromNative(key, value) {
|
|
|
64
65
|
windowMinutes: key === 'session' ? 300 : 10_080,
|
|
65
66
|
};
|
|
66
67
|
}
|
|
67
|
-
export function ingestClaudeStatusLineUsage(payload, identity) {
|
|
68
|
+
export function ingestClaudeStatusLineUsage(payload, identity, versionHome) {
|
|
68
69
|
if (!identity?.usageKey || !payload.rate_limits)
|
|
69
70
|
return false;
|
|
70
71
|
const windows = [
|
|
@@ -80,8 +81,19 @@ export function ingestClaudeStatusLineUsage(payload, identity) {
|
|
|
80
81
|
windows,
|
|
81
82
|
freshness: { source: 'statusline', poller: machineId() },
|
|
82
83
|
});
|
|
84
|
+
// A status-line payload with rate_limits IS a real inference response — the
|
|
85
|
+
// token authenticated moments ago. Record that as the per-account auth FACT
|
|
86
|
+
// (`last used ok`), keyed to the account this box actually ran (PHNX-4116).
|
|
87
|
+
recordRunAuthOutcome({
|
|
88
|
+
agent: 'claude',
|
|
89
|
+
accountId: resolveNativeAccount(identity)?.id ?? null,
|
|
90
|
+
home: versionHome ?? null,
|
|
91
|
+
outcome: { ok: true },
|
|
92
|
+
});
|
|
93
|
+
// Keep this box's own state file current so the next placement probe or
|
|
94
|
+
// usage-sync fan-out carries this reading; the file is local, no transport.
|
|
83
95
|
void import('./accounting/usage-sync.js')
|
|
84
|
-
.then((mod) => mod.
|
|
96
|
+
.then((mod) => mod.publishUsageSnapshotToSharedStore())
|
|
85
97
|
.catch(() => { });
|
|
86
98
|
return true;
|
|
87
99
|
}
|
|
@@ -211,7 +223,7 @@ export async function runClaudeStatusLine() {
|
|
|
211
223
|
const versionHome = versionHomeFromEnv(process.env);
|
|
212
224
|
const identity = readClaudeIdentity(claudeHomeFromEnv(process.env));
|
|
213
225
|
if (versionHome)
|
|
214
|
-
ingestClaudeStatusLineUsage(payload, identity);
|
|
226
|
+
ingestClaudeStatusLineUsage(payload, identity, versionHome);
|
|
215
227
|
process.stdout.write(renderClaudeStatusLine(payload, undefined, versionHome ? renderDelegate(raw, versionHome) : '', resolveReminderPart(payload.session_id), formatAccountPart(identity, resolveNativeAccount(identity))));
|
|
216
228
|
return 0;
|
|
217
229
|
}
|
package/dist/lib/commands.js
CHANGED
|
@@ -571,7 +571,7 @@ function commandContentMatches(agentId, commandName, sourcePath) {
|
|
|
571
571
|
* Get the project-scoped commands directory for an agent.
|
|
572
572
|
* Claude: .claude/commands/
|
|
573
573
|
* Codex: .codex/prompts/
|
|
574
|
-
*
|
|
574
|
+
* Cursor: .cursor/commands/
|
|
575
575
|
*/
|
|
576
576
|
function getProjectCommandsDirs(agentId, cwd = process.cwd()) {
|
|
577
577
|
const agent = AGENTS[agentId];
|
|
@@ -145,7 +145,7 @@ export function buildBootstrapScript(opts) {
|
|
|
145
145
|
// Runs regardless of --keep-box (it's in the box body, not teardown), so a kept
|
|
146
146
|
// box still loses the token after the run — minimizing the credential window.
|
|
147
147
|
const shredPaths = credentialRuntimes.flatMap((id) => {
|
|
148
|
-
const paths = { claude: ['.claude.json', CLAUDE_TOKEN_REMOTE], codex: ['.codex/auth.json'],
|
|
148
|
+
const paths = { claude: ['.claude.json', CLAUDE_TOKEN_REMOTE], codex: ['.codex/auth.json'], grok: ['.grok/auth.json'] }[id];
|
|
149
149
|
return paths ?? [];
|
|
150
150
|
});
|
|
151
151
|
if (opts.dispatchProfile)
|
|
@@ -22,13 +22,12 @@ import { listInstalledVersions, getVersionHomePath } from '../installations/vers
|
|
|
22
22
|
export const LEASE_RUNTIMES = [
|
|
23
23
|
{ id: 'claude', label: 'Claude Code', localCandidates: ['.claude/.claude.json', '.claude.json'], remote: '.claude.json' },
|
|
24
24
|
{ id: 'codex', label: 'Codex CLI', localCandidates: ['.codex/auth.json'], remote: '.codex/auth.json' },
|
|
25
|
-
{ id: 'gemini', label: 'Gemini CLI', localCandidates: ['.gemini/google_accounts.json'], remote: '.gemini/google_accounts.json' },
|
|
26
25
|
{ id: 'grok', label: 'Grok CLI', localCandidates: ['.grok/auth.json'], remote: '.grok/auth.json' },
|
|
27
26
|
];
|
|
28
27
|
/**
|
|
29
28
|
* Every runtime whose login this module can serialize (`LEASE_RUNTIMES`) is a
|
|
30
29
|
* native, rotating OAuth / session credential (Claude OAuth token, codex/grok
|
|
31
|
-
* `auth.json
|
|
30
|
+
* `auth.json`). The fleet-auth contract forbids
|
|
32
31
|
* copying any of them between devices — including to an ephemeral leased box —
|
|
33
32
|
* because a shared refresh token rotates server-side on the next refresh and
|
|
34
33
|
* invalidates every other copy (`docs/specifications.md` SING-1b,
|
|
@@ -142,7 +141,6 @@ const PROFILE_AUTH_ENV_KEYS_BY_RUNTIME = {
|
|
|
142
141
|
'ANTHROPIC_FOUNDRY_API_KEY',
|
|
143
142
|
],
|
|
144
143
|
codex: ['OPENAI_API_KEY'],
|
|
145
|
-
gemini: ['GEMINI_API_KEY', 'GOOGLE_API_KEY'],
|
|
146
144
|
grok: ['XAI_API_KEY', 'GROK_API_KEY'],
|
|
147
145
|
};
|
|
148
146
|
/** True when a profile already carries auth for its host runtime via env. */
|
|
@@ -3,25 +3,28 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This tick owns only the NON-git duties of auth sync: it materializes worker
|
|
5
5
|
* slots from durable keys already on the box, then (on the elected headed
|
|
6
|
-
* publisher) provisions peers
|
|
7
|
-
*
|
|
6
|
+
* publisher) provisions peers missing a reserved key by pushing the credential
|
|
7
|
+
* over SSH. The secret never enters Git.
|
|
8
8
|
*
|
|
9
|
-
* It no
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* (`
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
9
|
+
* It runs no transport of its own (PHNX-4051): the per-account readiness
|
|
10
|
+
* verdicts it reads are published and delivered by the usage-sync tick's SSH
|
|
11
|
+
* exchange, which stores each peer's reply at `devices/<peer>/daemon-state.json`
|
|
12
|
+
* stamped `receivedAt`. This tick keeps its own deadline and circuit breaker, so
|
|
13
|
+
* a hung peer SSH push parks only auth-sync and never the usage delivery.
|
|
14
|
+
*
|
|
15
|
+
* The pushes read each peer's OWN reply file (PHNX-4116 PR 5): a peer reporting
|
|
16
|
+
* a `missing` verdict for an account is pushed that account's key; a peer that
|
|
17
|
+
* has never sent a reply (no file), or whose reply carries no `accounts.rows`
|
|
18
|
+
* field (an older CLI — fail closed), is skipped this tick and logged at INFO.
|
|
19
|
+
* Presence is also gated on a delivered-fingerprint match, so a re-mint
|
|
20
|
+
* (`accounts login` bumps `workerCredential.mintedAt` while the old token still
|
|
21
|
+
* authenticates) re-pushes the rotated key within a tick instead of reading
|
|
22
|
+
* `present` forever. The push is idempotent, so a stale reply is harmless — a
|
|
23
|
+
* stale "has key" is fine, a stale "missing key" costs one redundant push —
|
|
24
|
+
* which is why the per-peer `receivedAt` replaced the old global freshness gate
|
|
25
|
+
* (`readLastSuccessfulExchangeMs`) that skipped EVERY push when the newest
|
|
26
|
+
* exchange across the fleet went stale. The reply file is first-hand and
|
|
27
|
+
* timestamped, so acting on it needs no such fleet-wide gate.
|
|
25
28
|
*/
|
|
26
29
|
import { BasePeriodicService, type DaemonContext } from './service.js';
|
|
27
30
|
import type { DaemonServiceId } from '../daemon-services.js';
|
|
@@ -3,28 +3,30 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This tick owns only the NON-git duties of auth sync: it materializes worker
|
|
5
5
|
* slots from durable keys already on the box, then (on the elected headed
|
|
6
|
-
* publisher) provisions peers
|
|
7
|
-
*
|
|
6
|
+
* publisher) provisions peers missing a reserved key by pushing the credential
|
|
7
|
+
* over SSH. The secret never enters Git.
|
|
8
8
|
*
|
|
9
|
-
* It no
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* (`
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
9
|
+
* It runs no transport of its own (PHNX-4051): the per-account readiness
|
|
10
|
+
* verdicts it reads are published and delivered by the usage-sync tick's SSH
|
|
11
|
+
* exchange, which stores each peer's reply at `devices/<peer>/daemon-state.json`
|
|
12
|
+
* stamped `receivedAt`. This tick keeps its own deadline and circuit breaker, so
|
|
13
|
+
* a hung peer SSH push parks only auth-sync and never the usage delivery.
|
|
14
|
+
*
|
|
15
|
+
* The pushes read each peer's OWN reply file (PHNX-4116 PR 5): a peer reporting
|
|
16
|
+
* a `missing` verdict for an account is pushed that account's key; a peer that
|
|
17
|
+
* has never sent a reply (no file), or whose reply carries no `accounts.rows`
|
|
18
|
+
* field (an older CLI — fail closed), is skipped this tick and logged at INFO.
|
|
19
|
+
* Presence is also gated on a delivered-fingerprint match, so a re-mint
|
|
20
|
+
* (`accounts login` bumps `workerCredential.mintedAt` while the old token still
|
|
21
|
+
* authenticates) re-pushes the rotated key within a tick instead of reading
|
|
22
|
+
* `present` forever. The push is idempotent, so a stale reply is harmless — a
|
|
23
|
+
* stale "has key" is fine, a stale "missing key" costs one redundant push —
|
|
24
|
+
* which is why the per-peer `receivedAt` replaced the old global freshness gate
|
|
25
|
+
* (`readLastSuccessfulExchangeMs`) that skipped EVERY push when the newest
|
|
26
|
+
* exchange across the fleet went stale. The reply file is first-hand and
|
|
27
|
+
* timestamped, so acting on it needs no such fleet-wide gate.
|
|
25
28
|
*/
|
|
26
29
|
import { BasePeriodicService } from './service.js';
|
|
27
|
-
import { USAGE_SYNC_TICK_MS } from './usage-sync-service.js';
|
|
28
30
|
export const AUTH_SYNC_TICK_MS = 15 * 60_000;
|
|
29
31
|
const AUTH_SYNC_DEADLINE_MS = 2 * 60_000;
|
|
30
32
|
export const AUTH_SYNC_KICKOFF_MS = 60_000;
|
|
@@ -40,7 +42,7 @@ export class AuthSyncService extends BasePeriodicService {
|
|
|
40
42
|
// Nothing to release — the supervisor's timer teardown is the only cleanup.
|
|
41
43
|
}
|
|
42
44
|
async onTick(ctx) {
|
|
43
|
-
const { reconcileLocalWorkerSlots, syncReservedAuthBundle, syncReservedStores, } = await import('../secrets-policy.js');
|
|
45
|
+
const { reconcileLocalWorkerSlots, syncReservedAuthBundle, syncReservedStores, SKIP_REASON_NO_PEER_REPLY, SKIP_REASON_NO_ACCOUNT_ROWS, } = await import('../secrets-policy.js');
|
|
44
46
|
// Worker-side slot materialization FIRST (PHNX-3940 T6): for each registered
|
|
45
47
|
// account whose durable key is already on this box, create the HOME-shaped
|
|
46
48
|
// slot the picker and spawn read. It touches only local state — the registry
|
|
@@ -67,29 +69,12 @@ export class AuthSyncService extends BasePeriodicService {
|
|
|
67
69
|
catch (err) {
|
|
68
70
|
ctx.log('WARN', `auth-sync: worker slot reconcile: ${err.message}`);
|
|
69
71
|
}
|
|
70
|
-
// The
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
// that already received the key still reads `missing`, or a cleared `missing`
|
|
77
|
-
// still reads stale — so skip the pushes and WARN rather than pushing off it.
|
|
78
|
-
// The worker-slot reconcile above is NOT gated: it reads only local durable
|
|
79
|
-
// keys, never delivered peer verdicts.
|
|
80
|
-
const { readLastSuccessfulExchangeMs } = await import('../fleet-shared-repo-sync.js');
|
|
81
|
-
const lastExchangeMs = readLastSuccessfulExchangeMs();
|
|
82
|
-
const ageMs = lastExchangeMs === null ? null : Date.now() - lastExchangeMs;
|
|
83
|
-
// The threshold is the PRODUCER's cadence (USAGE_SYNC_TICK_MS), not this
|
|
84
|
-
// service's own AUTH_SYNC_TICK_MS: the delivered peer verdicts are refreshed
|
|
85
|
-
// once per usage-sync exchange, so "stale" is measured against that tick, not
|
|
86
|
-
// this one. The two constants are equal today, but sourcing it here keeps the
|
|
87
|
-
// gate tracking the usage-sync cadence if either is ever retuned alone.
|
|
88
|
-
if (ageMs === null || ageMs > USAGE_SYNC_TICK_MS) {
|
|
89
|
-
const age = ageMs === null ? 'never completed' : `last completed ${Math.round(ageMs / 1000)}s ago`;
|
|
90
|
-
ctx.log('WARN', `auth-sync: skipping credential push — usage-sync exchange ${age} (need one within ${Math.round(USAGE_SYNC_TICK_MS / 1000)}s); peer verdicts may be stale`);
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
72
|
+
// The pushes read each peer's OWN daemon-state reply, first-hand and
|
|
73
|
+
// timestamped, so they run every tick with no fleet-wide freshness gate: a
|
|
74
|
+
// peer that has replied is planned off its verdict (any age — the push is
|
|
75
|
+
// idempotent), and a peer that has never replied is skipped and logged at
|
|
76
|
+
// INFO below. The worker-slot reconcile above reads only local durable keys,
|
|
77
|
+
// never peer verdicts, so it was never gated either.
|
|
93
78
|
const result = await syncReservedAuthBundle();
|
|
94
79
|
if (result.pushed.length > 0) {
|
|
95
80
|
ctx.log('INFO', `auth-sync: pushed auth to ${result.pushed.join(', ')}`);
|
|
@@ -107,6 +92,15 @@ export class AuthSyncService extends BasePeriodicService {
|
|
|
107
92
|
ctx.log('INFO', `auth-sync: adopted ${stores.adopted.length} legacy reserved item(s) into their bundle: ${stores.adopted.map((a) => `${a.bundle} ${a.key}`).join(', ')}`);
|
|
108
93
|
for (const p of stores.pushed)
|
|
109
94
|
ctx.log('INFO', `auth-sync: pushed ${p.bundle} (${p.keys.length} key(s)) to ${p.device}`);
|
|
95
|
+
// A peer with no reply yet — or a reply with no account rows (older CLI,
|
|
96
|
+
// fail-closed) — is informational, not a warning: a brand-new or never-
|
|
97
|
+
// dialed worker legitimately has none on an early tick, and the no-rows
|
|
98
|
+
// skip is transient during a rolling upgrade. One line per such peer per tick.
|
|
99
|
+
for (const s of stores.skipped) {
|
|
100
|
+
if (s.reason === SKIP_REASON_NO_PEER_REPLY || s.reason === SKIP_REASON_NO_ACCOUNT_ROWS) {
|
|
101
|
+
ctx.log('INFO', `auth-sync: ${s.device}: ${s.reason}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
110
104
|
for (const err of stores.errors)
|
|
111
105
|
ctx.log('WARN', `auth-sync: reserved-store ${err.device}: ${err.message}`);
|
|
112
106
|
}
|
|
@@ -51,9 +51,10 @@ export declare function daemonServiceLabel(): string;
|
|
|
51
51
|
export declare function daemonSystemdUnitName(): string;
|
|
52
52
|
/**
|
|
53
53
|
* How many consecutive failed daemon starts disable the *implicit* auto-start
|
|
54
|
-
* (`ensureDaemonStarted`).
|
|
55
|
-
*
|
|
56
|
-
*
|
|
54
|
+
* (`ensureDaemonStarted`). This bounds foreground-command auto-starts only; the
|
|
55
|
+
* OS-level unit restart is deliberately unbounded (`StartLimitIntervalSec=0`) so
|
|
56
|
+
* a wedged daemon always recovers (PHNX-4116). `agents daemon start` is the
|
|
57
|
+
* deliberate override and is never gated by this.
|
|
57
58
|
*/
|
|
58
59
|
export declare const DAEMON_AUTOSTART_FAILURE_LIMIT = 5;
|
|
59
60
|
/**
|
|
@@ -111,7 +112,6 @@ interface DaemonHeartbeat {
|
|
|
111
112
|
export declare function writeHeartbeat(pid?: number): void;
|
|
112
113
|
export declare function readHeartbeat(): DaemonHeartbeat | null;
|
|
113
114
|
export declare function removeHeartbeat(): void;
|
|
114
|
-
export declare function isDaemonWedged(): boolean;
|
|
115
115
|
/**
|
|
116
116
|
* Check whether a daemon is alive — via the pid file, or a fresh heartbeat when
|
|
117
117
|
* the pid file has been lost (see resolveLiveDaemonPid). The observation itself
|
|
@@ -349,7 +349,7 @@ export declare function ensureDaemonStarted(): {
|
|
|
349
349
|
* console-owning wrapper (cmd.exe / the npm shim). When that wrapper exits it
|
|
350
350
|
* closes its console, and the detached daemon sharing that console receives a
|
|
351
351
|
* console-close event that trips its shutdown handler — the daemon comes up,
|
|
352
|
-
* binds
|
|
352
|
+
* binds its sockets, then tears itself down ~36ms later (#556).
|
|
353
353
|
* Going through `process.execPath` means a real PE/binary is spawned with
|
|
354
354
|
* `detached: true` and no console, so nothing signals the daemon after launch.
|
|
355
355
|
*
|
|
@@ -452,24 +452,35 @@ export declare function findSurvivingStateDirDaemons(exclude: Set<number>): numb
|
|
|
452
452
|
* from launchd/systemd if applicable.
|
|
453
453
|
*
|
|
454
454
|
* The SIGTERM → grace → killTree sequence is unchanged; what it adds is
|
|
455
|
-
* verification
|
|
456
|
-
*
|
|
457
|
-
*
|
|
458
|
-
*
|
|
459
|
-
*
|
|
460
|
-
* left stale; this reclaims it (the owner is provably dead) and reports it. It
|
|
461
|
-
* never reports success on an unverified stop.
|
|
455
|
+
* verification: after the daemon is gone it checks that no `__daemon-run` for
|
|
456
|
+
* THIS state dir survives and reclaims the state files a killTree escalation
|
|
457
|
+
* (which skips the graceful handleShutdown) can leave stale. The browser IPC
|
|
458
|
+
* socket is no longer among them — it left with the standalone `browser` CLI
|
|
459
|
+
* (PHNX-4101). It never reports success on an unverified stop.
|
|
462
460
|
*/
|
|
463
461
|
export declare function stopDaemon(): DaemonStopResult;
|
|
464
|
-
/**
|
|
462
|
+
/**
|
|
463
|
+
* Get current daemon status including running state, PID, enabled job count, and
|
|
464
|
+
* the supervised-restart history `agents daemon status` renders (PHNX-4116).
|
|
465
|
+
*
|
|
466
|
+
* There is no `wedged` state: a stalled daemon exits for a supervised
|
|
467
|
+
* systemd/launchd restart rather than sitting unresponsive, so the daemon is
|
|
468
|
+
* simply `running` or `stopped`, and its recent restarts are reported instead.
|
|
469
|
+
*/
|
|
465
470
|
export declare function getDaemonStatus(): {
|
|
466
|
-
state: 'running' | '
|
|
471
|
+
state: 'running' | 'stopped';
|
|
467
472
|
running: boolean;
|
|
468
473
|
pid: number | null;
|
|
469
474
|
jobCount: number;
|
|
470
475
|
logPath: string;
|
|
471
476
|
binaryPath: string | null;
|
|
472
477
|
heartbeat: DaemonHeartbeat | null;
|
|
478
|
+
/** Supervised restarts in the last 24h (a service breaching its deadline exits the daemon for an OS restart). */
|
|
479
|
+
restarts24h: number;
|
|
480
|
+
/** The most recent supervised-restart cause, or null if none in the last 24h. */
|
|
481
|
+
lastRestartCause: string | null;
|
|
482
|
+
/** ISO timestamp of the most recent supervised restart in the last 24h, or null. */
|
|
483
|
+
lastRestartAt: string | null;
|
|
473
484
|
};
|
|
474
485
|
/** Read the daemon log, optionally limited to the last N lines. */
|
|
475
486
|
export declare function readDaemonLog(lines?: number): string;
|