@phnx-labs/agents-cli 1.22.46 → 1.22.49
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 +663 -0
- package/LICENSE +102 -182
- package/README.md +96 -49
- package/dist/bootstrap.js +4 -2
- package/dist/cli/command-registry.d.ts +2 -1
- package/dist/cli/command-registry.js +6 -2
- package/dist/commands/accounts.d.ts +58 -0
- package/dist/commands/accounts.js +243 -25
- package/dist/commands/artifacts.js +7 -6
- package/dist/commands/attach.js +1 -1
- package/dist/commands/auth.js +7 -5
- package/dist/commands/browser-sessions-picker.js +9 -7
- package/dist/commands/browser.js +711 -62
- package/dist/commands/config.d.ts +0 -1
- package/dist/commands/config.js +26 -24
- package/dist/commands/cost.d.ts +1 -1
- package/dist/commands/daemon.d.ts +21 -0
- package/dist/commands/daemon.js +147 -12
- package/dist/commands/detach.d.ts +13 -0
- package/dist/commands/detach.js +11 -8
- package/dist/commands/exec.js +107 -53
- package/dist/commands/feed-watch.d.ts +2 -0
- package/dist/commands/feed-watch.js +28 -0
- package/dist/commands/feed.js +27 -0
- package/dist/commands/feedback.js +3 -19
- package/dist/commands/focus.d.ts +14 -0
- package/dist/commands/focus.js +48 -5
- package/dist/commands/go.d.ts +3 -0
- package/dist/commands/go.js +16 -3
- package/dist/commands/insights.d.ts +1 -1
- package/dist/commands/insights.js +2 -2
- package/dist/commands/inspect.js +2 -2
- package/dist/commands/lease.d.ts +1 -1
- package/dist/commands/lease.js +7 -6
- package/dist/commands/mailboxes.js +4 -3
- package/dist/commands/menubar.js +11 -4
- package/dist/commands/projects.d.ts +16 -1
- package/dist/commands/projects.js +58 -64
- package/dist/commands/resume.js +1 -1
- package/dist/commands/route.js +4 -4
- package/dist/commands/routines.js +4 -3
- package/dist/commands/run-broadcast.d.ts +1 -1
- package/dist/commands/run-broadcast.js +1 -1
- package/dist/commands/sessions-picker-factory.d.ts +2 -1
- package/dist/commands/sessions-picker-factory.js +1 -1
- package/dist/commands/sessions-picker.d.ts +22 -0
- package/dist/commands/sessions-picker.js +48 -9
- package/dist/commands/sessions-stop.d.ts +24 -0
- package/dist/commands/sessions-stop.js +66 -0
- package/dist/commands/sessions-trace.d.ts +82 -0
- package/dist/commands/sessions-trace.js +387 -0
- package/dist/commands/sessions.js +5 -0
- package/dist/commands/setup-secrets.js +23 -0
- package/dist/commands/share.d.ts +20 -5
- package/dist/commands/share.js +152 -52
- package/dist/commands/ssh.d.ts +15 -0
- package/dist/commands/ssh.js +243 -18
- package/dist/commands/teams.js +34 -2
- package/dist/commands/traces.d.ts +2 -0
- package/dist/commands/traces.js +184 -0
- package/dist/commands/view.d.ts +2 -0
- package/dist/commands/view.js +32 -62
- package/dist/commands/watchdog.js +9 -14
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/lib/account-capabilities.d.ts +4 -0
- package/dist/lib/account-capabilities.js +13 -9
- package/dist/lib/account-registry.d.ts +2 -0
- package/dist/lib/account-registry.js +30 -2
- package/dist/lib/accounting/rotate.d.ts +9 -0
- package/dist/lib/accounting/rotate.js +12 -2
- package/dist/lib/accounting/usage.d.ts +84 -6
- package/dist/lib/accounting/usage.js +293 -75
- package/dist/lib/agent-modes.js +29 -1
- package/dist/lib/agent-spec/agents.d.ts +4 -3
- package/dist/lib/agent-spec/agents.js +25 -15
- package/dist/lib/analytics/mix-commands.d.ts +1 -1
- package/dist/lib/analytics/mix-commands.js +2 -2
- package/dist/lib/auth-health.d.ts +13 -3
- package/dist/lib/auth-health.js +50 -8
- package/dist/lib/browser/devices.d.ts +19 -0
- package/dist/lib/browser/devices.js +23 -0
- package/dist/lib/browser/drivers/ssh.d.ts +16 -2
- package/dist/lib/browser/drivers/ssh.js +36 -9
- package/dist/lib/browser/ipc.d.ts +1 -1
- package/dist/lib/browser/ipc.js +40 -9
- package/dist/lib/browser/profiles.d.ts +104 -65
- package/dist/lib/browser/profiles.js +277 -165
- package/dist/lib/browser/registry.d.ts +39 -0
- package/dist/lib/browser/registry.js +149 -0
- package/dist/lib/browser/remote-control.d.ts +28 -2
- package/dist/lib/browser/remote-control.js +35 -3
- package/dist/lib/browser/resolve-target.d.ts +68 -0
- package/dist/lib/browser/resolve-target.js +250 -0
- package/dist/lib/browser/runtime-state.d.ts +44 -11
- package/dist/lib/browser/runtime-state.js +98 -15
- package/dist/lib/browser/service.d.ts +65 -9
- package/dist/lib/browser/service.js +196 -61
- package/dist/lib/browser/sessions-list.d.ts +1 -3
- package/dist/lib/browser/sessions-list.js +3 -16
- package/dist/lib/browser/task-index.d.ts +70 -0
- package/dist/lib/browser/task-index.js +180 -0
- package/dist/lib/browser/types.d.ts +12 -1
- package/dist/lib/byok-usage.js +2 -2
- package/dist/lib/claude-statusline.d.ts +29 -0
- package/dist/lib/claude-statusline.js +139 -0
- package/dist/lib/cloud/host.d.ts +1 -1
- package/dist/lib/cloud/host.js +1 -1
- package/dist/lib/cloud/rush.js +1 -1
- package/dist/lib/codex-policy.d.ts +11 -1
- package/dist/lib/codex-policy.js +20 -5
- package/dist/lib/computer/computer-rpc.js +2 -2
- package/dist/lib/computer/download.d.ts +15 -15
- package/dist/lib/computer/download.js +35 -98
- package/dist/lib/computer/ssh-tunnel.d.ts +0 -3
- package/dist/lib/computer/ssh-tunnel.js +12 -22
- package/dist/lib/config-keys.d.ts +6 -11
- package/dist/lib/config-keys.js +18 -19
- package/dist/lib/crabbox/cli.d.ts +1 -1
- package/dist/lib/crabbox/cli.js +2 -2
- package/dist/lib/crabbox/lease.d.ts +9 -0
- package/dist/lib/crabbox/lease.js +70 -15
- package/dist/lib/crabbox/runtimes.js +1 -1
- package/dist/lib/daemon/account-state-daemon-service.d.ts +16 -0
- package/dist/lib/daemon/account-state-daemon-service.js +26 -0
- package/dist/lib/daemon/browser-ipc-service.d.ts +20 -0
- package/dist/lib/daemon/browser-ipc-service.js +46 -0
- package/dist/lib/daemon/daemon.d.ts +3 -0
- package/dist/lib/daemon/daemon.js +212 -444
- package/dist/lib/daemon/device-probe-service.d.ts +26 -0
- package/dist/lib/daemon/device-probe-service.js +47 -0
- package/dist/lib/daemon/keychain-reap-service.d.ts +17 -0
- package/dist/lib/daemon/keychain-reap-service.js +32 -0
- package/dist/lib/daemon/monitor-engine-service.d.ts +22 -0
- package/dist/lib/daemon/monitor-engine-service.js +29 -0
- package/dist/lib/daemon/runner.js +15 -2
- package/dist/lib/daemon/secrets-broker-service.d.ts +20 -0
- package/dist/lib/daemon/secrets-broker-service.js +66 -0
- package/dist/lib/daemon/self-heal-service.d.ts +27 -0
- package/dist/lib/daemon/self-heal-service.js +48 -0
- package/dist/lib/daemon/service.d.ts +102 -0
- package/dist/lib/daemon/service.js +98 -0
- package/dist/lib/daemon/session-index-service.d.ts +22 -0
- package/dist/lib/daemon/session-index-service.js +40 -0
- package/dist/lib/daemon/state-dir-check-service.d.ts +43 -0
- package/dist/lib/daemon/state-dir-check-service.js +63 -0
- package/dist/lib/daemon/supervisor.d.ts +87 -0
- package/dist/lib/daemon/supervisor.js +298 -0
- package/dist/lib/daemon/watchdog-service.d.ts +19 -0
- package/dist/lib/daemon/watchdog-service.js +40 -0
- package/dist/lib/daemon-health.d.ts +15 -0
- package/dist/lib/daemon-health.js +29 -5
- package/dist/lib/daemon-services.d.ts +8 -1
- package/dist/lib/daemon-services.js +55 -0
- package/dist/lib/daemon-ticks.d.ts +13 -2
- package/dist/lib/daemon-ticks.js +22 -20
- package/dist/lib/device-config.d.ts +25 -10
- package/dist/lib/device-config.js +90 -39
- package/dist/lib/devices/config-migration.d.ts +7 -0
- package/dist/lib/devices/config-migration.js +67 -3
- package/dist/lib/devices/health.d.ts +27 -2
- package/dist/lib/devices/health.js +39 -9
- package/dist/lib/devices/interactive-host.d.ts +15 -0
- package/dist/lib/devices/interactive-host.js +53 -0
- package/dist/lib/devices/registry.d.ts +61 -4
- package/dist/lib/devices/registry.js +94 -38
- package/dist/lib/devices/stats-cache.d.ts +26 -0
- package/dist/lib/devices/stats-cache.js +59 -7
- package/dist/lib/devices/worker-pick.d.ts +48 -0
- package/dist/lib/devices/worker-pick.js +113 -0
- package/dist/lib/exec.d.ts +70 -14
- package/dist/lib/exec.js +110 -38
- package/dist/lib/feed/answer.d.ts +29 -0
- package/dist/lib/feed/answer.js +145 -0
- package/dist/lib/feed/attention.d.ts +83 -0
- package/dist/lib/feed/attention.js +0 -0
- package/dist/lib/feed/feed.d.ts +109 -0
- package/dist/lib/feed/feed.js +154 -3
- package/dist/lib/feed/pr-status.d.ts +14 -0
- package/dist/lib/feed/pr-status.js +47 -0
- package/dist/lib/feed/watch.d.ts +59 -0
- package/dist/lib/feed/watch.js +180 -0
- package/dist/lib/fleet/capture.js +6 -0
- package/dist/lib/fleet/remote-login.js +8 -3
- package/dist/lib/fleet/types.d.ts +17 -0
- package/dist/lib/harness/adapter.d.ts +11 -0
- package/dist/lib/harness/adapters/claude.js +38 -24
- package/dist/lib/harness/adapters/codex.js +2 -4
- package/dist/lib/harness/adapters/cursor.js +8 -11
- package/dist/lib/helper-download.d.ts +108 -0
- package/dist/lib/helper-download.js +194 -0
- package/dist/lib/helper-versions.d.ts +52 -0
- package/dist/lib/helper-versions.js +58 -0
- package/dist/lib/hooks/cache.js +19 -0
- package/dist/lib/hooks/install.js +2 -2
- package/dist/lib/hooks/match.d.ts +3 -0
- package/dist/lib/hooks/match.js +25 -0
- package/dist/lib/hosts/credentials.d.ts +1 -1
- package/dist/lib/hosts/credentials.js +1 -1
- package/dist/lib/hosts/dispatch.d.ts +7 -1
- package/dist/lib/hosts/dispatch.js +24 -5
- package/dist/lib/hosts/passthrough.d.ts +8 -15
- package/dist/lib/hosts/passthrough.js +35 -16
- package/dist/lib/hosts/providers/devices.d.ts +1 -1
- package/dist/lib/hosts/providers/devices.js +1 -1
- package/dist/lib/hosts/reconnect.d.ts +179 -21
- package/dist/lib/hosts/reconnect.js +266 -45
- package/dist/lib/hosts/registry.js +7 -0
- package/dist/lib/installations/migrate.d.ts +4 -5
- package/dist/lib/installations/migrate.js +6 -7
- package/dist/lib/installations/shims.d.ts +1 -1
- package/dist/lib/installations/shims.js +23 -13
- package/dist/lib/installations/versions.js +25 -2
- package/dist/lib/linear-autoclose.d.ts +1 -1
- package/dist/lib/linear-autoclose.js +1 -1
- package/dist/lib/loop.d.ts +2 -2
- package/dist/lib/loop.js +1 -1
- package/dist/lib/menubar/download-menubar.d.ts +47 -0
- package/dist/lib/menubar/download-menubar.js +60 -0
- package/dist/lib/menubar/install-menubar.d.ts +44 -7
- package/dist/lib/menubar/install-menubar.js +105 -23
- package/dist/lib/menubar/notify-desktop.d.ts +3 -3
- package/dist/lib/menubar/notify-desktop.js +6 -6
- package/dist/lib/model-tiers.d.ts +1 -1
- package/dist/lib/open-url.d.ts +60 -2
- package/dist/lib/open-url.js +167 -12
- package/dist/lib/project-probe.d.ts +1 -1
- package/dist/lib/project-probe.js +1 -1
- package/dist/lib/project-pull.d.ts +1 -1
- package/dist/lib/project-pull.js +2 -2
- package/dist/lib/projects.d.ts +1 -1
- package/dist/lib/projects.js +1 -1
- package/dist/lib/refresh-coordinator.d.ts +1 -1
- package/dist/lib/refresh-coordinator.js +1 -1
- package/dist/lib/sandbox.js +6 -7
- package/dist/lib/secrets/download-keychain.d.ts +47 -0
- package/dist/lib/secrets/download-keychain.js +70 -0
- package/dist/lib/secrets/install-helper.d.ts +20 -1
- package/dist/lib/secrets/install-helper.js +44 -3
- package/dist/lib/session/active.d.ts +67 -0
- package/dist/lib/session/active.js +86 -2
- package/dist/lib/session/db.d.ts +11 -1
- package/dist/lib/session/db.js +142 -8
- package/dist/lib/session/discover.d.ts +23 -1
- package/dist/lib/session/discover.js +44 -10
- package/dist/lib/session/highlights.d.ts +49 -1
- package/dist/lib/session/highlights.js +95 -0
- package/dist/lib/session/host-link.d.ts +16 -2
- package/dist/lib/session/host-link.js +7 -1
- package/dist/lib/session/parse.js +33 -1
- package/dist/lib/session/prompt.d.ts +35 -0
- package/dist/lib/session/prompt.js +56 -0
- package/dist/lib/session/remote/remote-list.d.ts +9 -0
- package/dist/lib/session/remote/remote-list.js +19 -2
- package/dist/lib/session/remote/watch.d.ts +1 -1
- package/dist/lib/session/remote/watch.js +17 -3
- package/dist/lib/session/render.js +7 -3
- package/dist/lib/session/session-cache.d.ts +53 -0
- package/dist/lib/session/session-cache.js +93 -1
- package/dist/lib/session/state.js +1 -1
- package/dist/lib/session/tool-calls.d.ts +2 -0
- package/dist/lib/session/tool-calls.js +1 -1
- package/dist/lib/session/trajectory-compare.d.ts +47 -0
- package/dist/lib/session/trajectory-compare.js +116 -0
- package/dist/lib/session/trajectory-html.d.ts +19 -0
- package/dist/lib/session/trajectory-html.js +770 -0
- package/dist/lib/session/trajectory-lineage.d.ts +92 -0
- package/dist/lib/session/trajectory-lineage.js +195 -0
- package/dist/lib/session/trajectory-text.d.ts +37 -0
- package/dist/lib/session/trajectory-text.js +284 -0
- package/dist/lib/session/trajectory.d.ts +102 -0
- package/dist/lib/session/trajectory.js +384 -0
- package/dist/lib/session/types.d.ts +15 -0
- package/dist/lib/sha256-asset.d.ts +26 -0
- package/dist/lib/sha256-asset.js +41 -0
- package/dist/lib/share/backend.d.ts +95 -0
- package/dist/lib/share/backend.js +139 -0
- package/dist/lib/share/delete.d.ts +6 -2
- package/dist/lib/share/delete.js +8 -9
- package/dist/lib/share/html.d.ts +22 -0
- package/dist/lib/share/html.js +88 -0
- package/dist/lib/share/provision.d.ts +18 -4
- package/dist/lib/share/provision.js +38 -4
- package/dist/lib/share/publish.d.ts +39 -15
- package/dist/lib/share/publish.js +55 -26
- package/dist/lib/share/worker-template.js +171 -11
- package/dist/lib/sqlite.js +16 -3
- package/dist/lib/ssh-exec.d.ts +65 -0
- package/dist/lib/ssh-exec.js +120 -4
- package/dist/lib/startup/command-registry.d.ts +3 -1
- package/dist/lib/startup/command-registry.js +6 -3
- package/dist/lib/startup/dev-build.js +3 -3
- package/dist/lib/state.d.ts +2 -2
- package/dist/lib/state.js +11 -5
- package/dist/lib/subagents-registry.js +1 -1
- package/dist/lib/teams/agents.js +9 -0
- package/dist/lib/terminal/index.d.ts +1 -1
- package/dist/lib/terminal/inject.d.ts +19 -0
- package/dist/lib/terminal/inject.js +19 -9
- package/dist/lib/terminal/types.d.ts +1 -1
- package/dist/lib/terminal/types.js +1 -1
- package/dist/lib/tmux/orphan-reap.js +1 -1
- package/dist/lib/tmux/session.d.ts +46 -13
- package/dist/lib/tmux/session.js +204 -27
- package/dist/lib/traces/backend.d.ts +29 -0
- package/dist/lib/traces/backend.js +46 -0
- package/dist/lib/traces/classify.d.ts +52 -0
- package/dist/lib/traces/classify.js +72 -0
- package/dist/lib/traces/config.d.ts +10 -0
- package/dist/lib/traces/config.js +2 -0
- package/dist/lib/traces/provision.d.ts +19 -0
- package/dist/lib/traces/provision.js +30 -0
- package/dist/lib/traces/sync.d.ts +150 -0
- package/dist/lib/traces/sync.js +447 -0
- package/dist/lib/traces/worker-template.d.ts +4 -0
- package/dist/lib/traces/worker-template.js +159 -0
- package/dist/lib/types.d.ts +34 -19
- package/dist/lib/types.js +14 -0
- package/dist/lib/usage-backoff.d.ts +2 -1
- package/dist/lib/usage-backoff.js +60 -19
- package/dist/lib/usage-refresh.d.ts +18 -4
- package/dist/lib/usage-refresh.js +68 -19
- package/dist/lib/watchdog/log.d.ts +1 -1
- package/dist/lib/watchdog/log.js +1 -1
- package/dist/lib/watchdog/runner.d.ts +36 -45
- package/dist/lib/watchdog/runner.js +150 -119
- package/dist/lib/watchdog/service.d.ts +0 -1
- package/dist/lib/watchdog/service.js +0 -1
- package/dist/lib/watchdog/watchdog-agent.d.ts +38 -0
- package/dist/lib/watchdog/watchdog-agent.js +59 -0
- package/dist/lib/watchdog/watchdog.d.ts +11 -2
- package/dist/lib/watchdog/watchdog.js +37 -104
- package/dist/lib/workflows.d.ts +1 -1
- package/package.json +5 -7
- package/dist/commands/usage.d.ts +0 -27
- package/dist/commands/usage.js +0 -96
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +0 -24
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +0 -128
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -24
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +0 -123
- package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
- package/dist/lib/usage-fleet.d.ts +0 -32
- package/dist/lib/usage-fleet.js +0 -125
|
@@ -67,6 +67,11 @@ export const DAEMON_SERVICES = [
|
|
|
67
67
|
title: 'State-dir self-check',
|
|
68
68
|
description: 'Self-terminates the daemon if its state directory is removed.',
|
|
69
69
|
},
|
|
70
|
+
{
|
|
71
|
+
id: 'session-index',
|
|
72
|
+
title: 'Session-index warm',
|
|
73
|
+
description: 'Keeps this host\'s transcript index current so a locally-started session is discoverable within seconds.',
|
|
74
|
+
},
|
|
70
75
|
];
|
|
71
76
|
/** Stable order of service IDs. */
|
|
72
77
|
export const DAEMON_SERVICE_IDS = DAEMON_SERVICES.map((s) => s.id);
|
|
@@ -150,3 +155,53 @@ export function listDaemonServiceStates() {
|
|
|
150
155
|
const cfg = readDaemonServicesConfig();
|
|
151
156
|
return DAEMON_SERVICES.map((s) => ({ ...s, enabled: cfg.services[s.id] !== false }));
|
|
152
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* A tiny cross-process action queue for `agents daemon services restart <id>`
|
|
160
|
+
* (RUSH-3193 P4) — SIGHUP can only signal "reload", it carries no payload, so a
|
|
161
|
+
* restart request is dropped here first and the daemon's reload handler drains
|
|
162
|
+
* it. Same file-backed pattern as `services.yaml` above, for the same reason:
|
|
163
|
+
* the CLI invocation and the daemon are separate processes.
|
|
164
|
+
*/
|
|
165
|
+
function getDaemonServiceActionsPath() {
|
|
166
|
+
return path.join(getDaemonConfigDir(), 'service-actions.json');
|
|
167
|
+
}
|
|
168
|
+
/** Queue a live restart for `id`, to be picked up on the next SIGHUP reload. */
|
|
169
|
+
export function queueDaemonServiceRestart(id) {
|
|
170
|
+
const filePath = getDaemonServiceActionsPath();
|
|
171
|
+
let ids = [];
|
|
172
|
+
try {
|
|
173
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
174
|
+
if (Array.isArray(parsed.restart))
|
|
175
|
+
ids = parsed.restart;
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
// Missing or corrupt -> start a fresh queue.
|
|
179
|
+
}
|
|
180
|
+
if (!ids.includes(id))
|
|
181
|
+
ids.push(id);
|
|
182
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
183
|
+
atomicWriteFileSync(filePath, JSON.stringify({ restart: ids }), 'utf-8');
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Drain every queued restart request. Called once per SIGHUP reload by the
|
|
187
|
+
* daemon; never throws. Clears the file so a request is applied at most once.
|
|
188
|
+
*/
|
|
189
|
+
export function drainDaemonServiceRestartQueue() {
|
|
190
|
+
const filePath = getDaemonServiceActionsPath();
|
|
191
|
+
let ids = [];
|
|
192
|
+
try {
|
|
193
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
194
|
+
if (Array.isArray(parsed.restart))
|
|
195
|
+
ids = parsed.restart;
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
fs.unlinkSync(filePath);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// Already gone.
|
|
205
|
+
}
|
|
206
|
+
return ids;
|
|
207
|
+
}
|
|
@@ -67,8 +67,9 @@ export declare function runFleetCacheWarmTick(): Promise<void>;
|
|
|
67
67
|
/**
|
|
68
68
|
* Usage refresh: keep the usage cache the `agents run` router reads
|
|
69
69
|
* (RUSH-2061, readOnly hot path) fresh, WITHOUT the hot path ever fetching.
|
|
70
|
-
*
|
|
71
|
-
*
|
|
70
|
+
* Every host is its own provider-facing writer — it refreshes only the
|
|
71
|
+
* accounts it holds credentials for, straight from the provider APIs
|
|
72
|
+
* (RUSH-3193 #15; no cross-host broadcast).
|
|
72
73
|
*/
|
|
73
74
|
export declare function runUsageRefreshTick(): Promise<void>;
|
|
74
75
|
/**
|
|
@@ -79,9 +80,19 @@ export declare function runUsageRefreshTick(): Promise<void>;
|
|
|
79
80
|
* Cadence matches {@link DEFAULT_ACTIVE_CACHE_MAX_AGE_MS} so one-shot readers and
|
|
80
81
|
* long-lived watchers share one writer. Without this tick the journal has no
|
|
81
82
|
* continuous producer and Factory freezes after the initial cache snapshot.
|
|
83
|
+
*
|
|
84
|
+
* Gated on reader presence (RUSH-3193): when no `sessions watch` / `feed watch`
|
|
85
|
+
* consumer has checked in within {@link ACTIVE_SESSIONS_READER_IDLE_WINDOW_MS} the
|
|
86
|
+
* expensive `ps`+`lsof` gather is skipped entirely on this scheduled tick. A
|
|
87
|
+
* watcher connecting to a cold/idle daemon is NOT served by this tick alone —
|
|
88
|
+
* {@link noteActiveSessionsJournalReader} only writes a presence timestamp, with
|
|
89
|
+
* no path back to this timer — so the daemon separately runs
|
|
90
|
+
* {@link watchActiveSessionsReaderPresence} to detect that idle→recent edge and
|
|
91
|
+
* fire an out-of-band call into this same function immediately (RUSH-2484).
|
|
82
92
|
*/
|
|
83
93
|
export declare function runActiveSessionsWarmTick(opts?: {
|
|
84
94
|
gather?: () => Promise<import('./session/active.js').ActiveSession[]>;
|
|
95
|
+
nowMs?: number;
|
|
85
96
|
}): Promise<{
|
|
86
97
|
sessions: number;
|
|
87
98
|
}>;
|
package/dist/lib/daemon-ticks.js
CHANGED
|
@@ -89,7 +89,7 @@ export async function refreshLocalFleetAuthState(opts) {
|
|
|
89
89
|
// Fleet status publishes every tick regardless — it does not ride that endpoint.
|
|
90
90
|
const cached = readFleetAuthRows(self);
|
|
91
91
|
const reuse = shouldReuseCachedAuthProbe(force, cached, requestedAt);
|
|
92
|
-
const authRows = reuse ? cached : await probeLocalFleetAuth({ cliVersion: getCliVersion() });
|
|
92
|
+
const authRows = reuse ? cached : await probeLocalFleetAuth({ cliVersion: getCliVersion(), forceLive: force });
|
|
93
93
|
if (!reuse)
|
|
94
94
|
writeFleetAuthRows(self, authRows);
|
|
95
95
|
const row = await publishLocalFleetStatus(self);
|
|
@@ -108,35 +108,23 @@ export async function runFleetCacheWarmTick() {
|
|
|
108
108
|
/**
|
|
109
109
|
* Usage refresh: keep the usage cache the `agents run` router reads
|
|
110
110
|
* (RUSH-2061, readOnly hot path) fresh, WITHOUT the hot path ever fetching.
|
|
111
|
-
*
|
|
112
|
-
*
|
|
111
|
+
* Every host is its own provider-facing writer — it refreshes only the
|
|
112
|
+
* accounts it holds credentials for, straight from the provider APIs
|
|
113
|
+
* (RUSH-3193 #15; no cross-host broadcast).
|
|
113
114
|
*/
|
|
114
115
|
export async function runUsageRefreshTick() {
|
|
115
|
-
const { resolveUsagePrimaryHost } = await import('./device-config.js');
|
|
116
|
-
const { machineId } = await import('./machine-id.js');
|
|
117
|
-
// usage.primary-host, else interactive.host, else null (standalone local refresh)
|
|
118
|
-
const primaryHost = resolveUsagePrimaryHost() ?? undefined;
|
|
119
|
-
const self = machineId();
|
|
120
|
-
const { importUsageFleetFromHost, usageRefreshRole } = await import('./usage-fleet.js');
|
|
121
|
-
if (usageRefreshRole(primaryHost, self) === 'subscriber') {
|
|
122
|
-
const imported = await importUsageFleetFromHost(primaryHost);
|
|
123
|
-
console.log(`usage refresh: imported ${Object.keys(imported.usage).length} account(s) from primary host ${primaryHost}`);
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
116
|
const { runUsageRefresh, buildLocalUsageAccounts } = await import('./usage-refresh.js');
|
|
127
117
|
const { writeClaudeUsageCache } = await import('./accounting/usage.js');
|
|
128
118
|
const { usageRateLimitedUntil } = await import('./usage-backoff.js');
|
|
129
119
|
const r = await runUsageRefresh({
|
|
130
120
|
listAccounts: buildLocalUsageAccounts,
|
|
131
121
|
writeUsageCache: writeClaudeUsageCache,
|
|
132
|
-
backoffUntil: usageRateLimitedUntil,
|
|
122
|
+
backoffUntil: (agentId, usageKey) => usageRateLimitedUntil(agentId, Date.now(), usageKey),
|
|
133
123
|
});
|
|
134
124
|
const { listProfiles } = await import('./profiles.js');
|
|
135
125
|
const { refreshDueByokUsage } = await import('./byok-usage.js');
|
|
136
126
|
const byok = await refreshDueByokUsage(listProfiles());
|
|
137
|
-
|
|
138
|
-
const published = exportUsageFleet();
|
|
139
|
-
console.log(`usage refresh: ${r.refreshed} refreshed, ${r.failed} failed, ${r.skippedNotDue} not-due, ${r.skippedBackoff} backed-off, ${r.skippedCap} capped; BYOK ${byok.refreshed} refreshed, ${byok.skipped} not-due; published ${Object.keys(published.usage).length} account(s)`);
|
|
127
|
+
console.log(`usage refresh: ${r.refreshed} refreshed, ${r.failed} failed, ${r.skippedNotDue} not-due, ${r.skippedBackoff} backed-off, ${r.skippedCap} capped; BYOK ${byok.refreshed} refreshed, ${byok.skipped} not-due`);
|
|
140
128
|
}
|
|
141
129
|
/**
|
|
142
130
|
* Active-sessions warm (RUSH-2062 / RUSH-2484): publish THIS host's live session
|
|
@@ -146,10 +134,24 @@ export async function runUsageRefreshTick() {
|
|
|
146
134
|
* Cadence matches {@link DEFAULT_ACTIVE_CACHE_MAX_AGE_MS} so one-shot readers and
|
|
147
135
|
* long-lived watchers share one writer. Without this tick the journal has no
|
|
148
136
|
* continuous producer and Factory freezes after the initial cache snapshot.
|
|
137
|
+
*
|
|
138
|
+
* Gated on reader presence (RUSH-3193): when no `sessions watch` / `feed watch`
|
|
139
|
+
* consumer has checked in within {@link ACTIVE_SESSIONS_READER_IDLE_WINDOW_MS} the
|
|
140
|
+
* expensive `ps`+`lsof` gather is skipped entirely on this scheduled tick. A
|
|
141
|
+
* watcher connecting to a cold/idle daemon is NOT served by this tick alone —
|
|
142
|
+
* {@link noteActiveSessionsJournalReader} only writes a presence timestamp, with
|
|
143
|
+
* no path back to this timer — so the daemon separately runs
|
|
144
|
+
* {@link watchActiveSessionsReaderPresence} to detect that idle→recent edge and
|
|
145
|
+
* fire an out-of-band call into this same function immediately (RUSH-2484).
|
|
149
146
|
*/
|
|
150
147
|
export async function runActiveSessionsWarmTick(opts = {}) {
|
|
151
|
-
const { publishLocalActiveSessions } = await import('./session/session-cache.js');
|
|
152
|
-
const
|
|
148
|
+
const { publishLocalActiveSessions, isActiveSessionsJournalReaderRecent } = await import('./session/session-cache.js');
|
|
149
|
+
const nowMs = opts.nowMs ?? Date.now();
|
|
150
|
+
if (!isActiveSessionsJournalReaderRecent(nowMs)) {
|
|
151
|
+
console.log('active-sessions warm: idle (no recent reader), skipping gather');
|
|
152
|
+
return { sessions: 0 };
|
|
153
|
+
}
|
|
154
|
+
const r = await publishLocalActiveSessions({ gather: opts.gather, nowMs });
|
|
153
155
|
console.log(`active-sessions warm: ${r.sessions.length} session(s) published`);
|
|
154
156
|
return { sessions: r.sessions.length };
|
|
155
157
|
}
|
|
@@ -124,8 +124,6 @@ export declare function listConfig(opts?: ConfigTarget): ConfigEntry[];
|
|
|
124
124
|
/** List user-scope config keys with their values. Used to show inherited settings
|
|
125
125
|
* in per-device views without implying those keys are device-local. */
|
|
126
126
|
export declare function listUserConfig(): ConfigEntry[];
|
|
127
|
-
/** Resolve the explicit usage host, falling back to the user's interactive host. */
|
|
128
|
-
export declare function resolveUsagePrimaryHost(): string | null;
|
|
129
127
|
/**
|
|
130
128
|
* Set a config key (validated). Device-scope keys target this machine unless
|
|
131
129
|
* `opts.device` names a peer; `opts.fleet` writes the fleet-wide defaults layer
|
|
@@ -147,8 +145,32 @@ export type AutoPoolMode = (typeof AUTO_POOL_MODES)[number];
|
|
|
147
145
|
* explicit `worker` mark (see {@link listConfiguredDeviceRoles}).
|
|
148
146
|
*/
|
|
149
147
|
export declare function configuredDeviceRole(name: string): ConfiguredDeviceRole | undefined;
|
|
148
|
+
/**
|
|
149
|
+
* The role marked on THIS machine — the one running the CLI — or undefined when
|
|
150
|
+
* it was never marked. Keyed off {@link machineId} (overridable via
|
|
151
|
+
* AGENTS_SYNC_MACHINE_ID), so it matches the device's own config-folder key.
|
|
152
|
+
*
|
|
153
|
+
* The auth strategy reads this: a `personal` device (the user's own interactive
|
|
154
|
+
* box) holds a real per-version login and MUST authenticate from it for EVERY
|
|
155
|
+
* run, interactive or headless; only a `worker` uses the file-based setup-token
|
|
156
|
+
* (RUSH-2395). `undefined` is treated as non-personal (worker-equivalent) by
|
|
157
|
+
* that gate — an unmarked box has no login to defer to.
|
|
158
|
+
*/
|
|
159
|
+
export declare function selfConfiguredDeviceRole(): ConfiguredDeviceRole | undefined;
|
|
150
160
|
/** Mark a device's role fleet-wide; `undefined` clears the mark. */
|
|
151
161
|
export declare function setConfiguredDeviceRole(name: string, role: ConfiguredDeviceRole | undefined): void;
|
|
162
|
+
/**
|
|
163
|
+
* Devices whose OWN config pins one of the browser profile keys to `profile`.
|
|
164
|
+
*
|
|
165
|
+
* Used by `profiles rename` to warn rather than silently leave a peer pointing at
|
|
166
|
+
* a name that no longer exists. Deliberately read-only: rewriting another
|
|
167
|
+
* machine's device doc from here would be a cross-machine mutation nobody asked
|
|
168
|
+
* for, and the pin may well be correct there (a local profile of the same name).
|
|
169
|
+
*/
|
|
170
|
+
export declare function devicesPinningBrowserProfile(profile: string): Array<{
|
|
171
|
+
device: string;
|
|
172
|
+
key: 'browser.profile' | 'browser.viewer';
|
|
173
|
+
}>;
|
|
152
174
|
/**
|
|
153
175
|
* Every device with an effective role, keyed by device name. Layers like every
|
|
154
176
|
* other device-scope key: the fleet default (central fleet.defaults.config)
|
|
@@ -194,14 +216,7 @@ export declare function isSchedulerEnabled(): boolean;
|
|
|
194
216
|
* scheduler init) refuses with.
|
|
195
217
|
*/
|
|
196
218
|
export declare function assertSchedulerEnabled(): void;
|
|
197
|
-
/**
|
|
198
|
-
* True unless this machine's config turns off the managed tmux wrap for
|
|
199
|
-
* interactive `agents run` launches (`tmux.enabled=false`).
|
|
200
|
-
*
|
|
201
|
-
* Read as one of the guards in `shouldWrapInTmux` (lib/exec.ts) — the durable,
|
|
202
|
-
* per-machine form of `--no-tmux` / `AGENTS_NO_TMUX=1`, for a box whose tmux is
|
|
203
|
-
* broken or unwanted. Unset means today's behavior: wrap.
|
|
204
|
-
*/
|
|
219
|
+
/** True only when this machine explicitly enables the managed tmux wrap. */
|
|
205
220
|
export declare function isTmuxEnabled(): boolean;
|
|
206
221
|
/** True unless this machine's config disables the daemon outright (top-level kill switch). */
|
|
207
222
|
export declare function isDaemonEnabled(): boolean;
|
|
@@ -36,7 +36,7 @@ import * as yaml from 'yaml';
|
|
|
36
36
|
import { META_HEADER, getUserAgentsDir, readMeta, updateMeta, withMetaLock } from './state.js';
|
|
37
37
|
import { atomicWriteFileSync } from './fs-atomic.js';
|
|
38
38
|
import { machineId } from './machine-id.js';
|
|
39
|
-
import { assertValidDeviceName } from './devices/registry.js';
|
|
39
|
+
import { assertValidDeviceName, assertRegistrableDeviceName } from './devices/registry.js';
|
|
40
40
|
import { migrateDeviceConfigStores } from './devices/config-migration.js';
|
|
41
41
|
const DEVICE_PLATFORMS = ['windows', 'linux', 'macos', 'unknown'];
|
|
42
42
|
const SSH_AUTH_METHODS = ['key', 'password'];
|
|
@@ -53,23 +53,7 @@ export const CONFIG_KEYS = [
|
|
|
53
53
|
description: 'Device that shows the user artifacts (browser opens, dashboards) — the "online macOS box" skills should use instead of guessing.',
|
|
54
54
|
validate: (v) => {
|
|
55
55
|
try {
|
|
56
|
-
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
catch (err) {
|
|
60
|
-
return err?.message ?? String(err);
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
name: 'usage.primary-host',
|
|
66
|
-
yamlKey: 'usagePrimaryHost',
|
|
67
|
-
scope: 'user',
|
|
68
|
-
type: 'string',
|
|
69
|
-
description: 'Device whose usage snapshots are authoritative for fleet-wide usage reporting.',
|
|
70
|
-
validate: (v) => {
|
|
71
|
-
try {
|
|
72
|
-
assertValidDeviceName(v);
|
|
56
|
+
assertRegistrableDeviceName(v);
|
|
73
57
|
return null;
|
|
74
58
|
}
|
|
75
59
|
catch (err) {
|
|
@@ -90,6 +74,18 @@ export const CONFIG_KEYS = [
|
|
|
90
74
|
? null
|
|
91
75
|
: `auto.pool must be one of ${AUTO_POOL_MODES.join(' | ')}.`,
|
|
92
76
|
},
|
|
77
|
+
{
|
|
78
|
+
name: 'browser.viewer',
|
|
79
|
+
yamlKey: 'browserViewer',
|
|
80
|
+
scope: 'device',
|
|
81
|
+
visibility: 'machine',
|
|
82
|
+
type: 'string',
|
|
83
|
+
description: "Which browser THIS machine shows YOU a page in (an .html artifact, `agents feedback`, a login " +
|
|
84
|
+
"dashboard): a profile name, or `os` for the OS default handler. Unset follows browser.profile. " +
|
|
85
|
+
"Distinct from browser.profile, which is the profile agents DRIVE — see RUSH-2709 for why " +
|
|
86
|
+
"collapsing the two was a mistake. Set it to `os` to keep the OS default handler.",
|
|
87
|
+
validate: (v) => typeof v === 'string' && v.length > 0 ? null : 'browser.viewer must be `os` or a profile name.',
|
|
88
|
+
},
|
|
93
89
|
{
|
|
94
90
|
name: 'browser.profile',
|
|
95
91
|
yamlKey: 'defaultBrowserProfile',
|
|
@@ -144,11 +140,10 @@ export const CONFIG_KEYS = [
|
|
|
144
140
|
scope: 'device',
|
|
145
141
|
visibility: 'machine',
|
|
146
142
|
type: 'bool',
|
|
147
|
-
defaultValue:
|
|
143
|
+
defaultValue: false,
|
|
148
144
|
description: 'Whether an interactive `agents run` on this device is wrapped in the shared-socket tmux session. ' +
|
|
149
|
-
'
|
|
150
|
-
'`agents
|
|
151
|
-
'form of `--no-tmux`, for a machine whose tmux is broken or unwanted.',
|
|
145
|
+
'Off, the default, spawns the agent directly. Turn it on to give every agent an addressable pane for ' +
|
|
146
|
+
'`agents message`, injection, and `agents focus` once the tmux mouse, clipboard, and scrollback behavior suits this device.',
|
|
152
147
|
},
|
|
153
148
|
{
|
|
154
149
|
name: 'browser.remote-control',
|
|
@@ -170,7 +165,7 @@ export const CONFIG_KEYS = [
|
|
|
170
165
|
description: 'Minutes a browser task may sit with no IPC action (navigate, click, type, screenshot, …) before the daemon\'s ' +
|
|
171
166
|
'abandoned-task reaper closes its tabs and marks it done (RUSH-2622). 0 disables idle reaping — the reaper still ' +
|
|
172
167
|
"closes a task whose owning agent session has exited, whatever this is set to. Read only on THIS box's own " +
|
|
173
|
-
'reaper tick and `agents browser
|
|
168
|
+
'reaper tick and `agents browser prune`, so it never applies to a peer.',
|
|
174
169
|
validate: (v) => v >= 0 ? null : 'browser.task-idle-minutes must be >= 0 (0 disables idle reaping).',
|
|
175
170
|
},
|
|
176
171
|
{
|
|
@@ -179,7 +174,26 @@ export const CONFIG_KEYS = [
|
|
|
179
174
|
scope: 'device',
|
|
180
175
|
visibility: 'shared',
|
|
181
176
|
type: 'string-list',
|
|
182
|
-
description: 'Free-form operator notes about this device (one entry per `agents devices config <name> notes <text>`).'
|
|
177
|
+
description: 'Free-form operator notes about this device (one entry per `agents devices config <name> notes <text>`). ' +
|
|
178
|
+
'Long-form scratch, never shown in device listings — for the one-line synced summary of what the box is for, use `description`.',
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: 'description',
|
|
182
|
+
yamlKey: 'description',
|
|
183
|
+
scope: 'device',
|
|
184
|
+
visibility: 'shared',
|
|
185
|
+
type: 'string',
|
|
186
|
+
description: 'One line saying what this device is FOR ("gpu box — cuda 12.4"), synced fleet-wide. Kept to one short line ' +
|
|
187
|
+
'because the device-list renderer will show it (RUSH-3062, `surface` track). ' +
|
|
188
|
+
'Replaces the value on each set; for appended long-form scratch use `notes`.',
|
|
189
|
+
validate: (v) => {
|
|
190
|
+
const s = v;
|
|
191
|
+
if (s.includes('\n') || s.includes('\r'))
|
|
192
|
+
return 'description must be a single line.';
|
|
193
|
+
if (s.length > 80)
|
|
194
|
+
return `description must be at most 80 characters (got ${s.length}) — it is a one-line table cell.`;
|
|
195
|
+
return null;
|
|
196
|
+
},
|
|
183
197
|
},
|
|
184
198
|
{
|
|
185
199
|
name: 'ssh.user',
|
|
@@ -456,12 +470,6 @@ export function listConfig(opts) {
|
|
|
456
470
|
export function listUserConfig() {
|
|
457
471
|
return CONFIG_KEYS.filter((spec) => spec.scope === 'user').map((spec) => getConfigValue(spec.name));
|
|
458
472
|
}
|
|
459
|
-
/** Resolve the explicit usage host, falling back to the user's interactive host. */
|
|
460
|
-
export function resolveUsagePrimaryHost() {
|
|
461
|
-
return getConfigValue('usage.primary-host').value
|
|
462
|
-
?? getConfigValue('interactive.host').value
|
|
463
|
-
?? null;
|
|
464
|
-
}
|
|
465
473
|
// ─── Writes ───────────────────────────────────────────────────────────────────
|
|
466
474
|
/** The fleet manifest for a defaults write: `devices` materializes as an
|
|
467
475
|
* explicit empty map (NOT 'all') so `agents apply` targets nothing until the
|
|
@@ -593,6 +601,20 @@ export function configuredDeviceRole(name) {
|
|
|
593
601
|
assertValidDeviceName(name);
|
|
594
602
|
return getConfigValue('role', { device: name }).value;
|
|
595
603
|
}
|
|
604
|
+
/**
|
|
605
|
+
* The role marked on THIS machine — the one running the CLI — or undefined when
|
|
606
|
+
* it was never marked. Keyed off {@link machineId} (overridable via
|
|
607
|
+
* AGENTS_SYNC_MACHINE_ID), so it matches the device's own config-folder key.
|
|
608
|
+
*
|
|
609
|
+
* The auth strategy reads this: a `personal` device (the user's own interactive
|
|
610
|
+
* box) holds a real per-version login and MUST authenticate from it for EVERY
|
|
611
|
+
* run, interactive or headless; only a `worker` uses the file-based setup-token
|
|
612
|
+
* (RUSH-2395). `undefined` is treated as non-personal (worker-equivalent) by
|
|
613
|
+
* that gate — an unmarked box has no login to defer to.
|
|
614
|
+
*/
|
|
615
|
+
export function selfConfiguredDeviceRole() {
|
|
616
|
+
return configuredDeviceRole(machineId());
|
|
617
|
+
}
|
|
596
618
|
/** Mark a device's role fleet-wide; `undefined` clears the mark. */
|
|
597
619
|
export function setConfiguredDeviceRole(name, role) {
|
|
598
620
|
assertValidDeviceName(name);
|
|
@@ -601,6 +623,42 @@ export function setConfiguredDeviceRole(name, role) {
|
|
|
601
623
|
else
|
|
602
624
|
setConfigValue('role', role, { device: name });
|
|
603
625
|
}
|
|
626
|
+
/**
|
|
627
|
+
* Devices whose OWN config pins one of the browser profile keys to `profile`.
|
|
628
|
+
*
|
|
629
|
+
* Used by `profiles rename` to warn rather than silently leave a peer pointing at
|
|
630
|
+
* a name that no longer exists. Deliberately read-only: rewriting another
|
|
631
|
+
* machine's device doc from here would be a cross-machine mutation nobody asked
|
|
632
|
+
* for, and the pin may well be correct there (a local profile of the same name).
|
|
633
|
+
*/
|
|
634
|
+
export function devicesPinningBrowserProfile(profile) {
|
|
635
|
+
ensureDeviceConfigMigrated();
|
|
636
|
+
// Reports WHICH key each device used, not just that it matched. A caller
|
|
637
|
+
// telling the user to fix `browser.profile` on a device that actually pinned
|
|
638
|
+
// `browser.viewer` leaves the real pin broken and sets a second key nobody
|
|
639
|
+
// asked for.
|
|
640
|
+
const hits = [];
|
|
641
|
+
const devicesRoot = path.join(getUserAgentsDir(), 'devices');
|
|
642
|
+
let names = [];
|
|
643
|
+
try {
|
|
644
|
+
names = fs
|
|
645
|
+
.readdirSync(devicesRoot, { withFileTypes: true })
|
|
646
|
+
.filter((e) => e.isDirectory())
|
|
647
|
+
.map((e) => e.name);
|
|
648
|
+
}
|
|
649
|
+
catch {
|
|
650
|
+
return hits;
|
|
651
|
+
}
|
|
652
|
+
for (const name of names.sort()) {
|
|
653
|
+
const cfg = readDeviceDocConfig(name);
|
|
654
|
+
// A device can pin BOTH; each needs its own fix line.
|
|
655
|
+
if (cfg.defaultBrowserProfile === profile)
|
|
656
|
+
hits.push({ device: name, key: 'browser.profile' });
|
|
657
|
+
if (cfg.browserViewer === profile)
|
|
658
|
+
hits.push({ device: name, key: 'browser.viewer' });
|
|
659
|
+
}
|
|
660
|
+
return hits;
|
|
661
|
+
}
|
|
604
662
|
/**
|
|
605
663
|
* Every device with an effective role, keyed by device name. Layers like every
|
|
606
664
|
* other device-scope key: the fleet default (central fleet.defaults.config)
|
|
@@ -720,16 +778,9 @@ export function assertSchedulerEnabled() {
|
|
|
720
778
|
throw new Error(`The routines scheduler is disabled on this device (scheduler.enabled=false in ~/.agents/devices/${machineId()}/agents.yaml). ` +
|
|
721
779
|
`Re-enable with: agents devices config ${machineId()} scheduler.enabled on`);
|
|
722
780
|
}
|
|
723
|
-
/**
|
|
724
|
-
* True unless this machine's config turns off the managed tmux wrap for
|
|
725
|
-
* interactive `agents run` launches (`tmux.enabled=false`).
|
|
726
|
-
*
|
|
727
|
-
* Read as one of the guards in `shouldWrapInTmux` (lib/exec.ts) — the durable,
|
|
728
|
-
* per-machine form of `--no-tmux` / `AGENTS_NO_TMUX=1`, for a box whose tmux is
|
|
729
|
-
* broken or unwanted. Unset means today's behavior: wrap.
|
|
730
|
-
*/
|
|
781
|
+
/** True only when this machine explicitly enables the managed tmux wrap. */
|
|
731
782
|
export function isTmuxEnabled() {
|
|
732
|
-
return getConfigValue('tmux.enabled').value
|
|
783
|
+
return getConfigValue('tmux.enabled').value === true;
|
|
733
784
|
}
|
|
734
785
|
/** True unless this machine's config disables the daemon outright (top-level kill switch). */
|
|
735
786
|
export function isDaemonEnabled() {
|
|
@@ -13,6 +13,13 @@
|
|
|
13
13
|
* `.history/devices/pins-<host>.json` (pins file wins on conflict — it
|
|
14
14
|
* is the destination); peers' pins are simply dropped from the tracked
|
|
15
15
|
* file (each peer owns/rewrites its own pins locally).
|
|
16
|
+
* (e) legacy `.history/devices/ignored.json` (per-machine, UNTRACKED — a
|
|
17
|
+
* dismissal never reached the rest of the fleet) folds into the central
|
|
18
|
+
* TRACKED `fleet.ignored` list (RUSH-3062). Entries keep the legacy
|
|
19
|
+
* file's `updatedAt` as `ignoredAt` and take THIS machine's id as
|
|
20
|
+
* `ignoredOn` — the legacy store recorded neither, so the folding box
|
|
21
|
+
* is the only attribution available. The legacy file is removed only
|
|
22
|
+
* AFTER the central write lands.
|
|
16
23
|
*
|
|
17
24
|
* What stays put: a device doc's existing `config:` (already the right home)
|
|
18
25
|
* and its `routines:` list (operator-owned, read cross-device by
|
|
@@ -13,6 +13,13 @@
|
|
|
13
13
|
* `.history/devices/pins-<host>.json` (pins file wins on conflict — it
|
|
14
14
|
* is the destination); peers' pins are simply dropped from the tracked
|
|
15
15
|
* file (each peer owns/rewrites its own pins locally).
|
|
16
|
+
* (e) legacy `.history/devices/ignored.json` (per-machine, UNTRACKED — a
|
|
17
|
+
* dismissal never reached the rest of the fleet) folds into the central
|
|
18
|
+
* TRACKED `fleet.ignored` list (RUSH-3062). Entries keep the legacy
|
|
19
|
+
* file's `updatedAt` as `ignoredAt` and take THIS machine's id as
|
|
20
|
+
* `ignoredOn` — the legacy store recorded neither, so the folding box
|
|
21
|
+
* is the only attribution available. The legacy file is removed only
|
|
22
|
+
* AFTER the central write lands.
|
|
16
23
|
*
|
|
17
24
|
* What stays put: a device doc's existing `config:` (already the right home)
|
|
18
25
|
* and its `routines:` list (operator-owned, read cross-device by
|
|
@@ -33,9 +40,10 @@
|
|
|
33
40
|
import * as fs from 'fs';
|
|
34
41
|
import * as path from 'path';
|
|
35
42
|
import * as yaml from 'yaml';
|
|
36
|
-
import { META_HEADER, getDevicesAutoLaunchPath, getDevicePinsPath, getUserAgentsDir, readMeta, updateMeta, withMetaLock, } from '../state.js';
|
|
43
|
+
import { META_HEADER, getDevicesAutoLaunchPath, getDevicesIgnoredPath, getDevicePinsPath, getUserAgentsDir, readMeta, updateMeta, withMetaLock, } from '../state.js';
|
|
37
44
|
import { atomicWriteFileSync } from '../fs-atomic.js';
|
|
38
45
|
import { machineId } from '../machine-id.js';
|
|
46
|
+
import { withIgnoredAdded } from './registry.js';
|
|
39
47
|
/** Read every device doc. A doc that fails to parse is loudly skipped. */
|
|
40
48
|
function readDeviceDocs(devicesRoot) {
|
|
41
49
|
const docs = [];
|
|
@@ -124,6 +132,32 @@ function readAutoLaunchFlags(autoLaunchPath) {
|
|
|
124
132
|
function isConfigMap(v) {
|
|
125
133
|
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
126
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* The legacy per-machine ignored.json. Absent => null. A parse failure is
|
|
137
|
+
* logged loudly and reported as `undefined` so the caller LEAVES the file in
|
|
138
|
+
* place for a later retry — never silently emptied, the same corruption
|
|
139
|
+
* contract the ignore-list itself keeps (registry.ts's loadIgnoredEntries).
|
|
140
|
+
*/
|
|
141
|
+
function readLegacyIgnoredFile(p) {
|
|
142
|
+
let raw;
|
|
143
|
+
try {
|
|
144
|
+
raw = fs.readFileSync(p, 'utf-8');
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return null; // absent — nothing to fold
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
const parsed = JSON.parse(raw);
|
|
151
|
+
return {
|
|
152
|
+
names: Array.isArray(parsed.ignored) ? parsed.ignored.filter((n) => typeof n === 'string') : [],
|
|
153
|
+
updatedAt: typeof parsed.updatedAt === 'string' ? parsed.updatedAt : undefined,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
console.error(`device config migration: could not parse ${p} (${err.message}); leaving it for a later retry`);
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
127
161
|
/**
|
|
128
162
|
* Fold every legacy device-config/pins location into the current layout. Safe
|
|
129
163
|
* to call on every boot / config access: cheap no-op once folded.
|
|
@@ -139,6 +173,9 @@ export function migrateDeviceConfigStores() {
|
|
|
139
173
|
const centralHasConfig = Object.values(centralDevices).some((ov) => ov?.config && Object.keys(ov.config).length > 0);
|
|
140
174
|
const autoLaunchFlags = readAutoLaunchFlags(autoLaunchPath);
|
|
141
175
|
const docs = readDeviceDocs(devicesRoot);
|
|
176
|
+
const legacyIgnoredPath = getDevicesIgnoredPath();
|
|
177
|
+
const legacyIgnoredPending = fs.existsSync(legacyIgnoredPath);
|
|
178
|
+
const legacyIgnored = legacyIgnoredPending ? readLegacyIgnoredFile(legacyIgnoredPath) : null;
|
|
142
179
|
const pinsPath = getDevicePinsPath();
|
|
143
180
|
let selfPins = {};
|
|
144
181
|
try {
|
|
@@ -172,7 +209,7 @@ export function migrateDeviceConfigStores() {
|
|
|
172
209
|
: undefined;
|
|
173
210
|
const hasDestinationWork = plans.length > 0 || newDocs.length > 0 || docAgents !== undefined || docIsolated !== undefined;
|
|
174
211
|
const autoLaunchPending = fs.existsSync(autoLaunchPath);
|
|
175
|
-
if (!centralHasConfig && !hasDestinationWork && !autoLaunchPending)
|
|
212
|
+
if (!centralHasConfig && !hasDestinationWork && !autoLaunchPending && !legacyIgnoredPending)
|
|
176
213
|
return;
|
|
177
214
|
// ── 2. Destination writes FIRST (crash-safe), under the meta lock so they
|
|
178
215
|
// serialize against writeMetaUnlocked's own read-merge-write of the doc.
|
|
@@ -221,7 +258,18 @@ export function migrateDeviceConfigStores() {
|
|
|
221
258
|
nextDevices[name] = rest;
|
|
222
259
|
}
|
|
223
260
|
const fleet = { ...m.fleet, devices: nextDevices };
|
|
224
|
-
|
|
261
|
+
// Drop the whole `fleet` block only when NOTHING else lives in it. Every
|
|
262
|
+
// resident must be named here: `ignored` holds the user's dismissals and
|
|
263
|
+
// `discovery` their discovery policy, and deleting the block would not
|
|
264
|
+
// just lose them locally — `agents repo push` would sync the deletion
|
|
265
|
+
// fleet-wide. Add any new fleet.* key to this guard.
|
|
266
|
+
const fleetIsEmpty = Object.keys(nextDevices).length === 0 &&
|
|
267
|
+
!fleet.defaults &&
|
|
268
|
+
!fleet.secrets &&
|
|
269
|
+
!fleet.routines &&
|
|
270
|
+
!fleet.discovery &&
|
|
271
|
+
!(fleet.ignored && fleet.ignored.length > 0);
|
|
272
|
+
if (fleetIsEmpty) {
|
|
225
273
|
const { fleet: _, ...rest } = m;
|
|
226
274
|
void _;
|
|
227
275
|
return rest;
|
|
@@ -238,6 +286,22 @@ export function migrateDeviceConfigStores() {
|
|
|
238
286
|
console.error(`device config migration: could not remove ${autoLaunchPath} (${err.message}); a later run retries`);
|
|
239
287
|
}
|
|
240
288
|
}
|
|
289
|
+
// ── 4. Legacy ignored.json → central fleet.ignored ───────────────────────
|
|
290
|
+
// Destination write (updateMeta: withMetaLock + atomic write) lands BEFORE
|
|
291
|
+
// the legacy file is removed, so a crash re-folds on the next run and
|
|
292
|
+
// withIgnoredAdded's union-by-name makes that a no-op. A file that failed to
|
|
293
|
+
// parse (legacyIgnored === undefined) is left in place for that retry.
|
|
294
|
+
if (legacyIgnored) {
|
|
295
|
+
if (legacyIgnored.names.length > 0) {
|
|
296
|
+
updateMeta((m) => withIgnoredAdded(m, legacyIgnored.names, legacyIgnored.updatedAt ?? new Date().toISOString()));
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
fs.rmSync(legacyIgnoredPath, { force: true });
|
|
300
|
+
}
|
|
301
|
+
catch (err) {
|
|
302
|
+
console.error(`device config migration: could not remove ${legacyIgnoredPath} (${err.message}); a later run retries`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
241
305
|
}
|
|
242
306
|
/** Shallow-set equality for folded doc content (values compared deep via JSON). */
|
|
243
307
|
function sameDocContent(a, b) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Device resource probing for `agents devices list`.
|
|
3
3
|
*
|
|
4
|
-
* One SSH round-trip per device gathers load average, memory pressure, and core
|
|
4
|
+
* One SSH round-trip per device gathers load average, memory pressure, disk, and core
|
|
5
5
|
* count (mac + linux via a POSIX snippet, windows via a CIM one-liner), parsed
|
|
6
6
|
* into a {@link DeviceStats}. Probes run in parallel with a bounded timeout so
|
|
7
7
|
* the list stays responsive — a slow or hung box degrades to "no stats" instead
|
|
@@ -19,6 +19,19 @@ export declare const PROBE_TIMEOUT_MS = 2500;
|
|
|
19
19
|
* "Preparing modules for first use" cost on top of PowerShell startup, which
|
|
20
20
|
* routinely blows the 2.5s POSIX budget on a relayed connection. */
|
|
21
21
|
export declare const WIN_PROBE_TIMEOUT_MS = 6000;
|
|
22
|
+
/** One-shot remote snapshot: load, memory, core count, then root filesystem. */
|
|
23
|
+
export declare const PROBE_SNIPPET = "uptime; echo ---AGSTAT---; (vm_stat 2>/dev/null || cat /proc/meminfo 2>/dev/null); echo ---AGSTAT---; (nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null); echo ---AGSTAT---; df -Pk / 2>/dev/null | tail -1";
|
|
24
|
+
/** Windows equivalent, one labeled line via CIM. PowerShell 5.1-safe: no `||`
|
|
25
|
+
* chaining, plain string concatenation. `LoadPercentage` is $null on some
|
|
26
|
+
* hosts/VMs, which concatenates to an empty field — the parser treats that as
|
|
27
|
+
* "no load signal" and headroom falls back to memory pressure alone.
|
|
28
|
+
* wrapRemoteCommand base64-encodes this for powershell-shell devices, so the
|
|
29
|
+
* quoting survives ssh intact. */
|
|
30
|
+
export declare const WIN_PROBE_SNIPPET = "$os = Get-CimInstance Win32_OperatingSystem; $cpu = (Get-CimInstance Win32_Processor | Measure-Object -Property LoadPercentage -Average).Average; $disk = Get-CimInstance Win32_LogicalDisk -Filter \"DeviceID='C:'\"; Write-Output ('AGWINSTAT load=' + $cpu + ' freeKb=' + $os.FreePhysicalMemory + ' totalKb=' + $os.TotalVisibleMemorySize + ' ncpu=' + $env:NUMBER_OF_PROCESSORS + ' diskFreeKb=' + ($disk.FreeSpace / 1KB) + ' diskTotalKb=' + ($disk.Size / 1KB))";
|
|
31
|
+
export declare function localProbeInvocation(platform: NodeJS.Platform): {
|
|
32
|
+
file: string;
|
|
33
|
+
args: string[];
|
|
34
|
+
};
|
|
22
35
|
export interface DeviceStats {
|
|
23
36
|
host: string;
|
|
24
37
|
reachable: boolean;
|
|
@@ -30,6 +43,11 @@ export interface DeviceStats {
|
|
|
30
43
|
memPercent?: number;
|
|
31
44
|
memTotalBytes?: number;
|
|
32
45
|
memFreeBytes?: number;
|
|
46
|
+
diskTotalBytes?: number;
|
|
47
|
+
diskFreeBytes?: number;
|
|
48
|
+
diskUsedPercent?: number;
|
|
49
|
+
/** When the static ncpu/RAM/disk totals were last observed. */
|
|
50
|
+
specsFetchedAt?: number;
|
|
33
51
|
fetchedAt: number;
|
|
34
52
|
}
|
|
35
53
|
/** Compact human byte size: 512M, 64G, 1.5T (binary units, ≤1 decimal). */
|
|
@@ -47,7 +65,14 @@ export declare function parseLinuxMemInfo(out: string): MemStats;
|
|
|
47
65
|
export declare function parseNcpu(out: string): {
|
|
48
66
|
ncpu?: number;
|
|
49
67
|
};
|
|
50
|
-
|
|
68
|
+
interface DiskStats {
|
|
69
|
+
diskTotalBytes?: number;
|
|
70
|
+
diskFreeBytes?: number;
|
|
71
|
+
diskUsedPercent?: number;
|
|
72
|
+
}
|
|
73
|
+
/** Parse the final data row from `df -k /`, deriving usage from block counts. */
|
|
74
|
+
export declare function parseDf(out: string): DiskStats;
|
|
75
|
+
/** Assemble a DeviceStats from the four snippet sections. */
|
|
51
76
|
export declare function parseProbeOutput(host: string, stdout: string, fetchedAt: number): DeviceStats;
|
|
52
77
|
/** Assemble a DeviceStats from the windows one-liner. A missing marker line
|
|
53
78
|
* (e.g. CIM unavailable) keeps `reachable: true` — ssh answered — with no
|