@phnx-labs/agents-cli 1.22.46 → 1.22.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +663 -0
- package/LICENSE +102 -182
- package/README.md +96 -49
- package/dist/bootstrap.js +4 -2
- package/dist/cli/command-registry.d.ts +2 -1
- package/dist/cli/command-registry.js +6 -2
- package/dist/commands/accounts.d.ts +58 -0
- package/dist/commands/accounts.js +243 -25
- package/dist/commands/artifacts.js +7 -6
- package/dist/commands/attach.js +1 -1
- package/dist/commands/auth.js +7 -5
- package/dist/commands/browser-sessions-picker.js +9 -7
- package/dist/commands/browser.js +711 -62
- package/dist/commands/config.d.ts +0 -1
- package/dist/commands/config.js +26 -24
- package/dist/commands/cost.d.ts +1 -1
- package/dist/commands/daemon.d.ts +21 -0
- package/dist/commands/daemon.js +147 -12
- package/dist/commands/detach.d.ts +13 -0
- package/dist/commands/detach.js +11 -8
- package/dist/commands/exec.js +107 -53
- package/dist/commands/feed-watch.d.ts +2 -0
- package/dist/commands/feed-watch.js +28 -0
- package/dist/commands/feed.js +27 -0
- package/dist/commands/feedback.js +3 -19
- package/dist/commands/focus.d.ts +14 -0
- package/dist/commands/focus.js +48 -5
- package/dist/commands/go.d.ts +3 -0
- package/dist/commands/go.js +16 -3
- package/dist/commands/insights.d.ts +1 -1
- package/dist/commands/insights.js +2 -2
- package/dist/commands/inspect.js +2 -2
- package/dist/commands/lease.d.ts +1 -1
- package/dist/commands/lease.js +7 -6
- package/dist/commands/mailboxes.js +4 -3
- package/dist/commands/menubar.js +11 -4
- package/dist/commands/projects.d.ts +16 -1
- package/dist/commands/projects.js +58 -64
- package/dist/commands/resume.js +1 -1
- package/dist/commands/route.js +4 -4
- package/dist/commands/routines.js +4 -3
- package/dist/commands/run-broadcast.d.ts +1 -1
- package/dist/commands/run-broadcast.js +1 -1
- package/dist/commands/sessions-picker-factory.d.ts +2 -1
- package/dist/commands/sessions-picker-factory.js +1 -1
- package/dist/commands/sessions-picker.d.ts +22 -0
- package/dist/commands/sessions-picker.js +48 -9
- package/dist/commands/sessions-stop.d.ts +24 -0
- package/dist/commands/sessions-stop.js +66 -0
- package/dist/commands/sessions-trace.d.ts +82 -0
- package/dist/commands/sessions-trace.js +387 -0
- package/dist/commands/sessions.js +5 -0
- package/dist/commands/setup-secrets.js +23 -0
- package/dist/commands/share.d.ts +20 -5
- package/dist/commands/share.js +152 -52
- package/dist/commands/ssh.d.ts +15 -0
- package/dist/commands/ssh.js +243 -18
- package/dist/commands/teams.js +34 -2
- package/dist/commands/traces.d.ts +2 -0
- package/dist/commands/traces.js +184 -0
- package/dist/commands/view.d.ts +2 -0
- package/dist/commands/view.js +32 -62
- package/dist/commands/watchdog.js +9 -14
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/lib/account-capabilities.d.ts +4 -0
- package/dist/lib/account-capabilities.js +13 -9
- package/dist/lib/account-registry.d.ts +2 -0
- package/dist/lib/account-registry.js +30 -2
- package/dist/lib/accounting/rotate.d.ts +9 -0
- package/dist/lib/accounting/rotate.js +12 -2
- package/dist/lib/accounting/usage.d.ts +84 -6
- package/dist/lib/accounting/usage.js +293 -75
- package/dist/lib/agent-modes.js +29 -1
- package/dist/lib/agent-spec/agents.d.ts +4 -3
- package/dist/lib/agent-spec/agents.js +25 -15
- package/dist/lib/analytics/mix-commands.d.ts +1 -1
- package/dist/lib/analytics/mix-commands.js +2 -2
- package/dist/lib/auth-health.d.ts +13 -3
- package/dist/lib/auth-health.js +50 -8
- package/dist/lib/browser/devices.d.ts +19 -0
- package/dist/lib/browser/devices.js +23 -0
- package/dist/lib/browser/drivers/ssh.d.ts +16 -2
- package/dist/lib/browser/drivers/ssh.js +36 -9
- package/dist/lib/browser/ipc.d.ts +1 -1
- package/dist/lib/browser/ipc.js +40 -9
- package/dist/lib/browser/profiles.d.ts +104 -65
- package/dist/lib/browser/profiles.js +277 -165
- package/dist/lib/browser/registry.d.ts +39 -0
- package/dist/lib/browser/registry.js +149 -0
- package/dist/lib/browser/remote-control.d.ts +28 -2
- package/dist/lib/browser/remote-control.js +35 -3
- package/dist/lib/browser/resolve-target.d.ts +68 -0
- package/dist/lib/browser/resolve-target.js +250 -0
- package/dist/lib/browser/runtime-state.d.ts +44 -11
- package/dist/lib/browser/runtime-state.js +98 -15
- package/dist/lib/browser/service.d.ts +65 -9
- package/dist/lib/browser/service.js +196 -61
- package/dist/lib/browser/sessions-list.d.ts +1 -3
- package/dist/lib/browser/sessions-list.js +3 -16
- package/dist/lib/browser/task-index.d.ts +70 -0
- package/dist/lib/browser/task-index.js +180 -0
- package/dist/lib/browser/types.d.ts +12 -1
- package/dist/lib/byok-usage.js +2 -2
- package/dist/lib/claude-statusline.d.ts +29 -0
- package/dist/lib/claude-statusline.js +139 -0
- package/dist/lib/cloud/host.d.ts +1 -1
- package/dist/lib/cloud/host.js +1 -1
- package/dist/lib/cloud/rush.js +1 -1
- package/dist/lib/codex-policy.d.ts +11 -1
- package/dist/lib/codex-policy.js +20 -5
- package/dist/lib/computer/computer-rpc.js +2 -2
- package/dist/lib/computer/download.d.ts +15 -15
- package/dist/lib/computer/download.js +35 -98
- package/dist/lib/computer/ssh-tunnel.d.ts +0 -3
- package/dist/lib/computer/ssh-tunnel.js +12 -22
- package/dist/lib/config-keys.d.ts +6 -11
- package/dist/lib/config-keys.js +18 -19
- package/dist/lib/crabbox/cli.d.ts +1 -1
- package/dist/lib/crabbox/cli.js +2 -2
- package/dist/lib/crabbox/lease.d.ts +9 -0
- package/dist/lib/crabbox/lease.js +70 -15
- package/dist/lib/crabbox/runtimes.js +1 -1
- package/dist/lib/daemon/account-state-daemon-service.d.ts +16 -0
- package/dist/lib/daemon/account-state-daemon-service.js +26 -0
- package/dist/lib/daemon/browser-ipc-service.d.ts +20 -0
- package/dist/lib/daemon/browser-ipc-service.js +46 -0
- package/dist/lib/daemon/daemon.d.ts +3 -0
- package/dist/lib/daemon/daemon.js +212 -444
- package/dist/lib/daemon/device-probe-service.d.ts +26 -0
- package/dist/lib/daemon/device-probe-service.js +47 -0
- package/dist/lib/daemon/keychain-reap-service.d.ts +17 -0
- package/dist/lib/daemon/keychain-reap-service.js +32 -0
- package/dist/lib/daemon/monitor-engine-service.d.ts +22 -0
- package/dist/lib/daemon/monitor-engine-service.js +29 -0
- package/dist/lib/daemon/runner.js +15 -2
- package/dist/lib/daemon/secrets-broker-service.d.ts +20 -0
- package/dist/lib/daemon/secrets-broker-service.js +66 -0
- package/dist/lib/daemon/self-heal-service.d.ts +27 -0
- package/dist/lib/daemon/self-heal-service.js +48 -0
- package/dist/lib/daemon/service.d.ts +102 -0
- package/dist/lib/daemon/service.js +98 -0
- package/dist/lib/daemon/session-index-service.d.ts +22 -0
- package/dist/lib/daemon/session-index-service.js +40 -0
- package/dist/lib/daemon/state-dir-check-service.d.ts +43 -0
- package/dist/lib/daemon/state-dir-check-service.js +63 -0
- package/dist/lib/daemon/supervisor.d.ts +87 -0
- package/dist/lib/daemon/supervisor.js +298 -0
- package/dist/lib/daemon/watchdog-service.d.ts +19 -0
- package/dist/lib/daemon/watchdog-service.js +40 -0
- package/dist/lib/daemon-health.d.ts +15 -0
- package/dist/lib/daemon-health.js +29 -5
- package/dist/lib/daemon-services.d.ts +8 -1
- package/dist/lib/daemon-services.js +55 -0
- package/dist/lib/daemon-ticks.d.ts +13 -2
- package/dist/lib/daemon-ticks.js +22 -20
- package/dist/lib/device-config.d.ts +25 -10
- package/dist/lib/device-config.js +90 -39
- package/dist/lib/devices/config-migration.d.ts +7 -0
- package/dist/lib/devices/config-migration.js +67 -3
- package/dist/lib/devices/health.d.ts +27 -2
- package/dist/lib/devices/health.js +39 -9
- package/dist/lib/devices/interactive-host.d.ts +15 -0
- package/dist/lib/devices/interactive-host.js +53 -0
- package/dist/lib/devices/registry.d.ts +61 -4
- package/dist/lib/devices/registry.js +94 -38
- package/dist/lib/devices/stats-cache.d.ts +26 -0
- package/dist/lib/devices/stats-cache.js +59 -7
- package/dist/lib/devices/worker-pick.d.ts +48 -0
- package/dist/lib/devices/worker-pick.js +113 -0
- package/dist/lib/exec.d.ts +70 -14
- package/dist/lib/exec.js +110 -38
- package/dist/lib/feed/answer.d.ts +29 -0
- package/dist/lib/feed/answer.js +145 -0
- package/dist/lib/feed/attention.d.ts +83 -0
- package/dist/lib/feed/attention.js +0 -0
- package/dist/lib/feed/feed.d.ts +109 -0
- package/dist/lib/feed/feed.js +154 -3
- package/dist/lib/feed/pr-status.d.ts +14 -0
- package/dist/lib/feed/pr-status.js +47 -0
- package/dist/lib/feed/watch.d.ts +59 -0
- package/dist/lib/feed/watch.js +180 -0
- package/dist/lib/fleet/capture.js +6 -0
- package/dist/lib/fleet/remote-login.js +8 -3
- package/dist/lib/fleet/types.d.ts +17 -0
- package/dist/lib/harness/adapter.d.ts +11 -0
- package/dist/lib/harness/adapters/claude.js +38 -24
- package/dist/lib/harness/adapters/codex.js +2 -4
- package/dist/lib/harness/adapters/cursor.js +8 -11
- package/dist/lib/helper-download.d.ts +108 -0
- package/dist/lib/helper-download.js +194 -0
- package/dist/lib/helper-versions.d.ts +52 -0
- package/dist/lib/helper-versions.js +58 -0
- package/dist/lib/hooks/cache.js +19 -0
- package/dist/lib/hooks/install.js +2 -2
- package/dist/lib/hooks/match.d.ts +3 -0
- package/dist/lib/hooks/match.js +25 -0
- package/dist/lib/hosts/credentials.d.ts +1 -1
- package/dist/lib/hosts/credentials.js +1 -1
- package/dist/lib/hosts/dispatch.d.ts +7 -1
- package/dist/lib/hosts/dispatch.js +24 -5
- package/dist/lib/hosts/passthrough.d.ts +8 -15
- package/dist/lib/hosts/passthrough.js +35 -16
- package/dist/lib/hosts/providers/devices.d.ts +1 -1
- package/dist/lib/hosts/providers/devices.js +1 -1
- package/dist/lib/hosts/reconnect.d.ts +179 -21
- package/dist/lib/hosts/reconnect.js +266 -45
- package/dist/lib/hosts/registry.js +7 -0
- package/dist/lib/installations/migrate.d.ts +4 -5
- package/dist/lib/installations/migrate.js +6 -7
- package/dist/lib/installations/shims.d.ts +1 -1
- package/dist/lib/installations/shims.js +23 -13
- package/dist/lib/installations/versions.js +25 -2
- package/dist/lib/linear-autoclose.d.ts +1 -1
- package/dist/lib/linear-autoclose.js +1 -1
- package/dist/lib/loop.d.ts +2 -2
- package/dist/lib/loop.js +1 -1
- package/dist/lib/menubar/download-menubar.d.ts +47 -0
- package/dist/lib/menubar/download-menubar.js +60 -0
- package/dist/lib/menubar/install-menubar.d.ts +44 -7
- package/dist/lib/menubar/install-menubar.js +105 -23
- package/dist/lib/menubar/notify-desktop.d.ts +3 -3
- package/dist/lib/menubar/notify-desktop.js +6 -6
- package/dist/lib/model-tiers.d.ts +1 -1
- package/dist/lib/open-url.d.ts +60 -2
- package/dist/lib/open-url.js +167 -12
- package/dist/lib/project-probe.d.ts +1 -1
- package/dist/lib/project-probe.js +1 -1
- package/dist/lib/project-pull.d.ts +1 -1
- package/dist/lib/project-pull.js +2 -2
- package/dist/lib/projects.d.ts +1 -1
- package/dist/lib/projects.js +1 -1
- package/dist/lib/refresh-coordinator.d.ts +1 -1
- package/dist/lib/refresh-coordinator.js +1 -1
- package/dist/lib/sandbox.js +6 -7
- package/dist/lib/secrets/download-keychain.d.ts +47 -0
- package/dist/lib/secrets/download-keychain.js +70 -0
- package/dist/lib/secrets/install-helper.d.ts +20 -1
- package/dist/lib/secrets/install-helper.js +44 -3
- package/dist/lib/session/active.d.ts +67 -0
- package/dist/lib/session/active.js +86 -2
- package/dist/lib/session/db.d.ts +11 -1
- package/dist/lib/session/db.js +142 -8
- package/dist/lib/session/discover.d.ts +23 -1
- package/dist/lib/session/discover.js +44 -10
- package/dist/lib/session/highlights.d.ts +49 -1
- package/dist/lib/session/highlights.js +95 -0
- package/dist/lib/session/host-link.d.ts +16 -2
- package/dist/lib/session/host-link.js +7 -1
- package/dist/lib/session/parse.js +33 -1
- package/dist/lib/session/prompt.d.ts +35 -0
- package/dist/lib/session/prompt.js +56 -0
- package/dist/lib/session/remote/remote-list.d.ts +9 -0
- package/dist/lib/session/remote/remote-list.js +19 -2
- package/dist/lib/session/remote/watch.d.ts +1 -1
- package/dist/lib/session/remote/watch.js +17 -3
- package/dist/lib/session/render.js +7 -3
- package/dist/lib/session/session-cache.d.ts +53 -0
- package/dist/lib/session/session-cache.js +93 -1
- package/dist/lib/session/state.js +1 -1
- package/dist/lib/session/tool-calls.d.ts +2 -0
- package/dist/lib/session/tool-calls.js +1 -1
- package/dist/lib/session/trajectory-compare.d.ts +47 -0
- package/dist/lib/session/trajectory-compare.js +116 -0
- package/dist/lib/session/trajectory-html.d.ts +19 -0
- package/dist/lib/session/trajectory-html.js +770 -0
- package/dist/lib/session/trajectory-lineage.d.ts +92 -0
- package/dist/lib/session/trajectory-lineage.js +195 -0
- package/dist/lib/session/trajectory-text.d.ts +37 -0
- package/dist/lib/session/trajectory-text.js +284 -0
- package/dist/lib/session/trajectory.d.ts +102 -0
- package/dist/lib/session/trajectory.js +384 -0
- package/dist/lib/session/types.d.ts +15 -0
- package/dist/lib/sha256-asset.d.ts +26 -0
- package/dist/lib/sha256-asset.js +41 -0
- package/dist/lib/share/backend.d.ts +95 -0
- package/dist/lib/share/backend.js +139 -0
- package/dist/lib/share/delete.d.ts +6 -2
- package/dist/lib/share/delete.js +8 -9
- package/dist/lib/share/html.d.ts +22 -0
- package/dist/lib/share/html.js +88 -0
- package/dist/lib/share/provision.d.ts +18 -4
- package/dist/lib/share/provision.js +38 -4
- package/dist/lib/share/publish.d.ts +39 -15
- package/dist/lib/share/publish.js +55 -26
- package/dist/lib/share/worker-template.js +171 -11
- package/dist/lib/sqlite.js +16 -3
- package/dist/lib/ssh-exec.d.ts +65 -0
- package/dist/lib/ssh-exec.js +120 -4
- package/dist/lib/startup/command-registry.d.ts +3 -1
- package/dist/lib/startup/command-registry.js +6 -3
- package/dist/lib/startup/dev-build.js +3 -3
- package/dist/lib/state.d.ts +2 -2
- package/dist/lib/state.js +11 -5
- package/dist/lib/subagents-registry.js +1 -1
- package/dist/lib/teams/agents.js +9 -0
- package/dist/lib/terminal/index.d.ts +1 -1
- package/dist/lib/terminal/inject.d.ts +19 -0
- package/dist/lib/terminal/inject.js +19 -9
- package/dist/lib/terminal/types.d.ts +1 -1
- package/dist/lib/terminal/types.js +1 -1
- package/dist/lib/tmux/orphan-reap.js +1 -1
- package/dist/lib/tmux/session.d.ts +46 -13
- package/dist/lib/tmux/session.js +204 -27
- package/dist/lib/traces/backend.d.ts +29 -0
- package/dist/lib/traces/backend.js +46 -0
- package/dist/lib/traces/classify.d.ts +52 -0
- package/dist/lib/traces/classify.js +72 -0
- package/dist/lib/traces/config.d.ts +10 -0
- package/dist/lib/traces/config.js +2 -0
- package/dist/lib/traces/provision.d.ts +19 -0
- package/dist/lib/traces/provision.js +30 -0
- package/dist/lib/traces/sync.d.ts +150 -0
- package/dist/lib/traces/sync.js +447 -0
- package/dist/lib/traces/worker-template.d.ts +4 -0
- package/dist/lib/traces/worker-template.js +159 -0
- package/dist/lib/types.d.ts +34 -19
- package/dist/lib/types.js +14 -0
- package/dist/lib/usage-backoff.d.ts +2 -1
- package/dist/lib/usage-backoff.js +60 -19
- package/dist/lib/usage-refresh.d.ts +18 -4
- package/dist/lib/usage-refresh.js +68 -19
- package/dist/lib/watchdog/log.d.ts +1 -1
- package/dist/lib/watchdog/log.js +1 -1
- package/dist/lib/watchdog/runner.d.ts +36 -45
- package/dist/lib/watchdog/runner.js +150 -119
- package/dist/lib/watchdog/service.d.ts +0 -1
- package/dist/lib/watchdog/service.js +0 -1
- package/dist/lib/watchdog/watchdog-agent.d.ts +38 -0
- package/dist/lib/watchdog/watchdog-agent.js +59 -0
- package/dist/lib/watchdog/watchdog.d.ts +11 -2
- package/dist/lib/watchdog/watchdog.js +37 -104
- package/dist/lib/workflows.d.ts +1 -1
- package/package.json +5 -7
- package/dist/commands/usage.d.ts +0 -27
- package/dist/commands/usage.js +0 -96
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +0 -24
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +0 -128
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -24
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +0 -123
- package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
- package/dist/lib/usage-fleet.d.ts +0 -32
- package/dist/lib/usage-fleet.js +0 -125
|
@@ -3,48 +3,51 @@
|
|
|
3
3
|
* ("ComputerHelper.app").
|
|
4
4
|
*
|
|
5
5
|
* The helper is a signed + notarized universal `.app` bundle published as a
|
|
6
|
-
* GitHub release asset
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* GitHub release asset on its own `computer-mac/v<x.y.z>` tag (see
|
|
7
|
+
* `helper-versions.ts`). The Windows helper (`lib/computer/ssh-tunnel.ts`) does
|
|
8
|
+
* NOT yet follow this model — it still keys its exe to the CLI's own tag.
|
|
9
|
+
*
|
|
10
|
+
* A fresh `npm i -g` machine has no local build, so `agents computer setup` / `agents setup computer` fetch the
|
|
11
|
+
* asset for the pinned helper version, verify its sha256 against the published
|
|
10
12
|
* `.sha256`, then verify the code signature (Developer ID Team + notarization)
|
|
11
13
|
* before it is ever copied to /Applications.
|
|
12
14
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
+
* The download + verify machinery is shared with the menu-bar helper in
|
|
16
|
+
* `../helper-download.ts`; this file is the ComputerHelper spec + its wrappers,
|
|
17
|
+
* keeping the historical export surface (`verifyMacHelper`, `downloadMacHelperApp`,
|
|
18
|
+
* `ensureMacHelperApp`, …) stable for existing callers. ComputerHelper does NOT
|
|
19
|
+
* pin a designated requirement (no `expectedBundleId`), matching its verification
|
|
20
|
+
* strength before the shared primitive existed.
|
|
15
21
|
*/
|
|
16
|
-
import { execFileSync, spawnSync } from 'node:child_process';
|
|
17
|
-
import * as fs from 'node:fs';
|
|
18
22
|
import * as os from 'node:os';
|
|
19
|
-
import * as path from 'node:path';
|
|
20
|
-
import { Readable } from 'node:stream';
|
|
21
|
-
import { pipeline } from 'node:stream/promises';
|
|
22
|
-
import { getCacheDir } from '../state.js';
|
|
23
|
-
import { getCliVersion } from '../version.js';
|
|
24
|
-
import { parseSha256Asset, sha256File } from './ssh-tunnel.js';
|
|
25
23
|
import { resolveHelperApp } from './computer-rpc.js';
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
import { EXPECTED_TEAM_ID, HELPER_RELEASE_REPO, downloadHelperApp, helperAssetUrls, helperCacheDir, parseTeamId, verifyHelperApp, } from '../helper-download.js';
|
|
25
|
+
import { helperFloor } from '../helper-versions.js';
|
|
26
|
+
// Re-exported so existing importers (and download.test.ts) keep resolving these
|
|
27
|
+
// names from './download.js'.
|
|
28
|
+
export { EXPECTED_TEAM_ID, HELPER_RELEASE_REPO, parseTeamId };
|
|
28
29
|
/** The zipped `.app` release asset name. */
|
|
29
30
|
export const MAC_HELPER_ASSET = 'ComputerHelper.app.zip';
|
|
30
31
|
/** The bundle directory name once extracted. */
|
|
31
32
|
export const MAC_HELPER_APP_NAME = 'ComputerHelper.app';
|
|
32
|
-
/**
|
|
33
|
-
*
|
|
34
|
-
|
|
33
|
+
/** ComputerHelper identity + verification policy. No `expectedBundleId`: this
|
|
34
|
+
* helper has no upgrade-stable Accessibility grant to protect, so its
|
|
35
|
+
* verification is codesign + Team + notarization exactly as before. */
|
|
36
|
+
const COMPUTER_HELPER_SPEC = {
|
|
37
|
+
helper: 'computer-mac',
|
|
38
|
+
assetName: MAC_HELPER_ASSET,
|
|
39
|
+
appName: MAC_HELPER_APP_NAME,
|
|
40
|
+
cacheSubdir: ['computer', 'mac-helper'],
|
|
41
|
+
expectedTeamId: EXPECTED_TEAM_ID,
|
|
42
|
+
localBuildHint: 'bash native/computer-mac/scripts/build.sh release',
|
|
43
|
+
};
|
|
35
44
|
/** Cache dir for the downloaded helper, one subdir per release tag. */
|
|
36
45
|
export function macHelperCacheDir(version) {
|
|
37
|
-
return
|
|
46
|
+
return helperCacheDir(COMPUTER_HELPER_SPEC, version);
|
|
38
47
|
}
|
|
39
48
|
/** Release-asset URLs for the helper zip + its checksum at one `v<version>` tag. */
|
|
40
49
|
export function macHelperAssetUrls(version) {
|
|
41
|
-
|
|
42
|
-
return { zip: `${base}/${MAC_HELPER_ASSET}`, sha256: `${base}/${MAC_HELPER_ASSET}.sha256` };
|
|
43
|
-
}
|
|
44
|
-
/** Extract `TeamIdentifier=XXXX` from `codesign -dv --verbose=4` output (which is
|
|
45
|
-
* emitted on stderr). Returns null when absent (ad-hoc / unsigned). */
|
|
46
|
-
export function parseTeamId(codesignInfo) {
|
|
47
|
-
return codesignInfo.match(/TeamIdentifier=([A-Z0-9]+)/)?.[1] ?? null;
|
|
50
|
+
return helperAssetUrls(COMPUTER_HELPER_SPEC, version);
|
|
48
51
|
}
|
|
49
52
|
/**
|
|
50
53
|
* Verify a helper `.app` bundle is intact, signed by the expected Developer ID
|
|
@@ -52,31 +55,7 @@ export function parseTeamId(codesignInfo) {
|
|
|
52
55
|
* on any failure — a downloaded bundle is never trusted without this.
|
|
53
56
|
*/
|
|
54
57
|
export function verifyMacHelper(appPath) {
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
execFileSync('/usr/bin/codesign', ['--verify', '--deep', '--strict', appPath], { stdio: 'pipe' });
|
|
58
|
-
}
|
|
59
|
-
catch (e) {
|
|
60
|
-
throw new Error(`code signature invalid for ${appPath}: ${e.message}`);
|
|
61
|
-
}
|
|
62
|
-
// 2. Team identity — must be our Developer ID, not some other valid signer.
|
|
63
|
-
// `codesign -dv` writes its details to STDERR even on success (exit 0), so we
|
|
64
|
-
// must read stderr, not stdout. spawnSync captures both streams regardless of
|
|
65
|
-
// exit code; execFileSync would return only (empty) stdout and the Team check
|
|
66
|
-
// would falsely reject every validly-signed helper.
|
|
67
|
-
const dv = spawnSync('/usr/bin/codesign', ['-dv', '--verbose=4', appPath], { encoding: 'utf8' });
|
|
68
|
-
const info = `${dv.stdout ?? ''}${dv.stderr ?? ''}`;
|
|
69
|
-
const team = parseTeamId(info);
|
|
70
|
-
if (team !== EXPECTED_TEAM_ID) {
|
|
71
|
-
throw new Error(`helper signed by unexpected Team (${team ?? 'none'}), expected ${EXPECTED_TEAM_ID}. Refusing to install.`);
|
|
72
|
-
}
|
|
73
|
-
// 3. Notarization / Gatekeeper — confirms Apple stapled a notarization ticket.
|
|
74
|
-
try {
|
|
75
|
-
execFileSync('/usr/sbin/spctl', ['--assess', '--type', 'execute', '--verbose', appPath], { stdio: 'pipe' });
|
|
76
|
-
}
|
|
77
|
-
catch (e) {
|
|
78
|
-
throw new Error(`helper is not notarized / rejected by Gatekeeper: ${e.message}. Refusing to install.`);
|
|
79
|
-
}
|
|
58
|
+
verifyHelperApp(appPath, COMPUTER_HELPER_SPEC);
|
|
80
59
|
}
|
|
81
60
|
/**
|
|
82
61
|
* Download the helper release asset for `version`, verify sha256, extract the
|
|
@@ -84,58 +63,16 @@ export function verifyMacHelper(appPath) {
|
|
|
84
63
|
* `ComputerHelper.app`. A missing asset is a hard error naming the exact tag —
|
|
85
64
|
* never a silent fallback to another release.
|
|
86
65
|
*/
|
|
87
|
-
export
|
|
88
|
-
|
|
89
|
-
const cachedApp = path.join(dir, MAC_HELPER_APP_NAME);
|
|
90
|
-
if (fs.existsSync(cachedApp)) {
|
|
91
|
-
// Re-verify a cached bundle cheaply; a tampered cache must not be trusted.
|
|
92
|
-
verifyMacHelper(cachedApp);
|
|
93
|
-
return cachedApp;
|
|
94
|
-
}
|
|
95
|
-
const tag = `v${version}`;
|
|
96
|
-
const { zip: zipUrl, sha256: shaUrl } = macHelperAssetUrls(version);
|
|
97
|
-
const missing = (status, url) => new Error(`no ${MAC_HELPER_ASSET} release asset for tag ${tag} (HTTP ${status} on ${url}). ` +
|
|
98
|
-
`The macOS helper ships as a GitHub release asset per tagged CLI version; ` +
|
|
99
|
-
`from a repo checkout you can build it locally instead: ` +
|
|
100
|
-
`bash native/computer-mac/scripts/build.sh release`);
|
|
101
|
-
// Checksum first: it is tiny and 404s fast when the tag has no assets.
|
|
102
|
-
const shaRes = await fetch(shaUrl, { signal: AbortSignal.timeout(30_000) });
|
|
103
|
-
if (!shaRes.ok)
|
|
104
|
-
throw missing(shaRes.status, shaUrl);
|
|
105
|
-
const expected = parseSha256Asset(await shaRes.text());
|
|
106
|
-
console.error(`Downloading ${MAC_HELPER_ASSET} ${tag} from GitHub releases...`);
|
|
107
|
-
const zipRes = await fetch(zipUrl, { signal: AbortSignal.timeout(15 * 60_000) });
|
|
108
|
-
if (!zipRes.ok || !zipRes.body)
|
|
109
|
-
throw missing(zipRes.status, zipUrl);
|
|
110
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
111
|
-
const partial = path.join(dir, `${MAC_HELPER_ASSET}.download`);
|
|
112
|
-
try {
|
|
113
|
-
await pipeline(Readable.fromWeb(zipRes.body), fs.createWriteStream(partial));
|
|
114
|
-
const actual = await sha256File(partial);
|
|
115
|
-
if (actual !== expected) {
|
|
116
|
-
throw new Error(`sha256 mismatch for ${zipUrl}: expected ${expected}, got ${actual}`);
|
|
117
|
-
}
|
|
118
|
-
// Extract the zip (created with `ditto -c -k --keepParent`, so it contains
|
|
119
|
-
// ComputerHelper.app/ at top level) into the version cache dir.
|
|
120
|
-
fs.rmSync(cachedApp, { recursive: true, force: true });
|
|
121
|
-
execFileSync('/usr/bin/ditto', ['-x', '-k', partial, dir], { stdio: 'pipe' });
|
|
122
|
-
if (!fs.existsSync(cachedApp)) {
|
|
123
|
-
throw new Error(`extracted asset did not contain ${MAC_HELPER_APP_NAME}`);
|
|
124
|
-
}
|
|
125
|
-
verifyMacHelper(cachedApp);
|
|
126
|
-
}
|
|
127
|
-
finally {
|
|
128
|
-
fs.rmSync(partial, { force: true });
|
|
129
|
-
}
|
|
130
|
-
return cachedApp;
|
|
66
|
+
export function downloadMacHelperApp(version) {
|
|
67
|
+
return downloadHelperApp(COMPUTER_HELPER_SPEC, version);
|
|
131
68
|
}
|
|
132
69
|
/**
|
|
133
70
|
* Resolve the helper `.app` to install from: a local build / bundled copy first
|
|
134
71
|
* (repo checkout), else the checksum + signature-verified release-asset download
|
|
135
|
-
* for the
|
|
72
|
+
* for the pinned helper version. Throws with the tag it checked when neither
|
|
136
73
|
* exists. macOS only.
|
|
137
74
|
*/
|
|
138
|
-
export async function ensureMacHelperApp(version =
|
|
75
|
+
export async function ensureMacHelperApp(version = helperFloor('computer-mac')) {
|
|
139
76
|
if (os.platform() !== 'darwin') {
|
|
140
77
|
throw new Error('The macOS computer helper is only available on macOS.');
|
|
141
78
|
}
|
|
@@ -72,9 +72,6 @@ export declare function winHelperAssetUrls(version: string): {
|
|
|
72
72
|
* Parse the published `.sha256` asset — `sha256sum` format (`<hex> <name>`) or
|
|
73
73
|
* a bare hex digest. Throws on anything that does not lead with 64 hex chars.
|
|
74
74
|
*/
|
|
75
|
-
export declare function parseSha256Asset(text: string): string;
|
|
76
|
-
/** Stream a file through sha256 — the exe is ~157MB, never read it whole. */
|
|
77
|
-
export declare function sha256File(file: string): Promise<string>;
|
|
78
75
|
/**
|
|
79
76
|
* Download the exe release asset for this CLI version, verify its sha256
|
|
80
77
|
* against the published `.sha256` asset, and cache it under the agents cache
|
|
@@ -21,7 +21,8 @@ import * as net from 'net';
|
|
|
21
21
|
import * as fs from 'fs';
|
|
22
22
|
import * as path from 'path';
|
|
23
23
|
import { fileURLToPath } from 'url';
|
|
24
|
-
import { randomBytes
|
|
24
|
+
import { randomBytes } from 'crypto';
|
|
25
|
+
import { parseSha256Asset, sha256File } from '../sha256-asset.js';
|
|
25
26
|
import { Readable } from 'stream';
|
|
26
27
|
import { pipeline } from 'stream/promises';
|
|
27
28
|
import { sshExec, SSH_OPTS, assertValidSshTarget } from '../ssh-exec.js';
|
|
@@ -31,8 +32,8 @@ import { getDevice } from '../devices/registry.js';
|
|
|
31
32
|
import { deviceIdentityArgs, sshTargetFor } from '../devices/connect.js';
|
|
32
33
|
import { hostNameFor } from '../devices/ssh-config.js';
|
|
33
34
|
import { getCacheDir } from '../state.js';
|
|
34
|
-
import { getCliVersion } from '../version.js';
|
|
35
35
|
import { openComputerClient, resolveTcpEndpoint } from './computer-rpc.js';
|
|
36
|
+
import { helperFloor, helperTag } from '../helper-versions.js';
|
|
36
37
|
/** Build the ssh argv (after the `ssh` program name) for an `-L` tunnel. Pure.
|
|
37
38
|
*
|
|
38
39
|
* Composes the shared hardened baseline (`SSH_OPTS`) rather than re-listing it,
|
|
@@ -115,8 +116,8 @@ export const WIN_HELPER_TOKEN_FILE = 'helper-token';
|
|
|
115
116
|
export function resolveWinHelperExe() {
|
|
116
117
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
117
118
|
const candidates = [
|
|
118
|
-
// Running from the agents-cli checkout.
|
|
119
|
-
path.resolve(here, '..', '..', '..', '..', '
|
|
119
|
+
// Running from the agents-cli checkout. cli/dist/lib/computer -> repo root (4 up) -> native/computer-win.
|
|
120
|
+
path.resolve(here, '..', '..', '..', '..', 'native', 'computer-win', 'dist', WIN_HELPER_EXE),
|
|
120
121
|
// Bundled with the npm package (dist/lib/computer -> dist/).
|
|
121
122
|
path.resolve(here, '..', '..', 'computer-helper-win', WIN_HELPER_EXE),
|
|
122
123
|
];
|
|
@@ -134,29 +135,18 @@ export function winHelperCacheDir(version) {
|
|
|
134
135
|
}
|
|
135
136
|
/** Release-asset URLs for the exe + its checksum at one exact `v<version>` tag. */
|
|
136
137
|
export function winHelperAssetUrls(version) {
|
|
137
|
-
|
|
138
|
+
// The helper's OWN tag, not the CLI's. This exe is not an .app bundle so it
|
|
139
|
+
// cannot share helper-download.ts's zip/codesign machinery, but it had the
|
|
140
|
+
// identical coupling: keyed to `v${cliVersion}`, every CLI release had to
|
|
141
|
+
// re-stage a ~165MB exe or the download 404'd, and a helper fix could not
|
|
142
|
+
// reach anyone without a CLI release. Same fix, same tag namespace.
|
|
143
|
+
const base = `https://github.com/${WIN_HELPER_RELEASE_REPO}/releases/download/${helperTag('computer-win', version)}`;
|
|
138
144
|
return { exe: `${base}/${WIN_HELPER_EXE}`, sha256: `${base}/${WIN_HELPER_EXE}.sha256` };
|
|
139
145
|
}
|
|
140
146
|
/**
|
|
141
147
|
* Parse the published `.sha256` asset — `sha256sum` format (`<hex> <name>`) or
|
|
142
148
|
* a bare hex digest. Throws on anything that does not lead with 64 hex chars.
|
|
143
149
|
*/
|
|
144
|
-
export function parseSha256Asset(text) {
|
|
145
|
-
const m = text.trim().match(/^([A-Fa-f0-9]{64})(\s|$)/);
|
|
146
|
-
if (!m)
|
|
147
|
-
throw new Error(`malformed .sha256 release asset: ${JSON.stringify(text.slice(0, 80))}`);
|
|
148
|
-
return m[1].toLowerCase();
|
|
149
|
-
}
|
|
150
|
-
/** Stream a file through sha256 — the exe is ~157MB, never read it whole. */
|
|
151
|
-
export function sha256File(file) {
|
|
152
|
-
return new Promise((resolve, reject) => {
|
|
153
|
-
const hash = createHash('sha256');
|
|
154
|
-
fs.createReadStream(file)
|
|
155
|
-
.on('error', reject)
|
|
156
|
-
.on('data', (d) => hash.update(d))
|
|
157
|
-
.on('end', () => resolve(hash.digest('hex')));
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
150
|
/**
|
|
161
151
|
* Download the exe release asset for this CLI version, verify its sha256
|
|
162
152
|
* against the published `.sha256` asset, and cache it under the agents cache
|
|
@@ -203,7 +193,7 @@ export async function downloadWinHelperExe(version) {
|
|
|
203
193
|
* checkout / bundled), then the checksum-verified release-asset download for
|
|
204
194
|
* the running CLI version. Throws with the tag it checked when neither exists.
|
|
205
195
|
*/
|
|
206
|
-
export async function ensureWinHelperExe(version =
|
|
196
|
+
export async function ensureWinHelperExe(version = helperFloor('computer-win')) {
|
|
207
197
|
const local = resolveWinHelperExe();
|
|
208
198
|
if (local)
|
|
209
199
|
return local;
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import type { AgentId } from './types.js';
|
|
10
10
|
import { type ModelTier } from './model-tiers.js';
|
|
11
11
|
/** The top-level scope of a unified config key. */
|
|
12
|
-
export type ConfigScope = 'run' | 'interactive' | '
|
|
12
|
+
export type ConfigScope = 'run' | 'interactive' | 'auto' | 'browser' | 'project' | 'device';
|
|
13
13
|
/** A run-time default key: model, mode, effort, or tier override. */
|
|
14
14
|
export interface ParsedRunConfigKey {
|
|
15
15
|
scope: 'run';
|
|
@@ -23,20 +23,16 @@ export interface ParsedInteractiveConfigKey {
|
|
|
23
23
|
scope: 'interactive';
|
|
24
24
|
property: 'host';
|
|
25
25
|
}
|
|
26
|
-
/** The host whose usage snapshots are authoritative for fleet-wide reporting. */
|
|
27
|
-
export interface ParsedUsageConfigKey {
|
|
28
|
-
scope: 'usage';
|
|
29
|
-
property: 'primary-host';
|
|
30
|
-
}
|
|
31
26
|
/** Which devices automatic placement (`--device auto`) may pick. */
|
|
32
27
|
export interface ParsedAutoConfigKey {
|
|
33
28
|
scope: 'auto';
|
|
34
29
|
property: 'pool';
|
|
35
30
|
}
|
|
36
|
-
/**
|
|
31
|
+
/** A device-scope browser key (self or peer): the profile agents drive, or the
|
|
32
|
+
* browser that shows the user a page. */
|
|
37
33
|
export interface ParsedBrowserConfigKey {
|
|
38
34
|
scope: 'browser';
|
|
39
|
-
property: 'profile';
|
|
35
|
+
property: 'profile' | 'viewer';
|
|
40
36
|
device?: string;
|
|
41
37
|
}
|
|
42
38
|
export interface ParsedProjectConfigKey {
|
|
@@ -49,8 +45,8 @@ export interface ParsedDeviceConfigKey {
|
|
|
49
45
|
device: string;
|
|
50
46
|
property: DeviceConfigProperty;
|
|
51
47
|
}
|
|
52
|
-
export type ParsedConfigKey = ParsedRunConfigKey | ParsedInteractiveConfigKey |
|
|
53
|
-
export type DeviceConfigProperty = 'role' | 'max-agents' | 'scheduler' | 'daemon' | 'watchdog' | 'tmux' | 'browser.remote-control' | 'browser.task-idle-minutes' | 'notes' | 'browser.profile';
|
|
48
|
+
export type ParsedConfigKey = ParsedRunConfigKey | ParsedInteractiveConfigKey | ParsedAutoConfigKey | ParsedBrowserConfigKey | ParsedProjectConfigKey | ParsedDeviceConfigKey;
|
|
49
|
+
export type DeviceConfigProperty = 'role' | 'max-agents' | 'scheduler' | 'daemon' | 'watchdog' | 'tmux' | 'browser.remote-control' | 'browser.task-idle-minutes' | 'notes' | 'browser.profile' | 'browser.viewer';
|
|
54
50
|
/** Normalize agent@version to use `@` consistently. */
|
|
55
51
|
export declare function formatAgentVersion(agent: AgentId, version: string): string;
|
|
56
52
|
/**
|
|
@@ -62,7 +58,6 @@ export declare function formatAgentVersion(agent: AgentId, version: string): str
|
|
|
62
58
|
* run.<agent@version>.effort
|
|
63
59
|
* run.<agent@version>.tier.<cheap|default|best|ultra>
|
|
64
60
|
* interactive.host
|
|
65
|
-
* usage.primary-host
|
|
66
61
|
* auto.pool
|
|
67
62
|
* browser.profile
|
|
68
63
|
* project.root
|
package/dist/lib/config-keys.js
CHANGED
|
@@ -10,6 +10,7 @@ import { AGENTS } from './agents.js';
|
|
|
10
10
|
import { MODEL_TIERS } from './model-tiers.js';
|
|
11
11
|
import { VERSION_RE } from './run-defaults.js';
|
|
12
12
|
const DEVICE_CONFIG_PROPERTIES = [
|
|
13
|
+
'browser.viewer',
|
|
13
14
|
'role',
|
|
14
15
|
'max-agents',
|
|
15
16
|
'scheduler',
|
|
@@ -47,7 +48,6 @@ export function formatAgentVersion(agent, version) {
|
|
|
47
48
|
* run.<agent@version>.effort
|
|
48
49
|
* run.<agent@version>.tier.<cheap|default|best|ultra>
|
|
49
50
|
* interactive.host
|
|
50
|
-
* usage.primary-host
|
|
51
51
|
* auto.pool
|
|
52
52
|
* browser.profile
|
|
53
53
|
* project.root
|
|
@@ -79,19 +79,19 @@ export function parseConfigKey(key) {
|
|
|
79
79
|
if (raw === 'interactive.host') {
|
|
80
80
|
return { scope: 'interactive', property: 'host' };
|
|
81
81
|
}
|
|
82
|
-
if (raw === 'usage.primary-host') {
|
|
83
|
-
return { scope: 'usage', property: 'primary-host' };
|
|
84
|
-
}
|
|
85
82
|
if (raw === 'auto.pool') {
|
|
86
83
|
return { scope: 'auto', property: 'pool' };
|
|
87
84
|
}
|
|
88
85
|
if (raw === 'browser.profile') {
|
|
89
86
|
return { scope: 'browser', property: 'profile' };
|
|
90
87
|
}
|
|
88
|
+
if (raw === 'browser.viewer') {
|
|
89
|
+
return { scope: 'browser', property: 'viewer' };
|
|
90
|
+
}
|
|
91
91
|
if (raw === 'project.root') {
|
|
92
92
|
return { scope: 'project', property: 'root' };
|
|
93
93
|
}
|
|
94
|
-
const deviceMatch = raw.match(/^devices\.(.+)\.(role|max-agents|scheduler|daemon|watchdog|tmux|notes|browser\.remote-control|browser\.task-idle-minutes|browser\.profile)$/);
|
|
94
|
+
const deviceMatch = raw.match(/^devices\.(.+)\.(role|max-agents|scheduler|daemon|watchdog|tmux|notes|browser\.remote-control|browser\.task-idle-minutes|browser\.profile|browser\.viewer)$/);
|
|
95
95
|
if (deviceMatch) {
|
|
96
96
|
return {
|
|
97
97
|
scope: 'device',
|
|
@@ -106,14 +106,11 @@ export function parseConfigKey(key) {
|
|
|
106
106
|
if (raw.startsWith('interactive.')) {
|
|
107
107
|
throw new Error(`Invalid interactive config key '${key}'. Use interactive.host.`);
|
|
108
108
|
}
|
|
109
|
-
if (raw.startsWith('usage.')) {
|
|
110
|
-
throw new Error(`Invalid usage config key '${key}'. Use usage.primary-host.`);
|
|
111
|
-
}
|
|
112
109
|
if (raw.startsWith('auto.')) {
|
|
113
110
|
throw new Error(`Invalid auto config key '${key}'. Use auto.pool.`);
|
|
114
111
|
}
|
|
115
112
|
if (raw.startsWith('browser.')) {
|
|
116
|
-
throw new Error(`Invalid browser config key '${key}'. Use browser.profile.`);
|
|
113
|
+
throw new Error(`Invalid browser config key '${key}'. Use browser.profile or browser.viewer.`);
|
|
117
114
|
}
|
|
118
115
|
if (raw.startsWith('project.')) {
|
|
119
116
|
throw new Error(`Invalid project config key '${key}'. Use project.root.`);
|
|
@@ -121,7 +118,7 @@ export function parseConfigKey(key) {
|
|
|
121
118
|
if (raw.startsWith('devices.')) {
|
|
122
119
|
throw new Error(`Invalid device config key '${key}'. Expected devices.<name>.<${DEVICE_CONFIG_PROPERTIES.join('|')}>.`);
|
|
123
120
|
}
|
|
124
|
-
throw new Error(`Unknown config scope in '${key}'. Use one of: run, interactive,
|
|
121
|
+
throw new Error(`Unknown config scope in '${key}'. Use one of: run, interactive, auto, browser, project, devices.`);
|
|
125
122
|
}
|
|
126
123
|
/** Render a parsed key back to its canonical dotted string. */
|
|
127
124
|
export function formatConfigKey(parsed) {
|
|
@@ -133,12 +130,12 @@ export function formatConfigKey(parsed) {
|
|
|
133
130
|
return `run.${formatAgentVersion(parsed.agent, parsed.version)}.${parsed.property}`;
|
|
134
131
|
case 'interactive':
|
|
135
132
|
return 'interactive.host';
|
|
136
|
-
case 'usage':
|
|
137
|
-
return 'usage.primary-host';
|
|
138
133
|
case 'auto':
|
|
139
134
|
return 'auto.pool';
|
|
140
135
|
case 'browser':
|
|
141
|
-
return parsed.device
|
|
136
|
+
return parsed.device
|
|
137
|
+
? `devices.${parsed.device}.browser.${parsed.property}`
|
|
138
|
+
: `browser.${parsed.property}`;
|
|
142
139
|
case 'project':
|
|
143
140
|
return 'project.root';
|
|
144
141
|
case 'device':
|
|
@@ -152,7 +149,7 @@ export function listKnownConfigKeys() {
|
|
|
152
149
|
for (const tier of MODEL_TIERS) {
|
|
153
150
|
keys.push(`run.<agent@version>.tier.${tier}`);
|
|
154
151
|
}
|
|
155
|
-
keys.push('interactive.host', '
|
|
152
|
+
keys.push('interactive.host', 'auto.pool', 'browser.profile', 'browser.viewer', 'project.root');
|
|
156
153
|
for (const prop of DEVICE_CONFIG_PROPERTIES) {
|
|
157
154
|
keys.push(`devices.<name>.${prop}`);
|
|
158
155
|
}
|
|
@@ -185,6 +182,8 @@ export function devicePropertyToConfigName(property) {
|
|
|
185
182
|
return 'notes';
|
|
186
183
|
case 'browser.profile':
|
|
187
184
|
return 'browser.profile';
|
|
185
|
+
case 'browser.viewer':
|
|
186
|
+
return 'browser.viewer';
|
|
188
187
|
}
|
|
189
188
|
}
|
|
190
189
|
/**
|
|
@@ -200,14 +199,14 @@ export function configKeyStorageHint(parsed) {
|
|
|
200
199
|
return `run.defaults.${parsed.agent}:${parsed.version}.${parsed.property}`;
|
|
201
200
|
case 'interactive':
|
|
202
201
|
return 'config.interactiveHost';
|
|
203
|
-
case 'usage':
|
|
204
|
-
return 'config.usagePrimaryHost';
|
|
205
202
|
case 'auto':
|
|
206
203
|
return 'config.autoPool';
|
|
207
|
-
case 'browser':
|
|
204
|
+
case 'browser': {
|
|
205
|
+
const yamlKey = parsed.property === 'viewer' ? 'browserViewer' : 'defaultBrowserProfile';
|
|
208
206
|
return parsed.device
|
|
209
|
-
? `devices/${parsed.device}/agents.yaml config
|
|
210
|
-
:
|
|
207
|
+
? `devices/${parsed.device}/agents.yaml config.${yamlKey}`
|
|
208
|
+
: `devices/<self>/agents.yaml config.${yamlKey}`;
|
|
209
|
+
}
|
|
211
210
|
case 'project':
|
|
212
211
|
return 'devices.<self>.projectRoot';
|
|
213
212
|
case 'device':
|
|
@@ -242,7 +242,7 @@ export declare function reapSafeOrphans(boxes: CrabboxBox[], nowSecs: number): C
|
|
|
242
242
|
/**
|
|
243
243
|
* List reap-safe orphans and (unless `dryRun`) stop them. Returns the candidates
|
|
244
244
|
* considered and the slugs actually stopped. Best-effort per box — a stop failure
|
|
245
|
-
* is skipped, never thrown. Backs `agents devices lease
|
|
245
|
+
* is skipped, never thrown. Backs `agents devices lease prune` and the 403 auto-reap opt-in.
|
|
246
246
|
*/
|
|
247
247
|
export declare function reapOrphans(opts?: CrabboxOptions & {
|
|
248
248
|
nowSecs?: number;
|
package/dist/lib/crabbox/cli.js
CHANGED
|
@@ -394,7 +394,7 @@ export async function crabboxWarmup(opts = {}) {
|
|
|
394
394
|
if (orphans.length) {
|
|
395
395
|
hint =
|
|
396
396
|
` ${orphans.length} expired, idle box(es) are holding the quota — free them with ` +
|
|
397
|
-
`\`agents devices lease
|
|
397
|
+
`\`agents devices lease prune\` (or \`crabbox stop ${orphans[0].slug}\`).`;
|
|
398
398
|
}
|
|
399
399
|
}
|
|
400
400
|
catch {
|
|
@@ -586,7 +586,7 @@ export function reapSafeOrphans(boxes, nowSecs) {
|
|
|
586
586
|
/**
|
|
587
587
|
* List reap-safe orphans and (unless `dryRun`) stop them. Returns the candidates
|
|
588
588
|
* considered and the slugs actually stopped. Best-effort per box — a stop failure
|
|
589
|
-
* is skipped, never thrown. Backs `agents devices lease
|
|
589
|
+
* is skipped, never thrown. Backs `agents devices lease prune` and the 403 auto-reap opt-in.
|
|
590
590
|
*/
|
|
591
591
|
export function reapOrphans(opts = {}) {
|
|
592
592
|
const nowSecs = opts.nowSecs ?? Math.floor(Date.now() / 1000);
|
|
@@ -115,6 +115,15 @@ export interface LeaseRunResult {
|
|
|
115
115
|
exitCode: number | null;
|
|
116
116
|
toreDown: boolean;
|
|
117
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Exit code the box-side bootstrap raises when `agents setup` did not leave a
|
|
120
|
+
* usable install (`~/.agents/.system` is still not a git repo). The run-side gate
|
|
121
|
+
* (`ensureInitialized`, commands/setup.ts) refuses `agents run` with "agents-cli
|
|
122
|
+
* is not set up" in exactly that state, so the bootstrap aborts with this code
|
|
123
|
+
* instead of running the agent into that refusal. `leaseAndRun` reads it to stop
|
|
124
|
+
* a box THIS run provisioned — a newly created box that can't run is pure cost.
|
|
125
|
+
*/
|
|
126
|
+
export declare const LEASE_BOOTSTRAP_FAILED_CODE = 97;
|
|
118
127
|
/** Build a shell-safe, collision-resistant workspace id for one lease run. */
|
|
119
128
|
export declare function leaseWorkspaceId(repoRoot: string, startedAtMs?: number, pid?: number): string;
|
|
120
129
|
/** Isolated box-home path for one run, relative to the shared box user's home. */
|
|
@@ -35,6 +35,15 @@ import { assertNoNativeOAuthTransfer, buildCredentialScript, buildHomeFileWriteS
|
|
|
35
35
|
import { LEASE_AGENT_MARKER, leasePhaseSentinel } from './progress.js';
|
|
36
36
|
import { copySetupToBox } from './setup-copy.js';
|
|
37
37
|
import { DEFAULT_CRABBOX_PROFILE } from './config.js';
|
|
38
|
+
/**
|
|
39
|
+
* Exit code the box-side bootstrap raises when `agents setup` did not leave a
|
|
40
|
+
* usable install (`~/.agents/.system` is still not a git repo). The run-side gate
|
|
41
|
+
* (`ensureInitialized`, commands/setup.ts) refuses `agents run` with "agents-cli
|
|
42
|
+
* is not set up" in exactly that state, so the bootstrap aborts with this code
|
|
43
|
+
* instead of running the agent into that refusal. `leaseAndRun` reads it to stop
|
|
44
|
+
* a box THIS run provisioned — a newly created box that can't run is pure cost.
|
|
45
|
+
*/
|
|
46
|
+
export const LEASE_BOOTSTRAP_FAILED_CODE = 97;
|
|
38
47
|
/** POSIX single-quote for safe embedding in the generated bootstrap script. */
|
|
39
48
|
function q(s) {
|
|
40
49
|
return "'" + s.replace(/'/g, "'\\''") + "'";
|
|
@@ -74,12 +83,14 @@ function runtimeInstallSpec(id, dispatchProfile) {
|
|
|
74
83
|
return id;
|
|
75
84
|
}
|
|
76
85
|
/**
|
|
77
|
-
* Bash snippet that guarantees `agents` is runnable on the box. Fresh
|
|
78
|
-
* images ship without node, and the box user may not own the global npm
|
|
79
|
-
* so everything installs user-level under ~/.local (node from the official
|
|
86
|
+
* Bash snippet that guarantees `agents` is runnable AND set up on the box. Fresh
|
|
87
|
+
* crabbox images ship without node, and the box user may not own the global npm
|
|
88
|
+
* prefix, so everything installs user-level under ~/.local (node from the official
|
|
80
89
|
* latest-v22.x tarball to satisfy engines.node >=22.5.0). Exits 96 with a
|
|
81
|
-
* diagnostic when the CLI still isn't runnable
|
|
82
|
-
*
|
|
90
|
+
* diagnostic when the CLI still isn't runnable, and {@link LEASE_BOOTSTRAP_FAILED_CODE}
|
|
91
|
+
* when `agents setup` did not leave a git-repo `~/.agents/.system` — both used to
|
|
92
|
+
* surface only as a downstream failure (`agents: command not found`, or the run-side
|
|
93
|
+
* "agents-cli is not set up" gate) after a swallowed `|| true`.
|
|
83
94
|
*/
|
|
84
95
|
const ENSURE_AGENTS_CLI = [
|
|
85
96
|
'export PATH="$HOME/.local/bin:$PATH"',
|
|
@@ -97,9 +108,22 @@ const ENSURE_AGENTS_CLI = [
|
|
|
97
108
|
' echo "lease bootstrap: agents-cli install failed (node: $(command -v node || echo missing))" >&2',
|
|
98
109
|
' exit 96',
|
|
99
110
|
'fi',
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
|
|
111
|
+
// The run-side gate (`ensureInitialized`, commands/setup.ts) refuses `agents run`
|
|
112
|
+
// with "agents-cli is not set up" until `~/.agents/.system` is a GIT REPO. Gate
|
|
113
|
+
// setup on that same postcondition — not a swallowed exit: capture setup's output,
|
|
114
|
+
// and if the repo still isn't there afterward, print the real cause and abort so
|
|
115
|
+
// the box is never run into that refusal (a swallowed `|| true` here is what left
|
|
116
|
+
// a provisioned box dying at "agents-cli is not set up"). `-e` (not `-d`) matches
|
|
117
|
+
// the gate's `isGitRepo` (git.ts), which is `existsSync('.git')` — true for a
|
|
118
|
+
// gitfile/gitlink too, so a linked-worktree `.system` never false-aborts here.
|
|
119
|
+
'if [ ! -e "$HOME/.agents/.system/.git" ]; then',
|
|
120
|
+
' setup_out=$(agents setup 2>&1)',
|
|
121
|
+
' if [ ! -e "$HOME/.agents/.system/.git" ]; then',
|
|
122
|
+
' echo "lease bootstrap: agents setup did not complete (~/.agents/.system is not a git repo)" >&2',
|
|
123
|
+
' printf "%s\\n" "$setup_out" >&2',
|
|
124
|
+
` exit ${LEASE_BOOTSTRAP_FAILED_CODE}`,
|
|
125
|
+
' fi',
|
|
126
|
+
'fi',
|
|
103
127
|
].join('\n');
|
|
104
128
|
/**
|
|
105
129
|
* Build the single bootstrap script run on the box: ensure agents-cli, install
|
|
@@ -331,28 +355,59 @@ export async function leaseAndRun(opts) {
|
|
|
331
355
|
remoteDir: opts.workspaceId ? `${leaseHomeDir(opts.workspaceId)}/.agents/` : undefined,
|
|
332
356
|
});
|
|
333
357
|
}
|
|
334
|
-
catch {
|
|
335
|
-
|
|
358
|
+
catch (err) {
|
|
359
|
+
// Best-effort — a config-copy failure never blocks the run (the agent runs
|
|
360
|
+
// without the pushed ~/.agents config), but surface it instead of swallowing
|
|
361
|
+
// it silently so a broken sync is visible rather than a mystery.
|
|
362
|
+
opts.onData?.(`lease: setup copy failed — running without pushed ~/.agents config (${err.message})\n`);
|
|
336
363
|
}
|
|
337
364
|
}
|
|
338
365
|
const script = buildBootstrapScript(opts);
|
|
339
366
|
let exitCode = null;
|
|
340
367
|
let toreDown = false;
|
|
368
|
+
// Track whether the box-side script reached the agent — the bootstrap echoes
|
|
369
|
+
// LEASE_AGENT_MARKER on its own line immediately before `agents run`, and aborts
|
|
370
|
+
// (LEASE_BOOTSTRAP_FAILED_CODE) strictly BEFORE that. So "marker never seen" is
|
|
371
|
+
// how we tell a pre-agent bootstrap abort from an agent that merely happened to
|
|
372
|
+
// exit with the same numeric code — only the former is an unusable box. A short
|
|
373
|
+
// rolling tail keeps the check robust if the marker straddles two output chunks.
|
|
374
|
+
let sawAgentMarker = false;
|
|
375
|
+
let markerTail = '';
|
|
376
|
+
const trackedOnData = (chunk) => {
|
|
377
|
+
if (!sawAgentMarker) {
|
|
378
|
+
const combined = markerTail + chunk;
|
|
379
|
+
if (combined.includes(LEASE_AGENT_MARKER))
|
|
380
|
+
sawAgentMarker = true;
|
|
381
|
+
// Keep only enough tail to catch a marker straddling the NEXT chunk boundary.
|
|
382
|
+
else
|
|
383
|
+
markerTail = combined.slice(-(LEASE_AGENT_MARKER.length + 8));
|
|
384
|
+
}
|
|
385
|
+
opts.onData?.(chunk);
|
|
386
|
+
};
|
|
341
387
|
try {
|
|
342
388
|
const renewIdleTimeoutSecs = reused && !opts.reuseBox && box.idleTimeoutSecs !== null
|
|
343
389
|
? box.idleTimeoutSecs
|
|
344
390
|
: undefined;
|
|
345
391
|
exitCode = await crabboxRunScript(box.slug, script, {
|
|
346
392
|
secretsBundle: opts.secretsBundle,
|
|
347
|
-
onData:
|
|
393
|
+
onData: trackedOnData,
|
|
348
394
|
renewIdleTimeoutSecs,
|
|
349
395
|
});
|
|
350
396
|
}
|
|
351
397
|
finally {
|
|
352
|
-
//
|
|
353
|
-
//
|
|
354
|
-
// --
|
|
355
|
-
|
|
398
|
+
// A box this run PROVISIONED (never a reused one) whose BOOTSTRAP failed is
|
|
399
|
+
// unusable capacity — keep it and it bills until the idle GC window. Stop it
|
|
400
|
+
// regardless of --keep-box: the user asked to keep a working box, not a broken
|
|
401
|
+
// one. A reused box is left alone (a concurrent run may share it). The
|
|
402
|
+
// failed-code check is gated on the agent never having started (marker unseen),
|
|
403
|
+
// so an agent that legitimately exits with the same code keeps its --keep-box box.
|
|
404
|
+
const bootstrapFailed = exitCode === LEASE_BOOTSTRAP_FAILED_CODE && !sawAgentMarker;
|
|
405
|
+
// Normal --lease establishes or reuses the warm pool, so a healthy box outlives
|
|
406
|
+
// this run; credentials are still shredded inside the script above. Only --fresh
|
|
407
|
+
// requests the old one-shot lifecycle and tears its new box down.
|
|
408
|
+
const teardownHealthy = !opts.keep && opts.fresh && !reused;
|
|
409
|
+
const teardownBroken = bootstrapFailed && !reused;
|
|
410
|
+
if (teardownHealthy || teardownBroken) {
|
|
356
411
|
opts.onPhase?.({ kind: 'teardown' });
|
|
357
412
|
toreDown = crabboxStop(box.slug, { secretsBundle: opts.secretsBundle });
|
|
358
413
|
}
|
|
@@ -32,7 +32,7 @@ export const LEASE_RUNTIMES = [
|
|
|
32
32
|
* copying any of them between devices — including to an ephemeral leased box —
|
|
33
33
|
* because a shared refresh token rotates server-side on the next refresh and
|
|
34
34
|
* invalidates every other copy (`docs/specifications.md` SING-1b,
|
|
35
|
-
* `docs/
|
|
35
|
+
* `docs/secrets.md`). The set is derived from
|
|
36
36
|
* `LEASE_RUNTIMES` so a newly-added runtime can never be silently exempted. This
|
|
37
37
|
* is the single canonical predicate; `--copy-creds` (`hosts/credentials.ts`) and
|
|
38
38
|
* `--lease` (this module's `buildCredentialScript`) both refuse against it.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account-state service lifecycle as a `DaemonService` (RUSH-3193 P2).
|
|
3
|
+
*
|
|
4
|
+
* Wraps `startAccountStateService()` under the `ServiceSupervisor` contract
|
|
5
|
+
* so the supervisor owns start/stop and reports health through the uniform
|
|
6
|
+
* path. The account-state service owns its own internal intervals for usage
|
|
7
|
+
* and auth refreshes; the supervisor just starts and stops it.
|
|
8
|
+
*/
|
|
9
|
+
import { BaseDaemonService, type DaemonContext } from './service.js';
|
|
10
|
+
import type { DaemonServiceId } from '../daemon-services.js';
|
|
11
|
+
export declare class AccountStateDaemonService extends BaseDaemonService {
|
|
12
|
+
readonly id: DaemonServiceId;
|
|
13
|
+
private service;
|
|
14
|
+
protected onStart(ctx: DaemonContext): Promise<void>;
|
|
15
|
+
protected onStop(): Promise<void>;
|
|
16
|
+
}
|