@phnx-labs/agents-cli 1.22.35 → 1.22.37
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 +666 -2
- package/README.md +75 -35
- package/dist/bin/agents +0 -0
- package/dist/bootstrap.d.ts +15 -0
- package/dist/bootstrap.js +1211 -0
- package/dist/commands/accounts.d.ts +4 -11
- package/dist/commands/accounts.js +159 -27
- package/dist/commands/attach.js +17 -4
- package/dist/commands/audit.d.ts +8 -8
- package/dist/commands/audit.js +57 -47
- package/dist/commands/browser.js +15 -11
- package/dist/commands/cloud.js +1 -1
- package/dist/commands/commands.js +0 -11
- package/dist/commands/computer-actions.js +33 -0
- package/dist/commands/computer-sessions-picker.d.ts +21 -0
- package/dist/commands/computer-sessions-picker.js +141 -0
- package/dist/commands/computer.d.ts +16 -0
- package/dist/commands/computer.js +37 -0
- package/dist/commands/config.d.ts +17 -0
- package/dist/commands/config.js +401 -0
- package/dist/commands/cp.d.ts +84 -0
- package/dist/commands/cp.js +231 -0
- package/dist/commands/daemon.js +243 -14
- package/dist/commands/detach-core.d.ts +1 -1
- package/dist/commands/detach-core.js +12 -2
- package/dist/commands/detach.js +1 -1
- package/dist/commands/doctor.js +64 -5
- package/dist/commands/events.d.ts +24 -12
- package/dist/commands/events.js +144 -28
- package/dist/commands/exec.js +85 -41
- package/dist/commands/feed.d.ts +1 -1
- package/dist/commands/feed.js +16 -8
- package/dist/commands/focus.d.ts +33 -1
- package/dist/commands/focus.js +177 -17
- package/dist/commands/funnel.d.ts +1 -1
- package/dist/commands/go.js +9 -8
- package/dist/commands/harness-wizard.d.ts +1 -0
- package/dist/commands/harness-wizard.js +18 -20
- package/dist/commands/harness.d.ts +2 -0
- package/dist/commands/harness.js +59 -26
- package/dist/commands/hooks.js +0 -11
- package/dist/commands/inspect.d.ts +94 -1
- package/dist/commands/inspect.js +866 -40
- package/dist/commands/lease.d.ts +4 -4
- package/dist/commands/lease.js +6 -6
- package/dist/commands/login.js +0 -13
- package/dist/commands/logs.d.ts +2 -2
- package/dist/commands/logs.js +2 -2
- package/dist/commands/mcp.js +7 -2
- package/dist/commands/memory.js +7 -2
- package/dist/commands/message.d.ts +1 -1
- package/dist/commands/message.js +37 -6
- package/dist/commands/models.js +4 -0
- package/dist/commands/monitors.d.ts +0 -1
- package/dist/commands/monitors.js +199 -16
- package/dist/commands/open.d.ts +18 -0
- package/dist/commands/open.js +57 -0
- package/dist/commands/packages.js +2 -107
- package/dist/commands/plugins.js +6 -3
- package/dist/commands/profiles.d.ts +1 -0
- package/dist/commands/profiles.js +16 -160
- package/dist/commands/projects.d.ts +28 -0
- package/dist/commands/projects.js +125 -5
- package/dist/commands/reconnect.js +9 -3
- package/dist/commands/repo.js +42 -1
- package/dist/commands/resource-view.d.ts +17 -0
- package/dist/commands/resource-view.js +36 -14
- package/dist/commands/resume.js +9 -2
- package/dist/commands/routines.d.ts +0 -2
- package/dist/commands/routines.js +473 -289
- package/dist/commands/secrets.js +52 -9
- package/dist/commands/sessions-export.d.ts +47 -0
- package/dist/commands/sessions-export.js +116 -6
- package/dist/commands/sessions-import.d.ts +17 -0
- package/dist/commands/sessions-import.js +114 -6
- package/dist/commands/sessions-migrate.d.ts +27 -0
- package/dist/commands/sessions-migrate.js +50 -9
- package/dist/commands/sessions-picker.js +23 -4
- package/dist/commands/sessions-resume.d.ts +5 -3
- package/dist/commands/sessions-resume.js +30 -8
- package/dist/commands/sessions-watch.d.ts +2 -0
- package/dist/commands/sessions-watch.js +53 -0
- package/dist/commands/sessions.d.ts +66 -3
- package/dist/commands/sessions.js +298 -47
- package/dist/commands/set.d.ts +1 -1
- package/dist/commands/set.js +2 -2
- package/dist/commands/setup-browser.js +2 -2
- package/dist/commands/setup-mine.js +0 -1
- package/dist/commands/setup-preferences.js +1 -1
- package/dist/commands/setup-share.js +34 -4
- package/dist/commands/setup.d.ts +5 -0
- package/dist/commands/setup.js +26 -0
- package/dist/commands/share.d.ts +67 -0
- package/dist/commands/share.js +275 -12
- package/dist/commands/skills.js +0 -11
- package/dist/commands/snapshot.js +1 -1
- package/dist/commands/ssh.d.ts +18 -0
- package/dist/commands/ssh.js +549 -210
- package/dist/commands/sync.d.ts +5 -0
- package/dist/commands/sync.js +212 -32
- package/dist/commands/teams.d.ts +13 -0
- package/dist/commands/teams.js +187 -30
- package/dist/commands/tickets.d.ts +2 -0
- package/dist/commands/tickets.js +43 -0
- package/dist/commands/update.js +2 -4
- package/dist/commands/usage.js +5 -3
- package/dist/commands/versions.js +11 -5
- package/dist/commands/view.js +15 -7
- package/dist/commands/webhook.d.ts +2 -2
- package/dist/commands/webhook.js +7 -7
- package/dist/index.d.ts +18 -2
- package/dist/index.js +53 -1264
- package/dist/lib/account-catalog.d.ts +18 -0
- package/dist/lib/account-catalog.js +38 -0
- package/dist/lib/account-provider-registry.d.ts +18 -0
- package/dist/lib/account-provider-registry.js +59 -0
- package/dist/lib/account-registry.d.ts +40 -0
- package/dist/lib/account-registry.js +239 -0
- package/dist/lib/account-schema.d.ts +44 -0
- package/dist/lib/account-schema.js +91 -0
- package/dist/lib/account-state-service.d.ts +21 -0
- package/dist/lib/account-state-service.js +60 -0
- package/dist/lib/activity.d.ts +1 -1
- package/dist/lib/activity.js +8 -1
- package/dist/lib/add-dir.d.ts +80 -0
- package/dist/lib/add-dir.js +241 -0
- package/dist/lib/agent-cli-commands.d.ts +11 -0
- package/dist/lib/agent-cli-commands.js +30 -0
- package/dist/lib/agents.js +44 -36
- package/dist/lib/answer-router.js +1 -1
- package/dist/lib/audit/log.d.ts +10 -27
- package/dist/lib/audit/log.js +20 -33
- package/dist/lib/auth-health.d.ts +9 -2
- package/dist/lib/auth-health.js +42 -9
- package/dist/lib/auto-pull-worker.js +7 -3
- package/dist/lib/auto-pull.d.ts +44 -3
- package/dist/lib/auto-pull.js +88 -5
- package/dist/lib/binary-shadow.d.ts +28 -0
- package/dist/lib/binary-shadow.js +121 -0
- package/dist/lib/brand.js +9 -3
- package/dist/lib/browser/ipc.d.ts +30 -0
- package/dist/lib/browser/ipc.js +81 -4
- package/dist/lib/browser/profiles.d.ts +3 -2
- package/dist/lib/browser/profiles.js +65 -22
- package/dist/lib/browser/service.d.ts +4 -0
- package/dist/lib/browser/service.js +30 -3
- package/dist/lib/browser/sessions-list.d.ts +22 -2
- package/dist/lib/browser/sessions-list.js +54 -6
- package/dist/lib/browser/stream.d.ts +2 -0
- package/dist/lib/browser/stream.js +1 -0
- package/dist/lib/browser/types.d.ts +19 -0
- package/dist/lib/byok-usage.d.ts +9 -2
- package/dist/lib/byok-usage.js +133 -31
- package/dist/lib/cli-entry.d.ts +11 -0
- package/dist/lib/cli-entry.js +46 -0
- package/dist/lib/cloud/host.d.ts +1 -1
- package/dist/lib/cloud/host.js +2 -2
- package/dist/lib/cloud/registry.js +1 -1
- package/dist/lib/cloud/types.d.ts +1 -1
- package/dist/lib/commands.d.ts +2 -0
- package/dist/lib/commands.js +2 -0
- package/dist/lib/computer/sessions-list.d.ts +150 -0
- package/dist/lib/computer/sessions-list.js +363 -0
- package/dist/lib/config-keys.d.ts +86 -0
- package/dist/lib/config-keys.js +193 -0
- package/dist/lib/config-machine-keys.d.ts +12 -0
- package/dist/lib/config-machine-keys.js +17 -0
- package/dist/lib/config-transfer.js +3 -4
- package/dist/lib/crabbox/cli.d.ts +2 -2
- package/dist/lib/crabbox/cli.js +3 -3
- package/dist/lib/daemon-health.d.ts +22 -0
- package/dist/lib/daemon-health.js +33 -3
- package/dist/lib/daemon-services.d.ts +50 -0
- package/dist/lib/daemon-services.js +147 -0
- package/dist/lib/daemon-ticks.d.ts +35 -66
- package/dist/lib/daemon-ticks.js +77 -159
- package/dist/lib/daemon.d.ts +72 -4
- package/dist/lib/daemon.js +792 -202
- package/dist/lib/deeplink/register.d.ts +49 -0
- package/dist/lib/deeplink/register.js +253 -0
- package/dist/lib/deeplink/url.d.ts +34 -0
- package/dist/lib/deeplink/url.js +78 -0
- package/dist/lib/device-config.d.ts +124 -29
- package/dist/lib/device-config.js +346 -141
- package/dist/lib/devices/config-migration.d.ts +42 -0
- package/dist/lib/devices/config-migration.js +203 -0
- package/dist/lib/devices/connect.js +14 -7
- package/dist/lib/devices/doctor-findings.d.ts +4 -1
- package/dist/lib/devices/doctor-findings.js +20 -1
- package/dist/lib/devices/harness-inventory.d.ts +11 -2
- package/dist/lib/devices/harness-inventory.js +0 -0
- package/dist/lib/devices/health.d.ts +1 -1
- package/dist/lib/devices/health.js +1 -1
- package/dist/lib/devices/pending.d.ts +22 -1
- package/dist/lib/devices/pending.js +67 -2
- package/dist/lib/devices/registry.d.ts +0 -25
- package/dist/lib/devices/registry.js +2 -83
- package/dist/lib/devices/resolve-profile.d.ts +20 -0
- package/dist/lib/devices/resolve-profile.js +44 -0
- package/dist/lib/devices/rollout-verify.d.ts +101 -0
- package/dist/lib/devices/rollout-verify.js +193 -0
- package/dist/lib/devices/ssh-config.js +10 -6
- package/dist/lib/event-families.d.ts +28 -0
- package/dist/lib/event-families.js +159 -0
- package/dist/lib/event-stream.d.ts +9 -1
- package/dist/lib/event-stream.js +26 -9
- package/dist/lib/events.d.ts +9 -1
- package/dist/lib/events.js +21 -6
- package/dist/lib/exec.d.ts +30 -0
- package/dist/lib/exec.js +219 -121
- package/dist/lib/feed-broadcast.d.ts +25 -0
- package/dist/lib/feed-broadcast.js +33 -0
- package/dist/lib/feed.d.ts +1 -1
- package/dist/lib/feed.js +12 -2
- package/dist/lib/fleet/capture.js +15 -0
- package/dist/lib/fleet/manifest.js +9 -0
- package/dist/lib/fleet/types.d.ts +9 -0
- package/dist/lib/fleet-status.js +10 -8
- package/dist/lib/git.d.ts +20 -6
- package/dist/lib/git.js +121 -17
- package/dist/lib/hosts/dispatch.d.ts +8 -3
- package/dist/lib/hosts/dispatch.js +29 -3
- package/dist/lib/hosts/logs.d.ts +2 -2
- package/dist/lib/hosts/logs.js +2 -2
- package/dist/lib/hosts/option.js +1 -1
- package/dist/lib/hosts/passthrough.d.ts +2 -3
- package/dist/lib/hosts/passthrough.js +10 -24
- package/dist/lib/hosts/progress.js +2 -2
- package/dist/lib/hosts/providers/devices.d.ts +3 -3
- package/dist/lib/hosts/providers/devices.js +18 -12
- package/dist/lib/hosts/ready.d.ts +5 -1
- package/dist/lib/hosts/ready.js +9 -3
- package/dist/lib/hosts/reconcile.d.ts +2 -2
- package/dist/lib/hosts/reconcile.js +2 -2
- package/dist/lib/hosts/registry.js +14 -9
- package/dist/lib/hosts/remote-cmd.js +1 -1
- package/dist/lib/hosts/remote-os.d.ts +9 -6
- package/dist/lib/hosts/remote-os.js +13 -6
- package/dist/lib/hosts/routing-flag.d.ts +23 -0
- package/dist/lib/hosts/routing-flag.js +41 -0
- package/dist/lib/hosts/run-target.js +1 -1
- package/dist/lib/hosts/session-index.js +1 -1
- package/dist/lib/hosts/tasks.d.ts +6 -6
- package/dist/lib/hosts/tasks.js +4 -4
- package/dist/lib/hosts/types.js +1 -2
- package/dist/lib/installations/resolve.js +2 -8
- package/dist/lib/isolation-boundary-report.js +0 -1
- package/dist/lib/linear-cache.d.ts +7 -0
- package/dist/lib/linear-cache.js +31 -4
- package/dist/lib/linear-project-counts.d.ts +1 -1
- package/dist/lib/linear-project-counts.js +2 -3
- package/dist/lib/linear-projects.js +1 -1
- package/dist/lib/loop.d.ts +2 -2
- package/dist/lib/loop.js +1 -1
- package/dist/lib/mailbox-target.d.ts +1 -1
- package/dist/lib/mailbox-target.js +1 -1
- package/dist/lib/manifest.js +7 -4
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/snapshot.d.ts +3 -2
- package/dist/lib/menubar/snapshot.js +6 -2
- package/dist/lib/migrate-fold.d.ts +33 -0
- package/dist/lib/migrate-fold.js +130 -0
- package/dist/lib/migrate.d.ts +34 -34
- package/dist/lib/migrate.js +106 -128
- package/dist/lib/model-tier-overrides.d.ts +1 -1
- package/dist/lib/model-tier-overrides.js +15 -2
- package/dist/lib/monitors/config.d.ts +17 -3
- package/dist/lib/monitors/config.js +52 -18
- package/dist/lib/monitors/engine.d.ts +32 -1
- package/dist/lib/monitors/engine.js +94 -11
- package/dist/lib/monitors/fingerprint.d.ts +46 -0
- package/dist/lib/monitors/fingerprint.js +100 -0
- package/dist/lib/monitors/remote.d.ts +47 -0
- package/dist/lib/monitors/remote.js +81 -0
- package/dist/lib/monitors/state.d.ts +39 -0
- package/dist/lib/monitors/state.js +56 -0
- package/dist/lib/onepassword.d.ts +17 -0
- package/dist/lib/onepassword.js +52 -22
- package/dist/lib/open-url.js +1 -1
- package/dist/lib/picker.d.ts +14 -2
- package/dist/lib/picker.js +35 -13
- package/dist/lib/placement.d.ts +1 -1
- package/dist/lib/placement.js +1 -1
- package/dist/lib/pr-land-detach.d.ts +32 -0
- package/dist/lib/pr-land-detach.js +79 -0
- package/dist/lib/profiles-presets.js +13 -0
- package/dist/lib/profiles.d.ts +2 -0
- package/dist/lib/profiles.js +44 -1
- package/dist/lib/project-focus.d.ts +1 -1
- package/dist/lib/project-focus.js +1 -1
- package/dist/lib/project-probe.d.ts +4 -1
- package/dist/lib/project-probe.js +5 -4
- package/dist/lib/project-resources.js +2 -18
- package/dist/lib/project-root.d.ts +16 -0
- package/dist/lib/project-root.js +20 -2
- package/dist/lib/projects.d.ts +20 -1
- package/dist/lib/projects.js +78 -1
- package/dist/lib/refresh-coordinator.d.ts +24 -0
- package/dist/lib/refresh-coordinator.js +52 -0
- package/dist/lib/registry.d.ts +2 -2
- package/dist/lib/registry.js +1 -1
- package/dist/lib/resource-aliases.d.ts +13 -0
- package/dist/lib/resource-aliases.js +26 -0
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/resources.d.ts +16 -0
- package/dist/lib/resources.js +66 -10
- package/dist/lib/rotate.d.ts +3 -4
- package/dist/lib/rotate.js +4 -5
- package/dist/lib/routine-activation.d.ts +22 -2
- package/dist/lib/routine-activation.js +63 -16
- package/dist/lib/routine-context.d.ts +9 -2
- package/dist/lib/routine-context.js +21 -6
- package/dist/lib/routines-project.d.ts +58 -48
- package/dist/lib/routines-project.js +190 -159
- package/dist/lib/routines.d.ts +41 -6
- package/dist/lib/routines.js +92 -34
- package/dist/lib/run-defaults.d.ts +5 -0
- package/dist/lib/run-defaults.js +11 -1
- package/dist/lib/runner.d.ts +10 -8
- package/dist/lib/runner.js +122 -39
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/agent.d.ts +10 -3
- package/dist/lib/secrets/agent.js +43 -8
- package/dist/lib/secrets/bundles.d.ts +1 -0
- package/dist/lib/secrets/bundles.js +92 -80
- package/dist/lib/secrets/index.js +32 -9
- package/dist/lib/secrets/install-helper.d.ts +4 -4
- package/dist/lib/secrets/install-helper.js +4 -4
- package/dist/lib/secrets/push.d.ts +40 -6
- package/dist/lib/secrets/push.js +58 -11
- package/dist/lib/secrets/read-backoff.d.ts +1 -1
- package/dist/lib/secrets/read-backoff.js +1 -1
- package/dist/lib/secrets/remote.d.ts +24 -0
- package/dist/lib/secrets/remote.js +54 -4
- package/dist/lib/self-update.d.ts +108 -0
- package/dist/lib/self-update.js +276 -1
- package/dist/lib/session/active.d.ts +89 -5
- package/dist/lib/session/active.js +165 -10
- package/dist/lib/session/actor-sidecar.d.ts +1 -1
- package/dist/lib/session/actor-sidecar.js +2 -1
- package/dist/lib/session/db.d.ts +176 -2
- package/dist/lib/session/db.js +484 -17
- package/dist/lib/session/discover.js +27 -4
- package/dist/lib/session/hook-sessions.js +2 -2
- package/dist/lib/session/host-link.d.ts +2 -2
- package/dist/lib/session/host-link.js +2 -2
- package/dist/lib/session/pid-registry.d.ts +21 -0
- package/dist/lib/session/pid-registry.js +57 -0
- package/dist/lib/session/remote-active.js +15 -1
- package/dist/lib/session/remote.js +2 -2
- package/dist/lib/session/session-cache.d.ts +14 -7
- package/dist/lib/session/session-cache.js +51 -9
- package/dist/lib/session/state.d.ts +2 -2
- package/dist/lib/session/state.js +1 -1
- package/dist/lib/session/sync/config.d.ts +10 -10
- package/dist/lib/session/sync/config.js +10 -10
- package/dist/lib/session/sync/r2.d.ts +34 -0
- package/dist/lib/session/sync/r2.js +123 -0
- package/dist/lib/session/throughput.d.ts +1 -1
- package/dist/lib/session/throughput.js +1 -1
- package/dist/lib/session/types.d.ts +23 -1
- package/dist/lib/session/types.js +15 -0
- package/dist/lib/session/viewing-in.d.ts +1 -1
- package/dist/lib/session/viewing-in.js +1 -1
- package/dist/lib/session/watch.d.ts +101 -0
- package/dist/lib/session/watch.js +242 -0
- package/dist/lib/share/config.d.ts +4 -0
- package/dist/lib/share/config.js +1 -0
- package/dist/lib/share/provision.d.ts +38 -0
- package/dist/lib/share/provision.js +50 -0
- package/dist/lib/share/publish.d.ts +40 -1
- package/dist/lib/share/publish.js +116 -3
- package/dist/lib/share/worker-template.js +71 -6
- package/dist/lib/shims.d.ts +17 -3
- package/dist/lib/shims.js +98 -26
- package/dist/lib/signin-badge.js +3 -2
- package/dist/lib/skills.js +2 -0
- package/dist/lib/smart-launch.d.ts +8 -8
- package/dist/lib/smart-launch.js +80 -59
- package/dist/lib/ssh-exec.d.ts +1 -1
- package/dist/lib/ssh-exec.js +1 -1
- package/dist/lib/staleness/prune.d.ts +88 -0
- package/dist/lib/staleness/prune.js +69 -0
- package/dist/lib/staleness/registry.d.ts +1 -1
- package/dist/lib/staleness/writers/commands.js +34 -2
- package/dist/lib/staleness/writers/hooks.js +16 -0
- package/dist/lib/staleness/writers/kinds.d.ts +6 -0
- package/dist/lib/staleness/writers/skills.js +19 -0
- package/dist/lib/staleness/writers/subagents.d.ts +2 -3
- package/dist/lib/staleness/writers/subagents.js +0 -4
- package/dist/lib/staleness/writers/types.d.ts +32 -0
- package/dist/lib/startup/command-registry.d.ts +6 -12
- package/dist/lib/startup/command-registry.js +11 -31
- package/dist/lib/startup/dev-build.d.ts +10 -0
- package/dist/lib/startup/dev-build.js +13 -1
- package/dist/lib/startup/spellcheck.d.ts +18 -0
- package/dist/lib/startup/spellcheck.js +45 -0
- package/dist/lib/state.d.ts +36 -9
- package/dist/lib/state.js +96 -46
- package/dist/lib/subagents-registry.d.ts +0 -7
- package/dist/lib/subagents-registry.js +38 -65
- package/dist/lib/subagents.d.ts +0 -33
- package/dist/lib/subagents.js +0 -75
- package/dist/lib/sync-umbrella.js +1 -1
- package/dist/lib/teams/agents.d.ts +41 -1
- package/dist/lib/teams/agents.js +187 -9
- package/dist/lib/teams/api.d.ts +11 -1
- package/dist/lib/teams/api.js +11 -2
- package/dist/lib/teams/delivery.d.ts +41 -0
- package/dist/lib/teams/delivery.js +60 -0
- package/dist/lib/teams/registry.d.ts +14 -0
- package/dist/lib/teams/registry.js +51 -1
- package/dist/lib/teams/remoteWorktree.d.ts +19 -0
- package/dist/lib/teams/remoteWorktree.js +29 -0
- package/dist/lib/teams/scheduler.d.ts +1 -1
- package/dist/lib/teams/scheduler.js +1 -1
- package/dist/lib/teams/worktree.d.ts +24 -0
- package/dist/lib/teams/worktree.js +42 -0
- package/dist/lib/terminal/backends/vscodium-agent.d.ts +10 -1
- package/dist/lib/terminal/backends/vscodium-agent.js +16 -5
- package/dist/lib/terminal/engine.d.ts +4 -0
- package/dist/lib/terminal/engine.js +8 -2
- package/dist/lib/terminal/types.d.ts +21 -2
- package/dist/lib/testdata/refresh-coordinator-worker.d.ts +1 -0
- package/dist/lib/testdata/refresh-coordinator-worker.js +23 -0
- package/dist/lib/tickets/list.d.ts +53 -0
- package/dist/lib/tickets/list.js +153 -0
- package/dist/lib/tmux/orphan-reap.d.ts +320 -0
- package/dist/lib/tmux/orphan-reap.js +644 -0
- package/dist/lib/tmux/session.d.ts +68 -3
- package/dist/lib/tmux/session.js +95 -5
- package/dist/lib/triggers/handlers.d.ts +20 -0
- package/dist/lib/triggers/handlers.js +6 -1
- package/dist/lib/types.d.ts +46 -34
- package/dist/lib/usage-fleet.d.ts +32 -0
- package/dist/lib/usage-fleet.js +125 -0
- package/dist/lib/usage-refresh.d.ts +3 -3
- package/dist/lib/usage-refresh.js +19 -16
- package/dist/lib/usage.d.ts +51 -47
- package/dist/lib/usage.js +145 -153
- package/dist/lib/version-duplicates.js +2 -2
- package/dist/lib/versions.d.ts +56 -13
- package/dist/lib/versions.js +195 -45
- package/dist/lib/watchdog/log.d.ts +1 -1
- package/dist/lib/watchdog/log.js +3 -3
- package/dist/lib/watchdog/rotate.d.ts +6 -6
- package/dist/lib/watchdog/rotate.js +6 -6
- package/dist/lib/workflows.d.ts +1 -1
- package/dist/lib/yaml-io.d.ts +47 -0
- package/dist/lib/yaml-io.js +55 -0
- package/package.json +2 -1
- package/scripts/install-helper.js +2 -2
- package/scripts/postinstall.js +23 -20
- package/dist/commands/defaults.d.ts +0 -7
- package/dist/commands/defaults.js +0 -107
- package/dist/commands/export.d.ts +0 -11
- package/dist/commands/export.js +0 -215
- package/dist/commands/helper.d.ts +0 -12
- package/dist/commands/helper.js +0 -87
- package/dist/commands/hosts.d.ts +0 -11
- package/dist/commands/hosts.js +0 -330
- package/dist/commands/lock.d.ts +0 -12
- package/dist/commands/lock.js +0 -70
- package/dist/commands/pull.d.ts +0 -17
- package/dist/commands/pull.js +0 -39
- package/dist/commands/push.d.ts +0 -14
- package/dist/commands/push.js +0 -30
- package/dist/commands/wallet.d.ts +0 -20
- package/dist/commands/wallet.js +0 -216
- package/dist/commands/worktree.d.ts +0 -19
- package/dist/commands/worktree.js +0 -272
- package/dist/lib/account-labels.d.ts +0 -24
- package/dist/lib/account-labels.js +0 -72
- package/dist/lib/auto-dispatch-linear.d.ts +0 -18
- package/dist/lib/auto-dispatch-linear.js +0 -107
- package/dist/lib/auto-dispatch-provider.d.ts +0 -10
- package/dist/lib/auto-dispatch-provider.js +0 -30
- package/dist/lib/auto-dispatch.d.ts +0 -93
- package/dist/lib/auto-dispatch.js +0 -128
- package/dist/lib/export.d.ts +0 -72
- package/dist/lib/export.js +0 -269
- package/dist/lib/lock.d.ts +0 -93
- package/dist/lib/lock.js +0 -207
- package/dist/lib/wallet/index.d.ts +0 -78
- package/dist/lib/wallet/index.js +0 -253
|
@@ -13,12 +13,30 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { listMonitors, monitorRunsOnThisDevice, parseInterval, setMonitorEnabled, } from './config.js';
|
|
15
15
|
import { evaluateSource } from './sources/index.js';
|
|
16
|
-
import { hasChanged, readState, writeState, recordFireTime, writeFireRecord, } from './state.js';
|
|
16
|
+
import { hasChanged, readState, writeState, recordFireTime, writeFireRecord, recordCheck, markDroughtNotified, } from './state.js';
|
|
17
17
|
import { dispatchAction } from './dispatch.js';
|
|
18
|
+
import { sendToOwner } from '../notify.js';
|
|
18
19
|
/** How often the engine wakes to check which monitors are due. */
|
|
19
20
|
const TICK_MS = 5_000;
|
|
20
21
|
/** Default evaluation cadence for sources that carry no explicit interval. */
|
|
21
22
|
const DEFAULT_INTERVAL_MS = 60_000;
|
|
23
|
+
/**
|
|
24
|
+
* Consecutive failed polls before the engine escalates a drought to the owner.
|
|
25
|
+
* A monitor that looks healthy (enabled, daemon up) but whose source errors
|
|
26
|
+
* every poll does no real work — the "every signal reads healthy while zero work
|
|
27
|
+
* happened" failure RUSH-2485 is about. One notification per drought.
|
|
28
|
+
*/
|
|
29
|
+
const DROUGHT_THRESHOLD = 5;
|
|
30
|
+
/** Poll-model source types the engine actually evaluates on a cadence; ws/webhook are push-only and inert here. */
|
|
31
|
+
export const POLL_SOURCE_TYPES = new Set(['command', 'poll', 'poll-http', 'file', 'device']);
|
|
32
|
+
/**
|
|
33
|
+
* Whether a monitor's liveness has crossed into a drought worth notifying the
|
|
34
|
+
* owner about: enough consecutive failed checks, and not already notified for
|
|
35
|
+
* this drought. Pure so the branch is unit-testable without a real notify.
|
|
36
|
+
*/
|
|
37
|
+
export function shouldEscalateDrought(liveness) {
|
|
38
|
+
return liveness.consecutiveErrors >= DROUGHT_THRESHOLD && !liveness.droughtNotifiedAt;
|
|
39
|
+
}
|
|
22
40
|
function truncateSummary(text) {
|
|
23
41
|
const oneLine = text.replace(/\s+/g, ' ').trim();
|
|
24
42
|
return oneLine.length > 240 ? oneLine.slice(0, 240) + '…' : oneLine;
|
|
@@ -36,6 +54,15 @@ export function decideFire(monitor, observation) {
|
|
|
36
54
|
const payload = observation.meta ?? {};
|
|
37
55
|
const dedupeKey = cond.dedupeKey;
|
|
38
56
|
if (cond.mode === 'every') {
|
|
57
|
+
// Fire on every tick that carries a real observation. An empty (or
|
|
58
|
+
// whitespace-only) observation means "nothing to report": firing an action
|
|
59
|
+
// with an empty {event} is never useful, and skipping it lets a poll whose
|
|
60
|
+
// command yields no rows (e.g. no mergeable PR) stay silent while still
|
|
61
|
+
// re-firing every tick the set is non-empty — the retry semantics a
|
|
62
|
+
// silently-failed action dispatch needs (RUSH-2488).
|
|
63
|
+
if (raw.trim() === '') {
|
|
64
|
+
return { fire: false, value: raw, dedupeKey, persist: false, event: null };
|
|
65
|
+
}
|
|
39
66
|
return {
|
|
40
67
|
fire: true,
|
|
41
68
|
value: raw,
|
|
@@ -145,21 +172,74 @@ export class MonitorEngine {
|
|
|
145
172
|
this.ticking = false;
|
|
146
173
|
}
|
|
147
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Evaluate one monitor once and record the outcome. Public so the daemon tick
|
|
177
|
+
* and tests drive the exact same path. A "failed check" is an evaluation that
|
|
178
|
+
* produced no observation, threw, OR fired an action that failed — all three
|
|
179
|
+
* are "the monitor ran and accomplished nothing", the drought signal. A poll
|
|
180
|
+
* that observes and either fires cleanly or matches nothing is a success and
|
|
181
|
+
* resets the streak.
|
|
182
|
+
*/
|
|
148
183
|
async runMonitor(monitor) {
|
|
184
|
+
// Push-only sources (ws/webhook) deliver through subscribe, not this loop —
|
|
185
|
+
// they return null from evaluate by design, so they have no poll to record.
|
|
186
|
+
const isPollSource = POLL_SOURCE_TYPES.has(monitor.source.type);
|
|
187
|
+
if (!isPollSource)
|
|
188
|
+
return;
|
|
189
|
+
const checkedAt = new Date().toISOString();
|
|
190
|
+
let checkError;
|
|
149
191
|
try {
|
|
150
|
-
const
|
|
151
|
-
if (!observation
|
|
152
|
-
|
|
153
|
-
if (decision.fire && decision.event) {
|
|
154
|
-
await this.fire(monitor, decision, decision.event);
|
|
192
|
+
const observation = await evaluateSource(monitor.source);
|
|
193
|
+
if (!observation) {
|
|
194
|
+
checkError = 'source produced no observation';
|
|
155
195
|
}
|
|
156
|
-
else
|
|
157
|
-
|
|
158
|
-
|
|
196
|
+
else {
|
|
197
|
+
const decision = decideFire(monitor, observation);
|
|
198
|
+
if (decision.fire && decision.event) {
|
|
199
|
+
const result = await this.fire(monitor, decision, decision.event);
|
|
200
|
+
if (!result.ok)
|
|
201
|
+
checkError = `action ${result.kind} failed: ${result.error ?? 'unknown'}`;
|
|
202
|
+
}
|
|
203
|
+
else if (decision.persist) {
|
|
204
|
+
// Silent baseline / no-change: record the value so we don't re-fire.
|
|
205
|
+
writeState(monitor.name, decision.value, decision.dedupeKey);
|
|
206
|
+
}
|
|
159
207
|
}
|
|
160
208
|
}
|
|
161
209
|
catch (err) {
|
|
162
|
-
|
|
210
|
+
checkError = err.message;
|
|
211
|
+
this.logFn('ERROR', `monitor '${monitor.name}' evaluation failed: ${checkError}`);
|
|
212
|
+
}
|
|
213
|
+
this.afterCheck(monitor, checkedAt, checkError);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Record the poll heartbeat and, on a sustained failure streak, escalate a
|
|
217
|
+
* drought to the owner exactly once. The heartbeat is what makes a
|
|
218
|
+
* polling-but-not-matching monitor visibly distinct from one the engine never
|
|
219
|
+
* touched (RUSH-2485); the streak is what turns "every poll fails and the owner
|
|
220
|
+
* never hears about it" into one notification.
|
|
221
|
+
*/
|
|
222
|
+
afterCheck(monitor, checkedAt, error) {
|
|
223
|
+
const liveness = recordCheck(monitor.name, checkedAt, error);
|
|
224
|
+
if (error && shouldEscalateDrought(liveness)) {
|
|
225
|
+
void this.escalateDrought(monitor, liveness.consecutiveErrors, error);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/** Notify the owner that an enabled monitor has failed N checks in a row and done nothing. */
|
|
229
|
+
async escalateDrought(monitor, consecutiveErrors, error) {
|
|
230
|
+
const at = new Date().toISOString();
|
|
231
|
+
// Stamp the marker BEFORE the send so a slow/failing notify can't re-fire the
|
|
232
|
+
// drought on the next tick; recordCheck clears it on the first good check.
|
|
233
|
+
markDroughtNotified(monitor.name, at);
|
|
234
|
+
const text = `Monitor '${monitor.name}' has failed ${consecutiveErrors} checks in a row and accomplished nothing. ` +
|
|
235
|
+
`Last error: ${error}`;
|
|
236
|
+
try {
|
|
237
|
+
const result = await sendToOwner(text);
|
|
238
|
+
this.logFn(result.ok ? 'WARN' : 'ERROR', `monitor '${monitor.name}' drought (${consecutiveErrors} failed checks) → notify owner` +
|
|
239
|
+
(result.ok ? '' : ` FAILED: ${result.error}`));
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
this.logFn('ERROR', `monitor '${monitor.name}' drought notify threw: ${err.message}`);
|
|
163
243
|
}
|
|
164
244
|
}
|
|
165
245
|
async fire(monitor, decision, event) {
|
|
@@ -181,7 +261,9 @@ export class MonitorEngine {
|
|
|
181
261
|
catch { /* best-effort pause */ }
|
|
182
262
|
this.logFn('WARN', `monitor '${monitor.name}' exceeded rate limit (${monitor.rateLimit.max}/${monitor.rateLimit.per}) — auto-paused`);
|
|
183
263
|
this.loadAll();
|
|
184
|
-
|
|
264
|
+
// An auto-pause is a deliberate stop, not a failed action — don't let it
|
|
265
|
+
// feed the drought streak (the monitor is now disabled anyway).
|
|
266
|
+
return { kind: monitor.action.type, ok: true };
|
|
185
267
|
}
|
|
186
268
|
}
|
|
187
269
|
let result;
|
|
@@ -201,5 +283,6 @@ export class MonitorEngine {
|
|
|
201
283
|
this.logFn(result.ok ? 'INFO' : 'ERROR', `monitor '${monitor.name}' fired → ${result.kind}` +
|
|
202
284
|
(result.runId ? ` (run: ${result.runId})` : '') +
|
|
203
285
|
(result.ok ? '' : ` FAILED: ${result.error}`));
|
|
286
|
+
return result;
|
|
204
287
|
}
|
|
205
288
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic identity of a monitor — "are these two definitions the same watcher?"
|
|
3
|
+
*
|
|
4
|
+
* A monitor's NAME is not its identity. Two monitors can poll the same command
|
|
5
|
+
* on the same interval and fire the same action under two different names, and
|
|
6
|
+
* nothing today notices: `writeMonitor` overwrites by name and never compares
|
|
7
|
+
* arguments. Observed on one box: `open-pr-watch`, `pr-ci-fail`, three stale
|
|
8
|
+
* `pr2222-*` watchers, and an agent-added lander, all polling
|
|
9
|
+
* `gh pr list ... phnx-labs/agents-cli` — four overlapping triggers on one PR
|
|
10
|
+
* queue, added without a single warning.
|
|
11
|
+
*
|
|
12
|
+
* That is the double-trigger bug class. The fingerprint is what lets `add`
|
|
13
|
+
* refuse it.
|
|
14
|
+
*
|
|
15
|
+
* WHAT IS HASHED: only fields that change what the monitor DOES — the source it
|
|
16
|
+
* watches, how often, the condition that fires, and the action it takes. Name,
|
|
17
|
+
* description, and `enabled` are deliberately excluded, because a duplicate
|
|
18
|
+
* under a new name is exactly the case being caught, and a paused duplicate is
|
|
19
|
+
* still a duplicate the moment someone resumes it.
|
|
20
|
+
*
|
|
21
|
+
* WHAT IS NOT HASHED: `device`/`devices`/`runOn`. Placement is who executes, not
|
|
22
|
+
* what runs — and two boxes deliberately owning the same watcher is a different
|
|
23
|
+
* decision (see `monitorRunsOnThisDevice`). Including placement would let the
|
|
24
|
+
* same watcher be added N times by varying only the owner, which is the very
|
|
25
|
+
* duplication this guards.
|
|
26
|
+
*/
|
|
27
|
+
import type { MonitorConfig } from './config.js';
|
|
28
|
+
/**
|
|
29
|
+
* The behavioral identity of a monitor, as a short hex digest. Two monitors with
|
|
30
|
+
* the same fingerprint watch the same thing and do the same thing on fire —
|
|
31
|
+
* whatever they are called and wherever they are pinned.
|
|
32
|
+
*
|
|
33
|
+
* Pure: no I/O, so the duplicate check is unit-tested without a monitors dir.
|
|
34
|
+
*/
|
|
35
|
+
export declare function monitorFingerprint(config: Pick<MonitorConfig, 'source' | 'condition' | 'action'>): string;
|
|
36
|
+
/**
|
|
37
|
+
* The already-existing monitor that this config would duplicate, or null.
|
|
38
|
+
*
|
|
39
|
+
* `existing` is the full monitor set (user + system layers), so a user monitor
|
|
40
|
+
* duplicating a shipped built-in is caught too. A monitor never collides with
|
|
41
|
+
* itself: a same-NAME entry is a rewrite of that monitor, which `add` reports
|
|
42
|
+
* separately and more precisely than "duplicate".
|
|
43
|
+
*
|
|
44
|
+
* Pure — the caller supplies the list.
|
|
45
|
+
*/
|
|
46
|
+
export declare function findDuplicateMonitor(config: Pick<MonitorConfig, 'name' | 'source' | 'condition' | 'action'>, existing: Array<Pick<MonitorConfig, 'name' | 'source' | 'condition' | 'action'>>): string | null;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic identity of a monitor — "are these two definitions the same watcher?"
|
|
3
|
+
*
|
|
4
|
+
* A monitor's NAME is not its identity. Two monitors can poll the same command
|
|
5
|
+
* on the same interval and fire the same action under two different names, and
|
|
6
|
+
* nothing today notices: `writeMonitor` overwrites by name and never compares
|
|
7
|
+
* arguments. Observed on one box: `open-pr-watch`, `pr-ci-fail`, three stale
|
|
8
|
+
* `pr2222-*` watchers, and an agent-added lander, all polling
|
|
9
|
+
* `gh pr list ... phnx-labs/agents-cli` — four overlapping triggers on one PR
|
|
10
|
+
* queue, added without a single warning.
|
|
11
|
+
*
|
|
12
|
+
* That is the double-trigger bug class. The fingerprint is what lets `add`
|
|
13
|
+
* refuse it.
|
|
14
|
+
*
|
|
15
|
+
* WHAT IS HASHED: only fields that change what the monitor DOES — the source it
|
|
16
|
+
* watches, how often, the condition that fires, and the action it takes. Name,
|
|
17
|
+
* description, and `enabled` are deliberately excluded, because a duplicate
|
|
18
|
+
* under a new name is exactly the case being caught, and a paused duplicate is
|
|
19
|
+
* still a duplicate the moment someone resumes it.
|
|
20
|
+
*
|
|
21
|
+
* WHAT IS NOT HASHED: `device`/`devices`/`runOn`. Placement is who executes, not
|
|
22
|
+
* what runs — and two boxes deliberately owning the same watcher is a different
|
|
23
|
+
* decision (see `monitorRunsOnThisDevice`). Including placement would let the
|
|
24
|
+
* same watcher be added N times by varying only the owner, which is the very
|
|
25
|
+
* duplication this guards.
|
|
26
|
+
*/
|
|
27
|
+
import { createHash } from 'crypto';
|
|
28
|
+
/**
|
|
29
|
+
* Stable JSON: object keys sorted, so key order in the YAML cannot change the hash.
|
|
30
|
+
*
|
|
31
|
+
* Cycle-safe by construction. A monitor can arrive from arbitrary YAML
|
|
32
|
+
* (`agents monitors add ./watcher.yml`), and a recursive anchor produces a
|
|
33
|
+
* genuinely cyclic object that `validateMonitor` accepts — it checks named
|
|
34
|
+
* fields, while this walks the whole graph. Without the seen-set the recursion
|
|
35
|
+
* blew the stack before `JSON.stringify` could raise its own circular-structure
|
|
36
|
+
* error, turning a bad input into a stack trace instead of a clear refusal.
|
|
37
|
+
*
|
|
38
|
+
* A `Date` is serialized to its ISO string rather than falling into the object
|
|
39
|
+
* branch, where every Date collapsed to `{}` and two different timestamps
|
|
40
|
+
* fingerprinted identically.
|
|
41
|
+
*/
|
|
42
|
+
function stable(value, seen = new WeakSet()) {
|
|
43
|
+
if (value instanceof Date)
|
|
44
|
+
return value.toISOString();
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
if (seen.has(value))
|
|
47
|
+
return '[circular]';
|
|
48
|
+
seen.add(value);
|
|
49
|
+
return value.map((v) => stable(v, seen));
|
|
50
|
+
}
|
|
51
|
+
if (value && typeof value === 'object') {
|
|
52
|
+
if (seen.has(value))
|
|
53
|
+
return '[circular]';
|
|
54
|
+
seen.add(value);
|
|
55
|
+
const out = {};
|
|
56
|
+
for (const key of Object.keys(value).sort()) {
|
|
57
|
+
const v = value[key];
|
|
58
|
+
if (v === undefined)
|
|
59
|
+
continue;
|
|
60
|
+
out[key] = stable(v, seen);
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The behavioral identity of a monitor, as a short hex digest. Two monitors with
|
|
68
|
+
* the same fingerprint watch the same thing and do the same thing on fire —
|
|
69
|
+
* whatever they are called and wherever they are pinned.
|
|
70
|
+
*
|
|
71
|
+
* Pure: no I/O, so the duplicate check is unit-tested without a monitors dir.
|
|
72
|
+
*/
|
|
73
|
+
export function monitorFingerprint(config) {
|
|
74
|
+
const identity = stable({
|
|
75
|
+
source: config.source,
|
|
76
|
+
condition: config.condition,
|
|
77
|
+
action: config.action,
|
|
78
|
+
});
|
|
79
|
+
return createHash('sha256').update(JSON.stringify(identity)).digest('hex').slice(0, 16);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The already-existing monitor that this config would duplicate, or null.
|
|
83
|
+
*
|
|
84
|
+
* `existing` is the full monitor set (user + system layers), so a user monitor
|
|
85
|
+
* duplicating a shipped built-in is caught too. A monitor never collides with
|
|
86
|
+
* itself: a same-NAME entry is a rewrite of that monitor, which `add` reports
|
|
87
|
+
* separately and more precisely than "duplicate".
|
|
88
|
+
*
|
|
89
|
+
* Pure — the caller supplies the list.
|
|
90
|
+
*/
|
|
91
|
+
export function findDuplicateMonitor(config, existing) {
|
|
92
|
+
const fp = monitorFingerprint(config);
|
|
93
|
+
for (const other of existing) {
|
|
94
|
+
if (other.name === config.name)
|
|
95
|
+
continue;
|
|
96
|
+
if (monitorFingerprint(other) === fp)
|
|
97
|
+
return other.name;
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fleet-wide monitor lookup — "is any box already watching this?"
|
|
3
|
+
*
|
|
4
|
+
* A monitor's identity is its ARGUMENTS, not its name or its device. Agents
|
|
5
|
+
* create monitors per work item, so the same shape runs many times over with
|
|
6
|
+
* different arguments (PR #2517 vs #2600) and those must all coexist. The case
|
|
7
|
+
* that must NOT happen is two agents, on two different machines, creating a
|
|
8
|
+
* watcher with the SAME arguments — one work item, two triggers.
|
|
9
|
+
*
|
|
10
|
+
* A local-only check cannot see that: each box reads its own `monitors/` dir and
|
|
11
|
+
* neither sees the other. So the duplicate guard asks the fleet, reusing the
|
|
12
|
+
* same cross-machine fan-out `sessions --active` and `fleet status` already run
|
|
13
|
+
* (`lib/remote-agents-json.ts`).
|
|
14
|
+
*
|
|
15
|
+
* Deliberately NOT solved by syncing `monitors/*.yml` through the DotAgents repo:
|
|
16
|
+
* an agent's per-PR watcher is running state, not config, and syncing would push
|
|
17
|
+
* every one of them onto every box — the accumulation this guard exists to stop.
|
|
18
|
+
*/
|
|
19
|
+
import type { MonitorConfig } from './config.js';
|
|
20
|
+
/** Recursion guard: a peer answering the fan-out must not fan out again. */
|
|
21
|
+
export declare const NO_MONITOR_FANOUT_ENV = "AGENTS_MONITORS_LOCAL";
|
|
22
|
+
/** One monitor as seen on a peer, tagged with the box it lives on. */
|
|
23
|
+
export interface RemoteMonitor {
|
|
24
|
+
machine: string;
|
|
25
|
+
monitor: Pick<MonitorConfig, 'name' | 'source' | 'condition' | 'action'>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Parse a peer's `monitors list --json`. Defensive against version skew: a peer
|
|
29
|
+
* on an older CLI may emit a different shape or no JSON at all, and one bad peer
|
|
30
|
+
* must never blank the guard for the rest of the fleet.
|
|
31
|
+
*/
|
|
32
|
+
export declare function parseRemoteMonitors(stdout: string, machine: string): RemoteMonitor[];
|
|
33
|
+
export interface FleetMonitorsResult {
|
|
34
|
+
monitors: RemoteMonitor[];
|
|
35
|
+
/** Target discovery failed before any peer was dialed — the fleet was not
|
|
36
|
+
* consulted at all, which must not read as "no duplicate anywhere". */
|
|
37
|
+
discoveryFailed: boolean;
|
|
38
|
+
/** Peers dialed but unreachable / erroring — the guard reports these rather than
|
|
39
|
+
* silently treating "we could not ask" as "there is no duplicate". */
|
|
40
|
+
skipped: string[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Every monitor on every other registered device. Never throws: an unreachable
|
|
44
|
+
* fleet degrades to an empty list plus the names we could not consult, and the
|
|
45
|
+
* caller decides what to say about them.
|
|
46
|
+
*/
|
|
47
|
+
export declare function gatherFleetMonitors(): Promise<FleetMonitorsResult>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fleet-wide monitor lookup — "is any box already watching this?"
|
|
3
|
+
*
|
|
4
|
+
* A monitor's identity is its ARGUMENTS, not its name or its device. Agents
|
|
5
|
+
* create monitors per work item, so the same shape runs many times over with
|
|
6
|
+
* different arguments (PR #2517 vs #2600) and those must all coexist. The case
|
|
7
|
+
* that must NOT happen is two agents, on two different machines, creating a
|
|
8
|
+
* watcher with the SAME arguments — one work item, two triggers.
|
|
9
|
+
*
|
|
10
|
+
* A local-only check cannot see that: each box reads its own `monitors/` dir and
|
|
11
|
+
* neither sees the other. So the duplicate guard asks the fleet, reusing the
|
|
12
|
+
* same cross-machine fan-out `sessions --active` and `fleet status` already run
|
|
13
|
+
* (`lib/remote-agents-json.ts`).
|
|
14
|
+
*
|
|
15
|
+
* Deliberately NOT solved by syncing `monitors/*.yml` through the DotAgents repo:
|
|
16
|
+
* an agent's per-PR watcher is running state, not config, and syncing would push
|
|
17
|
+
* every one of them onto every box — the accumulation this guard exists to stop.
|
|
18
|
+
*/
|
|
19
|
+
import { gatherRemoteAgentsJson } from '../remote-agents-json.js';
|
|
20
|
+
/** Recursion guard: a peer answering the fan-out must not fan out again. */
|
|
21
|
+
export const NO_MONITOR_FANOUT_ENV = 'AGENTS_MONITORS_LOCAL';
|
|
22
|
+
/**
|
|
23
|
+
* Parse a peer's `monitors list --json`. Defensive against version skew: a peer
|
|
24
|
+
* on an older CLI may emit a different shape or no JSON at all, and one bad peer
|
|
25
|
+
* must never blank the guard for the rest of the fleet.
|
|
26
|
+
*/
|
|
27
|
+
export function parseRemoteMonitors(stdout, machine) {
|
|
28
|
+
let parsed;
|
|
29
|
+
try {
|
|
30
|
+
parsed = JSON.parse(stdout);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
// `monitors list --json` writes a BARE array (stdoutJson -> JSON.stringify of
|
|
36
|
+
// monitors.map(...)); it has never wrapped. Guarding an envelope shape that has
|
|
37
|
+
// never existed would be a fallback for an imaginary bug.
|
|
38
|
+
if (!Array.isArray(parsed))
|
|
39
|
+
return [];
|
|
40
|
+
const rows = parsed;
|
|
41
|
+
const out = [];
|
|
42
|
+
for (const row of rows) {
|
|
43
|
+
if (!row || typeof row !== 'object' || Array.isArray(row))
|
|
44
|
+
continue;
|
|
45
|
+
const m = row;
|
|
46
|
+
// Without source+condition+action there is no identity to compare, so the
|
|
47
|
+
// row cannot participate in the duplicate check either way.
|
|
48
|
+
if (!m.name || !m.source || !m.condition || !m.action)
|
|
49
|
+
continue;
|
|
50
|
+
out.push({
|
|
51
|
+
machine,
|
|
52
|
+
monitor: { name: m.name, source: m.source, condition: m.condition, action: m.action },
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Every monitor on every other registered device. Never throws: an unreachable
|
|
59
|
+
* fleet degrades to an empty list plus the names we could not consult, and the
|
|
60
|
+
* caller decides what to say about them.
|
|
61
|
+
*/
|
|
62
|
+
export async function gatherFleetMonitors() {
|
|
63
|
+
try {
|
|
64
|
+
const result = await gatherRemoteAgentsJson({
|
|
65
|
+
args: ['monitors', 'list', '--json'],
|
|
66
|
+
noFanoutEnv: NO_MONITOR_FANOUT_ENV,
|
|
67
|
+
parse: parseRemoteMonitors,
|
|
68
|
+
quiet: true,
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
monitors: result.items,
|
|
72
|
+
skipped: [...result.skipped, ...result.parseFailed],
|
|
73
|
+
discoveryFailed: result.discoveryFailed,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// Could not consult the fleet at all. Reported as such by the caller, never
|
|
78
|
+
// as an absence of duplicates.
|
|
79
|
+
return { monitors: [], skipped: [], discoveryFailed: true };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -25,6 +25,31 @@ export interface MonitorState {
|
|
|
25
25
|
/** Epoch-ms timestamps of recent fires, for the rate-limit / firehose guard. */
|
|
26
26
|
fireTimes?: number[];
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Per-monitor liveness heartbeat, recorded on EVERY poll — fire or not, match or
|
|
30
|
+
* not. This is deliberately a separate record from MonitorState: change-detection
|
|
31
|
+
* state (lastHash/lastValue) is written only when a monitor fires or establishes
|
|
32
|
+
* a baseline, so a monitor that polls steadily but never matches leaves no
|
|
33
|
+
* change-detection trace. Without a heartbeat, `view` on such a monitor showed
|
|
34
|
+
* `state: null` — indistinguishable from a monitor the engine never touched, the
|
|
35
|
+
* exact confusion RUSH-2485 reports. The heartbeat makes "never checked" (no
|
|
36
|
+
* record) visibly distinct from "checked N times, not matching" (recent record,
|
|
37
|
+
* zero fires). Kept in its own file so it can never perturb the baseline logic in
|
|
38
|
+
* decideFire/hasChanged.
|
|
39
|
+
*/
|
|
40
|
+
export interface MonitorLiveness {
|
|
41
|
+
monitorName: string;
|
|
42
|
+
/** RFC3339 timestamp of the last poll attempt. */
|
|
43
|
+
lastCheckedAt: string;
|
|
44
|
+
/** Total polls the engine has run against this monitor's source. */
|
|
45
|
+
checkCount: number;
|
|
46
|
+
/** The last poll's error (source produced nothing / threw), cleared on the next good poll. */
|
|
47
|
+
lastError?: string;
|
|
48
|
+
/** Consecutive failed polls; reset to 0 on any successful observation. Drives drought escalation. */
|
|
49
|
+
consecutiveErrors: number;
|
|
50
|
+
/** RFC3339 timestamp of the last drought notification, so the engine notifies once per drought. */
|
|
51
|
+
droughtNotifiedAt?: string;
|
|
52
|
+
}
|
|
28
53
|
/** Per-monitor history root, with the (untrusted) name contained to one segment. */
|
|
29
54
|
export declare function getMonitorHistoryDir(name: string): string;
|
|
30
55
|
/** Directory holding a monitor's fire history. */
|
|
@@ -51,6 +76,20 @@ export declare function dedupeSignature(observation: string, dedupeKey?: string)
|
|
|
51
76
|
* caller persists the new value via writeState only on a real fire.
|
|
52
77
|
*/
|
|
53
78
|
export declare function hasChanged(name: string, observation: string, dedupeKey?: string): boolean;
|
|
79
|
+
/** Read a monitor's liveness heartbeat, or null if the engine has never polled it. */
|
|
80
|
+
export declare function readLiveness(name: string): MonitorLiveness | null;
|
|
81
|
+
/**
|
|
82
|
+
* Record one poll attempt as the monitor's liveness heartbeat — the single call
|
|
83
|
+
* the engine makes on every evaluation, regardless of fire/match. A successful
|
|
84
|
+
* observation clears `lastError`/`consecutiveErrors` and any drought flag; a
|
|
85
|
+
* failed one (source threw or produced nothing) records the error and increments
|
|
86
|
+
* the consecutive-failure counter the drought escalation reads. Written
|
|
87
|
+
* atomically (temp + rename) like writeStateRaw, and never touches state.json, so
|
|
88
|
+
* change-detection is untouched.
|
|
89
|
+
*/
|
|
90
|
+
export declare function recordCheck(name: string, checkedAt: string, error?: string): MonitorLiveness;
|
|
91
|
+
/** Stamp the drought-notified marker so the engine notifies at most once per drought. */
|
|
92
|
+
export declare function markDroughtNotified(name: string, at: string): void;
|
|
54
93
|
/**
|
|
55
94
|
* Append a fire timestamp and return the pruned window (fires within `windowMs`).
|
|
56
95
|
* The engine uses the returned length to decide whether the rate limit tripped.
|
|
@@ -23,6 +23,9 @@ export function getMonitorHistoryDir(name) {
|
|
|
23
23
|
function getStatePath(name) {
|
|
24
24
|
return path.join(getMonitorHistoryDir(name), 'state.json');
|
|
25
25
|
}
|
|
26
|
+
function getLivenessPath(name) {
|
|
27
|
+
return path.join(getMonitorHistoryDir(name), 'liveness.json');
|
|
28
|
+
}
|
|
26
29
|
/** Directory holding a monitor's fire history. */
|
|
27
30
|
export function getMonitorFiresDir(name) {
|
|
28
31
|
return path.join(getMonitorHistoryDir(name), 'fires');
|
|
@@ -100,6 +103,59 @@ export function hasChanged(name, observation, dedupeKey) {
|
|
|
100
103
|
return true;
|
|
101
104
|
return prev.lastHash !== hashSignature(observation, dedupeKey);
|
|
102
105
|
}
|
|
106
|
+
/** Read a monitor's liveness heartbeat, or null if the engine has never polled it. */
|
|
107
|
+
export function readLiveness(name) {
|
|
108
|
+
const livenessPath = getLivenessPath(name);
|
|
109
|
+
if (!fs.existsSync(livenessPath))
|
|
110
|
+
return null;
|
|
111
|
+
try {
|
|
112
|
+
return JSON.parse(fs.readFileSync(livenessPath, 'utf-8'));
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Record one poll attempt as the monitor's liveness heartbeat — the single call
|
|
120
|
+
* the engine makes on every evaluation, regardless of fire/match. A successful
|
|
121
|
+
* observation clears `lastError`/`consecutiveErrors` and any drought flag; a
|
|
122
|
+
* failed one (source threw or produced nothing) records the error and increments
|
|
123
|
+
* the consecutive-failure counter the drought escalation reads. Written
|
|
124
|
+
* atomically (temp + rename) like writeStateRaw, and never touches state.json, so
|
|
125
|
+
* change-detection is untouched.
|
|
126
|
+
*/
|
|
127
|
+
export function recordCheck(name, checkedAt, error) {
|
|
128
|
+
const prev = readLiveness(name);
|
|
129
|
+
const consecutiveErrors = error ? (prev?.consecutiveErrors ?? 0) + 1 : 0;
|
|
130
|
+
const liveness = {
|
|
131
|
+
monitorName: name,
|
|
132
|
+
lastCheckedAt: checkedAt,
|
|
133
|
+
checkCount: (prev?.checkCount ?? 0) + 1,
|
|
134
|
+
consecutiveErrors,
|
|
135
|
+
...(error ? { lastError: error } : {}),
|
|
136
|
+
// A drought flag only survives while the drought does — cleared on recovery.
|
|
137
|
+
...(error && prev?.droughtNotifiedAt ? { droughtNotifiedAt: prev.droughtNotifiedAt } : {}),
|
|
138
|
+
};
|
|
139
|
+
ensureAgentsDir();
|
|
140
|
+
const dir = getMonitorHistoryDir(name);
|
|
141
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
142
|
+
const livenessPath = path.join(dir, 'liveness.json');
|
|
143
|
+
const tmp = `${livenessPath}.tmp-${process.pid}`;
|
|
144
|
+
fs.writeFileSync(tmp, JSON.stringify(liveness, null, 2), 'utf-8');
|
|
145
|
+
fs.renameSync(tmp, livenessPath);
|
|
146
|
+
return liveness;
|
|
147
|
+
}
|
|
148
|
+
/** Stamp the drought-notified marker so the engine notifies at most once per drought. */
|
|
149
|
+
export function markDroughtNotified(name, at) {
|
|
150
|
+
const prev = readLiveness(name);
|
|
151
|
+
if (!prev)
|
|
152
|
+
return;
|
|
153
|
+
const livenessPath = getLivenessPath(name);
|
|
154
|
+
const next = { ...prev, droughtNotifiedAt: at };
|
|
155
|
+
const tmp = `${livenessPath}.tmp-${process.pid}`;
|
|
156
|
+
fs.writeFileSync(tmp, JSON.stringify(next, null, 2), 'utf-8');
|
|
157
|
+
fs.renameSync(tmp, livenessPath);
|
|
158
|
+
}
|
|
103
159
|
/**
|
|
104
160
|
* Append a fire timestamp and return the pruned window (fires within `windowMs`).
|
|
105
161
|
* The engine uses the returned length to decide whether the rate limit tripped.
|
|
@@ -29,6 +29,12 @@ export interface ImportableSecret {
|
|
|
29
29
|
itemTitle: string;
|
|
30
30
|
fieldLabel: string;
|
|
31
31
|
value: string;
|
|
32
|
+
/**
|
|
33
|
+
* The item's free-form notes (1Password's `notesPlain` / any NOTES-purpose
|
|
34
|
+
* field), carried as descriptive metadata for the imported secret. Never the
|
|
35
|
+
* secret value — omitted when the item has no notes.
|
|
36
|
+
*/
|
|
37
|
+
description?: string;
|
|
32
38
|
}
|
|
33
39
|
export interface SkippedField {
|
|
34
40
|
itemTitle: string;
|
|
@@ -41,6 +47,17 @@ export declare function listItems(vaultName: string): OpItemSummary[];
|
|
|
41
47
|
export declare function getItem(itemId: string, vaultName: string): OpItem;
|
|
42
48
|
export declare function toEnvKey(title: string): string;
|
|
43
49
|
export declare function slugify(name: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* Pure transform from a fetched 1Password item to an importable secret (or the
|
|
52
|
+
* reason it was skipped). Split out of extractSecrets so the field-selection
|
|
53
|
+
* and notes-extraction logic is exercised directly by tests without shelling
|
|
54
|
+
* out to `op`.
|
|
55
|
+
*/
|
|
56
|
+
export declare function itemToSecret(item: OpItem): {
|
|
57
|
+
secret: ImportableSecret;
|
|
58
|
+
} | {
|
|
59
|
+
skipped: SkippedField;
|
|
60
|
+
};
|
|
44
61
|
export declare function extractSecrets(items: OpItemSummary[], vaultName: string): {
|
|
45
62
|
secrets: ImportableSecret[];
|
|
46
63
|
skipped: SkippedField[];
|