@phnx-labs/agents-cli 1.22.117 → 1.22.119
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 +301 -0
- package/dist/bootstrap.js +4 -11
- package/dist/commands/accounts.js +7 -4
- 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/projects.js +92 -7
- 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/add.d.ts +3 -1
- package/dist/lib/accounts/add.js +24 -13
- 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 +7 -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-account-token.d.ts +7 -1
- package/dist/lib/claude-account-token.js +26 -4
- 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 +10 -13
- 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/github/pr-mergeable.d.ts +4 -2
- package/dist/lib/github/pr-mergeable.js +5 -3
- package/dist/lib/github/project-prs.d.ts +58 -3
- package/dist/lib/github/project-prs.js +205 -7
- package/dist/lib/harness/adapters/cursor.js +14 -4
- package/dist/lib/harness-auth-capabilities.d.ts +4 -3
- package/dist/lib/harness-auth-capabilities.js +1 -2
- 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.d.ts +1 -1
- package/dist/lib/installations/shims.js +32 -12
- package/dist/lib/installations/store.d.ts +9 -0
- package/dist/lib/installations/store.js +24 -0
- package/dist/lib/installations/update.js +21 -15
- package/dist/lib/installations/versions.js +15 -3
- 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/projects.d.ts +16 -0
- package/dist/lib/projects.js +31 -0
- 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/active.d.ts +17 -6
- package/dist/lib/session/active.js +105 -49
- 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/pid-registry.d.ts +6 -3
- package/dist/lib/session/pid-registry.js +18 -8
- package/dist/lib/session/remote/watch.d.ts +1 -1
- package/dist/lib/session/remote/watch.js +7 -3
- 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 +16 -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
|
@@ -1,29 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Fleet
|
|
3
|
-
* PHNX-3792 session mirror).
|
|
2
|
+
* Fleet state exchange as a `PeriodicService` (PHNX-3392 usage-sync,
|
|
3
|
+
* PHNX-3792 session mirror, PHNX-4051 auth verdict, PHNX-4116 SSH transport).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
5
|
+
* Each tick: (1) refreshes every field this box owns in its own
|
|
6
|
+
* `devices/<device>/daemon-state.json` — a headed box's Claude usage snapshot,
|
|
7
|
+
* EVERY box's lightweight session digests, and the reserved-auth readiness
|
|
8
|
+
* verdict; (2) on a headed box (`personal`/`desktop`) only, dials every dialable
|
|
9
|
+
* peer in parallel with `agents __usage-ingest --reply`, sending that envelope
|
|
10
|
+
* on stdin and storing each peer's reply as `devices/<peer>/daemon-state.json`
|
|
11
|
+
* stamped `receivedAt` (`exchangeFleetStateWithPeers`); (3) folds the peers'
|
|
12
|
+
* session digests into the local index so the picker renders remote-host
|
|
13
|
+
* previews inline. A worker never initiates: its state leaves the box only as
|
|
14
|
+
* the reply to a headed peer's dial, and a headed peer's usage rows arrive on
|
|
15
|
+
* that same dial.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* workers never received a fresh usage snapshot, which the 40-min placement gate
|
|
23
|
-
* turned into "no ready device". Folding the auth verdict into this single
|
|
24
|
-
* committer removes the second caller entirely. Auth-sync keeps its non-git
|
|
25
|
-
* duties (worker-slot reconcile + the credential SSH pushes) under its own
|
|
26
|
-
* deadline and circuit breaker.
|
|
17
|
+
* There is no git in this tick. The exchange used to be a commit/rebase/push of
|
|
18
|
+
* the fleet-synced user repo, which needed a cross-process lock, a 45 s
|
|
19
|
+
* process-tree deadline, an 8-minute kickoff offset from auth-sync to dodge that
|
|
20
|
+
* lock, and untracked-collision backups — and still wedged every clone behind a
|
|
21
|
+
* bloated remote. Peers that time out are skipped this tick; none blocks another.
|
|
27
22
|
*/
|
|
28
23
|
import { BasePeriodicService } from './service.js';
|
|
29
24
|
import { USAGE_SYNC_INTERVAL_MS } from '../accounting/usage-sync.js';
|
|
@@ -37,12 +32,8 @@ import { USAGE_SYNC_INTERVAL_MS } from '../accounting/usage-sync.js';
|
|
|
37
32
|
*/
|
|
38
33
|
export const USAGE_SYNC_TICK_MS = USAGE_SYNC_INTERVAL_MS;
|
|
39
34
|
const USAGE_SYNC_DEADLINE_MS = 2 * 60_000;
|
|
40
|
-
/**
|
|
41
|
-
|
|
42
|
-
* shared-repo lock. Auth fires at T+1m, T+16m, …; usage at T+8m, T+23m, …
|
|
43
|
-
* The git exchange deadline is 45s, so a 7-minute gap is the whole point.
|
|
44
|
-
*/
|
|
45
|
-
export const USAGE_SYNC_KICKOFF_MS = 8 * 60_000;
|
|
35
|
+
/** Let the daemon's registry/device probes settle before the first fan-out. */
|
|
36
|
+
export const USAGE_SYNC_KICKOFF_MS = 90_000;
|
|
46
37
|
export class UsageSyncService extends BasePeriodicService {
|
|
47
38
|
id = 'usage-sync';
|
|
48
39
|
intervalMs = USAGE_SYNC_TICK_MS;
|
|
@@ -55,57 +46,34 @@ export class UsageSyncService extends BasePeriodicService {
|
|
|
55
46
|
// Nothing to release — the supervisor's timer teardown is the only cleanup.
|
|
56
47
|
}
|
|
57
48
|
async onTick(ctx) {
|
|
58
|
-
const {
|
|
59
|
-
const {
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
ctx.log('INFO', `browser-profiles: published ${profiles.published.join(', ')} to this device's declaration`);
|
|
71
|
-
for (const [browser, message] of Object.entries(profiles.errors))
|
|
72
|
-
ctx.log('WARN', `browser-profiles: ${browser}: ${message}`);
|
|
73
|
-
const published = await publishUsageSnapshotToSharedStore();
|
|
74
|
-
if (published.changed)
|
|
75
|
-
ctx.log('INFO', `usage-sync: published usage snapshot to ${published.path}`);
|
|
76
|
-
if (published.error)
|
|
77
|
-
ctx.log('WARN', `usage-sync: publish: ${published.error}`);
|
|
78
|
-
const mirrored = await publishSessionMirrorToSharedStore();
|
|
79
|
-
if (mirrored.changed)
|
|
80
|
-
ctx.log('INFO', `session-mirror: published ${mirrored.count} session digest(s)`);
|
|
81
|
-
if (mirrored.error)
|
|
82
|
-
ctx.log('WARN', `session-mirror: publish: ${mirrored.error}`);
|
|
83
|
-
// The reserved-auth readiness verdict rides this single git exchange too
|
|
84
|
-
// (PHNX-4051): it is a conflict-free field in the same owned daemon-state
|
|
85
|
-
// file, so publishing it here — instead of from a second committer in
|
|
86
|
-
// auth-sync — is what keeps exactly one caller of syncFleetSharedStateRepo on
|
|
87
|
-
// the periodic path.
|
|
88
|
-
const { publishReservedAuthVerdict } = await import('../secrets-policy.js');
|
|
89
|
-
const authVerdict = await publishReservedAuthVerdict();
|
|
90
|
-
if (authVerdict.error)
|
|
91
|
-
ctx.log('WARN', `usage-sync: auth verdict: ${authVerdict.error}`);
|
|
92
|
-
const { syncFleetSharedStateRepo } = await import('../fleet-shared-repo-sync.js');
|
|
93
|
-
const transport = await syncFleetSharedStateRepo();
|
|
94
|
-
if (transport.skipped)
|
|
95
|
-
ctx.log('WARN', `usage-sync: ${transport.skipped}`);
|
|
96
|
-
if (transport.error)
|
|
97
|
-
ctx.log('WARN', `usage-sync: shared-store transport: ${transport.error}`);
|
|
98
|
-
if (transport.untrackedBackedUp?.length) {
|
|
99
|
-
ctx.log('WARN', `usage-sync: backed up ${transport.untrackedBackedUp.length} untracked shared-store collision(s) to ${transport.untrackedBackupDir}: ${transport.untrackedBackedUp.join(', ')}`);
|
|
100
|
-
}
|
|
101
|
-
if (!transport.success)
|
|
49
|
+
const { exchangeFleetStateWithPeers, publishOwnFleetState } = await import('../accounting/usage-sync.js');
|
|
50
|
+
const { isHeadedDeviceRole, selfConfiguredDeviceRole } = await import('../device-config.js');
|
|
51
|
+
// Refresh every owned field BEFORE the fan-out so peers receive this tick's state.
|
|
52
|
+
const published = await publishOwnFleetState();
|
|
53
|
+
if (published.usage.changed)
|
|
54
|
+
ctx.log('INFO', `usage-sync: published usage snapshot to ${published.usage.path}`);
|
|
55
|
+
if (published.mirror.changed)
|
|
56
|
+
ctx.log('INFO', `session-mirror: published ${published.mirror.count} session digest(s)`);
|
|
57
|
+
for (const err of published.errors)
|
|
58
|
+
ctx.log('WARN', `usage-sync: publish ${err}`);
|
|
59
|
+
if (!isHeadedDeviceRole(selfConfiguredDeviceRole())) {
|
|
60
|
+
ctx.log('INFO', 'usage-sync: not a headed device; peers dial in with `agents __usage-ingest --reply`, nothing to send');
|
|
102
61
|
return;
|
|
103
|
-
const consumed = consumeUsageSnapshotsFromSharedStore();
|
|
104
|
-
if (consumed.merged > 0) {
|
|
105
|
-
ctx.log('INFO', `usage-sync: merged ${consumed.merged} row(s) from ${consumed.sources.join(', ')}`);
|
|
106
62
|
}
|
|
107
|
-
|
|
108
|
-
|
|
63
|
+
const exchange = await exchangeFleetStateWithPeers();
|
|
64
|
+
if (exchange.skipped)
|
|
65
|
+
ctx.log('WARN', `usage-sync: ${exchange.skipped}`);
|
|
66
|
+
const delivered = exchange.outcomes.filter((o) => o.delivered);
|
|
67
|
+
if (delivered.length > 0) {
|
|
68
|
+
ctx.log('INFO', `usage-sync: exchanged with ${delivered.map((o) => `${o.device}${o.merged ? ` (+${o.merged} usage row(s))` : ''}`).join(', ')}`);
|
|
69
|
+
}
|
|
70
|
+
for (const o of exchange.outcomes) {
|
|
71
|
+
if (o.error)
|
|
72
|
+
ctx.log('WARN', `usage-sync: ${o.device}: ${o.error}`);
|
|
73
|
+
for (const err of o.peerErrors)
|
|
74
|
+
ctx.log('WARN', `usage-sync: ${o.device} reported: ${err}`);
|
|
75
|
+
}
|
|
76
|
+
const { consumeSessionMirrorFromSharedStore } = await import('../session/mirror.js');
|
|
109
77
|
const foldedIn = consumeSessionMirrorFromSharedStore();
|
|
110
78
|
if (foldedIn.merged > 0) {
|
|
111
79
|
ctx.log('INFO', `session-mirror: folded ${foldedIn.merged} session(s) from ${foldedIn.sources.join(', ')}`);
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/** Stable subsystem identifiers shared by the daemon (writer) and `agents daemon` (reader). */
|
|
2
|
-
export declare const SUBSYSTEM_BROWSER_IPC = "browser-ipc";
|
|
3
1
|
/**
|
|
4
2
|
* Daemon startup itself (RUSH-2418). Unlike the two above, this record is
|
|
5
3
|
* written from BOTH sides: the launching CLI records a start that produced no
|
|
@@ -22,8 +20,8 @@ export interface SubsystemHealth {
|
|
|
22
20
|
/** ISO timestamp of the most recent success, or null if it has never succeeded. */
|
|
23
21
|
lastOkAt: string | null;
|
|
24
22
|
/**
|
|
25
|
-
* `ServiceSupervisor`'s lifecycle state (`idle`/`running`/`
|
|
26
|
-
*
|
|
23
|
+
* `ServiceSupervisor`'s lifecycle state (`idle`/`running`/`stopped`), written
|
|
24
|
+
* by `recordSubsystemState` on every transition. Only present for
|
|
27
25
|
* supervisor-managed subsystems (RUSH-3193 P4) — a subsystem that predates the
|
|
28
26
|
* supervisor (e.g. `daemon-start`) never has this field, which is how `agents
|
|
29
27
|
* daemon services` tells a measured state from an inferred one.
|
|
@@ -50,10 +48,29 @@ export declare function recordSubsystemErrorReason(subsystem: string, error: str
|
|
|
50
48
|
/**
|
|
51
49
|
* Record a `ServiceSupervisor` lifecycle-state transition, without touching
|
|
52
50
|
* the ok/error streak. Cross-process readers (`agents daemon services`) have
|
|
53
|
-
* no other way to see `
|
|
51
|
+
* no other way to see `stopped`/`idle` vs `running` — `agents daemon
|
|
54
52
|
* status` runs as a separate process from the daemon (see module docblock).
|
|
55
53
|
*/
|
|
56
54
|
export declare function recordSubsystemState(subsystem: string, state: string): void;
|
|
55
|
+
/** One supervised restart: the service whose deadline breach forced the daemon to exit, and when. */
|
|
56
|
+
export interface DaemonRestartRecord {
|
|
57
|
+
/** ISO timestamp of the breach that forced the restart. */
|
|
58
|
+
at: string;
|
|
59
|
+
/** The service id whose tick or lifecycle call breached its deadline. */
|
|
60
|
+
subsystem: string;
|
|
61
|
+
/** The deadline-breach message. */
|
|
62
|
+
cause: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Append a supervised-restart record and flush it to disk SYNCHRONOUSLY. The
|
|
66
|
+
* caller is `ServiceSupervisor.exitForRestart`, which calls `process.exit`
|
|
67
|
+
* immediately after, so the write must be durable before the process dies.
|
|
68
|
+
* Never throws (same contract as the health file above) — a dropped ledger entry
|
|
69
|
+
* must not keep the process from exiting for its restart.
|
|
70
|
+
*/
|
|
71
|
+
export declare function recordDaemonRestart(subsystem: string, cause: string, at?: string): void;
|
|
72
|
+
/** Every supervised restart recorded at or after `sinceMs`, oldest first. */
|
|
73
|
+
export declare function readRecentDaemonRestarts(sinceMs: number): DaemonRestartRecord[];
|
|
57
74
|
/** Read one subsystem's health record, or null if it has never reported in. */
|
|
58
75
|
export declare function readSubsystemHealth(subsystem: string): SubsystemHealth | null;
|
|
59
76
|
/** Read every subsystem's health record, sorted by subsystem name. */
|
|
@@ -27,8 +27,6 @@ import * as path from 'path';
|
|
|
27
27
|
import { getDaemonDir } from './state.js';
|
|
28
28
|
import { atomicWriteFileSync, ensureLockTarget, withFileLock } from './fs-atomic.js';
|
|
29
29
|
const HEALTH_FILE = 'health.json';
|
|
30
|
-
/** Stable subsystem identifiers shared by the daemon (writer) and `agents daemon` (reader). */
|
|
31
|
-
export const SUBSYSTEM_BROWSER_IPC = 'browser-ipc';
|
|
32
30
|
/**
|
|
33
31
|
* Daemon startup itself (RUSH-2418). Unlike the two above, this record is
|
|
34
32
|
* written from BOTH sides: the launching CLI records a start that produced no
|
|
@@ -125,7 +123,7 @@ export function recordSubsystemErrorReason(subsystem, error, at = new Date().toI
|
|
|
125
123
|
/**
|
|
126
124
|
* Record a `ServiceSupervisor` lifecycle-state transition, without touching
|
|
127
125
|
* the ok/error streak. Cross-process readers (`agents daemon services`) have
|
|
128
|
-
* no other way to see `
|
|
126
|
+
* no other way to see `stopped`/`idle` vs `running` — `agents daemon
|
|
129
127
|
* status` runs as a separate process from the daemon (see module docblock).
|
|
130
128
|
*/
|
|
131
129
|
export function recordSubsystemState(subsystem, state) {
|
|
@@ -134,6 +132,52 @@ export function recordSubsystemState(subsystem, state) {
|
|
|
134
132
|
all[subsystem] = { ...existing, subsystem, state };
|
|
135
133
|
});
|
|
136
134
|
}
|
|
135
|
+
// ─── Supervised-restart ledger (PHNX-4116) ────────────────────────────────────
|
|
136
|
+
const RESTARTS_FILE = 'restarts.json';
|
|
137
|
+
/** Bound so a crash loop cannot grow the ledger file without limit. */
|
|
138
|
+
const MAX_RESTART_RECORDS = 200;
|
|
139
|
+
function getRestartsPath() {
|
|
140
|
+
return path.join(getDaemonDir(), RESTARTS_FILE);
|
|
141
|
+
}
|
|
142
|
+
function readRestarts() {
|
|
143
|
+
try {
|
|
144
|
+
const parsed = JSON.parse(fs.readFileSync(getRestartsPath(), 'utf-8'));
|
|
145
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Append a supervised-restart record and flush it to disk SYNCHRONOUSLY. The
|
|
153
|
+
* caller is `ServiceSupervisor.exitForRestart`, which calls `process.exit`
|
|
154
|
+
* immediately after, so the write must be durable before the process dies.
|
|
155
|
+
* Never throws (same contract as the health file above) — a dropped ledger entry
|
|
156
|
+
* must not keep the process from exiting for its restart.
|
|
157
|
+
*/
|
|
158
|
+
export function recordDaemonRestart(subsystem, cause, at = new Date().toISOString()) {
|
|
159
|
+
try {
|
|
160
|
+
const restartsPath = getRestartsPath();
|
|
161
|
+
ensureLockTarget(restartsPath, '[]');
|
|
162
|
+
withFileLock(restartsPath, () => {
|
|
163
|
+
const records = readRestarts();
|
|
164
|
+
records.push({ at, subsystem, cause });
|
|
165
|
+
atomicWriteFileSync(restartsPath, JSON.stringify(records.slice(-MAX_RESTART_RECORDS)), { encoding: 'utf-8', mode: 0o600 });
|
|
166
|
+
try {
|
|
167
|
+
fs.chmodSync(restartsPath, 0o600);
|
|
168
|
+
}
|
|
169
|
+
catch { /* best effort */ }
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
catch { /* a dropped restart record must never block the exit-for-restart */ }
|
|
173
|
+
}
|
|
174
|
+
/** Every supervised restart recorded at or after `sinceMs`, oldest first. */
|
|
175
|
+
export function readRecentDaemonRestarts(sinceMs) {
|
|
176
|
+
return readRestarts().filter((r) => {
|
|
177
|
+
const t = Date.parse(r.at);
|
|
178
|
+
return Number.isFinite(t) && t >= sinceMs;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
137
181
|
/** Read one subsystem's health record, or null if it has never reported in. */
|
|
138
182
|
export function readSubsystemHealth(subsystem) {
|
|
139
183
|
return readAll()[subsystem] ?? null;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* enabled without pulling in the whole daemon lifecycle.
|
|
8
8
|
*/
|
|
9
9
|
/** Every service the daemon can host. IDs are kebab-case and stable. */
|
|
10
|
-
export type DaemonServiceId = 'scheduler' | 'catchup' | 'monitors' | '
|
|
10
|
+
export type DaemonServiceId = 'scheduler' | 'catchup' | 'monitors' | 'webhook-receiver' | 'self-heal' | 'self-update' | 'account-state' | 'account-auth' | 'watchdog' | 'device-probe' | 'state-dir-check' | 'session-index' | 'session-title' | 'auth-sync' | 'usage-sync' | 'daemon-heartbeat' | 'tmux-reap' | 'session-state' | 'feed-stream' | 'session-summarizer' | 'attention-notify' | 'harness-update';
|
|
11
11
|
/** Human-readable metadata for each service. */
|
|
12
12
|
interface DaemonServiceDef {
|
|
13
13
|
id: DaemonServiceId;
|
|
@@ -27,11 +27,6 @@ export const DAEMON_SERVICES = [
|
|
|
27
27
|
title: 'Monitor engine',
|
|
28
28
|
description: 'Watches event sources and triggers monitor-driven routines.',
|
|
29
29
|
},
|
|
30
|
-
{
|
|
31
|
-
id: 'browser-ipc',
|
|
32
|
-
title: 'Browser IPC',
|
|
33
|
-
description: 'Keeps a supervised browser automation IPC socket available for sessions.',
|
|
34
|
-
},
|
|
35
30
|
{
|
|
36
31
|
id: 'webhook-receiver',
|
|
37
32
|
title: 'Webhook receiver',
|
|
@@ -82,11 +77,6 @@ export const DAEMON_SERVICES = [
|
|
|
82
77
|
title: 'Tmux reap',
|
|
83
78
|
description: 'Reaps dead managed tmux sessions and their orphaned helper processes.',
|
|
84
79
|
},
|
|
85
|
-
{
|
|
86
|
-
id: 'browser-task-reap',
|
|
87
|
-
title: 'Browser task reap',
|
|
88
|
-
description: 'Closes abandoned browser-task tabs whose owner exited or idle lease expired.',
|
|
89
|
-
},
|
|
90
80
|
{
|
|
91
81
|
id: 'session-state',
|
|
92
82
|
title: 'Live session state',
|
package/dist/lib/daemon-ticks.js
CHANGED
|
@@ -158,9 +158,11 @@ export async function runUsageRefreshTick(signal) {
|
|
|
158
158
|
// is bounded by deadlineMs, not just each fetch's own 5s timeout (PHNX-3608).
|
|
159
159
|
signal,
|
|
160
160
|
pollerDevice: machineId(),
|
|
161
|
+
// A changed poll refreshes this box's own state file so the next usage-sync
|
|
162
|
+
// fan-out (or a placement probe before it) sends the new reading.
|
|
161
163
|
onSnapshotsChanged: async () => {
|
|
162
|
-
const {
|
|
163
|
-
await
|
|
164
|
+
const { publishUsageSnapshotToSharedStore } = await import('./accounting/usage-sync.js');
|
|
165
|
+
await publishUsageSnapshotToSharedStore();
|
|
164
166
|
},
|
|
165
167
|
});
|
|
166
168
|
const { listProfiles } = await import('./profiles.js');
|
|
@@ -315,12 +315,7 @@ export function remediationFor(finding) {
|
|
|
315
315
|
// shape only, for a kind constructed through finding() by a future caller.
|
|
316
316
|
return 'kill <pid>';
|
|
317
317
|
case 'owner-sink-unreachable':
|
|
318
|
-
|
|
319
|
-
// PATH AND a usable session in THIS context. Non-interactive shells miss a
|
|
320
|
-
// ~/.zshrc export (RUSH-2258), and the session is keychain-bound, not in
|
|
321
|
-
// ~/.rush/user.yaml (RUSH-2262) — so `rush login` here, or the Rush App for
|
|
322
|
-
// a GUI keychain, is what makes it reachable.
|
|
323
|
-
return "put rush on PATH for non-interactive shells (~/.zshenv) and run 'rush login'";
|
|
318
|
+
return 'check the channel transport: iMessage needs macOS, Slack needs SLACK_BOT_TOKEN in env or the webhooks bundle';
|
|
324
319
|
}
|
|
325
320
|
}
|
|
326
321
|
function finding(f) {
|
|
@@ -369,11 +364,13 @@ export function buildLocalFindings(input) {
|
|
|
369
364
|
const sink = input.ownerSink;
|
|
370
365
|
if (sink?.configured && !sink.reachable) {
|
|
371
366
|
const chan = sink.channel ?? 'owner';
|
|
372
|
-
const why = sink.reason === '
|
|
373
|
-
?
|
|
374
|
-
: sink.reason === '
|
|
375
|
-
? '
|
|
376
|
-
:
|
|
367
|
+
const why = sink.reason === 'imessage-not-macos'
|
|
368
|
+
? 'iMessage requires macOS (peer-forward delivers from a macOS peer)'
|
|
369
|
+
: sink.reason === 'slack-no-token'
|
|
370
|
+
? 'no SLACK_BOT_TOKEN — set it in env or the webhooks secrets bundle'
|
|
371
|
+
: sink.reason === 'channel-unsupported'
|
|
372
|
+
? `${chan} transport removed (Rush daemon)`
|
|
373
|
+
: 'transport unreachable';
|
|
377
374
|
out.push(finding({
|
|
378
375
|
severity: FINDING_SEVERITY['owner-sink-unreachable'], kind: 'owner-sink-unreachable', device,
|
|
379
376
|
message: `${chan} → owner unreachable: ${why}`,
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import chalk from 'chalk';
|
|
17
17
|
import { ALL_AGENT_IDS, accountDisplayLabel, getAccountInfo, } from '../agents.js';
|
|
18
|
-
import { deriveUsageStatusFromSnapshot, classifyUsageErrorKind, getUsageInfoByIdentity, getUsageLookupKey, usageErrorForDisplay, } from '../accounting/usage.js';
|
|
18
|
+
import { deriveUsageStatusFromSnapshot, liveUsageWindows, classifyUsageErrorKind, getUsageInfoByIdentity, getUsageLookupKey, usageErrorForDisplay, } from '../accounting/usage.js';
|
|
19
19
|
import { getVersionHomePath, listInstalledVersions } from '../installations/store.js';
|
|
20
20
|
import { readMeta } from '../state.js';
|
|
21
21
|
import { findNativeAccountByIdentity } from '../account-registry.js';
|
|
@@ -59,11 +59,20 @@ export function summarizeQuota(snapshot, unavailableReason = null, accountStatus
|
|
|
59
59
|
unavailableReason: status ? null : (reason ?? 'usage unavailable'),
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
// Percentage and status MUST read from the SAME live windows, or a window past
|
|
63
|
+
// its reset (usedPercent from the PREVIOUS period) makes the status `available`
|
|
64
|
+
// while the displayed percentage still shows a stale ~100% (#3705). Select the
|
|
65
|
+
// live set first, then narrow to blocking exactly as
|
|
66
|
+
// `deriveUsageStatusFromSnapshot` does.
|
|
67
|
+
const live = liveUsageWindows(snapshot);
|
|
68
|
+
const blocking = live.filter((w) => w.key !== 'sonnet_week');
|
|
69
|
+
const windows = blocking.length > 0 ? blocking : live;
|
|
64
70
|
const derived = deriveUsageStatusFromSnapshot(snapshot);
|
|
65
71
|
const status = accountStatus === 'out_of_credits' ? accountStatus : derived;
|
|
66
|
-
|
|
72
|
+
// Every window rolled over: no live utilization to show (status is `available`).
|
|
73
|
+
let usedPercent = windows.length > 0
|
|
74
|
+
? Math.round(Math.max(...windows.map((w) => w.usedPercent)))
|
|
75
|
+
: 0;
|
|
67
76
|
// Never show 100% for an account that isn't actually capped: a genuinely-100
|
|
68
77
|
// blocking window makes the status `rate_limited` (rendered "limited"), so a
|
|
69
78
|
// rounded 100 on an `available` account (e.g. 99.6% → 100) would read as maxed
|
|
@@ -123,7 +132,7 @@ export function applyUsageHonesty(verdict, usage) {
|
|
|
123
132
|
}
|
|
124
133
|
return { verdict, usage: stripped };
|
|
125
134
|
}
|
|
126
|
-
if ((usage.status === 'rate_limited' || usage.status === 'out_of_credits') && (verdict === 'live' || verdict === 'unverified')) {
|
|
135
|
+
if ((usage.status === 'rate_limited' || usage.status === 'out_of_credits') && (verdict === 'live' || verdict === 'unverified' || verdict === 'no_evidence')) {
|
|
127
136
|
return { verdict: 'rate_limited', usage };
|
|
128
137
|
}
|
|
129
138
|
return { verdict, usage };
|
|
@@ -142,6 +142,11 @@ async function matchLegacyIdentityHome(agent, account, meta) {
|
|
|
142
142
|
const needles = [row?.identityKey, row?.identityLabel]
|
|
143
143
|
.filter((value) => typeof value === 'string' && value.trim().length > 0)
|
|
144
144
|
.map((value) => value.trim().toLowerCase());
|
|
145
|
+
// For accounts that predate the native registry, the row is missing but the
|
|
146
|
+
// account name carries the email the caller selected with `#email`.
|
|
147
|
+
if (needles.length === 0 && account.name.includes('@')) {
|
|
148
|
+
needles.push(account.name.trim().toLowerCase());
|
|
149
|
+
}
|
|
145
150
|
if (needles.length === 0)
|
|
146
151
|
return null;
|
|
147
152
|
const needleSet = new Set(needles);
|
package/dist/lib/exec.js
CHANGED
|
@@ -33,12 +33,12 @@ import { recordRunName } from './session/run-names.js';
|
|
|
33
33
|
import { mailboxDir, isValidMailboxId } from './mailbox.js';
|
|
34
34
|
import { composeWin32CommandLine } from './platform/index.js';
|
|
35
35
|
import { isTmuxInstalled } from './tmux/binary.js';
|
|
36
|
-
import { isTmuxEnabled, selfConfiguredDeviceRole } from './device-config.js';
|
|
36
|
+
import { isHeadedDeviceRole, isTmuxEnabled, selfConfiguredDeviceRole } from './device-config.js';
|
|
37
37
|
import { machineId } from './machine-id.js';
|
|
38
38
|
import { shellQuote } from './ssh-exec.js';
|
|
39
39
|
import { codexEditWritableRoots, codexPolicyArgs } from './codex-policy.js';
|
|
40
40
|
import { probeUnprivilegedUserns } from './linux-userns.js';
|
|
41
|
-
import { resolveClaudeSetupToken } from './claude-account-token.js';
|
|
41
|
+
import { resolveClaudeSetupToken, seedClaudeWorkerHomeIdentity } from './claude-account-token.js';
|
|
42
42
|
import { applyAddDirs } from './add-dir.js';
|
|
43
43
|
import { applyActiveRulesPresetAtRun } from './rules/run-sync.js';
|
|
44
44
|
import { applySystemResourcesAtRun } from './system-run-sync.js';
|
|
@@ -541,17 +541,6 @@ export const AGENT_COMMANDS = {
|
|
|
541
541
|
jsonFlags: ['--json'],
|
|
542
542
|
modelFlag: '--model',
|
|
543
543
|
},
|
|
544
|
-
gemini: {
|
|
545
|
-
base: ['gemini'],
|
|
546
|
-
promptFlag: 'positional',
|
|
547
|
-
modeFlags: {
|
|
548
|
-
plan: ['--approval-mode', 'plan'],
|
|
549
|
-
edit: ['--approval-mode', 'auto_edit'],
|
|
550
|
-
skip: ['--yolo'],
|
|
551
|
-
},
|
|
552
|
-
jsonFlags: ['--output-format', 'stream-json'],
|
|
553
|
-
modelFlag: '--model',
|
|
554
|
-
},
|
|
555
544
|
cursor: {
|
|
556
545
|
base: ['cursor-agent'],
|
|
557
546
|
promptFlag: '-p',
|
|
@@ -1943,6 +1932,14 @@ async function spawnAgentLeased(options) {
|
|
|
1943
1932
|
process.stderr.write(`\x1b[31m${loginTrap}\x1b[0m\n`);
|
|
1944
1933
|
return { exitCode: 1, stdout: '', stderr: loginTrap };
|
|
1945
1934
|
}
|
|
1935
|
+
// A credentialed run on a worker never needs Claude Code's first-run
|
|
1936
|
+
// onboarding, and in a provider (setup-token) launch's shared version home
|
|
1937
|
+
// nothing else completes it: the tab opened on the theme picker instead of
|
|
1938
|
+
// the agent (yosemite-m1, 2026-09-28). A headed device keeps its own
|
|
1939
|
+
// onboarding; the person at it answers it.
|
|
1940
|
+
if (hasWorkerCredential && versionHome && !isHeadedDeviceRole(selfConfiguredDeviceRole())) {
|
|
1941
|
+
seedClaudeWorkerHomeIdentity(versionHome);
|
|
1942
|
+
}
|
|
1946
1943
|
}
|
|
1947
1944
|
// Budget live kill-switch (issue #346). For headless runs we incrementally
|
|
1948
1945
|
// parse stream-json usage off stdout, accumulate cost, and kill the child the
|
package/dist/lib/feed/events.js
CHANGED
|
@@ -28,7 +28,6 @@ import * as fs from 'node:fs';
|
|
|
28
28
|
import * as path from 'node:path';
|
|
29
29
|
import { getBrowserRuntimeDir } from '../state.js';
|
|
30
30
|
import { getEventsDir } from './events.js';
|
|
31
|
-
import { listTaskBindings } from '../browser/task-index.js';
|
|
32
31
|
import { buildBrowserSessionRows } from '../browser/sessions-list.js';
|
|
33
32
|
import { buildComputerSessionRows, standaloneComputerActionsDir } from '../computer/sessions-list.js';
|
|
34
33
|
import { boundBrowserRow, projectBrowserToolRow, projectComputerToolRow, sortToolRows } from './tools.js';
|
|
@@ -52,8 +51,12 @@ export function collectToolRows(scope, sources = {}) {
|
|
|
52
51
|
return empty;
|
|
53
52
|
}
|
|
54
53
|
};
|
|
54
|
+
// Task→device binding is browser-cli's now (bound at `start`, PHNX-4101), so
|
|
55
|
+
// agents-cli keeps no separate binding index. Cross-device tasks are driven by
|
|
56
|
+
// browser-cli from THIS box, so their `tasks.json` is local and reached by
|
|
57
|
+
// `readLiveBrowserTasks` below. The `bindings` source stays a test seam.
|
|
55
58
|
const bindings = new Map();
|
|
56
|
-
for (const binding of read(sources.bindings ??
|
|
59
|
+
for (const binding of read(sources.bindings ?? (() => []), []))
|
|
57
60
|
bindings.set(binding.name, binding);
|
|
58
61
|
const liveTasks = new Map();
|
|
59
62
|
for (const task of read(sources.liveTasks ?? (() => readLiveBrowserTasks()), []))
|
|
@@ -56,11 +56,27 @@ export interface FleetSharedDeviceState {
|
|
|
56
56
|
sessions?: {
|
|
57
57
|
rows: SessionMirrorRow[];
|
|
58
58
|
};
|
|
59
|
+
/**
|
|
60
|
+
* Per-account auth verdict rows, written by the account-state daemon service
|
|
61
|
+
* (`account-state-daemon-service.ts`) and read by `readSharedAccountVerdicts`.
|
|
62
|
+
* Opaque here: the envelope carries them across the exchange unchanged.
|
|
63
|
+
*/
|
|
64
|
+
accounts?: {
|
|
65
|
+
rows: unknown[];
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Epoch ms this box received the envelope from its owner over the SSH
|
|
69
|
+
* exchange. Present ONLY in a peer's file on this box, never in a device's own
|
|
70
|
+
* file. auth-sync reads it per peer to know that peer has replied at all.
|
|
71
|
+
*/
|
|
72
|
+
receivedAt?: number;
|
|
59
73
|
}
|
|
60
74
|
interface FleetSharedStatePatch {
|
|
61
75
|
usage?: FleetSharedDeviceState['usage'];
|
|
62
76
|
auth?: FleetSharedDeviceState['auth'];
|
|
63
77
|
sessions?: FleetSharedDeviceState['sessions'];
|
|
78
|
+
accounts?: FleetSharedDeviceState['accounts'];
|
|
79
|
+
receivedAt?: number;
|
|
64
80
|
}
|
|
65
81
|
interface FleetSharedStateReadResult {
|
|
66
82
|
states: FleetSharedDeviceState[];
|
|
@@ -71,6 +87,13 @@ interface FleetSharedStateReadResult {
|
|
|
71
87
|
}
|
|
72
88
|
/** Path owned by one device in the conflict-free tracked device-doc tree. */
|
|
73
89
|
export declare function fleetSharedStatePath(device: string, userAgentsDir?: string): string;
|
|
90
|
+
/**
|
|
91
|
+
* Validate an already-parsed envelope. `owner` pins the `device` field when the
|
|
92
|
+
* caller knows whose envelope this must be (a file under `devices/<owner>/`);
|
|
93
|
+
* an envelope arriving over the exchange names its own owner, so the caller
|
|
94
|
+
* passes none and checks `device` against the dialed peer afterwards.
|
|
95
|
+
*/
|
|
96
|
+
export declare function parseFleetSharedDeviceStateEnvelope(parsed: unknown, owner?: string): FleetSharedDeviceState;
|
|
74
97
|
export declare function updateFleetSharedDeviceState(device: string, patch: FleetSharedStatePatch, userAgentsDir?: string): {
|
|
75
98
|
changed: boolean;
|
|
76
99
|
path: string;
|
|
@@ -89,4 +112,26 @@ export declare function updateFleetSharedDeviceStateAsync(device: string, patch:
|
|
|
89
112
|
}>;
|
|
90
113
|
/** Read every valid peer-owned state file; malformed peers fail separately. */
|
|
91
114
|
export declare function readFleetSharedDeviceStates(userAgentsDir?: string): FleetSharedStateReadResult;
|
|
115
|
+
/**
|
|
116
|
+
* This device's own envelope as it stands on disk — what the usage-sync exchange
|
|
117
|
+
* sends to peers and what `__usage-ingest --reply` prints back. A device that has
|
|
118
|
+
* never published anything yields the bare `{version, device}` envelope, so a
|
|
119
|
+
* peer still learns the device exists and answered.
|
|
120
|
+
*/
|
|
121
|
+
export declare function readOwnFleetSharedDeviceState(device: string, userAgentsDir?: string): FleetSharedDeviceState;
|
|
122
|
+
/**
|
|
123
|
+
* Store a PEER's envelope, received over the SSH exchange, in that peer's file on
|
|
124
|
+
* this box, stamped with `receivedAt`. Merges field-by-field onto whatever the
|
|
125
|
+
* file already holds, so a partial envelope (a placement probe sends usage only)
|
|
126
|
+
* refreshes that field without erasing the peer's last auth verdict, session
|
|
127
|
+
* digests, or account rows. The five readers of `devices/<peer>/daemon-state.json`
|
|
128
|
+
* (usage merge, poller claims, auth verdicts, session mirror, account catalog)
|
|
129
|
+
* are unchanged by the transport swap. Async because it runs on the daemon's
|
|
130
|
+
* usage-sync tick once per peer reply: the sync lock would `sleepSync` the event
|
|
131
|
+
* loop for up to 30 s under contention (see {@link updateFleetSharedDeviceStateAsync}).
|
|
132
|
+
*/
|
|
133
|
+
export declare function storePeerFleetSharedDeviceState(state: FleetSharedDeviceState, userAgentsDir?: string, receivedAt?: number): Promise<{
|
|
134
|
+
changed: boolean;
|
|
135
|
+
path: string;
|
|
136
|
+
}>;
|
|
92
137
|
export {};
|