@phnx-labs/agents-cli 1.22.35 → 1.22.38
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 +708 -2
- package/README.md +83 -43
- package/dist/bin/agents +0 -0
- package/dist/bootstrap.d.ts +15 -0
- package/dist/bootstrap.js +1212 -0
- package/dist/commands/accounts.d.ts +4 -11
- package/dist/commands/accounts.js +159 -27
- package/dist/commands/artifacts-setup.d.ts +53 -0
- package/dist/commands/artifacts-setup.js +161 -0
- package/dist/commands/artifacts.d.ts +18 -0
- package/dist/commands/artifacts.js +58 -0
- 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 +17 -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 +86 -42
- 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 +71 -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/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.d.ts +5 -0
- package/dist/commands/setup.js +31 -5
- package/dist/commands/share.d.ts +87 -7
- package/dist/commands/share.js +320 -58
- 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/hygiene.d.ts +90 -0
- package/dist/lib/browser/hygiene.js +146 -0
- package/dist/lib/browser/ipc.d.ts +30 -0
- package/dist/lib/browser/ipc.js +93 -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 +79 -1
- package/dist/lib/browser/service.js +231 -14
- 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 +63 -1
- 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 +21 -7
- package/dist/lib/git.js +122 -18
- 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 +44 -7
- 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/analytics.js +1 -1
- package/dist/lib/share/capture.d.ts +1 -1
- package/dist/lib/share/capture.js +3 -3
- package/dist/lib/share/config.d.ts +6 -2
- package/dist/lib/share/config.js +6 -5
- package/dist/lib/share/delete.js +3 -3
- package/dist/lib/share/provision.d.ts +38 -0
- package/dist/lib/share/provision.js +51 -1
- package/dist/lib/share/publish.d.ts +40 -1
- package/dist/lib/share/publish.js +119 -6
- package/dist/lib/share/worker-template.d.ts +12 -1
- package/dist/lib/share/worker-template.js +83 -7
- 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 +16 -14
- package/dist/lib/startup/command-registry.js +26 -37
- 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 +316 -0
- package/dist/lib/tmux/orphan-reap.js +638 -0
- package/dist/lib/tmux/session.d.ts +68 -3
- package/dist/lib/tmux/session.js +96 -5
- package/dist/lib/triggers/handlers.d.ts +20 -0
- package/dist/lib/triggers/handlers.js +16 -1
- package/dist/lib/triggers/webhook.js +10 -0
- package/dist/lib/types.d.ts +47 -35
- 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/set.d.ts +0 -15
- package/dist/commands/set.js +0 -79
- package/dist/commands/setup-share.d.ts +0 -17
- package/dist/commands/setup-share.js +0 -85
- 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
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agents cp <src> <dst>` — first-class fleet file transfer.
|
|
3
|
+
*
|
|
4
|
+
* Accepts `host:path` on either side (local = bare absolute path). Resolves
|
|
5
|
+
* `~` and `$HOME` remote paths ON the remote before transfer so a `$HOME` in
|
|
6
|
+
* the remote spec can never silently expand to the local user's home directory
|
|
7
|
+
* — the documented silent-failure class in `remote-dispatch-mutation-safety`.
|
|
8
|
+
*
|
|
9
|
+
* Reuses the same SSH fabric (SSH_OPTS, deviceIdentityArgs, resolveDeviceTarget)
|
|
10
|
+
* that `agents ssh` and `--host` dispatch use.
|
|
11
|
+
*/
|
|
12
|
+
import { spawnSync } from 'child_process';
|
|
13
|
+
import chalk from 'chalk';
|
|
14
|
+
import { setHelpSections } from '../lib/help.js';
|
|
15
|
+
import { resolveDeviceTarget } from '../lib/devices/resolve-target.js';
|
|
16
|
+
import { sshTargetFor, deviceIdentityArgs } from '../lib/devices/connect.js';
|
|
17
|
+
import { sshExec, SSH_OPTS } from '../lib/ssh-exec.js';
|
|
18
|
+
/**
|
|
19
|
+
* Parse one CLI token into an {@link Endpoint}.
|
|
20
|
+
*
|
|
21
|
+
* Grammar: `host:path` is remote; anything else is local. The host part must
|
|
22
|
+
* not contain a `/` (that would be a local absolute path with a colon in it —
|
|
23
|
+
* unusual but valid; the colon test covers the common fleet pattern and rejects
|
|
24
|
+
* the almost-never-seen edge case in favour of local-path semantics).
|
|
25
|
+
*
|
|
26
|
+
* Windows drive letters (`C:path`) are excluded by requiring the host part to
|
|
27
|
+
* be at least two characters and not a single capital letter.
|
|
28
|
+
*/
|
|
29
|
+
export function parseEndpoint(token) {
|
|
30
|
+
const colonIdx = token.indexOf(':');
|
|
31
|
+
if (colonIdx > 1 && !token.startsWith('/')) {
|
|
32
|
+
const host = token.slice(0, colonIdx);
|
|
33
|
+
const path = token.slice(colonIdx + 1);
|
|
34
|
+
// A bare single uppercase letter before `:` looks like a Windows drive
|
|
35
|
+
// letter — treat as local to avoid a confusing error.
|
|
36
|
+
if (!/^[A-Z]$/.test(host) && !host.includes('/')) {
|
|
37
|
+
return { isRemote: true, host, path };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return { isRemote: false, path: token };
|
|
41
|
+
}
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Remote path resolution — the core safeguard
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
/**
|
|
46
|
+
* Resolve the remote HOME directory in one SSH round-trip. Returns the
|
|
47
|
+
* absolute path (always starts with `/`), or throws on failure.
|
|
48
|
+
*/
|
|
49
|
+
export async function resolveRemoteHome(target, extraSshArgs) {
|
|
50
|
+
const res = sshExec(target, 'printf "%s" "$HOME"', {
|
|
51
|
+
extraSshArgs,
|
|
52
|
+
timeoutMs: 10_000,
|
|
53
|
+
});
|
|
54
|
+
if (res.code !== 0) {
|
|
55
|
+
throw new Error(`Cannot resolve remote $HOME on ${target}: ${res.stderr.trim() || 'ssh exited ' + String(res.code)}`);
|
|
56
|
+
}
|
|
57
|
+
const home = res.stdout.trim();
|
|
58
|
+
if (!home || !home.startsWith('/')) {
|
|
59
|
+
throw new Error(`Remote $HOME on ${target} is not an absolute path: ${JSON.stringify(home)}`);
|
|
60
|
+
}
|
|
61
|
+
return home;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Pure home-prefix expansion. Given a remote HOME (already resolved on the
|
|
65
|
+
* remote) and a raw path token, substitute a leading `~` or `$HOME`. Paths
|
|
66
|
+
* without those prefixes are returned unchanged. Never consults
|
|
67
|
+
* `process.env.HOME` — the local home must not leak into a remote path.
|
|
68
|
+
*/
|
|
69
|
+
export function expandRemoteHomePrefix(rawPath, remoteHome) {
|
|
70
|
+
if (rawPath === '~' || rawPath.startsWith('~/')) {
|
|
71
|
+
return rawPath === '~' ? remoteHome : remoteHome + rawPath.slice(1);
|
|
72
|
+
}
|
|
73
|
+
if (rawPath === '$HOME' || rawPath.startsWith('$HOME/')) {
|
|
74
|
+
return rawPath === '$HOME' ? remoteHome : remoteHome + rawPath.slice(5);
|
|
75
|
+
}
|
|
76
|
+
return rawPath;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Expand a remote path's leading `~` or literal `$HOME` to an absolute path
|
|
80
|
+
* resolved **on the remote**, not locally. A path that already starts with `/`
|
|
81
|
+
* is returned unchanged (no round-trip).
|
|
82
|
+
*
|
|
83
|
+
* This is the safeguard against the silent-failure class: a `$HOME` or `~`
|
|
84
|
+
* that would expand to the **local** user's home if left in a shell command.
|
|
85
|
+
*/
|
|
86
|
+
export async function resolveRemotePath(rawPath, target, extraSshArgs) {
|
|
87
|
+
if (rawPath === '~' ||
|
|
88
|
+
rawPath.startsWith('~/') ||
|
|
89
|
+
rawPath === '$HOME' ||
|
|
90
|
+
rawPath.startsWith('$HOME/')) {
|
|
91
|
+
const home = await resolveRemoteHome(target, extraSshArgs);
|
|
92
|
+
return expandRemoteHomePrefix(rawPath, home);
|
|
93
|
+
}
|
|
94
|
+
return rawPath;
|
|
95
|
+
}
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// scp invocation
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
/**
|
|
100
|
+
* Build the argv for the `scp` invocation. Pure — no spawning — so it is
|
|
101
|
+
* unit-testable.
|
|
102
|
+
*
|
|
103
|
+
* `scp` accepts ssh `-o` options, so `SSH_OPTS` flows through unchanged.
|
|
104
|
+
* Identity args (`-i key -o IdentitiesOnly=yes`) are included only when a
|
|
105
|
+
* single remote device is involved; for two-remote transfers the identity
|
|
106
|
+
* applies to both connections, which works when the fleet uses a shared key.
|
|
107
|
+
*
|
|
108
|
+
* Remote endpoints are formatted as `user@host:path` (or bare `host:path`),
|
|
109
|
+
* local endpoints as their absolute path. Paths have already been resolved
|
|
110
|
+
* (no `~` or `$HOME`) by the time this is called.
|
|
111
|
+
*/
|
|
112
|
+
export function buildScpArgv({ srcSpec, dstSpec, identityArgs, recursive, }) {
|
|
113
|
+
const opts = [...SSH_OPTS, ...identityArgs];
|
|
114
|
+
if (recursive)
|
|
115
|
+
opts.push('-r');
|
|
116
|
+
// Two-remote transfers route through the local machine (-3) to avoid
|
|
117
|
+
// requiring direct SSH access between the two fleet boxes.
|
|
118
|
+
if (srcSpec.includes(':') && dstSpec.includes(':'))
|
|
119
|
+
opts.push('-3');
|
|
120
|
+
return [...opts, srcSpec, dstSpec];
|
|
121
|
+
}
|
|
122
|
+
async function resolveEndpoint(ep) {
|
|
123
|
+
if (!ep.isRemote) {
|
|
124
|
+
return { spec: ep.path, identityArgs: [] };
|
|
125
|
+
}
|
|
126
|
+
const device = await resolveDeviceTarget(ep.host);
|
|
127
|
+
if (!device) {
|
|
128
|
+
throw new Error(`Unknown device ${JSON.stringify(ep.host)}. Register it with \`agents devices add\` or check \`agents devices\`.`);
|
|
129
|
+
}
|
|
130
|
+
const target = sshTargetFor(device);
|
|
131
|
+
const idArgs = deviceIdentityArgs(device);
|
|
132
|
+
const resolvedPath = await resolveRemotePath(ep.path, target, idArgs);
|
|
133
|
+
if (!resolvedPath.startsWith('/')) {
|
|
134
|
+
throw new Error(`Remote path on ${ep.host} must be absolute after expansion, got: ${JSON.stringify(resolvedPath)}. Use an absolute path or start with ~/`);
|
|
135
|
+
}
|
|
136
|
+
return { spec: `${target}:${resolvedPath}`, identityArgs: idArgs };
|
|
137
|
+
}
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
// Validation
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
/** Validate a local path: must be absolute. */
|
|
142
|
+
function validateLocalPath(raw) {
|
|
143
|
+
if (!raw.startsWith('/')) {
|
|
144
|
+
throw new Error(`Local path must be absolute, got: ${JSON.stringify(raw)}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/** Validate a remote path: non-empty. */
|
|
148
|
+
function validateRemotePath(raw, host) {
|
|
149
|
+
if (!raw) {
|
|
150
|
+
throw new Error(`Remote path for ${JSON.stringify(host)} is empty. Expected: ${host}:/abs/path`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// Command registration
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
export function registerCpCommand(program) {
|
|
157
|
+
const cmd = program
|
|
158
|
+
.command('cp <src> <dst>')
|
|
159
|
+
.description('Copy a file or directory between fleet hosts. Either endpoint may be host:path (remote) or an absolute local path.')
|
|
160
|
+
.option('-r, --recursive', 'Copy directories recursively')
|
|
161
|
+
.action(async (srcToken, dstToken, opts) => {
|
|
162
|
+
const recursive = opts.recursive ?? false;
|
|
163
|
+
const srcEp = parseEndpoint(srcToken);
|
|
164
|
+
const dstEp = parseEndpoint(dstToken);
|
|
165
|
+
if (!srcEp.isRemote && !dstEp.isRemote) {
|
|
166
|
+
process.stderr.write(chalk.red('error:') +
|
|
167
|
+
' At least one endpoint must be remote (host:path). For local-only copies use `cp`.\n');
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
let srcResolved;
|
|
171
|
+
let dstResolved;
|
|
172
|
+
try {
|
|
173
|
+
// Validate shapes before any network calls.
|
|
174
|
+
if (!srcEp.isRemote)
|
|
175
|
+
validateLocalPath(srcEp.path);
|
|
176
|
+
if (srcEp.isRemote)
|
|
177
|
+
validateRemotePath(srcEp.path, srcEp.host);
|
|
178
|
+
if (!dstEp.isRemote)
|
|
179
|
+
validateLocalPath(dstEp.path);
|
|
180
|
+
if (dstEp.isRemote)
|
|
181
|
+
validateRemotePath(dstEp.path, dstEp.host);
|
|
182
|
+
[srcResolved, dstResolved] = await Promise.all([
|
|
183
|
+
resolveEndpoint(srcEp),
|
|
184
|
+
resolveEndpoint(dstEp),
|
|
185
|
+
]);
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
process.stderr.write(chalk.red('error:') + ' ' + err.message + '\n');
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
// Merge identity args: for two-remote transfers, omit per-device identity
|
|
192
|
+
// args (they apply to both connections via -3; a conflict would need
|
|
193
|
+
// separate -i per connection which scp doesn't support). A shared fleet
|
|
194
|
+
// key works without explicit -i.
|
|
195
|
+
const identityArgs = srcResolved.identityArgs.length > 0 && dstResolved.identityArgs.length === 0
|
|
196
|
+
? srcResolved.identityArgs
|
|
197
|
+
: dstResolved.identityArgs.length > 0 && srcResolved.identityArgs.length === 0
|
|
198
|
+
? dstResolved.identityArgs
|
|
199
|
+
: srcResolved.identityArgs.join(',') === dstResolved.identityArgs.join(',')
|
|
200
|
+
? srcResolved.identityArgs
|
|
201
|
+
: []; // two remotes with different keys — skip per-device flag, use agent's default key
|
|
202
|
+
const argv = buildScpArgv({
|
|
203
|
+
srcSpec: srcResolved.spec,
|
|
204
|
+
dstSpec: dstResolved.spec,
|
|
205
|
+
identityArgs,
|
|
206
|
+
recursive,
|
|
207
|
+
});
|
|
208
|
+
const res = spawnSync('scp', argv, { stdio: 'inherit' });
|
|
209
|
+
const code = typeof res.status === 'number' ? res.status : 1;
|
|
210
|
+
if (code !== 0) {
|
|
211
|
+
// scp already printed a diagnostic; just exit non-zero.
|
|
212
|
+
process.exit(code);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
setHelpSections(cmd, {
|
|
216
|
+
examples: `
|
|
217
|
+
agents cp yosemite-s0:/abs/data.json /tmp/data.json # download a file from a fleet host
|
|
218
|
+
agents cp /tmp/build.tar.gz mac-mini:/abs/deploy/ # upload a local file to a fleet host
|
|
219
|
+
agents cp -r yosemite-s0:/abs/src/ mac-mini:/abs/dst/ # copy a directory between two fleet hosts
|
|
220
|
+
agents cp yosemite-s0:~/logs/run.log /tmp/run.log # ~ is resolved on the remote host
|
|
221
|
+
`,
|
|
222
|
+
notes: `
|
|
223
|
+
Local paths must be absolute (use /abs/path, not ./relative).
|
|
224
|
+
Remote paths must be absolute after ~ / $HOME expansion.
|
|
225
|
+
Remote $HOME is resolved on the remote host — never in the local shell.
|
|
226
|
+
Both local-to-remote and remote-to-local transfers are supported.
|
|
227
|
+
Remote-to-remote transfers route through the local machine (-3).
|
|
228
|
+
Devices must be registered: 'agents devices' to list, 'agents devices add' to enroll.
|
|
229
|
+
`,
|
|
230
|
+
});
|
|
231
|
+
}
|
package/dist/commands/daemon.js
CHANGED
|
@@ -19,14 +19,105 @@ import { execFileSync } from 'child_process';
|
|
|
19
19
|
import * as fs from 'fs';
|
|
20
20
|
import * as path from 'path';
|
|
21
21
|
import { setHelpSections } from '../lib/help.js';
|
|
22
|
-
import { getDaemonStatus, isDaemonRunning, isDaemonWedged, readDaemonLog, startDaemon, stopDaemon, signalDaemonReload, findSurvivingStateDirDaemons, } from '../lib/daemon.js';
|
|
22
|
+
import { getDaemonStatus, isDaemonRunning, isDaemonWedged, readDaemonLog, startDaemon, stopDaemon, signalDaemonReload, findSurvivingStateDirDaemons, getDaemonLogPath, isDaemonAutostartCircuitOpen, } from '../lib/daemon.js';
|
|
23
23
|
import { getConfigValue, setConfigValue, isDaemonEnabled } from '../lib/device-config.js';
|
|
24
|
-
import { readSubsystemHealth, SUBSYSTEM_SECRETS_BROKER, SUBSYSTEM_BROWSER_IPC, } from '../lib/daemon-health.js';
|
|
24
|
+
import { readSubsystemHealth, SUBSYSTEM_SECRETS_BROKER, SUBSYSTEM_BROWSER_IPC, SUBSYSTEM_DAEMON_START, } from '../lib/daemon-health.js';
|
|
25
|
+
import { DAEMON_SERVICE_IDS, listDaemonServiceStates, setDaemonServiceEnabled, getDaemonServicesConfigPath, } from '../lib/daemon-services.js';
|
|
25
26
|
import { listJobs, getLatestRun } from '../lib/routines.js';
|
|
26
27
|
import { JobScheduler } from '../lib/scheduler.js';
|
|
27
|
-
import { getDaemonDir } from '../lib/state.js';
|
|
28
28
|
import { followFile } from '../lib/log-follow.js';
|
|
29
29
|
import { parseDuration } from '../lib/hooks/cache.js';
|
|
30
|
+
import { registerFunnelCommand } from './funnel.js';
|
|
31
|
+
/**
|
|
32
|
+
* Provably absent, or null when unknowable.
|
|
33
|
+
*
|
|
34
|
+
* `fs.existsSync` cannot express the difference: it returns false for ANY failed
|
|
35
|
+
* stat, so `EACCES` on a parent directory is indistinguishable from deletion.
|
|
36
|
+
* The feeding scan is box-wide `ps` with no uid filter, so that gap is reachable
|
|
37
|
+
* on any shared box — `/root` is mode 700, and a stat of a root-owned daemon's
|
|
38
|
+
* entry from an ordinary uid returns false while the file is perfectly present.
|
|
39
|
+
* Reporting that would tell the user to `kill` a healthy daemon they do not own,
|
|
40
|
+
* with a command that would `EPERM` anyway.
|
|
41
|
+
*
|
|
42
|
+
* `statSync(p, { throwIfNoEntry: false })` returns `undefined` only for `ENOENT`
|
|
43
|
+
* and throws for everything else, which is exactly the distinction needed.
|
|
44
|
+
*/
|
|
45
|
+
function entryAbsent(p) {
|
|
46
|
+
try {
|
|
47
|
+
return fs.statSync(p, { throwIfNoEntry: false }) === undefined;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A daemon whose launch entry is provably gone from disk (RUSH-2493).
|
|
55
|
+
*
|
|
56
|
+
* This predicate answers only "is this one's code gone?". WHO may be told about
|
|
57
|
+
* it, and whether that report is actionable, is {@link staleDaemons}'s job — a
|
|
58
|
+
* separation reached the hard way: an early revision reported every box-wide
|
|
59
|
+
* `ps` match as actionable (re-opening RUSH-2368), and the correction then
|
|
60
|
+
* over-swung to registry-only, which silently excluded the very incident below.
|
|
61
|
+
*
|
|
62
|
+
* Observed 2026-08-10 on yosemite-s0: a daemon ran for 4h14m from
|
|
63
|
+
* `.agents/worktrees/rush-2431-binary-shadow/apps/cli/dist/index.js` after that
|
|
64
|
+
* worktree was deleted. `systemctl --user is-active` said `active`,
|
|
65
|
+
* `agents daemon status` reported healthy, and nothing anywhere named it —
|
|
66
|
+
* while it held a second routine scheduler, the double-fire class the
|
|
67
|
+
* one-scheduler-one-executor rule exists to prevent. A restart would also have
|
|
68
|
+
* failed, since the manifest pointed at the same missing path.
|
|
69
|
+
*
|
|
70
|
+
* ABSOLUTE PATHS ONLY. `entryFromTokens` returns the second-to-last argv token,
|
|
71
|
+
* which is the entry for a real daemon (`getDaemonLaunch` always spawns an
|
|
72
|
+
* absolute one) but is arbitrary text for anything else — `node -e '<code>'
|
|
73
|
+
* __daemon-run` yields the code blob, which of course does not exist on disk.
|
|
74
|
+
* Requiring absoluteness keeps a non-path token from being reported as deleted
|
|
75
|
+
* code, the same false-positive class RUSH-2368 had to correct for duplicates.
|
|
76
|
+
*
|
|
77
|
+
* That guard also covers an entry path containing SPACES, which `ps` renders
|
|
78
|
+
* unquoted and the tokenizer therefore splits: `/tmp/ghost space/sub/index.js`
|
|
79
|
+
* yields `space/sub/index.js`, not absolute, so a healthy daemon on such a path
|
|
80
|
+
* is never accused (verified live). The cost is a false NEGATIVE — a genuinely
|
|
81
|
+
* deleted entry containing a space is not reported either. That is the safe
|
|
82
|
+
* direction to fail: missing one detection is a silence we already lived with,
|
|
83
|
+
* whereas telling someone to `kill` a healthy shared daemon is a new harm.
|
|
84
|
+
*/
|
|
85
|
+
function entryIsGone(p) {
|
|
86
|
+
return p.entry !== null && path.isAbsolute(p.entry) && p.entryMissing === true;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Stale daemons split into two tiers, because DETECTION and ACCUSATION are
|
|
90
|
+
* different acts with different blast radii.
|
|
91
|
+
*
|
|
92
|
+
* RUSH-2368's harm was the accusation, not the sighting: a leaked fixture "was
|
|
93
|
+
* reported as a stray to `kill`". So the narrow registry scope is what gates
|
|
94
|
+
* anything actionable — a `doctor` problem, a non-zero exit, a `kill` — while a
|
|
95
|
+
* same-uid sighting is merely shown.
|
|
96
|
+
*
|
|
97
|
+
* That split is not academic. The incident this feature exists to catch was
|
|
98
|
+
* neither the tracked pid nor in the registry: it ran under an ephemeral `/tmp`
|
|
99
|
+
* cwd from a deleted worktree, and `lib/daemon.ts` documents that such a process
|
|
100
|
+
* "registers under its own state dir and is invisible here" BY DESIGN. Gating
|
|
101
|
+
* the display on the registry too would have made this command silent on the
|
|
102
|
+
* exact 4h14m ghost that motivated it.
|
|
103
|
+
*
|
|
104
|
+
* `actionable` — this device's daemon, or this install's registry. Drives
|
|
105
|
+
* `doctor` problems and the `kill`/`restart` remediation.
|
|
106
|
+
* `visible` — additionally any daemon running as THIS uid. Shown in
|
|
107
|
+
* `status`, never exits non-zero, never told to kill.
|
|
108
|
+
*
|
|
109
|
+
* A different uid is never named at all: we cannot reliably stat its entry
|
|
110
|
+
* ({@link entryAbsent}), and we could not signal it if we tried.
|
|
111
|
+
*/
|
|
112
|
+
function staleDaemons(processes, ownerPid, registered) {
|
|
113
|
+
const isOurs = (p) => p.pid === ownerPid || registered.has(p.pid);
|
|
114
|
+
const myUid = typeof process.getuid === 'function' ? process.getuid() : null;
|
|
115
|
+
const gone = processes.filter(entryIsGone);
|
|
116
|
+
return {
|
|
117
|
+
actionable: gone.filter(isOurs),
|
|
118
|
+
visible: gone.filter((p) => isOurs(p) || (myUid !== null && p.uid === myUid)),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
30
121
|
/**
|
|
31
122
|
* Resolve the working directory a live process was started from, or null if
|
|
32
123
|
* unavailable. Linux only (`/proc/<pid>/cwd`) — there is no equivalent
|
|
@@ -118,26 +209,31 @@ function scanDaemonProcesses() {
|
|
|
118
209
|
return [];
|
|
119
210
|
let out;
|
|
120
211
|
try {
|
|
121
|
-
out = execFileSync('ps', ['-eo', 'pid=,args='], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
212
|
+
out = execFileSync('ps', ['-eo', 'pid=,uid=,args='], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
122
213
|
}
|
|
123
214
|
catch {
|
|
124
215
|
return [];
|
|
125
216
|
}
|
|
126
217
|
const found = [];
|
|
127
218
|
for (const line of out.split('\n')) {
|
|
128
|
-
const m = line.trim().match(/^(\d+)\s+(.*)$/);
|
|
219
|
+
const m = line.trim().match(/^(\d+)\s+(\d+)\s+(.*)$/);
|
|
129
220
|
if (!m)
|
|
130
221
|
continue;
|
|
131
|
-
const args = m[
|
|
222
|
+
const args = m[3].trim();
|
|
132
223
|
const tokens = args.split(/\s+/);
|
|
133
224
|
if (tokens.length === 0 || tokens[tokens.length - 1] !== '__daemon-run')
|
|
134
225
|
continue;
|
|
135
226
|
const pid = parseInt(m[1], 10);
|
|
136
227
|
if (isNaN(pid))
|
|
137
228
|
continue;
|
|
229
|
+
const uidParsed = parseInt(m[2], 10);
|
|
230
|
+
const uid = isNaN(uidParsed) ? null : uidParsed;
|
|
138
231
|
const entry = entryFromTokens(tokens);
|
|
139
232
|
const version = entry ? resolveVersionNear(entry, pid) : null;
|
|
140
|
-
|
|
233
|
+
// Asks whether the code is on disk for the NEXT launch -- what a restart and
|
|
234
|
+
// every other reader will see -- not what this pid currently has mapped.
|
|
235
|
+
const entryMissing = entry ? entryAbsent(entry) : false;
|
|
236
|
+
found.push({ pid, entry, version, entryMissing, uid });
|
|
141
237
|
}
|
|
142
238
|
return found;
|
|
143
239
|
}
|
|
@@ -255,6 +351,17 @@ async function runStatus(opts) {
|
|
|
255
351
|
const processes = scanDaemonProcesses();
|
|
256
352
|
const owner = pid ? processes.find((p) => p.pid === pid) : undefined;
|
|
257
353
|
const duplicates = registryScopedDuplicates(processes, pid ?? null);
|
|
354
|
+
// Every daemon whose code is gone from disk, including this device's own if
|
|
355
|
+
// it is one. Not filtered by the duplicate scope — see entryIsGone.
|
|
356
|
+
// Deliberately its own read, NOT shared with registryScopedDuplicates above.
|
|
357
|
+
// That one passes `exclude` INTO findSurvivingStateDirDaemons to drop the
|
|
358
|
+
// owner, so handing it a shared empty-exclude snapshot would put ownerPid back
|
|
359
|
+
// in the set and report this device's daemon as its own duplicate. The saving
|
|
360
|
+
// would be one `ps` spawn per registered pid, at the 1-3 that actually run.
|
|
361
|
+
const registeredPids = new Set(findSurvivingStateDirDaemons(new Set()));
|
|
362
|
+
const staleTiers = staleDaemons(processes, pid ?? null, registeredPids);
|
|
363
|
+
const stale = staleTiers.visible;
|
|
364
|
+
const ownerEntryGone = owner ? entryIsGone(owner) : false;
|
|
258
365
|
const [secrets, browserIpc] = await Promise.all([probeSecretsBroker(), probeBrowserIPC()]);
|
|
259
366
|
const scheduler = schedulerSummary();
|
|
260
367
|
if (opts.json) {
|
|
@@ -266,7 +373,18 @@ async function runStatus(opts) {
|
|
|
266
373
|
logPath: status.logPath,
|
|
267
374
|
binaryPath: owner?.entry ?? status.binaryPath,
|
|
268
375
|
binaryVersion: owner?.version ?? null,
|
|
376
|
+
binaryMissing: ownerEntryGone,
|
|
269
377
|
duplicates: duplicates.map((d) => ({ pid: d.pid, entry: d.entry, version: d.version })),
|
|
378
|
+
// `actionable` is part of the contract, not decoration: a routine or
|
|
379
|
+
// monitor reading this must be able to tell a ghost it may act on from one
|
|
380
|
+
// that is merely visible. Without it the machine surface re-opens exactly
|
|
381
|
+
// what the two-tier split closed for the text surface.
|
|
382
|
+
staleBinaries: stale.map((d) => ({
|
|
383
|
+
pid: d.pid,
|
|
384
|
+
entry: d.entry,
|
|
385
|
+
version: d.version,
|
|
386
|
+
actionable: staleTiers.actionable.some((a) => a.pid === d.pid),
|
|
387
|
+
})),
|
|
270
388
|
daemonEnabled: enabled,
|
|
271
389
|
services: {
|
|
272
390
|
secretsBroker: {
|
|
@@ -304,11 +422,36 @@ async function runStatus(opts) {
|
|
|
304
422
|
console.log(` Uptime: ${humanDuration(uptime)}`);
|
|
305
423
|
if (heartbeatAgeMs !== null)
|
|
306
424
|
console.log(` Heartbeat: ${Math.round(heartbeatAgeMs / 1000)}s ago`);
|
|
307
|
-
|
|
425
|
+
const binaryLabel = owner?.entry ?? status.binaryPath ?? 'unknown';
|
|
426
|
+
console.log(` Binary: ${ownerEntryGone ? chalk.red(`${binaryLabel} (MISSING from disk)`) : chalk.gray(binaryLabel)}`);
|
|
308
427
|
console.log(` Version: ${chalk.gray(owner?.version ?? 'unknown')}`);
|
|
309
428
|
console.log(` Log: ${chalk.gray(status.logPath)}`);
|
|
310
429
|
if (!enabled)
|
|
311
430
|
console.log(chalk.yellow(` daemon.enabled is false — nothing auto-starts it. Explicit start: agents daemon start`));
|
|
431
|
+
if (stale.length > 0) {
|
|
432
|
+
console.log(chalk.red(`\nStale code (${stale.length})\n`));
|
|
433
|
+
const actionablePids = new Set(staleTiers.actionable.map((d) => d.pid));
|
|
434
|
+
for (const d of stale) {
|
|
435
|
+
const mine = pid !== null && d.pid === pid ? ' — this is the daemon above' : '';
|
|
436
|
+
const note = mine || (actionablePids.has(d.pid) ? '' : ' — not this install; shown for visibility');
|
|
437
|
+
console.log(` PID ${d.pid} ${chalk.gray(d.entry ?? 'unknown entry')}${chalk.red(' (deleted)')}${chalk.gray(note)}`);
|
|
438
|
+
}
|
|
439
|
+
const advice = [
|
|
440
|
+
'\n These run code that no longer exists on disk, so a restart fails and their',
|
|
441
|
+
' behaviour is whatever was loaded when the file was deleted.',
|
|
442
|
+
];
|
|
443
|
+
// Only offer a remedy when a row here is one we may act on. Printing
|
|
444
|
+
// `kill <pid>` under a section whose only rows are visibility-tier is
|
|
445
|
+
// RUSH-2368's harm re-entering through the render layer after the data
|
|
446
|
+
// layer stopped producing it.
|
|
447
|
+
if (staleTiers.actionable.length > 0) {
|
|
448
|
+
advice.push(` Yours: ${chalk.white('agents daemon restart')} A stray this install owns: ${chalk.white('kill <pid>')}`);
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
advice.push(' None belong to this install — nothing for you to stop here.');
|
|
452
|
+
}
|
|
453
|
+
console.log(chalk.gray(advice.join('\n')));
|
|
454
|
+
}
|
|
312
455
|
if (duplicates.length > 0) {
|
|
313
456
|
console.log(chalk.red(`\nDuplicates (${duplicates.length})\n`));
|
|
314
457
|
for (const d of duplicates) {
|
|
@@ -377,7 +520,7 @@ async function runLogs(opts) {
|
|
|
377
520
|
const sinceMs = opts.since ? Date.now() - (parseDuration(opts.since) ?? 0) * 1000 : undefined;
|
|
378
521
|
const lineCount = opts.lines ? parseInt(opts.lines, 10) : 50;
|
|
379
522
|
if (opts.follow) {
|
|
380
|
-
const logPath =
|
|
523
|
+
const logPath = getDaemonLogPath();
|
|
381
524
|
for (const entry of parseLogLines(readDaemonLog(lineCount)).filter((e) => passesFilters(e, opts.level, sinceMs))) {
|
|
382
525
|
if (opts.json)
|
|
383
526
|
console.log(JSON.stringify(entry));
|
|
@@ -419,10 +562,37 @@ async function runDoctor(opts) {
|
|
|
419
562
|
problems.push('Daemon is not running. Start it: agents daemon start');
|
|
420
563
|
if (status.running && isDaemonWedged())
|
|
421
564
|
problems.push('Daemon is wedged (heartbeat stale). Restart: agents daemon restart');
|
|
422
|
-
|
|
565
|
+
// RUSH-2418: an open auto-start circuit breaker is the FIRST thing to report
|
|
566
|
+
// for a stopped daemon — otherwise the only advice is "agents daemon start",
|
|
567
|
+
// which is the exact action the breaker just refused, with no hint that a
|
|
568
|
+
// breaker exists or what the underlying failure was. This is where the
|
|
569
|
+
// breaker's own message sends the operator, so it has to answer them.
|
|
570
|
+
const startHealth = readSubsystemHealth(SUBSYSTEM_DAEMON_START);
|
|
571
|
+
if (isDaemonAutostartCircuitOpen()) {
|
|
572
|
+
problems.push(`Daemon auto-start is disabled after ${startHealth?.consecutiveFailures ?? 0} consecutive starts that never reported healthy: ` +
|
|
573
|
+
`${startHealth?.lastError ?? 'no reason recorded'}. Fix the cause, then retry with: agents daemon start`);
|
|
574
|
+
}
|
|
575
|
+
else if (!status.running && startHealth && startHealth.consecutiveFailures > 0) {
|
|
576
|
+
// Only while the daemon is DOWN. A start is marked as failed the moment it
|
|
577
|
+
// is issued and cleared once the daemon finishes booting, so a running
|
|
578
|
+
// daemon with a non-zero streak is just the boot window (or a daemon
|
|
579
|
+
// launched outside startDaemon) — reporting it there is a false alarm that
|
|
580
|
+
// clears itself a second later.
|
|
581
|
+
problems.push(`Daemon start has ${startHealth.consecutiveFailures} consecutive failure(s): ${startHealth.lastError}`);
|
|
582
|
+
}
|
|
583
|
+
const healthProcesses = scanDaemonProcesses();
|
|
584
|
+
const duplicates = registryScopedDuplicates(healthProcesses, status.pid);
|
|
423
585
|
if (duplicates.length > 0) {
|
|
424
586
|
problems.push(`${duplicates.length} duplicate daemon process(es) running: ${duplicates.map((d) => d.pid).join(', ')}. Stop the stray(s).`);
|
|
425
587
|
}
|
|
588
|
+
// A daemon whose entry is gone from disk is a problem even when it answers
|
|
589
|
+
// every probe: it cannot restart, and it is running whatever was loaded
|
|
590
|
+
// before the file was deleted (RUSH-2493).
|
|
591
|
+
for (const p of staleDaemons(healthProcesses, status.pid, new Set(findSurvivingStateDirDaemons(new Set()))).actionable) {
|
|
592
|
+
const own = status.pid !== null && p.pid === status.pid;
|
|
593
|
+
problems.push(`Daemon pid ${p.pid} runs code deleted from disk (${p.entry}). ` +
|
|
594
|
+
(own ? 'Restart it: agents daemon restart' : 'Stray from a removed install/worktree. Stop it: kill ' + p.pid));
|
|
595
|
+
}
|
|
426
596
|
const secrets = await probeSecretsBroker();
|
|
427
597
|
if (!secrets.reachable)
|
|
428
598
|
problems.push('Secrets broker is unreachable.');
|
|
@@ -468,7 +638,7 @@ export function registerDaemonCommand(program) {
|
|
|
468
638
|
# Identity, duplicates, and per-service health in one view
|
|
469
639
|
agents daemon status
|
|
470
640
|
|
|
471
|
-
# Machine-readable status (for scripts /
|
|
641
|
+
# Machine-readable status (for scripts / AGI EXT)
|
|
472
642
|
agents daemon status --json
|
|
473
643
|
|
|
474
644
|
# Start / stop / restart the daemon process
|
|
@@ -486,6 +656,10 @@ export function registerDaemonCommand(program) {
|
|
|
486
656
|
# Just the two hosted services (secrets broker, browser IPC)
|
|
487
657
|
agents daemon services
|
|
488
658
|
|
|
659
|
+
# Manage public ingress for daemon-world webhook receivers
|
|
660
|
+
agents daemon funnel status yosemite-s0
|
|
661
|
+
agents daemon funnel up yosemite-s0 --local-port 8787 --port 443
|
|
662
|
+
|
|
489
663
|
# Tail the daemon's own log, warnings and up, from the last hour
|
|
490
664
|
agents daemon logs -f --level warn --since 1h
|
|
491
665
|
|
|
@@ -504,7 +678,7 @@ export function registerDaemonCommand(program) {
|
|
|
504
678
|
`,
|
|
505
679
|
});
|
|
506
680
|
cmd.command('status')
|
|
507
|
-
.description('Identity (state/pid/uptime/binary), duplicate
|
|
681
|
+
.description('Identity (state/pid/uptime/binary), duplicate daemons, daemons running deleted code, and per-service health.')
|
|
508
682
|
.option('--json', 'Emit as JSON')
|
|
509
683
|
.action(async (opts, command) => {
|
|
510
684
|
await runStatus({ json: command.optsWithGlobals().json === true });
|
|
@@ -595,12 +769,67 @@ export function registerDaemonCommand(program) {
|
|
|
595
769
|
const ok = signalDaemonReload();
|
|
596
770
|
console.log(ok ? chalk.green('Daemon reloaded') : chalk.yellow('Reload signal not delivered (unsupported on this platform, or the daemon just exited)'));
|
|
597
771
|
});
|
|
598
|
-
cmd
|
|
599
|
-
.
|
|
772
|
+
const servicesCmd = cmd
|
|
773
|
+
.command('services')
|
|
774
|
+
.description('The hosted services: health, bound state, socket path, and per-service toggles.')
|
|
600
775
|
.option('--json', 'Emit as JSON')
|
|
601
776
|
.action(async (opts, command) => {
|
|
602
777
|
await runServices({ json: command.optsWithGlobals().json === true });
|
|
603
778
|
});
|
|
779
|
+
servicesCmd
|
|
780
|
+
.command('list')
|
|
781
|
+
.description('List every daemon service and whether it is enabled.')
|
|
782
|
+
.option('--json', 'Emit as JSON')
|
|
783
|
+
.action(async (opts, command) => {
|
|
784
|
+
const json = command.optsWithGlobals().json === true;
|
|
785
|
+
const states = listDaemonServiceStates();
|
|
786
|
+
if (json) {
|
|
787
|
+
console.log(JSON.stringify(states.map((s) => ({
|
|
788
|
+
id: s.id,
|
|
789
|
+
title: s.title,
|
|
790
|
+
enabled: s.enabled,
|
|
791
|
+
description: s.description,
|
|
792
|
+
})), null, 2));
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
console.log(chalk.bold('Daemon services'));
|
|
796
|
+
for (const s of states) {
|
|
797
|
+
const state = s.enabled ? chalk.green('enabled') : chalk.gray('disabled');
|
|
798
|
+
console.log(` ${s.id.padEnd(18)} ${state}`);
|
|
799
|
+
console.log(` ${chalk.gray(s.description)}`);
|
|
800
|
+
}
|
|
801
|
+
console.log(chalk.gray(`\nConfig: ${getDaemonServicesConfigPath()}`));
|
|
802
|
+
console.log(chalk.gray('Changes take effect on the next daemon reload or restart.'));
|
|
803
|
+
});
|
|
804
|
+
servicesCmd
|
|
805
|
+
.command('enable <service>')
|
|
806
|
+
.description('Enable a daemon service.')
|
|
807
|
+
.action((service) => {
|
|
808
|
+
if (!DAEMON_SERVICE_IDS.includes(service)) {
|
|
809
|
+
console.error(chalk.red(`Unknown service '${service}'. Run 'agents daemon services list' for valid services.`));
|
|
810
|
+
process.exit(1);
|
|
811
|
+
}
|
|
812
|
+
setDaemonServiceEnabled(service, true);
|
|
813
|
+
console.log(chalk.green(`Enabled '${service}'.`));
|
|
814
|
+
if (isDaemonRunning()) {
|
|
815
|
+
console.log(chalk.gray('Run `agents daemon reload` (or restart) to apply.'));
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
servicesCmd
|
|
819
|
+
.command('disable <service>')
|
|
820
|
+
.description('Disable a daemon service.')
|
|
821
|
+
.action((service) => {
|
|
822
|
+
if (!DAEMON_SERVICE_IDS.includes(service)) {
|
|
823
|
+
console.error(chalk.red(`Unknown service '${service}'. Run 'agents daemon services list' for valid services.`));
|
|
824
|
+
process.exit(1);
|
|
825
|
+
}
|
|
826
|
+
setDaemonServiceEnabled(service, false);
|
|
827
|
+
console.log(chalk.green(`Disabled '${service}'.`));
|
|
828
|
+
if (isDaemonRunning()) {
|
|
829
|
+
console.log(chalk.gray('Run `agents daemon reload` (or restart) to apply.'));
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
registerFunnelCommand(cmd);
|
|
604
833
|
cmd.command('logs')
|
|
605
834
|
.description('Read the daemon\'s own log (lifecycle + subsystem errors — not routine run output).')
|
|
606
835
|
.option('-n, --lines <number>', 'Show this many recent lines', '50')
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* resolver. Kept import-light (only a type import) so it is unit-tested without
|
|
4
4
|
* loading the live-session discovery graph.
|
|
5
5
|
*/
|
|
6
|
-
import type
|
|
6
|
+
import { type ActiveSession } from '../lib/session/active.js';
|
|
7
7
|
/**
|
|
8
8
|
* The prompt the backgrounded run resumes with. Its whole job is to stop a
|
|
9
9
|
* now-unwatched agent from stalling on a confirmation nobody will answer: it
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure core of `agents sessions detach` — the nudge, the resumed-run argv, and the id
|
|
3
|
+
* resolver. Kept import-light (only a type import) so it is unit-tested without
|
|
4
|
+
* loading the live-session discovery graph.
|
|
5
|
+
*/
|
|
6
|
+
import { sessionProcessHost } from '../lib/session/active.js';
|
|
1
7
|
/**
|
|
2
8
|
* The prompt the backgrounded run resumes with. Its whole job is to stop a
|
|
3
9
|
* now-unwatched agent from stalling on a confirmation nobody will answer: it
|
|
@@ -40,8 +46,12 @@ export function resolveDetachTarget(s, self) {
|
|
|
40
46
|
if (!sessionId) {
|
|
41
47
|
return { kind: 'refuse', reason: 'That session has no id to resume, so it cannot be detached.' };
|
|
42
48
|
}
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
// The box to hop to is where the PROCESS is (a shim lives on its dispatcher),
|
|
50
|
+
// which is not always `machine`. `sessionProcessHost` returns undefined when
|
|
51
|
+
// the process is here, so a value means a genuine remote takeover.
|
|
52
|
+
const processHost = sessionProcessHost(s, self);
|
|
53
|
+
if (processHost) {
|
|
54
|
+
return { kind: 'remote', machine: processHost, sessionId };
|
|
45
55
|
}
|
|
46
56
|
return { kind: 'local', sessionId };
|
|
47
57
|
}
|
package/dist/commands/detach.js
CHANGED
|
@@ -166,6 +166,6 @@ export async function detachAction(id, opts = {}) {
|
|
|
166
166
|
detachedAtMs: Date.now(),
|
|
167
167
|
});
|
|
168
168
|
console.log(chalk.green(`◒ Backgrounded ${agent} ${short}`) +
|
|
169
|
-
chalk.gray(` — running headless (pid ${pid}). Bring it back: agents sessions
|
|
169
|
+
chalk.gray(` — running headless (pid ${pid}). Bring it back: agents sessions resume ${short}`));
|
|
170
170
|
console.log(chalk.gray(` logs: ${logFile}`));
|
|
171
171
|
}
|