@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,90 @@
|
|
|
1
|
+
import { type PidSessionEntry } from '../session/pid-registry.js';
|
|
2
|
+
import type { ReapResult, Task } from './types.js';
|
|
3
|
+
/** Default idle window before an untouched task is reaped. */
|
|
4
|
+
export declare const DEFAULT_IDLE_MS: number;
|
|
5
|
+
/**
|
|
6
|
+
* The slice of `BrowserService` the reaper needs. Structural, so a test drives
|
|
7
|
+
* it with a stub and `BrowserService` satisfies it without an import — which
|
|
8
|
+
* also keeps this module off the service's import cycle.
|
|
9
|
+
*/
|
|
10
|
+
export interface ReapableService {
|
|
11
|
+
listTasks(): Array<{
|
|
12
|
+
profile: string;
|
|
13
|
+
task: Task;
|
|
14
|
+
}>;
|
|
15
|
+
recordStatus(taskName: string): Promise<{
|
|
16
|
+
recording: boolean;
|
|
17
|
+
}>;
|
|
18
|
+
stop(taskName: string): Promise<{
|
|
19
|
+
ok: boolean;
|
|
20
|
+
profile?: string;
|
|
21
|
+
}>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Injectable liveness sources. Same shape as `feed-post.ts`
|
|
25
|
+
* (`input.listEntries ?? listPidSessionEntries`, feed-post.ts:119) — the
|
|
26
|
+
* defaults are the real registry and the real process table, so a test can
|
|
27
|
+
* substitute them without mocking a module.
|
|
28
|
+
*/
|
|
29
|
+
export interface ReapDeps {
|
|
30
|
+
listEntries?: () => PidSessionEntry[];
|
|
31
|
+
pidAlive?: (pid: number, startedAtMs?: number) => boolean;
|
|
32
|
+
sessionIdOfPid?: (pid: number) => string | undefined;
|
|
33
|
+
sessionLiveOnProcessTable?: (sessionId: string) => Promise<boolean>;
|
|
34
|
+
}
|
|
35
|
+
export interface ReapOptions {
|
|
36
|
+
/** Idle window in ms. Default {@link DEFAULT_IDLE_MS}. */
|
|
37
|
+
idleMs?: number;
|
|
38
|
+
/** Clock, injectable for tests. Default `Date.now()`. */
|
|
39
|
+
now?: number;
|
|
40
|
+
/** Report what would be closed without closing anything. */
|
|
41
|
+
dryRun?: boolean;
|
|
42
|
+
deps?: ReapDeps;
|
|
43
|
+
}
|
|
44
|
+
export interface LiveIdentities {
|
|
45
|
+
sessions: Set<string>;
|
|
46
|
+
launches: Set<string>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Session and launch ids belonging to a process that is alive right now.
|
|
50
|
+
*
|
|
51
|
+
* Built from the per-pid launch registry, filtered to live pids —
|
|
52
|
+
* `isPidAlive(pid, startedAtMs)` rather than a bare existence check, so a pid
|
|
53
|
+
* the OS recycled onto an unrelated process does not read as a live agent.
|
|
54
|
+
* An entry with no recorded `sessionId` still contributes one when the live
|
|
55
|
+
* process carries `--session-id` on its argv, which is the RUSH-2384 recovery
|
|
56
|
+
* path the registry itself documents (pid-registry.ts:102-107).
|
|
57
|
+
*/
|
|
58
|
+
export declare function resolveLiveIdentities(deps?: ReapDeps): LiveIdentities;
|
|
59
|
+
/**
|
|
60
|
+
* True when the task's owner is PROVABLY gone. The bar is proof, not absence of
|
|
61
|
+
* evidence, because being wrong here closes a working agent's tabs.
|
|
62
|
+
*
|
|
63
|
+
* Only a `sessionId` can carry that proof. It has two independent sources — the
|
|
64
|
+
* per-pid launch registry, and a live process carrying `--session-id <id>` in
|
|
65
|
+
* its argv — so a session the registry missed is still caught by the process
|
|
66
|
+
* table. The registry misses constantly: a wrapper pid exits, a prune sweeps
|
|
67
|
+
* the entry, or the agent was never launched via `agents run`
|
|
68
|
+
* (pid-registry.ts:102-107, RUSH-2384).
|
|
69
|
+
*
|
|
70
|
+
* A `launchId` has NO second source — the registry is its only witness, and
|
|
71
|
+
* that witness is the unreliable one. So a task carrying only a `launchId` is
|
|
72
|
+
* never session-reaped, exactly like a task carrying no identity at all; both
|
|
73
|
+
* fall through to the idle rule. This is not a corner case: `launchId` is
|
|
74
|
+
* minted for every run (`exec.ts` `resolveLaunchId`) while `AGENT_SESSION_ID`
|
|
75
|
+
* is Claude-only and skipped on resume, so treating a missing registry entry as
|
|
76
|
+
* proof of death would close the tabs of every live codex/droid/grok run whose
|
|
77
|
+
* launch pid had already exited.
|
|
78
|
+
*
|
|
79
|
+
* A live `launchId` still RESCUES a task whose `sessionId` looks dead — proof of
|
|
80
|
+
* life needs only one witness, unlike proof of death.
|
|
81
|
+
*/
|
|
82
|
+
export declare function taskOwnerIsGone(task: Task, live: LiveIdentities, deps?: ReapDeps): Promise<boolean>;
|
|
83
|
+
/**
|
|
84
|
+
* Stop every task whose owner is gone or that has sat untouched past `idleMs`.
|
|
85
|
+
*
|
|
86
|
+
* Returns what it closed and how many it left alone. A task that is mid-
|
|
87
|
+
* recording is always left alone: reaping it would truncate a capture the user
|
|
88
|
+
* asked for, and an in-flight recording is itself proof the task is in use.
|
|
89
|
+
*/
|
|
90
|
+
export declare function reapAbandonedTasks(service: ReapableService, opts?: ReapOptions): Promise<ReapResult>;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abandoned browser-task reaper (RUSH-2622).
|
|
3
|
+
*
|
|
4
|
+
* `agents browser done` / `stop` already close a task's tabs, but agents
|
|
5
|
+
* routinely never call them — the run ends, the process exits, and the task's
|
|
6
|
+
* tabs stay open in the profile window forever. Over a day of fleet activity
|
|
7
|
+
* that is dozens of leftover tabs in Comet/Chrome.
|
|
8
|
+
*
|
|
9
|
+
* This closes the loop from the other end: a periodic pass that finds tasks
|
|
10
|
+
* nobody is driving any more and stops them.
|
|
11
|
+
*
|
|
12
|
+
* Two independent reasons, both conservative:
|
|
13
|
+
*
|
|
14
|
+
* - `session-dead` — the task recorded WHICH agent session (`sessionId`) or
|
|
15
|
+
* WHICH run (`launchId`) started it, and neither is alive on this host.
|
|
16
|
+
* That is the honest end of the task: the thing that would have called
|
|
17
|
+
* `done` no longer exists.
|
|
18
|
+
* - `idle` — nothing has touched the task for `idleMs` (default 30 minutes).
|
|
19
|
+
* The catch-all for a task with no recorded identity (a human running
|
|
20
|
+
* `agents browser start` by hand) or an agent that stalled without exiting.
|
|
21
|
+
*
|
|
22
|
+
* Closing always goes through `BrowserService.stop`, never a direct
|
|
23
|
+
* `Target.closeTarget`, so history, the session cache, the target cache, and
|
|
24
|
+
* forked-profile teardown all stay on the one code path that already handles
|
|
25
|
+
* them. Two things this deliberately never does: touch a tab that is not in
|
|
26
|
+
* `task.tabs` (a tab the user opened themselves is not ours to close), and kill
|
|
27
|
+
* the profile window or the browser process because one task went idle.
|
|
28
|
+
*/
|
|
29
|
+
import { isPidAlive, isSessionIdLiveOnProcessTable } from '../session/active.js';
|
|
30
|
+
import { listPidSessionEntries, sessionIdFromLivePid } from '../session/pid-registry.js';
|
|
31
|
+
/** Default idle window before an untouched task is reaped. */
|
|
32
|
+
export const DEFAULT_IDLE_MS = 30 * 60_000;
|
|
33
|
+
/**
|
|
34
|
+
* Session and launch ids belonging to a process that is alive right now.
|
|
35
|
+
*
|
|
36
|
+
* Built from the per-pid launch registry, filtered to live pids —
|
|
37
|
+
* `isPidAlive(pid, startedAtMs)` rather than a bare existence check, so a pid
|
|
38
|
+
* the OS recycled onto an unrelated process does not read as a live agent.
|
|
39
|
+
* An entry with no recorded `sessionId` still contributes one when the live
|
|
40
|
+
* process carries `--session-id` on its argv, which is the RUSH-2384 recovery
|
|
41
|
+
* path the registry itself documents (pid-registry.ts:102-107).
|
|
42
|
+
*/
|
|
43
|
+
export function resolveLiveIdentities(deps = {}) {
|
|
44
|
+
const listEntries = deps.listEntries ?? listPidSessionEntries;
|
|
45
|
+
const alive = deps.pidAlive ?? isPidAlive;
|
|
46
|
+
const sessionIdOf = deps.sessionIdOfPid ?? sessionIdFromLivePid;
|
|
47
|
+
const sessions = new Set();
|
|
48
|
+
const launches = new Set();
|
|
49
|
+
for (const entry of listEntries()) {
|
|
50
|
+
if (!alive(entry.pid, entry.startedAtMs))
|
|
51
|
+
continue;
|
|
52
|
+
const sessionId = entry.sessionId ?? sessionIdOf(entry.pid);
|
|
53
|
+
if (sessionId)
|
|
54
|
+
sessions.add(sessionId);
|
|
55
|
+
if (entry.launchId)
|
|
56
|
+
launches.add(entry.launchId);
|
|
57
|
+
}
|
|
58
|
+
return { sessions, launches };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* True when the task's owner is PROVABLY gone. The bar is proof, not absence of
|
|
62
|
+
* evidence, because being wrong here closes a working agent's tabs.
|
|
63
|
+
*
|
|
64
|
+
* Only a `sessionId` can carry that proof. It has two independent sources — the
|
|
65
|
+
* per-pid launch registry, and a live process carrying `--session-id <id>` in
|
|
66
|
+
* its argv — so a session the registry missed is still caught by the process
|
|
67
|
+
* table. The registry misses constantly: a wrapper pid exits, a prune sweeps
|
|
68
|
+
* the entry, or the agent was never launched via `agents run`
|
|
69
|
+
* (pid-registry.ts:102-107, RUSH-2384).
|
|
70
|
+
*
|
|
71
|
+
* A `launchId` has NO second source — the registry is its only witness, and
|
|
72
|
+
* that witness is the unreliable one. So a task carrying only a `launchId` is
|
|
73
|
+
* never session-reaped, exactly like a task carrying no identity at all; both
|
|
74
|
+
* fall through to the idle rule. This is not a corner case: `launchId` is
|
|
75
|
+
* minted for every run (`exec.ts` `resolveLaunchId`) while `AGENT_SESSION_ID`
|
|
76
|
+
* is Claude-only and skipped on resume, so treating a missing registry entry as
|
|
77
|
+
* proof of death would close the tabs of every live codex/droid/grok run whose
|
|
78
|
+
* launch pid had already exited.
|
|
79
|
+
*
|
|
80
|
+
* A live `launchId` still RESCUES a task whose `sessionId` looks dead — proof of
|
|
81
|
+
* life needs only one witness, unlike proof of death.
|
|
82
|
+
*/
|
|
83
|
+
export async function taskOwnerIsGone(task, live, deps = {}) {
|
|
84
|
+
if (!task.sessionId)
|
|
85
|
+
return false;
|
|
86
|
+
if (task.launchId && live.launches.has(task.launchId))
|
|
87
|
+
return false;
|
|
88
|
+
if (live.sessions.has(task.sessionId))
|
|
89
|
+
return false;
|
|
90
|
+
const onProcessTable = deps.sessionLiveOnProcessTable ?? ((id) => isSessionIdLiveOnProcessTable(id));
|
|
91
|
+
return !(await onProcessTable(task.sessionId));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Stop every task whose owner is gone or that has sat untouched past `idleMs`.
|
|
95
|
+
*
|
|
96
|
+
* Returns what it closed and how many it left alone. A task that is mid-
|
|
97
|
+
* recording is always left alone: reaping it would truncate a capture the user
|
|
98
|
+
* asked for, and an in-flight recording is itself proof the task is in use.
|
|
99
|
+
*/
|
|
100
|
+
export async function reapAbandonedTasks(service, opts = {}) {
|
|
101
|
+
const idleMs = opts.idleMs ?? DEFAULT_IDLE_MS;
|
|
102
|
+
// Fail loud rather than reap everything. A caller-supplied `0` survives `??`
|
|
103
|
+
// and would close every task including one created a millisecond ago; a
|
|
104
|
+
// non-numeric value makes every `>=` comparison false and silently disables
|
|
105
|
+
// idle reaping. Both are worse than an error.
|
|
106
|
+
if (!Number.isFinite(idleMs) || idleMs <= 0) {
|
|
107
|
+
throw new Error(`idleMs must be a positive number of milliseconds, got ${String(idleMs)}`);
|
|
108
|
+
}
|
|
109
|
+
const now = opts.now ?? Date.now();
|
|
110
|
+
const deps = opts.deps ?? {};
|
|
111
|
+
const live = resolveLiveIdentities(deps);
|
|
112
|
+
const closed = [];
|
|
113
|
+
let skipped = 0;
|
|
114
|
+
for (const { profile, task } of service.listTasks()) {
|
|
115
|
+
if ((await service.recordStatus(task.name)).recording) {
|
|
116
|
+
skipped++;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
let reason;
|
|
120
|
+
if (await taskOwnerIsGone(task, live, deps)) {
|
|
121
|
+
reason = 'session-dead';
|
|
122
|
+
}
|
|
123
|
+
else if (now - (task.lastActionAt ?? task.createdAt) >= idleMs) {
|
|
124
|
+
reason = 'idle';
|
|
125
|
+
}
|
|
126
|
+
if (!reason) {
|
|
127
|
+
skipped++;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (opts.dryRun) {
|
|
131
|
+
closed.push({ task: task.name, profile, reason });
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const result = await service.stop(task.name);
|
|
135
|
+
if (result.ok) {
|
|
136
|
+
closed.push({ task: task.name, profile: result.profile ?? profile, reason });
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
// `stop` reports not-found only when the task already left the map — a
|
|
140
|
+
// concurrent `done`, or a profile torn down mid-pass. Nothing was closed,
|
|
141
|
+
// so it is not reported as closed.
|
|
142
|
+
skipped++;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return { closed, skipped };
|
|
146
|
+
}
|
|
@@ -34,9 +34,39 @@ export declare class BrowserIPCConnection {
|
|
|
34
34
|
export declare class BrowserIPCServer {
|
|
35
35
|
private server;
|
|
36
36
|
private service;
|
|
37
|
+
/** Live client connections, so {@link stop} can end them rather than wait. */
|
|
38
|
+
private connections;
|
|
39
|
+
/** In-flight stop, so a second SIGTERM awaits the same close, never skips it. */
|
|
40
|
+
private stopping;
|
|
37
41
|
constructor(service: BrowserService);
|
|
38
42
|
start(): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Stop accepting, release the binding, and only then resolve.
|
|
45
|
+
*
|
|
46
|
+
* RUSH-2421: this used to call `server.close()` and move on. `close()` is
|
|
47
|
+
* asynchronous — it stops accepting immediately but the binding is not
|
|
48
|
+
* actually released until the `'close'` event fires — so `stop()` resolved
|
|
49
|
+
* while the socket (or named pipe) was still held. The daemon's
|
|
50
|
+
* `handleShutdown` awaits this before it exits, and a successor's
|
|
51
|
+
* `claimDaemonInstance` waits for that exit as its proof the predecessor's
|
|
52
|
+
* resources are free (SING-11). Resolving early made that proof false: the
|
|
53
|
+
* newcomer could bind before the incumbent's binding was gone, which is the
|
|
54
|
+
* two-servers-on-one-socket orphan the eviction protocol exists to prevent.
|
|
55
|
+
*
|
|
56
|
+
* The first version of this waited out a timeout instead, and that made every
|
|
57
|
+
* graceful daemon stop escalate to `killTree`: `close()` does not complete
|
|
58
|
+
* while a connection is open, browser clients hold one open on purpose, and
|
|
59
|
+
* the timeout was set equal to the daemon's own SIGTERM grace window
|
|
60
|
+
* (`STOP_GRACE_MS`) — so `handleShutdown` was still inside `stop()` when
|
|
61
|
+
* `stopDaemon` gave up waiting and killed it. So we END the connections
|
|
62
|
+
* rather than wait for them: `close()` stops accepting, destroying the
|
|
63
|
+
* tracked sockets lets it complete, and the timeout is only a backstop for a
|
|
64
|
+
* socket that will not die, deliberately far below `STOP_GRACE_MS`.
|
|
65
|
+
*/
|
|
39
66
|
stop(): Promise<void>;
|
|
67
|
+
private doStop;
|
|
68
|
+
/** Release the listening binding, resolving only once it is genuinely gone. */
|
|
69
|
+
private closeServer;
|
|
40
70
|
private handleRequest;
|
|
41
71
|
}
|
|
42
72
|
/**
|
package/dist/lib/browser/ipc.js
CHANGED
|
@@ -6,6 +6,23 @@ import { getHelpersDir } from '../state.js';
|
|
|
6
6
|
import { startDaemon, stopDaemon } from '../daemon.js';
|
|
7
7
|
import { getCliVersion } from '../version.js';
|
|
8
8
|
const SOCKET_NAME = 'browser.sock';
|
|
9
|
+
/**
|
|
10
|
+
* Backstop for {@link BrowserIPCServer.stop} — how long to wait for the socket
|
|
11
|
+
* to be released before unlinking it anyway (RUSH-2421).
|
|
12
|
+
*
|
|
13
|
+
* This MUST stay well under the daemon's SIGTERM grace window
|
|
14
|
+
* (`STOP_GRACE_MS`, daemon.ts). `handleShutdown` awaits `stop()`, so a close
|
|
15
|
+
* that takes as long as the grace window means `stopDaemon` gives up waiting
|
|
16
|
+
* and escalates to `killTree` — turning every graceful stop into a kill. The
|
|
17
|
+
* first version of this constant was set to exactly 5000, the same value as
|
|
18
|
+
* `STOP_GRACE_MS`, and did precisely that whenever a browser client held its
|
|
19
|
+
* (deliberately) long-lived connection open.
|
|
20
|
+
*
|
|
21
|
+
* With connections now ended explicitly in `closeServer`, reaching this
|
|
22
|
+
* timeout at all means a socket refused to die; 1.5s bounds that without
|
|
23
|
+
* coming near the grace window.
|
|
24
|
+
*/
|
|
25
|
+
const IPC_CLOSE_TIMEOUT_MS = 1_500;
|
|
9
26
|
export class BrowserDaemonNotRunningError extends Error {
|
|
10
27
|
constructor() {
|
|
11
28
|
super(formatBrowserDaemonNotRunningError());
|
|
@@ -153,6 +170,10 @@ export class BrowserIPCConnection {
|
|
|
153
170
|
export class BrowserIPCServer {
|
|
154
171
|
server = null;
|
|
155
172
|
service;
|
|
173
|
+
/** Live client connections, so {@link stop} can end them rather than wait. */
|
|
174
|
+
connections = new Set();
|
|
175
|
+
/** In-flight stop, so a second SIGTERM awaits the same close, never skips it. */
|
|
176
|
+
stopping = null;
|
|
156
177
|
constructor(service) {
|
|
157
178
|
this.service = service;
|
|
158
179
|
}
|
|
@@ -170,6 +191,12 @@ export class BrowserIPCServer {
|
|
|
170
191
|
}
|
|
171
192
|
}
|
|
172
193
|
this.server = net.createServer((socket) => {
|
|
194
|
+
// `net.Server.close()` stops accepting but does not complete until every
|
|
195
|
+
// EXISTING connection ends, and clients here hold one open on purpose
|
|
196
|
+
// ("the process and socket stay warm between actions"). Tracking them is
|
|
197
|
+
// what lets stop() end them deliberately instead of waiting out a timeout.
|
|
198
|
+
this.connections.add(socket);
|
|
199
|
+
socket.on('close', () => this.connections.delete(socket));
|
|
173
200
|
let buffer = '';
|
|
174
201
|
socket.on('data', async (data) => {
|
|
175
202
|
buffer += data.toString();
|
|
@@ -229,11 +256,38 @@ export class BrowserIPCServer {
|
|
|
229
256
|
});
|
|
230
257
|
});
|
|
231
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* Stop accepting, release the binding, and only then resolve.
|
|
261
|
+
*
|
|
262
|
+
* RUSH-2421: this used to call `server.close()` and move on. `close()` is
|
|
263
|
+
* asynchronous — it stops accepting immediately but the binding is not
|
|
264
|
+
* actually released until the `'close'` event fires — so `stop()` resolved
|
|
265
|
+
* while the socket (or named pipe) was still held. The daemon's
|
|
266
|
+
* `handleShutdown` awaits this before it exits, and a successor's
|
|
267
|
+
* `claimDaemonInstance` waits for that exit as its proof the predecessor's
|
|
268
|
+
* resources are free (SING-11). Resolving early made that proof false: the
|
|
269
|
+
* newcomer could bind before the incumbent's binding was gone, which is the
|
|
270
|
+
* two-servers-on-one-socket orphan the eviction protocol exists to prevent.
|
|
271
|
+
*
|
|
272
|
+
* The first version of this waited out a timeout instead, and that made every
|
|
273
|
+
* graceful daemon stop escalate to `killTree`: `close()` does not complete
|
|
274
|
+
* while a connection is open, browser clients hold one open on purpose, and
|
|
275
|
+
* the timeout was set equal to the daemon's own SIGTERM grace window
|
|
276
|
+
* (`STOP_GRACE_MS`) — so `handleShutdown` was still inside `stop()` when
|
|
277
|
+
* `stopDaemon` gave up waiting and killed it. So we END the connections
|
|
278
|
+
* rather than wait for them: `close()` stops accepting, destroying the
|
|
279
|
+
* tracked sockets lets it complete, and the timeout is only a backstop for a
|
|
280
|
+
* socket that will not die, deliberately far below `STOP_GRACE_MS`.
|
|
281
|
+
*/
|
|
232
282
|
async stop() {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
283
|
+
// A second SIGTERM must await the SAME stop, not skip past it because
|
|
284
|
+
// `this.server` was already nulled by the first.
|
|
285
|
+
if (!this.stopping)
|
|
286
|
+
this.stopping = this.doStop();
|
|
287
|
+
return this.stopping;
|
|
288
|
+
}
|
|
289
|
+
async doStop() {
|
|
290
|
+
await this.closeServer();
|
|
237
291
|
if (!IS_WINDOWS) {
|
|
238
292
|
const socketPath = getSocketPath();
|
|
239
293
|
if (fs.existsSync(socketPath)) {
|
|
@@ -242,6 +296,28 @@ export class BrowserIPCServer {
|
|
|
242
296
|
}
|
|
243
297
|
await this.service.shutdown();
|
|
244
298
|
}
|
|
299
|
+
/** Release the listening binding, resolving only once it is genuinely gone. */
|
|
300
|
+
closeServer() {
|
|
301
|
+
const server = this.server;
|
|
302
|
+
this.server = null;
|
|
303
|
+
if (!server)
|
|
304
|
+
return Promise.resolve();
|
|
305
|
+
return new Promise((resolve) => {
|
|
306
|
+
const done = () => { clearTimeout(timer); resolve(); };
|
|
307
|
+
// Backstop only, for a socket that refuses to die. Far below the daemon's
|
|
308
|
+
// SIGTERM grace window so a slow close can never be what gets the daemon
|
|
309
|
+
// killed — the whole point is that the graceful path stays graceful.
|
|
310
|
+
const timer = setTimeout(done, IPC_CLOSE_TIMEOUT_MS);
|
|
311
|
+
// `close()` reports an error when the server was never listening —
|
|
312
|
+
// nothing to release, so that is a completed close, not a failure.
|
|
313
|
+
server.close(() => done());
|
|
314
|
+
// `close()` waits for existing connections; ending them is what lets it
|
|
315
|
+
// finish now instead of at the timeout.
|
|
316
|
+
for (const socket of this.connections)
|
|
317
|
+
socket.destroy();
|
|
318
|
+
this.connections.clear();
|
|
319
|
+
});
|
|
320
|
+
}
|
|
245
321
|
async handleRequest(request) {
|
|
246
322
|
if (request.action === 'upload-stage') {
|
|
247
323
|
const source = request.source;
|
|
@@ -264,8 +340,10 @@ export class BrowserIPCServer {
|
|
|
264
340
|
url: request.url,
|
|
265
341
|
endpointName: request.endpoint,
|
|
266
342
|
skipDomainSkill: request.skipDomainSkill,
|
|
343
|
+
fresh: request.fresh,
|
|
267
344
|
actor: request.actor,
|
|
268
345
|
launchId: request.launchId,
|
|
346
|
+
sessionId: request.sessionId,
|
|
269
347
|
});
|
|
270
348
|
return {
|
|
271
349
|
ok: true,
|
|
@@ -275,6 +353,17 @@ export class BrowserIPCServer {
|
|
|
275
353
|
skill: result.skill,
|
|
276
354
|
};
|
|
277
355
|
}
|
|
356
|
+
// The out-of-process seam onto the abandoned-task reaper: the daemon
|
|
357
|
+
// owns the live BrowserService, so a CLI verb (`agents browser gc`) can
|
|
358
|
+
// only reach `reapAbandoned` through IPC. The daemon's own periodic tick
|
|
359
|
+
// calls the service method directly.
|
|
360
|
+
case 'gc': {
|
|
361
|
+
const reaped = await this.service.reapAbandoned({
|
|
362
|
+
idleMs: request.idleMinutes !== undefined ? request.idleMinutes * 60_000 : undefined,
|
|
363
|
+
dryRun: request.dryRun,
|
|
364
|
+
});
|
|
365
|
+
return { ok: true, reaped };
|
|
366
|
+
}
|
|
278
367
|
case 'done': {
|
|
279
368
|
if (!request.task) {
|
|
280
369
|
return { ok: false, error: 'Task required' };
|
|
@@ -5,8 +5,9 @@ export declare const DEFAULT_BROWSER_PROFILE_NAME = "default";
|
|
|
5
5
|
* The device-local configured default profile name (set via
|
|
6
6
|
* `agents browser profiles set-default`), or undefined when unset. When set, it
|
|
7
7
|
* is the profile `agents browser start` resolves to for BOTH the no-`--profile`
|
|
8
|
-
* path and an explicit `--profile default`. Stored
|
|
9
|
-
* `
|
|
8
|
+
* path and an explicit `--profile default`. Stored as this machine's
|
|
9
|
+
* `browser.profile` device-config key (central `fleet.devices.<name>.config`
|
|
10
|
+
* block — see lib/device-config.ts).
|
|
10
11
|
*/
|
|
11
12
|
export declare function getConfiguredDefaultProfileName(): string | undefined;
|
|
12
13
|
export declare function getBrowserRuntimeDir(): string;
|
|
@@ -1,17 +1,33 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
|
-
import { getBrowserRuntimeDir as getBrowserRuntimeDirRoot, readMeta,
|
|
2
|
+
import { getBrowserRuntimeDir as getBrowserRuntimeDirRoot, readMeta, updateMeta, } from '../state.js';
|
|
3
|
+
import { getConfigValue } from '../device-config.js';
|
|
3
4
|
import { findBrowserPath, findFirstInstalledBrowser, isPortInUse } from './chrome.js';
|
|
4
5
|
import { DEFAULT_VIEWPORT } from './devices.js';
|
|
5
6
|
export const DEFAULT_BROWSER_PROFILE_NAME = 'default';
|
|
7
|
+
/**
|
|
8
|
+
* True for a profile whose contents only make sense on THIS machine, so it must
|
|
9
|
+
* live in the per-machine doc rather than the fleet-synced agents.yaml.
|
|
10
|
+
*
|
|
11
|
+
* Only the auto-detected `default` qualifies: it is generated by probing local
|
|
12
|
+
* browser installs and a free local port, so its `binary:` is an absolute path
|
|
13
|
+
* for one OS. Shared fleet-wide it caused a permanent rewrite loop — a macOS box
|
|
14
|
+
* wrote `/Applications/...`, a Linux box rewrote `/usr/bin/chromium-browser`,
|
|
15
|
+
* each on every `agents browser start`. Named profiles a user created are real
|
|
16
|
+
* fleet config and keep syncing.
|
|
17
|
+
*/
|
|
18
|
+
function isMachineLocalProfile(name) {
|
|
19
|
+
return name === DEFAULT_BROWSER_PROFILE_NAME;
|
|
20
|
+
}
|
|
6
21
|
/**
|
|
7
22
|
* The device-local configured default profile name (set via
|
|
8
23
|
* `agents browser profiles set-default`), or undefined when unset. When set, it
|
|
9
24
|
* is the profile `agents browser start` resolves to for BOTH the no-`--profile`
|
|
10
|
-
* path and an explicit `--profile default`. Stored
|
|
11
|
-
* `
|
|
25
|
+
* path and an explicit `--profile default`. Stored as this machine's
|
|
26
|
+
* `browser.profile` device-config key (central `fleet.devices.<name>.config`
|
|
27
|
+
* block — see lib/device-config.ts).
|
|
12
28
|
*/
|
|
13
29
|
export function getConfiguredDefaultProfileName() {
|
|
14
|
-
return
|
|
30
|
+
return getConfigValue('browser.profile').value || undefined;
|
|
15
31
|
}
|
|
16
32
|
export function getBrowserRuntimeDir() {
|
|
17
33
|
return getBrowserRuntimeDirRoot();
|
|
@@ -82,15 +98,22 @@ function profileToConfig(profile) {
|
|
|
82
98
|
config.logHost = profile.logHost;
|
|
83
99
|
return config;
|
|
84
100
|
}
|
|
85
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Every profile visible on this machine: the fleet-shared ones plus this
|
|
103
|
+
* machine's own. A machine-local entry WINS on a name collision — it was written
|
|
104
|
+
* by this box for this box, whereas the shared copy may carry another OS's
|
|
105
|
+
* binary path (that mismatch is exactly why `default` moved local).
|
|
106
|
+
*/
|
|
107
|
+
function allProfileConfigs() {
|
|
86
108
|
const meta = readMeta();
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
109
|
+
return { ...meta.browser, ...meta.deviceBrowser };
|
|
110
|
+
}
|
|
111
|
+
export async function listProfiles() {
|
|
112
|
+
const configs = allProfileConfigs();
|
|
113
|
+
return Object.entries(configs).map(([name, config]) => configToProfile(name, config));
|
|
90
114
|
}
|
|
91
115
|
export async function getProfile(name) {
|
|
92
|
-
const
|
|
93
|
-
const config = meta.browser?.[name];
|
|
116
|
+
const config = allProfileConfigs()[name];
|
|
94
117
|
if (!config)
|
|
95
118
|
return null;
|
|
96
119
|
return configToProfile(name, config);
|
|
@@ -271,7 +294,8 @@ function hasSshEndpoint(endpoints) {
|
|
|
271
294
|
}
|
|
272
295
|
export async function createProfile(profile) {
|
|
273
296
|
const meta = readMeta();
|
|
274
|
-
|
|
297
|
+
const existingConfigs = allProfileConfigs();
|
|
298
|
+
if (existingConfigs[profile.name]) {
|
|
275
299
|
throw new Error(`Profile "${profile.name}" already exists`);
|
|
276
300
|
}
|
|
277
301
|
// Collision check. Every CDP/SSH profile ends up listening on (or
|
|
@@ -280,8 +304,8 @@ export async function createProfile(profile) {
|
|
|
280
304
|
// longer need to scope by host. Two profiles that would need the same
|
|
281
305
|
// local port can't both run at the same time.
|
|
282
306
|
const newLocal = effectiveLocalPort(profile);
|
|
283
|
-
if (newLocal !== undefined
|
|
284
|
-
for (const [existingName, existingConfig] of Object.entries(
|
|
307
|
+
if (newLocal !== undefined) {
|
|
308
|
+
for (const [existingName, existingConfig] of Object.entries(existingConfigs)) {
|
|
285
309
|
const existingProfile = configToProfile(existingName, existingConfig);
|
|
286
310
|
const existingLocal = effectiveLocalPort(existingProfile);
|
|
287
311
|
if (existingLocal === newLocal) {
|
|
@@ -302,25 +326,44 @@ export async function createProfile(profile) {
|
|
|
302
326
|
if (!hasSshEndpoint(profile.endpoints)) {
|
|
303
327
|
findBrowserPath(profile.browser, profile.binary);
|
|
304
328
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
329
|
+
const config = profileToConfig(profile);
|
|
330
|
+
if (isMachineLocalProfile(profile.name)) {
|
|
331
|
+
updateMeta((m) => ({ ...m, deviceBrowser: { ...m.deviceBrowser, [profile.name]: config } }));
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
updateMeta((m) => ({ ...m, browser: { ...m.browser, [profile.name]: config } }));
|
|
308
335
|
}
|
|
309
336
|
export async function updateProfile(profile) {
|
|
310
337
|
const meta = readMeta();
|
|
311
|
-
|
|
338
|
+
const local = meta.deviceBrowser?.[profile.name];
|
|
339
|
+
if (!local && !meta.browser?.[profile.name]) {
|
|
312
340
|
throw new Error(`Profile "${profile.name}" does not exist`);
|
|
313
341
|
}
|
|
314
|
-
|
|
315
|
-
|
|
342
|
+
const config = profileToConfig(profile);
|
|
343
|
+
// A profile that already lives machine-locally stays there; so does the auto
|
|
344
|
+
// `default`, whose binary path and port are specific to this machine.
|
|
345
|
+
if (local || isMachineLocalProfile(profile.name)) {
|
|
346
|
+
updateMeta((m) => ({ ...m, deviceBrowser: { ...m.deviceBrowser, [profile.name]: config } }));
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
updateMeta((m) => ({ ...m, browser: { ...m.browser, [profile.name]: config } }));
|
|
316
350
|
}
|
|
317
351
|
export async function deleteProfile(name) {
|
|
318
352
|
const meta = readMeta();
|
|
319
|
-
|
|
353
|
+
const isLocal = !!meta.deviceBrowser?.[name];
|
|
354
|
+
if (!isLocal && !meta.browser?.[name]) {
|
|
320
355
|
throw new Error(`Profile "${name}" does not exist`);
|
|
321
356
|
}
|
|
322
|
-
|
|
323
|
-
|
|
357
|
+
updateMeta((m) => {
|
|
358
|
+
if (isLocal) {
|
|
359
|
+
const next = { ...m.deviceBrowser };
|
|
360
|
+
delete next[name];
|
|
361
|
+
return { ...m, deviceBrowser: Object.keys(next).length > 0 ? next : undefined };
|
|
362
|
+
}
|
|
363
|
+
const next = { ...m.browser };
|
|
364
|
+
delete next[name];
|
|
365
|
+
return { ...m, browser: Object.keys(next).length > 0 ? next : undefined };
|
|
366
|
+
});
|
|
324
367
|
}
|
|
325
368
|
/**
|
|
326
369
|
* Resolve a profile's endpoint presets into a normalized map regardless of
|