@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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ResolvedDomainSkill } from './domain-skills.js';
|
|
2
|
-
import { type TabInfo, type ProfileStatus, type HistoricalTask } from './types.js';
|
|
2
|
+
import { type Task, type TabInfo, type ProfileStatus, type HistoricalTask, type ReapResult } from './types.js';
|
|
3
|
+
import { type ReapOptions } from './hygiene.js';
|
|
3
4
|
import { type RefOpts, type RefNode } from './refs.js';
|
|
4
5
|
import type { TargetFilter } from './types.js';
|
|
5
6
|
export type UploadMode = 'auto' | 'input' | 'drop' | 'chooser';
|
|
@@ -62,9 +63,11 @@ export interface HealInfo {
|
|
|
62
63
|
export declare function resolveTaskIdentity(forwarded: {
|
|
63
64
|
actor?: string;
|
|
64
65
|
launchId?: string;
|
|
66
|
+
sessionId?: string;
|
|
65
67
|
}, resolveLocalActor: () => string): {
|
|
66
68
|
owner: string;
|
|
67
69
|
launchId?: string;
|
|
70
|
+
sessionId?: string;
|
|
68
71
|
};
|
|
69
72
|
export declare class BrowserService {
|
|
70
73
|
private static readonly SOURCE_PREFIX;
|
|
@@ -75,14 +78,22 @@ export declare class BrowserService {
|
|
|
75
78
|
private networkRequests;
|
|
76
79
|
private pendingDownloads;
|
|
77
80
|
private enabledSessions;
|
|
81
|
+
/** Profile -> when a `touchTask` last persisted tasks.json for it. */
|
|
82
|
+
private lastTouchPersist;
|
|
83
|
+
/** Coalescing window for the `touchTask` write. See {@link touchTask}. */
|
|
84
|
+
private static readonly TOUCH_PERSIST_INTERVAL_MS;
|
|
78
85
|
start(profileName: string, opts?: {
|
|
79
86
|
taskName?: string;
|
|
80
87
|
url?: string;
|
|
81
88
|
endpointName?: string;
|
|
82
89
|
skipDomainSkill?: boolean;
|
|
90
|
+
/** Always open a new tab, skipping the abandoned-task reclaim. */
|
|
91
|
+
fresh?: boolean;
|
|
83
92
|
/** Caller identity, forwarded from the CLI (see IPCRequest.actor/launchId). */
|
|
84
93
|
actor?: string;
|
|
85
94
|
launchId?: string;
|
|
95
|
+
/** Calling agent session, forwarded from the CLI (see IPCRequest.sessionId). */
|
|
96
|
+
sessionId?: string;
|
|
86
97
|
}): Promise<{
|
|
87
98
|
task: string;
|
|
88
99
|
name: string;
|
|
@@ -91,6 +102,54 @@ export declare class BrowserService {
|
|
|
91
102
|
profile: string;
|
|
92
103
|
skill?: ResolvedDomainSkill;
|
|
93
104
|
}>;
|
|
105
|
+
/**
|
|
106
|
+
* Reclaim a live page already showing `url` from an ABANDONED task, instead
|
|
107
|
+
* of opening a second copy of the same page (RUSH-2622). Returns its CDP
|
|
108
|
+
* targetId, or undefined when there is nothing safe to reclaim.
|
|
109
|
+
*
|
|
110
|
+
* "Safe" is narrow on purpose, because both of the wider readings close a tab
|
|
111
|
+
* somebody is using:
|
|
112
|
+
*
|
|
113
|
+
* - An UNOWNED matching page is NOT taken. No task claims it, which most
|
|
114
|
+
* often means the user opened it themselves — and adopting it would make
|
|
115
|
+
* this task's `done` close the user's tab.
|
|
116
|
+
* - A page owned by a LIVE task is NOT taken. Stealing it would leave that
|
|
117
|
+
* agent's `screenshot`/`click` throwing "No tabs open for this task", its
|
|
118
|
+
* `navigate` silently opening the duplicate this feature exists to
|
|
119
|
+
* prevent, and any in-flight recording truncated when the new owner calls
|
|
120
|
+
* `done`.
|
|
121
|
+
*
|
|
122
|
+
* What is left is exactly the pile-up case: a page held by a task whose owner
|
|
123
|
+
* is provably gone. Liveness uses the reaper's own predicate
|
|
124
|
+
* (`taskOwnerIsGone`), so "dead" has one definition in this codebase rather
|
|
125
|
+
* than two that can drift. A task the reaper cannot prove dead keeps its tab
|
|
126
|
+
* and is closed later by the idle rule instead.
|
|
127
|
+
*
|
|
128
|
+
* Reclaiming transfers rather than shares: two tasks holding one targetId
|
|
129
|
+
* means the first `done` closes the other's tab. `start --fresh` skips this
|
|
130
|
+
* path entirely.
|
|
131
|
+
*
|
|
132
|
+
* URLs are compared canonically (`new URL(...).href`), so a requested
|
|
133
|
+
* `https://example.com` matches the `https://example.com/` Chrome reports. A
|
|
134
|
+
* page that has since redirected elsewhere simply does not match, and the
|
|
135
|
+
* caller opens a tab as before.
|
|
136
|
+
*/
|
|
137
|
+
private adoptTabShowing;
|
|
138
|
+
/**
|
|
139
|
+
* Every live task across every connected profile — the reaper's input
|
|
140
|
+
* (`hygiene.ts`). A read-only view: mutating a returned `Task` mutates the
|
|
141
|
+
* daemon's live state, so callers only read it and act through `stop`.
|
|
142
|
+
*/
|
|
143
|
+
listTasks(): Array<{
|
|
144
|
+
profile: string;
|
|
145
|
+
task: Task;
|
|
146
|
+
}>;
|
|
147
|
+
/**
|
|
148
|
+
* Close tasks whose owning agent session is gone, or that have sat untouched
|
|
149
|
+
* past the idle window. The entry point the daemon's periodic tick and
|
|
150
|
+
* `agents browser gc` both call; the policy lives in `hygiene.ts`.
|
|
151
|
+
*/
|
|
152
|
+
reapAbandoned(opts?: ReapOptions): Promise<ReapResult>;
|
|
94
153
|
stop(taskName: string): Promise<{
|
|
95
154
|
ok: boolean;
|
|
96
155
|
profile?: string;
|
|
@@ -277,6 +336,25 @@ export declare class BrowserService {
|
|
|
277
336
|
private getOrCreateWindow;
|
|
278
337
|
private hasTaskNamed;
|
|
279
338
|
private generateUniqueTaskName;
|
|
339
|
+
/**
|
|
340
|
+
* Mark a task as active, so the reaper's idle window measures time since the
|
|
341
|
+
* last real use rather than time since `start` (RUSH-2622).
|
|
342
|
+
*
|
|
343
|
+
* Called from `findTask` — the single funnel every task-scoped operation
|
|
344
|
+
* resolves through — rather than from each of the ~26 call sites, so a new
|
|
345
|
+
* action added later cannot forget to stamp it.
|
|
346
|
+
*
|
|
347
|
+
* The in-memory stamp is what the reaper reads: it runs inside the same
|
|
348
|
+
* daemon that owns these `Task` objects. The write to tasks.json exists only
|
|
349
|
+
* so a daemon RESTART does not inherit a stale stamp and reap a task an agent
|
|
350
|
+
* has been clicking through for the last half hour — `click`, `type`,
|
|
351
|
+
* `evaluate`, and `screenshot` never call `saveTaskState` on their own, so
|
|
352
|
+
* without this the on-disk stamp would sit at the last navigation. It is
|
|
353
|
+
* coalesced to at most one write per profile per minute so a screenshot loop
|
|
354
|
+
* does not become a write loop; the resulting on-disk stamp trails by at most
|
|
355
|
+
* a minute, well inside the 30-minute idle window.
|
|
356
|
+
*/
|
|
357
|
+
private touchTask;
|
|
280
358
|
private findTask;
|
|
281
359
|
private getTabsForTask;
|
|
282
360
|
private getProfileStatus;
|
|
@@ -9,13 +9,29 @@ import { connectSSH, shellQuote } from './drivers/ssh.js';
|
|
|
9
9
|
import { clearProfileRuntime, listProfileCacheDirs, readProfileRuntimeMeta, isProcessAlive } from './runtime-state.js';
|
|
10
10
|
import { resolveDomainSkill } from './domain-skills.js';
|
|
11
11
|
import { generateTaskId, generateShortId, generateTaskName, } from './types.js';
|
|
12
|
+
import { reapAbandonedTasks, resolveLiveIdentities, taskOwnerIsGone, } from './hygiene.js';
|
|
12
13
|
import { getRefs, resolveRefToCoords, describeRefs, healRef } from './refs.js';
|
|
13
14
|
import { clickAtCoords, hoverAtCoords, scrollAtCoords, typeText, pressKey, focusNode } from './input.js';
|
|
14
15
|
import { typeEditorText } from './editor.js';
|
|
15
16
|
import { detectUploadPattern, stageUploadFile, uploadToDropTarget, uploadToFileInput, uploadViaFileChooser, } from './upload.js';
|
|
16
17
|
import { emit } from '../events.js';
|
|
17
18
|
import { resolveActor } from '../actor.js';
|
|
19
|
+
import { recordBrowserSession } from '../session/db.js';
|
|
18
20
|
import { sshExecAsync } from '../ssh-exec.js';
|
|
21
|
+
/**
|
|
22
|
+
* Canonical form for comparing a requested URL against what CDP reports for a
|
|
23
|
+
* live target. `new URL('https://example.com').href` is `https://example.com/`,
|
|
24
|
+
* which is exactly what Chrome reports — comparing the raw strings would miss
|
|
25
|
+
* every bare-origin match. Unparseable input compares as itself.
|
|
26
|
+
*/
|
|
27
|
+
function canonicalTabUrl(raw) {
|
|
28
|
+
try {
|
|
29
|
+
return new URL(raw).href;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return raw;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
19
35
|
function isPathInside(candidate, dir) {
|
|
20
36
|
const rel = path.relative(dir, candidate);
|
|
21
37
|
return rel === '' || (!!rel && !rel.startsWith('..') && !path.isAbsolute(rel));
|
|
@@ -235,6 +251,7 @@ export function resolveTaskIdentity(forwarded, resolveLocalActor) {
|
|
|
235
251
|
return {
|
|
236
252
|
owner: forwarded.actor ?? resolveLocalActor(),
|
|
237
253
|
launchId: forwarded.launchId,
|
|
254
|
+
sessionId: forwarded.sessionId,
|
|
238
255
|
};
|
|
239
256
|
}
|
|
240
257
|
export class BrowserService {
|
|
@@ -250,6 +267,10 @@ export class BrowserService {
|
|
|
250
267
|
networkRequests = new Map();
|
|
251
268
|
pendingDownloads = new Map();
|
|
252
269
|
enabledSessions = new Map(); // sessionId -> enabled domains
|
|
270
|
+
/** Profile -> when a `touchTask` last persisted tasks.json for it. */
|
|
271
|
+
lastTouchPersist = new Map();
|
|
272
|
+
/** Coalescing window for the `touchTask` write. See {@link touchTask}. */
|
|
273
|
+
static TOUCH_PERSIST_INTERVAL_MS = 60_000;
|
|
253
274
|
async start(profileName, opts = {}) {
|
|
254
275
|
const profile = await getProfile(profileName);
|
|
255
276
|
if (!profile) {
|
|
@@ -329,29 +350,49 @@ export class BrowserService {
|
|
|
329
350
|
// Browsers launch with --no-startup-window (session-cookie persistence,
|
|
330
351
|
// see launchBrowser), so a bare `start` with no --url would otherwise
|
|
331
352
|
// leave the user staring at a process with zero windows. Recreate the
|
|
332
|
-
// old startup-window affordance: if no page target exists, open a blank
|
|
333
|
-
//
|
|
334
|
-
//
|
|
353
|
+
// old startup-window affordance: if no page target exists, open a blank one.
|
|
354
|
+
//
|
|
355
|
+
// This tab IS registered on the task below. It used to be deliberately
|
|
356
|
+
// unregistered ("tasks track only tabs they created") — but this daemon did
|
|
357
|
+
// create it, and an unregistered tab is one `done`/`stop` can never close:
|
|
358
|
+
// `stop` closes exactly the entries in `task.tabs`. So every bare `start`
|
|
359
|
+
// left a blank globe tab behind forever, which is a large share of the
|
|
360
|
+
// pile-up in RUSH-2622. Tabs the daemon did NOT open are still left alone —
|
|
361
|
+
// the branch only fires when the profile has no page target at all.
|
|
362
|
+
let startupBlankTargetId;
|
|
335
363
|
if (!opts.url && !conn.electron) {
|
|
336
364
|
const { targetInfos } = (await conn.cdp.send('Target.getTargets'));
|
|
337
365
|
if (!targetInfos.some((t) => t.type === 'page')) {
|
|
338
|
-
await conn.cdp.send('Target.createTarget', {
|
|
366
|
+
const created = (await conn.cdp.send('Target.createTarget', {
|
|
367
|
+
url: 'about:blank',
|
|
368
|
+
}));
|
|
369
|
+
startupBlankTargetId = created.targetId;
|
|
339
370
|
this.invalidateTargetCache(conn);
|
|
340
371
|
}
|
|
341
372
|
}
|
|
373
|
+
const now = Date.now();
|
|
342
374
|
const task = {
|
|
343
375
|
id: taskId,
|
|
344
376
|
name: taskName,
|
|
345
377
|
profile: effectiveProfileName,
|
|
346
378
|
tabs: {},
|
|
347
379
|
currentTabId: undefined,
|
|
348
|
-
createdAt:
|
|
380
|
+
createdAt: now,
|
|
381
|
+
// A brand-new task has done exactly one thing — start — so its last
|
|
382
|
+
// action is its creation. The reaper's idle window runs from here.
|
|
383
|
+
lastActionAt: now,
|
|
349
384
|
pid: conn.pid,
|
|
350
385
|
// Identity is forwarded from the caller (see resolveTaskIdentity): WHO
|
|
351
|
-
// (owner)
|
|
352
|
-
// every task to the shared daemon's
|
|
353
|
-
|
|
386
|
+
// (owner), WHICH run (launchId), and WHICH agent session (sessionId).
|
|
387
|
+
// Resolving daemon-side would attribute every task to the shared daemon's
|
|
388
|
+
// actor (the RUSH-2020 bug).
|
|
389
|
+
...resolveTaskIdentity({ actor: opts.actor, launchId: opts.launchId, sessionId: opts.sessionId }, () => resolveActor().id),
|
|
354
390
|
};
|
|
391
|
+
if (startupBlankTargetId) {
|
|
392
|
+
const shortId = generateShortId();
|
|
393
|
+
task.tabs[shortId] = startupBlankTargetId;
|
|
394
|
+
task.currentTabId = shortId;
|
|
395
|
+
}
|
|
355
396
|
// For Electron, get the existing window as the tab
|
|
356
397
|
if (conn.electron) {
|
|
357
398
|
const windowId = await this.getOrCreateWindow(conn);
|
|
@@ -363,6 +404,30 @@ export class BrowserService {
|
|
|
363
404
|
}
|
|
364
405
|
conn.tasks.set(taskName, task);
|
|
365
406
|
await this.saveTaskState(effectiveProfileName, conn.tasks);
|
|
407
|
+
// Durable identity, written ONCE at start and never deleted (RUSH-2549).
|
|
408
|
+
// tasks.json above stays live state: `stop` drops the task from that map, so
|
|
409
|
+
// anything recorded only there is gone the moment the task ends -- which is
|
|
410
|
+
// why every finished task used to list as "unlinked". This row outlives the
|
|
411
|
+
// task, the daemon, and a reboot. Metadata only: the capture bytes stay on
|
|
412
|
+
// disk under capture_dir.
|
|
413
|
+
// Guarded like `emit` below ("logging should never break the CLI",
|
|
414
|
+
// events.ts): the browser task is already started and registered. An
|
|
415
|
+
// unwritable session DB must not turn a working `agents browser start` into
|
|
416
|
+
// a failure -- the cost of a miss is one unlinked row, not a dead browser.
|
|
417
|
+
try {
|
|
418
|
+
recordBrowserSession({
|
|
419
|
+
task: taskName,
|
|
420
|
+
profile: effectiveProfileName,
|
|
421
|
+
sessionId: task.sessionId,
|
|
422
|
+
launchId: task.launchId,
|
|
423
|
+
actor: task.owner,
|
|
424
|
+
startedAt: task.createdAt,
|
|
425
|
+
captureDir: getProfileSessionsDir(effectiveProfileName, taskName),
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
catch {
|
|
429
|
+
// Recording is best-effort; the task itself is live either way.
|
|
430
|
+
}
|
|
366
431
|
emit('browser.launch', { profile: effectiveProfileName, task: taskName, pid: conn.pid });
|
|
367
432
|
void import('../analytics/usage-db.js').then(({ recordUsage }) => {
|
|
368
433
|
recordUsage({
|
|
@@ -373,14 +438,17 @@ export class BrowserService {
|
|
|
373
438
|
meta: { task: taskName },
|
|
374
439
|
});
|
|
375
440
|
}).catch(() => { });
|
|
376
|
-
// If URL provided,
|
|
441
|
+
// If URL provided, reclaim a tab an abandoned task is holding on it, else
|
|
442
|
+
// create one directly (no about:blank).
|
|
377
443
|
let tabId;
|
|
378
444
|
if (opts.url && !conn.electron) {
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
445
|
+
const adopted = opts.fresh ? undefined : await this.adoptTabShowing(conn, opts.url);
|
|
446
|
+
const targetId = adopted ??
|
|
447
|
+
(await conn.cdp.send('Target.createTarget', {
|
|
448
|
+
url: opts.url,
|
|
449
|
+
})).targetId;
|
|
382
450
|
const shortId = generateShortId();
|
|
383
|
-
task.tabs[shortId] =
|
|
451
|
+
task.tabs[shortId] = targetId;
|
|
384
452
|
task.currentTabId = shortId;
|
|
385
453
|
this.invalidateTargetCache(conn);
|
|
386
454
|
await this.saveTaskState(effectiveProfileName, conn.tasks);
|
|
@@ -401,6 +469,110 @@ export class BrowserService {
|
|
|
401
469
|
}
|
|
402
470
|
return { task: taskId, name: taskName, tabId, profile: effectiveProfileName, skill };
|
|
403
471
|
}
|
|
472
|
+
/**
|
|
473
|
+
* Reclaim a live page already showing `url` from an ABANDONED task, instead
|
|
474
|
+
* of opening a second copy of the same page (RUSH-2622). Returns its CDP
|
|
475
|
+
* targetId, or undefined when there is nothing safe to reclaim.
|
|
476
|
+
*
|
|
477
|
+
* "Safe" is narrow on purpose, because both of the wider readings close a tab
|
|
478
|
+
* somebody is using:
|
|
479
|
+
*
|
|
480
|
+
* - An UNOWNED matching page is NOT taken. No task claims it, which most
|
|
481
|
+
* often means the user opened it themselves — and adopting it would make
|
|
482
|
+
* this task's `done` close the user's tab.
|
|
483
|
+
* - A page owned by a LIVE task is NOT taken. Stealing it would leave that
|
|
484
|
+
* agent's `screenshot`/`click` throwing "No tabs open for this task", its
|
|
485
|
+
* `navigate` silently opening the duplicate this feature exists to
|
|
486
|
+
* prevent, and any in-flight recording truncated when the new owner calls
|
|
487
|
+
* `done`.
|
|
488
|
+
*
|
|
489
|
+
* What is left is exactly the pile-up case: a page held by a task whose owner
|
|
490
|
+
* is provably gone. Liveness uses the reaper's own predicate
|
|
491
|
+
* (`taskOwnerIsGone`), so "dead" has one definition in this codebase rather
|
|
492
|
+
* than two that can drift. A task the reaper cannot prove dead keeps its tab
|
|
493
|
+
* and is closed later by the idle rule instead.
|
|
494
|
+
*
|
|
495
|
+
* Reclaiming transfers rather than shares: two tasks holding one targetId
|
|
496
|
+
* means the first `done` closes the other's tab. `start --fresh` skips this
|
|
497
|
+
* path entirely.
|
|
498
|
+
*
|
|
499
|
+
* URLs are compared canonically (`new URL(...).href`), so a requested
|
|
500
|
+
* `https://example.com` matches the `https://example.com/` Chrome reports. A
|
|
501
|
+
* page that has since redirected elsewhere simply does not match, and the
|
|
502
|
+
* caller opens a tab as before.
|
|
503
|
+
*/
|
|
504
|
+
async adoptTabShowing(conn, url) {
|
|
505
|
+
const { targetInfos } = (await conn.cdp.send('Target.getTargets'));
|
|
506
|
+
const wanted = canonicalTabUrl(url);
|
|
507
|
+
const matching = new Set(targetInfos
|
|
508
|
+
.filter((t) => t.type === 'page' && canonicalTabUrl(t.url) === wanted)
|
|
509
|
+
.map((t) => t.targetId));
|
|
510
|
+
if (matching.size === 0)
|
|
511
|
+
return undefined;
|
|
512
|
+
const candidates = [];
|
|
513
|
+
for (const task of conn.tasks.values()) {
|
|
514
|
+
for (const [shortId, cdpId] of Object.entries(task.tabs)) {
|
|
515
|
+
if (matching.has(cdpId))
|
|
516
|
+
candidates.push({ task, shortId, targetId: cdpId });
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if (candidates.length === 0)
|
|
520
|
+
return undefined;
|
|
521
|
+
const live = resolveLiveIdentities();
|
|
522
|
+
for (const { task, shortId, targetId } of candidates) {
|
|
523
|
+
// An in-flight capture means the task is in use whatever its owner looks
|
|
524
|
+
// like — same guard the reaper applies before stopping anything.
|
|
525
|
+
if (this.recordings.has(task.name))
|
|
526
|
+
continue;
|
|
527
|
+
if (!(await taskOwnerIsGone(task, live)))
|
|
528
|
+
continue;
|
|
529
|
+
// Re-check the claim after the await. Nothing serializes IPC requests —
|
|
530
|
+
// `BrowserIPCServer` registers a per-connection `socket.on('data', async …)`
|
|
531
|
+
// that Node never awaits — so two concurrent `start`s can both pass the
|
|
532
|
+
// liveness check on one candidate and both return the same targetId,
|
|
533
|
+
// putting two live tasks on one tab and re-opening the double-owner bug
|
|
534
|
+
// this reclaim is careful to avoid. Whoever deletes it first owns it.
|
|
535
|
+
if (task.tabs[shortId] !== targetId)
|
|
536
|
+
continue;
|
|
537
|
+
delete task.tabs[shortId];
|
|
538
|
+
delete task.refDescriptors?.[shortId];
|
|
539
|
+
if (task.currentTabId === shortId) {
|
|
540
|
+
const remaining = Object.keys(task.tabs);
|
|
541
|
+
task.currentTabId = remaining.length > 0 ? remaining[remaining.length - 1] : undefined;
|
|
542
|
+
}
|
|
543
|
+
try {
|
|
544
|
+
await conn.cdp.send('Target.activateTarget', { targetId });
|
|
545
|
+
}
|
|
546
|
+
catch {
|
|
547
|
+
// Bringing the tab to the front is cosmetic and not every endpoint
|
|
548
|
+
// implements activateTarget; the tab is reclaimed either way.
|
|
549
|
+
}
|
|
550
|
+
return targetId;
|
|
551
|
+
}
|
|
552
|
+
return undefined;
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Every live task across every connected profile — the reaper's input
|
|
556
|
+
* (`hygiene.ts`). A read-only view: mutating a returned `Task` mutates the
|
|
557
|
+
* daemon's live state, so callers only read it and act through `stop`.
|
|
558
|
+
*/
|
|
559
|
+
listTasks() {
|
|
560
|
+
const out = [];
|
|
561
|
+
for (const [key, conn] of this.connections) {
|
|
562
|
+
for (const task of conn.tasks.values()) {
|
|
563
|
+
out.push({ profile: conn.profileName ?? key, task });
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return out;
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Close tasks whose owning agent session is gone, or that have sat untouched
|
|
570
|
+
* past the idle window. The entry point the daemon's periodic tick and
|
|
571
|
+
* `agents browser gc` both call; the policy lives in `hygiene.ts`.
|
|
572
|
+
*/
|
|
573
|
+
async reapAbandoned(opts = {}) {
|
|
574
|
+
return reapAbandonedTasks(this, opts);
|
|
575
|
+
}
|
|
404
576
|
async stop(taskName) {
|
|
405
577
|
for (const [profileName, conn] of this.connections) {
|
|
406
578
|
const task = conn.tasks.get(taskName);
|
|
@@ -1850,6 +2022,41 @@ export class BrowserService {
|
|
|
1850
2022
|
}
|
|
1851
2023
|
throw new Error('Could not generate unique task name after 8 attempts');
|
|
1852
2024
|
}
|
|
2025
|
+
/**
|
|
2026
|
+
* Mark a task as active, so the reaper's idle window measures time since the
|
|
2027
|
+
* last real use rather than time since `start` (RUSH-2622).
|
|
2028
|
+
*
|
|
2029
|
+
* Called from `findTask` — the single funnel every task-scoped operation
|
|
2030
|
+
* resolves through — rather than from each of the ~26 call sites, so a new
|
|
2031
|
+
* action added later cannot forget to stamp it.
|
|
2032
|
+
*
|
|
2033
|
+
* The in-memory stamp is what the reaper reads: it runs inside the same
|
|
2034
|
+
* daemon that owns these `Task` objects. The write to tasks.json exists only
|
|
2035
|
+
* so a daemon RESTART does not inherit a stale stamp and reap a task an agent
|
|
2036
|
+
* has been clicking through for the last half hour — `click`, `type`,
|
|
2037
|
+
* `evaluate`, and `screenshot` never call `saveTaskState` on their own, so
|
|
2038
|
+
* without this the on-disk stamp would sit at the last navigation. It is
|
|
2039
|
+
* coalesced to at most one write per profile per minute so a screenshot loop
|
|
2040
|
+
* does not become a write loop; the resulting on-disk stamp trails by at most
|
|
2041
|
+
* a minute, well inside the 30-minute idle window.
|
|
2042
|
+
*/
|
|
2043
|
+
async touchTask(conn, task) {
|
|
2044
|
+
const now = Date.now();
|
|
2045
|
+
task.lastActionAt = now;
|
|
2046
|
+
const last = this.lastTouchPersist.get(task.profile) ?? 0;
|
|
2047
|
+
if (now - last < BrowserService.TOUCH_PERSIST_INTERVAL_MS)
|
|
2048
|
+
return;
|
|
2049
|
+
this.lastTouchPersist.set(task.profile, now);
|
|
2050
|
+
try {
|
|
2051
|
+
await this.saveTaskState(task.profile, conn.tasks);
|
|
2052
|
+
}
|
|
2053
|
+
catch {
|
|
2054
|
+
// Durability here is best-effort — the in-memory stamp above is
|
|
2055
|
+
// authoritative for the running daemon — and an unwritable runtime dir
|
|
2056
|
+
// must not turn a working browser action into a failure. Same guard the
|
|
2057
|
+
// `recordBrowserSession` call in `start` uses for the same reason.
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
1853
2060
|
async findTask(taskId, profileName) {
|
|
1854
2061
|
if (profileName) {
|
|
1855
2062
|
const conn = this.connections.get(profileName);
|
|
@@ -1860,11 +2067,13 @@ export class BrowserService {
|
|
|
1860
2067
|
if (!task) {
|
|
1861
2068
|
throw new Error(`Task "${taskId}" not found on profile "${profileName}"`);
|
|
1862
2069
|
}
|
|
2070
|
+
await this.touchTask(conn, task);
|
|
1863
2071
|
return { conn, task, profileName };
|
|
1864
2072
|
}
|
|
1865
2073
|
for (const [key, conn] of this.connections) {
|
|
1866
2074
|
const task = conn.tasks.get(taskId);
|
|
1867
2075
|
if (task) {
|
|
2076
|
+
await this.touchTask(conn, task);
|
|
1868
2077
|
return { conn, task, profileName: conn.profileName ?? key };
|
|
1869
2078
|
}
|
|
1870
2079
|
}
|
|
@@ -2028,11 +2237,19 @@ export class BrowserService {
|
|
|
2028
2237
|
tabs,
|
|
2029
2238
|
currentTabId: tabIds.length > 0 ? tabIds[tabIds.length - 1] : undefined,
|
|
2030
2239
|
createdAt: task.createdAt,
|
|
2240
|
+
lastActionAt: task.lastActionAt ?? task.createdAt,
|
|
2031
2241
|
pid: task.pid,
|
|
2032
2242
|
});
|
|
2033
2243
|
}
|
|
2034
2244
|
else {
|
|
2035
|
-
|
|
2245
|
+
const loaded = task;
|
|
2246
|
+
// Tasks persisted before RUSH-2622 carry no lastActionAt. Normalize on
|
|
2247
|
+
// read so every in-memory task has one: its own createdAt is the last
|
|
2248
|
+
// moment we can prove the task did anything.
|
|
2249
|
+
if (typeof loaded.lastActionAt !== 'number') {
|
|
2250
|
+
loaded.lastActionAt = loaded.createdAt;
|
|
2251
|
+
}
|
|
2252
|
+
tasks.set(key, loaded);
|
|
2036
2253
|
}
|
|
2037
2254
|
}
|
|
2038
2255
|
// Save migrated data back to disk
|
|
@@ -33,10 +33,17 @@ export declare function renderBrowserSessions(groups: ProfileArtifacts[]): strin
|
|
|
33
33
|
export declare function resolveArtifact(groups: ProfileArtifacts[], selector: string): string | null;
|
|
34
34
|
/** Open a file in the OS default app. Returns true on success. */
|
|
35
35
|
export declare function openArtifact(filePath: string): boolean;
|
|
36
|
-
/** The identity fields this module cares about
|
|
36
|
+
/** The identity fields this module cares about for one task. */
|
|
37
37
|
export interface TaskIdentity {
|
|
38
38
|
owner?: string;
|
|
39
39
|
launchId?: string;
|
|
40
|
+
/**
|
|
41
|
+
* The agent session that drove the task. Primary identity: it is carried by
|
|
42
|
+
* every agent process, whereas a launch id is present on a minority of them.
|
|
43
|
+
* Sourced from the durable `browser_sessions` row, which survives task stop —
|
|
44
|
+
* `tasks.json` never records it beyond the task's own lifetime (RUSH-2549).
|
|
45
|
+
*/
|
|
46
|
+
sessionId?: string;
|
|
40
47
|
}
|
|
41
48
|
/**
|
|
42
49
|
* Read a profile's `tasks.json` for the identity of its CURRENTLY LIVE tasks,
|
|
@@ -48,6 +55,17 @@ export interface TaskIdentity {
|
|
|
48
55
|
* browser daemon is running.
|
|
49
56
|
*/
|
|
50
57
|
export declare function loadTaskIdentities(profile: string): Map<string, TaskIdentity>;
|
|
58
|
+
/**
|
|
59
|
+
* Identity for one profile's tasks, durable copy first.
|
|
60
|
+
*
|
|
61
|
+
* `browser_sessions` is the source of truth: it is written at task start and
|
|
62
|
+
* never deleted, so it answers for tasks that have already stopped — the case
|
|
63
|
+
* `tasks.json` structurally cannot, since `saveTaskState` rewrites that file
|
|
64
|
+
* from the LIVE task map. The live file is still merged on top for a task that
|
|
65
|
+
* is running right now, so a task started by an older CLI (no DB row yet) keeps
|
|
66
|
+
* whatever identity it does have rather than regressing to nothing.
|
|
67
|
+
*/
|
|
68
|
+
export declare function loadDurableTaskIdentities(profile: string): Map<string, TaskIdentity>;
|
|
51
69
|
/** launchId -> indexed session id, built once from every source that records
|
|
52
70
|
* this join, so a task-heavy profile resolves without re-scanning per task. */
|
|
53
71
|
export interface LaunchSessionIndex {
|
|
@@ -85,6 +103,8 @@ export interface BrowserSessionRow {
|
|
|
85
103
|
task?: string;
|
|
86
104
|
owner?: string;
|
|
87
105
|
launchId?: string;
|
|
106
|
+
/** The agent session that drove the task, when one was recorded. */
|
|
107
|
+
sessionId?: string;
|
|
88
108
|
linkStatus: BrowserSessionLinkStatus;
|
|
89
109
|
linkedSession?: SessionMeta;
|
|
90
110
|
/** Newest first. */
|
|
@@ -99,7 +119,7 @@ export interface BrowserSessionRow {
|
|
|
99
119
|
* or session-index dependency and is unit-testable with synthetic data (see
|
|
100
120
|
* {@link buildBrowserSessionRows} for the impure disk/index-reading caller).
|
|
101
121
|
*/
|
|
102
|
-
export declare function groupIntoRows(groups: ProfileArtifacts[], taskIdentities: Map<string, Map<string, TaskIdentity>>, resolveLaunch?: (launchId: string) => SessionMeta | null): BrowserSessionRow[];
|
|
122
|
+
export declare function groupIntoRows(groups: ProfileArtifacts[], taskIdentities: Map<string, Map<string, TaskIdentity>>, resolveLaunch?: (launchId: string) => SessionMeta | null, resolveSession?: (sessionId: string) => SessionMeta | null): BrowserSessionRow[];
|
|
103
123
|
/** Build task-first rows for one profile (or every profile with captures),
|
|
104
124
|
* reading `tasks.json` and resolving launchIds against the live indexes.
|
|
105
125
|
* The interactive picker's data source. */
|
|
@@ -19,7 +19,7 @@ import * as path from 'path';
|
|
|
19
19
|
import { spawnSync } from 'child_process';
|
|
20
20
|
import { getBrowserRuntimeDir, getProfileRuntimeDir } from './profiles.js';
|
|
21
21
|
import { formatRelativeTime } from '../session/relative-time.js';
|
|
22
|
-
import { getSessionById } from '../session/db.js';
|
|
22
|
+
import { getSessionById, listBrowserSessionRecords, pruneToolSessions } from '../session/db.js';
|
|
23
23
|
import { listPidSessionEntries } from '../session/pid-registry.js';
|
|
24
24
|
import { loadHookSessionIndex } from '../session/hook-sessions.js';
|
|
25
25
|
import { readRecentActivity } from '../activity.js';
|
|
@@ -214,10 +214,40 @@ export function loadTaskIdentities(profile) {
|
|
|
214
214
|
out.set(name, {
|
|
215
215
|
owner: typeof t.owner === 'string' ? t.owner : undefined,
|
|
216
216
|
launchId: typeof t.launchId === 'string' ? t.launchId : undefined,
|
|
217
|
+
sessionId: typeof t.sessionId === 'string' ? t.sessionId : undefined,
|
|
217
218
|
});
|
|
218
219
|
}
|
|
219
220
|
return out;
|
|
220
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* Identity for one profile's tasks, durable copy first.
|
|
224
|
+
*
|
|
225
|
+
* `browser_sessions` is the source of truth: it is written at task start and
|
|
226
|
+
* never deleted, so it answers for tasks that have already stopped — the case
|
|
227
|
+
* `tasks.json` structurally cannot, since `saveTaskState` rewrites that file
|
|
228
|
+
* from the LIVE task map. The live file is still merged on top for a task that
|
|
229
|
+
* is running right now, so a task started by an older CLI (no DB row yet) keeps
|
|
230
|
+
* whatever identity it does have rather than regressing to nothing.
|
|
231
|
+
*/
|
|
232
|
+
export function loadDurableTaskIdentities(profile) {
|
|
233
|
+
const merged = new Map();
|
|
234
|
+
for (const record of listBrowserSessionRecords(profile)) {
|
|
235
|
+
merged.set(record.task, {
|
|
236
|
+
owner: record.actor,
|
|
237
|
+
launchId: record.launchId,
|
|
238
|
+
sessionId: record.sessionId,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
for (const [task, live] of loadTaskIdentities(profile)) {
|
|
242
|
+
const durable = merged.get(task);
|
|
243
|
+
merged.set(task, {
|
|
244
|
+
owner: live.owner ?? durable?.owner,
|
|
245
|
+
launchId: live.launchId ?? durable?.launchId,
|
|
246
|
+
sessionId: live.sessionId ?? durable?.sessionId,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
return merged;
|
|
250
|
+
}
|
|
221
251
|
/**
|
|
222
252
|
* Build the launchId -> sessionId index from the same three sources
|
|
223
253
|
* `feed-post.ts` `resolvePostIdentity` already uses for this exact problem —
|
|
@@ -260,7 +290,7 @@ export function resolveLaunchSession(index, launchId) {
|
|
|
260
290
|
* or session-index dependency and is unit-testable with synthetic data (see
|
|
261
291
|
* {@link buildBrowserSessionRows} for the impure disk/index-reading caller).
|
|
262
292
|
*/
|
|
263
|
-
export function groupIntoRows(groups, taskIdentities, resolveLaunch) {
|
|
293
|
+
export function groupIntoRows(groups, taskIdentities, resolveLaunch, resolveSession) {
|
|
264
294
|
const rows = [];
|
|
265
295
|
for (const g of groups) {
|
|
266
296
|
const identities = taskIdentities.get(g.profile) ?? new Map();
|
|
@@ -278,14 +308,24 @@ export function groupIntoRows(groups, taskIdentities, resolveLaunch) {
|
|
|
278
308
|
for (const [task, artifacts] of byTask) {
|
|
279
309
|
artifacts.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
280
310
|
const identity = identities.get(task);
|
|
281
|
-
|
|
311
|
+
// Session id first: it is the direct answer and the one agents actually
|
|
312
|
+
// carry. The launchId join stays as the fallback for a task that recorded
|
|
313
|
+
// only that (an older CLI, or a run whose session id was unset).
|
|
314
|
+
let linkedSession = null;
|
|
315
|
+
if (identity?.sessionId && resolveSession)
|
|
316
|
+
linkedSession = resolveSession(identity.sessionId);
|
|
317
|
+
if (!linkedSession && identity?.launchId && resolveLaunch) {
|
|
318
|
+
linkedSession = resolveLaunch(identity.launchId);
|
|
319
|
+
}
|
|
320
|
+
const hasIdentityKey = !!(identity?.sessionId || identity?.launchId);
|
|
282
321
|
rows.push({
|
|
283
322
|
kind: 'task',
|
|
284
323
|
profile: g.profile,
|
|
285
324
|
task,
|
|
286
325
|
owner: identity?.owner,
|
|
287
326
|
launchId: identity?.launchId,
|
|
288
|
-
|
|
327
|
+
sessionId: identity?.sessionId,
|
|
328
|
+
linkStatus: linkedSession ? 'linked' : hasIdentityKey ? 'unresolved' : 'unlinked',
|
|
289
329
|
linkedSession: linkedSession ?? undefined,
|
|
290
330
|
artifacts,
|
|
291
331
|
counts: countByKind(artifacts),
|
|
@@ -311,10 +351,18 @@ export function groupIntoRows(groups, taskIdentities, resolveLaunch) {
|
|
|
311
351
|
* reading `tasks.json` and resolving launchIds against the live indexes.
|
|
312
352
|
* The interactive picker's data source. */
|
|
313
353
|
export function buildBrowserSessionRows(profile) {
|
|
354
|
+
// Retention runs from BOTH listing paths, not just the computer one: a box
|
|
355
|
+
// that uses `agents browser` and never runs `agents sessions --computer`
|
|
356
|
+
// would otherwise never sweep either table. Best-effort — a read-only DB
|
|
357
|
+
// must not break a listing.
|
|
358
|
+
try {
|
|
359
|
+
pruneToolSessions();
|
|
360
|
+
}
|
|
361
|
+
catch { /* listing must not fail on retention */ }
|
|
314
362
|
const groups = listBrowserSessions(profile);
|
|
315
|
-
const taskIdentities = new Map(groups.map((g) => [g.profile,
|
|
363
|
+
const taskIdentities = new Map(groups.map((g) => [g.profile, loadDurableTaskIdentities(g.profile)]));
|
|
316
364
|
const index = buildLaunchSessionIndex();
|
|
317
|
-
return groupIntoRows(groups, taskIdentities, (launchId) => resolveLaunchSession(index, launchId));
|
|
365
|
+
return groupIntoRows(groups, taskIdentities, (launchId) => resolveLaunchSession(index, launchId), (sessionId) => getSessionById(sessionId));
|
|
318
366
|
}
|
|
319
367
|
/**
|
|
320
368
|
* Search predicate for the interactive picker: task name, profile, the linked
|
|
@@ -53,6 +53,7 @@ export async function runBrowserIPCStream(options) {
|
|
|
53
53
|
taskName: request.taskName ?? defaultTask,
|
|
54
54
|
actor: request.actor ?? options.actor,
|
|
55
55
|
launchId: request.launchId ?? options.launchId,
|
|
56
|
+
sessionId: request.sessionId ?? options.sessionId,
|
|
56
57
|
};
|
|
57
58
|
}
|
|
58
59
|
else if (!request.task && defaultTask) {
|