@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
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type SessionWatchEnvelope, type SessionWatchRow, type SessionWatchScopeStatus } from '../session/watch.js';
|
|
2
|
+
import { type AttentionItem } from './attention.js';
|
|
3
|
+
import { type ActivityEvent } from './activity.js';
|
|
4
|
+
export declare const FEED_WATCH_VERSION: 1;
|
|
5
|
+
type Base = {
|
|
6
|
+
v: 1;
|
|
7
|
+
type: string;
|
|
8
|
+
streamId: string;
|
|
9
|
+
sequence: number;
|
|
10
|
+
scope: string;
|
|
11
|
+
};
|
|
12
|
+
export type FeedWatchEnvelope = Base & {
|
|
13
|
+
type: 'reset';
|
|
14
|
+
capturedAt: number;
|
|
15
|
+
agents: SessionWatchRow[];
|
|
16
|
+
attention: AttentionItem[];
|
|
17
|
+
} | Base & {
|
|
18
|
+
type: 'agent.upsert';
|
|
19
|
+
rowKey: string;
|
|
20
|
+
agent: SessionWatchRow;
|
|
21
|
+
} | Base & {
|
|
22
|
+
type: 'attention.upsert';
|
|
23
|
+
rowKey: string;
|
|
24
|
+
attention: AttentionItem;
|
|
25
|
+
} | Base & {
|
|
26
|
+
type: 'attention.remove';
|
|
27
|
+
rowKey: string;
|
|
28
|
+
} | Base & {
|
|
29
|
+
type: 'activity.append';
|
|
30
|
+
event: ActivityEvent;
|
|
31
|
+
} | Base & {
|
|
32
|
+
type: 'scope';
|
|
33
|
+
capturedAt: number;
|
|
34
|
+
status: SessionWatchScopeStatus;
|
|
35
|
+
reason?: string;
|
|
36
|
+
} | Base & {
|
|
37
|
+
type: 'heartbeat';
|
|
38
|
+
capturedAt: number;
|
|
39
|
+
};
|
|
40
|
+
type FeedWatchPayload = FeedWatchEnvelope extends infer Envelope ? Envelope extends FeedWatchEnvelope ? Omit<Envelope, 'v' | 'streamId' | 'sequence'> : never : never;
|
|
41
|
+
export declare class FeedWatchState {
|
|
42
|
+
readonly streamId: string;
|
|
43
|
+
private sequence;
|
|
44
|
+
constructor(streamId?: `${string}-${string}-${string}-${string}-${string}`);
|
|
45
|
+
emit(event: FeedWatchPayload): FeedWatchEnvelope;
|
|
46
|
+
}
|
|
47
|
+
export declare function projectSessionEnvelope(event: SessionWatchEnvelope, state: FeedWatchState): Promise<FeedWatchEnvelope[]>;
|
|
48
|
+
export declare function watchLocalFeed(options: {
|
|
49
|
+
scope: string;
|
|
50
|
+
signal: AbortSignal;
|
|
51
|
+
emit: (event: FeedWatchEnvelope) => void;
|
|
52
|
+
activityPollMs?: number;
|
|
53
|
+
}): Promise<void>;
|
|
54
|
+
export declare function watchFleetFeed(options: {
|
|
55
|
+
signal: AbortSignal;
|
|
56
|
+
emit: (event: FeedWatchEnvelope) => void;
|
|
57
|
+
reconnectMs?: number;
|
|
58
|
+
}): Promise<void>;
|
|
59
|
+
export {};
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { createInterface } from 'node:readline';
|
|
4
|
+
import { loadDevices, isDialableDevice } from '../devices/registry.js';
|
|
5
|
+
import { deviceIdentityArgs, sshTargetFor } from '../devices/connect.js';
|
|
6
|
+
import { machineId, normalizeHost } from '../machine-id.js';
|
|
7
|
+
import { SSH_OPTS, controlOpts, shellQuote } from '../ssh-exec.js';
|
|
8
|
+
import { buildWindowsAgentsCommand, remoteShellFor } from '../hosts/remote-cmd.js';
|
|
9
|
+
import { watchLocalSessions } from '../session/watch.js';
|
|
10
|
+
import { readBlock, readResolution, blockIdForSession } from './feed.js';
|
|
11
|
+
import { reconcileAttention } from './attention.js';
|
|
12
|
+
import { readRecentActivity } from './activity.js';
|
|
13
|
+
import { readPullRequestStatus } from './pr-status.js';
|
|
14
|
+
export const FEED_WATCH_VERSION = 1;
|
|
15
|
+
export class FeedWatchState {
|
|
16
|
+
streamId;
|
|
17
|
+
sequence = 0;
|
|
18
|
+
constructor(streamId = randomUUID()) { this.streamId = streamId; }
|
|
19
|
+
emit(event) {
|
|
20
|
+
return { v: 1, streamId: this.streamId, sequence: ++this.sequence, ...event };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function attentionFor(agent) {
|
|
24
|
+
if (!agent.sessionId)
|
|
25
|
+
return undefined;
|
|
26
|
+
const blockId = blockIdForSession(agent.sessionId);
|
|
27
|
+
// ActiveSession.host names the terminal app; the feed contract's host is the
|
|
28
|
+
// device scope. Normalize only the reconciler input so lifecycle/PR keys are
|
|
29
|
+
// routable across the fleet while the projected agent row stays compatible.
|
|
30
|
+
const session = { ...agent, host: agent.sourceDevice, viewingIn: undefined };
|
|
31
|
+
return reconcileAttention({
|
|
32
|
+
block: readBlock(blockId), session,
|
|
33
|
+
resolution: readResolution(blockId),
|
|
34
|
+
pullRequest: await readPullRequestStatus(session), nowMs: Date.now(),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
export async function projectSessionEnvelope(event, state) {
|
|
38
|
+
if (event.type === 'reset') {
|
|
39
|
+
const attention = (await Promise.all(event.rows.map(attentionFor))).filter((item) => item !== undefined);
|
|
40
|
+
return [state.emit({ type: 'reset', capturedAt: event.capturedAt, scope: event.scope, agents: event.rows, attention })];
|
|
41
|
+
}
|
|
42
|
+
if (event.type === 'upsert') {
|
|
43
|
+
const attention = await attentionFor(event.row);
|
|
44
|
+
return [
|
|
45
|
+
state.emit({ type: 'agent.upsert', scope: event.scope, rowKey: event.rowKey, agent: event.row }),
|
|
46
|
+
attention
|
|
47
|
+
? state.emit({ type: 'attention.upsert', scope: event.scope, rowKey: event.rowKey, attention })
|
|
48
|
+
: state.emit({ type: 'attention.remove', scope: event.scope, rowKey: event.rowKey }),
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
if (event.type === 'remove')
|
|
52
|
+
return [state.emit({ type: 'attention.remove', scope: event.scope, rowKey: event.rowKey })];
|
|
53
|
+
if (event.type === 'scope')
|
|
54
|
+
return [state.emit({ type: 'scope', capturedAt: event.capturedAt, scope: event.scope, status: event.status, ...(event.reason ? { reason: event.reason } : {}) })];
|
|
55
|
+
return [state.emit({ type: 'heartbeat', capturedAt: event.capturedAt, scope: event.scope })];
|
|
56
|
+
}
|
|
57
|
+
export async function watchLocalFeed(options) {
|
|
58
|
+
const state = new FeedWatchState();
|
|
59
|
+
let activityCursor = Date.now();
|
|
60
|
+
const agents = new Map();
|
|
61
|
+
const attention = new Map();
|
|
62
|
+
let pending = Promise.resolve();
|
|
63
|
+
const reconcileRows = async () => {
|
|
64
|
+
for (const [rowKey, agent] of agents) {
|
|
65
|
+
const item = await attentionFor(agent);
|
|
66
|
+
const next = item ? JSON.stringify(item) : '';
|
|
67
|
+
if (attention.get(rowKey) === next)
|
|
68
|
+
continue;
|
|
69
|
+
attention.set(rowKey, next);
|
|
70
|
+
options.emit(item
|
|
71
|
+
? state.emit({ type: 'attention.upsert', scope: options.scope, rowKey, attention: item })
|
|
72
|
+
: state.emit({ type: 'attention.remove', scope: options.scope, rowKey }));
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const activityTimer = setInterval(() => {
|
|
76
|
+
pending = pending.then(async () => {
|
|
77
|
+
const events = readRecentActivity({ sinceMs: activityCursor + 1 }).reverse();
|
|
78
|
+
for (const event of events) {
|
|
79
|
+
activityCursor = Math.max(activityCursor, Date.parse(event.ts));
|
|
80
|
+
options.emit(state.emit({ type: 'activity.append', scope: options.scope, event }));
|
|
81
|
+
}
|
|
82
|
+
await reconcileRows();
|
|
83
|
+
});
|
|
84
|
+
}, options.activityPollMs ?? 500);
|
|
85
|
+
const stopActivity = () => clearInterval(activityTimer);
|
|
86
|
+
options.signal.addEventListener('abort', stopActivity, { once: true });
|
|
87
|
+
try {
|
|
88
|
+
await watchLocalSessions({ scope: options.scope, signal: options.signal, emit: (event) => {
|
|
89
|
+
if (event.type === 'reset') {
|
|
90
|
+
agents.clear();
|
|
91
|
+
for (const row of event.rows)
|
|
92
|
+
agents.set(row.rowKey, row);
|
|
93
|
+
}
|
|
94
|
+
else if (event.type === 'upsert')
|
|
95
|
+
agents.set(event.rowKey, event.row);
|
|
96
|
+
else if (event.type === 'remove') {
|
|
97
|
+
agents.delete(event.rowKey);
|
|
98
|
+
attention.delete(event.rowKey);
|
|
99
|
+
}
|
|
100
|
+
pending = pending.then(() => projectSessionEnvelope(event, state)).then((events) => {
|
|
101
|
+
for (const projected of events) {
|
|
102
|
+
if (projected.type === 'reset') {
|
|
103
|
+
attention.clear();
|
|
104
|
+
for (const row of projected.agents)
|
|
105
|
+
attention.set(row.rowKey, '');
|
|
106
|
+
for (const item of projected.attention) {
|
|
107
|
+
const row = projected.agents.find((agent) => agent.sessionId === item.sessionId);
|
|
108
|
+
if (row)
|
|
109
|
+
attention.set(row.rowKey, JSON.stringify(item));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else if (projected.type === 'attention.upsert')
|
|
113
|
+
attention.set(projected.rowKey, JSON.stringify(projected.attention));
|
|
114
|
+
else if (projected.type === 'attention.remove')
|
|
115
|
+
attention.set(projected.rowKey, '');
|
|
116
|
+
options.emit(projected);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
} });
|
|
120
|
+
await pending;
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
stopActivity();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function remoteFeedWatchCommand(os) {
|
|
127
|
+
const args = ['feed', 'watch', '--json', '--local'];
|
|
128
|
+
return remoteShellFor(os) === 'powershell'
|
|
129
|
+
? buildWindowsAgentsCommand({ args })
|
|
130
|
+
: `bash -lc ${shellQuote(`agents ${args.map(shellQuote).join(' ')}`)}`;
|
|
131
|
+
}
|
|
132
|
+
export async function watchFleetFeed(options) {
|
|
133
|
+
const coordinator = new FeedWatchState();
|
|
134
|
+
const forward = (event) => {
|
|
135
|
+
const { v: _v, streamId: peerStreamId, sequence: peerSequence, ...payload } = event;
|
|
136
|
+
options.emit(coordinator.emit({ ...payload, peerStreamId, peerSequence }));
|
|
137
|
+
};
|
|
138
|
+
const local = watchLocalFeed({ scope: machineId(), signal: options.signal, emit: forward });
|
|
139
|
+
let devices;
|
|
140
|
+
try {
|
|
141
|
+
devices = await loadDevices();
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
await local;
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const self = machineId();
|
|
148
|
+
const peers = Object.values(devices).filter((device) => isDialableDevice(device) && normalizeHost(device.name) !== self && ['windows', 'linux', 'macos'].includes(device.platform));
|
|
149
|
+
const tasks = peers.map(async (device) => {
|
|
150
|
+
const scope = normalizeHost(device.name);
|
|
151
|
+
while (!options.signal.aborted) {
|
|
152
|
+
let target;
|
|
153
|
+
try {
|
|
154
|
+
target = sshTargetFor(device);
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
options.emit(coordinator.emit({ type: 'scope', capturedAt: Date.now(), scope, status: 'unavailable', reason: String(error) }));
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
const child = spawn('ssh', [...SSH_OPTS, ...controlOpts(), ...deviceIdentityArgs(device), target, remoteFeedWatchCommand(device.platform)], { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
161
|
+
const stop = () => child.kill('SIGTERM');
|
|
162
|
+
options.signal.addEventListener('abort', stop, { once: true });
|
|
163
|
+
const reader = createInterface({ input: child.stdout });
|
|
164
|
+
reader.on('line', (line) => { try {
|
|
165
|
+
const event = JSON.parse(line);
|
|
166
|
+
if (event.v === 1)
|
|
167
|
+
forward(event);
|
|
168
|
+
}
|
|
169
|
+
catch { /* protocol only */ } });
|
|
170
|
+
const code = await new Promise((resolve) => { child.once('error', () => resolve(null)); child.once('close', resolve); });
|
|
171
|
+
reader.close();
|
|
172
|
+
options.signal.removeEventListener('abort', stop);
|
|
173
|
+
if (options.signal.aborted)
|
|
174
|
+
break;
|
|
175
|
+
options.emit(coordinator.emit({ type: 'scope', capturedAt: Date.now(), scope, status: 'unavailable', reason: code == null ? 'ssh failed' : `ssh exited ${code}` }));
|
|
176
|
+
await new Promise((resolve) => { const timer = setTimeout(resolve, options.reconnectMs ?? 2_000); options.signal.addEventListener('abort', () => { clearTimeout(timer); resolve(); }, { once: true }); });
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
await Promise.all([local, ...tasks]);
|
|
180
|
+
}
|
|
@@ -58,6 +58,12 @@ export function captureFleet(prev, inputs) {
|
|
|
58
58
|
if (prev?.discovery && Object.keys(prev.discovery).length > 0) {
|
|
59
59
|
manifest.discovery = { ...prev.discovery };
|
|
60
60
|
}
|
|
61
|
+
// Dismissals are operator state, not live state — a capture must never wipe
|
|
62
|
+
// them (fleet.ignored syncs; losing it re-suggests every dismissed node
|
|
63
|
+
// fleet-wide). Carry forward verbatim, same contract as `discovery`.
|
|
64
|
+
if (prev?.ignored && prev.ignored.length > 0) {
|
|
65
|
+
manifest.ignored = prev.ignored.map((e) => ({ ...e }));
|
|
66
|
+
}
|
|
61
67
|
const bundles = inputs.secretsBundles ?? prev?.secrets?.bundles;
|
|
62
68
|
if (bundles && bundles.length > 0)
|
|
63
69
|
manifest.secrets = { bundles: [...bundles].sort() };
|
|
@@ -33,7 +33,7 @@ import { planFleetTargets } from '../devices/fleet.js';
|
|
|
33
33
|
import { assertValidSshTarget, shellQuote, sshExecAsync } from '../ssh-exec.js';
|
|
34
34
|
import { machineId } from '../session/sync/config.js';
|
|
35
35
|
import { ptyRequest } from '../pty-client.js';
|
|
36
|
-
import {
|
|
36
|
+
import { showUrl } from '../open-url.js';
|
|
37
37
|
import { readAuthHealthCache, } from '../auth-health.js';
|
|
38
38
|
import { FLEET_LOGIN_FLOWS, KEYCHAIN_BOUND_ON_MAC, } from './auth-sync.js';
|
|
39
39
|
/**
|
|
@@ -500,8 +500,13 @@ export async function runFleetLogin(opts = {}) {
|
|
|
500
500
|
if (!opts.json) {
|
|
501
501
|
console.log(`Dashboard: ${dashUrl}`);
|
|
502
502
|
}
|
|
503
|
-
if (opts.open !== false)
|
|
504
|
-
|
|
503
|
+
if (opts.open !== false) {
|
|
504
|
+
const shown = await showUrl(dashUrl);
|
|
505
|
+
if (shown.via === 'none') {
|
|
506
|
+
console.error('Could not open a browser — open this yourself:');
|
|
507
|
+
console.error(` ${dashUrl}`);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
505
510
|
const driver = opts.driver ?? defaultPtyDriver();
|
|
506
511
|
const remotable = pending.filter((p) => p.remotable);
|
|
507
512
|
const driveOne = async (p) => {
|
|
@@ -80,6 +80,23 @@ export interface FleetManifest {
|
|
|
80
80
|
* machine's local device registry and is never committed.
|
|
81
81
|
*/
|
|
82
82
|
discovery?: Record<string, 'approved' | 'ignored'>;
|
|
83
|
+
/**
|
|
84
|
+
* Tailnet node names the user dismissed from auto-discovery, with who
|
|
85
|
+
* dismissed each and when. Lives here rather than in a per-device doc because
|
|
86
|
+
* a dismissed node is deliberately NOT a device — it never enters the
|
|
87
|
+
* registry, so it has no per-device folder. Syncs fleet-wide with the rest of
|
|
88
|
+
* `agents.yaml`, so a dismissal on one box stops the suggestion on every box.
|
|
89
|
+
*/
|
|
90
|
+
ignored?: IgnoredDeviceEntry[];
|
|
91
|
+
}
|
|
92
|
+
/** One dismissal record in {@link FleetManifest.ignored}. */
|
|
93
|
+
export interface IgnoredDeviceEntry {
|
|
94
|
+
/** Tailscale node name the user dismissed. */
|
|
95
|
+
name: string;
|
|
96
|
+
/** ISO-8601 timestamp of the dismissal. */
|
|
97
|
+
ignoredAt: string;
|
|
98
|
+
/** machineId() of the box the dismissal was made on. */
|
|
99
|
+
ignoredOn: string;
|
|
83
100
|
}
|
|
84
101
|
/**
|
|
85
102
|
* A device's desired state after merging defaults with its override and
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
*/
|
|
29
29
|
import type { AgentId, Mode } from '../types.js';
|
|
30
30
|
import type { JobConfig } from '../scheduling/routines.js';
|
|
31
|
+
import type { ConfiguredDeviceRole } from '../device-config.js';
|
|
31
32
|
/**
|
|
32
33
|
* Context for the exec-time config-env pin (mapping A, exec.ts side). The caller
|
|
33
34
|
* resolves the interactive/version facts once; the adapter only expresses the
|
|
@@ -42,6 +43,16 @@ export interface ExecConfigEnvCtx {
|
|
|
42
43
|
versionHome: string | null;
|
|
43
44
|
/** resolveInteractive(options) — computed once by the caller. */
|
|
44
45
|
interactive: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* The role marked on THIS machine (worker | personal | undefined), resolved
|
|
48
|
+
* once by the caller from selfConfiguredDeviceRole(). A `personal` device is
|
|
49
|
+
* the user's own interactive box: it holds a real per-version login and the
|
|
50
|
+
* credential decision MUST defer to it for EVERY run — interactive OR headless
|
|
51
|
+
* — never the worker-only setup-token (RUSH-2395). Injected as a plain value
|
|
52
|
+
* (not imported) to keep the adapter import-leaf. Absent/undefined is treated
|
|
53
|
+
* as non-personal (worker-equivalent).
|
|
54
|
+
*/
|
|
55
|
+
deviceRole?: ConfiguredDeviceRole;
|
|
45
56
|
/**
|
|
46
57
|
* claude-account-token's resolveClaudeSetupToken, injected. The adapters MUST
|
|
47
58
|
* stay import-leaf: claude-account-token pulls in the secrets stack, which
|
|
@@ -26,35 +26,49 @@ export const claudeAdapter = {
|
|
|
26
26
|
}
|
|
27
27
|
// The `auth` bundle's setup-token exists so a run with NO human present
|
|
28
28
|
// authenticates without the Touch-ID-gated login item — usage probes,
|
|
29
|
-
// routines, dispatched runs (claude-account-token.ts).
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
// `
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
29
|
+
// routines, dispatched runs (claude-account-token.ts). It is a WORKER
|
|
30
|
+
// credential. Two kinds of run defer to the per-version login instead:
|
|
31
|
+
//
|
|
32
|
+
// 1. An interactive run: a human is at the TTY, and their per-version login
|
|
33
|
+
// is the credential they established and expect. Overriding it made
|
|
34
|
+
// `/status` report `Auth token: CLAUDE_CODE_OAUTH_TOKEN` on a personal
|
|
35
|
+
// machine and took every hand-driven session off that login.
|
|
36
|
+
// 2. ANY run on a `personal` device — the user's own interactive box (zion),
|
|
37
|
+
// marked `config.role: personal`. That box holds a real per-version login
|
|
38
|
+
// and is the single origin of it, so every run there — interactive TUI OR
|
|
39
|
+
// a headless one-shot like `agents run claude "fix the bug"` — MUST use
|
|
40
|
+
// that login, not the setup-token. Gating on run mode ALONE
|
|
41
|
+
// (resolveInteractive = "this opens a TUI", NOT "a human is present")
|
|
42
|
+
// sent a headless run on the laptop onto the setup-token and hijacked the
|
|
43
|
+
// login. Keying the credential on DEVICE ROLE is the fix — RUSH-2395.
|
|
44
|
+
//
|
|
45
|
+
// macOS cannot cheaply confirm a home's login first (probing the Keychain
|
|
46
|
+
// raises an authorization sheet per installed version on the `agents run` hot
|
|
47
|
+
// path — agents.ts `isClaudeCredentialFileBlank`), so this path defers to
|
|
48
|
+
// Claude Code, which reads its own ACL-trusted login item without a prompt and
|
|
49
|
+
// asks a present human to log in only if the login is missing.
|
|
50
|
+
const personalDevice = ctx.deviceRole === 'personal';
|
|
51
|
+
if (ctx.interactive || personalDevice) {
|
|
52
|
+
// Drop an INHERITED copy of OUR OWN setup-token: a launch from inside a
|
|
53
|
+
// headless agent's shell inherits that agent's injected value via
|
|
54
|
+
// sanitizeProcessEnv(process.env) and would keep authenticating as it,
|
|
55
|
+
// overriding the login this branch is protecting. Matched by VALUE, so a
|
|
56
|
+
// token the user exported deliberately is a different string and is left
|
|
57
|
+
// alone (#2383). This is NARROWER than the worker path below, which
|
|
58
|
+
// overwrites-or-deletes unconditionally and never inspects the inherited
|
|
59
|
+
// value — a DIFFERENT account's inherited setup-token passing through this
|
|
60
|
+
// equality check is the adjacent hole RUSH-2360 leaves as follow-up (it does
|
|
61
|
+
// not silently run on a *shared, rotating* token, which is what caused the
|
|
62
|
+
// RUSH-1822 logout storm).
|
|
49
63
|
if (setupToken && result.CLAUDE_CODE_OAUTH_TOKEN === setupToken) {
|
|
50
64
|
delete result.CLAUDE_CODE_OAUTH_TOKEN;
|
|
51
65
|
}
|
|
52
66
|
}
|
|
53
67
|
else {
|
|
54
|
-
//
|
|
55
|
-
// path (`runner.ts
|
|
56
|
-
// setup-token when one resolves — it replaces any ambient shared
|
|
57
|
-
// inherited from the launcher. When NONE resolves, STRIP the ambient
|
|
68
|
+
// Headless run on a NON-personal device (worker, dispatched, provisioned
|
|
69
|
+
// box): mirror the routines path (`runner.ts`) UNCONDITIONALLY. Inject the
|
|
70
|
+
// per-account setup-token when one resolves — it replaces any ambient shared
|
|
71
|
+
// value inherited from the launcher. When NONE resolves, STRIP the ambient
|
|
58
72
|
// CLAUDE_CODE_OAUTH_TOKEN so a run on a provisioned box can never silently
|
|
59
73
|
// authenticate as the shared, rotating token an earlier version of this path
|
|
60
74
|
// let through — the RUSH-1822 fleet-wide-logout hazard, tracked by RUSH-2360.
|
|
@@ -63,21 +63,19 @@ fi
|
|
|
63
63
|
exec "$BINARY"${launchArgs} "$@"`;
|
|
64
64
|
},
|
|
65
65
|
execModeArgs(ctx) {
|
|
66
|
-
const policyMode = ctx.resolvedMode === 'plan' || ctx.resolvedMode === 'skip' ? ctx.resolvedMode : 'edit';
|
|
67
66
|
const writableRoots = [
|
|
68
67
|
...codexEditWritableRoots(ctx.cwd),
|
|
69
68
|
...ctx.addDirs,
|
|
70
69
|
];
|
|
71
|
-
return codexPolicyArgs(
|
|
70
|
+
return codexPolicyArgs(ctx.resolvedMode, writableRoots);
|
|
72
71
|
},
|
|
73
72
|
routineModeArgs(cmd, ctx) {
|
|
74
|
-
const policyMode = ctx.mode === 'plan' || ctx.mode === 'skip' ? ctx.mode : 'edit';
|
|
75
73
|
const routineRoots = (ctx.config.allow?.dirs ?? []).map((dir) => {
|
|
76
74
|
if (dir.startsWith('-')) {
|
|
77
75
|
throw new Error(`allow.dirs entries must not start with '-': ${JSON.stringify(dir)}`);
|
|
78
76
|
}
|
|
79
77
|
return dir.replace(/^~/, os.homedir());
|
|
80
78
|
});
|
|
81
|
-
cmd.push(...codexPolicyArgs(
|
|
79
|
+
cmd.push(...codexPolicyArgs(ctx.mode, [...codexEditWritableRoots(), ...routineRoots]));
|
|
82
80
|
},
|
|
83
81
|
};
|
|
@@ -1,19 +1,16 @@
|
|
|
1
|
-
import * as path from 'path';
|
|
2
1
|
import { stripForeignConfigDir } from '../adapter.js';
|
|
3
2
|
export const cursorAdapter = {
|
|
4
3
|
id: 'cursor',
|
|
5
|
-
// Cursor
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// authenticates from its own token, isolated per run — no global ~/.cursor
|
|
11
|
-
// symlink swap, so concurrent runs on different accounts never clobber one
|
|
12
|
-
// another. cli-config.json (HOME-relative) has no override and stays on the
|
|
13
|
-
// shared home; only the token is per-account, which is what gates the login.
|
|
4
|
+
// Cursor defaults to one machine-global OS-keychain login on macOS, which
|
|
5
|
+
// ignores XDG_CONFIG_HOME. Select the file credential store; Cursor writes
|
|
6
|
+
// that store to HOME-relative ~/.cursor/auth.json, and buildExecEnv already
|
|
7
|
+
// swaps HOME to the selected version home. Existing keychain credentials
|
|
8
|
+
// remain untouched.
|
|
14
9
|
applyExecConfigEnv(result, ctx) {
|
|
15
10
|
if (ctx.versionHome) {
|
|
16
|
-
result.
|
|
11
|
+
result.AGENTS_REAL_HOME ||= result.HOME;
|
|
12
|
+
result.HOME = ctx.versionHome;
|
|
13
|
+
result.AGENT_CLI_CREDENTIAL_STORE = 'file';
|
|
17
14
|
}
|
|
18
15
|
stripForeignConfigDir(result);
|
|
19
16
|
},
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared on-demand download + verification for the macOS native helper `.app`
|
|
3
|
+
* bundles that ship as signed + notarized GitHub release assets on each helper's
|
|
4
|
+
* OWN tag (`ComputerHelper.app`, `MenubarHelper.app`, `Agents CLI.app`) — see
|
|
5
|
+
* `helper-versions.ts` for why they are no longer keyed to the CLI's tag.
|
|
6
|
+
*
|
|
7
|
+
* A `.app` is a directory, so each asset is a zip (`ditto -c -k --keepParent`);
|
|
8
|
+
* a fresh `npm i -g` machine with no bundled copy fetches the asset for the
|
|
9
|
+
* pinned helper version, verifies its sha256 against the published `.sha256`,
|
|
10
|
+
* then verifies the code signature (Developer ID Team + notarization — and, for
|
|
11
|
+
* the menu-bar helper, its designated requirement) before it is ever installed.
|
|
12
|
+
*
|
|
13
|
+
* `computer/download.ts` (ComputerHelper) and `menubar/download-menubar.ts`
|
|
14
|
+
* (MenubarHelper) are thin per-helper wrappers over the primitives here — one
|
|
15
|
+
* download+verify machinery, two specs — so a fix to the verify/download logic
|
|
16
|
+
* lands for both helpers at once.
|
|
17
|
+
*/
|
|
18
|
+
import { type HelperName } from './helper-versions.js';
|
|
19
|
+
/** GitHub repo whose `v<version>` releases carry the helper assets. */
|
|
20
|
+
export declare const HELPER_RELEASE_REPO = "phnx-labs/agents-cli";
|
|
21
|
+
/** Apple Developer ID Team every helper must be signed by ("Developer ID
|
|
22
|
+
* Application: Muqit Nawaz"). Defense in depth on top of `spctl` notarization. */
|
|
23
|
+
export declare const EXPECTED_TEAM_ID = "2HTP252L87";
|
|
24
|
+
/** One helper's identity + verification policy — everything that differs between
|
|
25
|
+
* the computer helper and the menu-bar helper. */
|
|
26
|
+
export interface HelperSpec {
|
|
27
|
+
/**
|
|
28
|
+
* Which helper this is — the key into the independent release train
|
|
29
|
+
* (`helper-versions.ts`). This is what makes the download URL a function of
|
|
30
|
+
* the HELPER's version rather than the CLI's.
|
|
31
|
+
*/
|
|
32
|
+
helper: HelperName;
|
|
33
|
+
/** The zipped `.app` release asset name, e.g. `ComputerHelper.app.zip`. */
|
|
34
|
+
assetName: string;
|
|
35
|
+
/** The bundle directory name once extracted, e.g. `ComputerHelper.app`. */
|
|
36
|
+
appName: string;
|
|
37
|
+
/** Cache dir components under `getCacheDir()`, e.g. `['computer', 'mac-helper']`. */
|
|
38
|
+
cacheSubdir: string[];
|
|
39
|
+
/** Developer ID Team the bundle must be signed by. */
|
|
40
|
+
expectedTeamId: string;
|
|
41
|
+
/**
|
|
42
|
+
* When set, the bundle's designated requirement MUST pin this
|
|
43
|
+
* CFBundleIdentifier in addition to `expectedTeamId`. Used by the menu-bar
|
|
44
|
+
* helper: macOS keys the Accessibility (TCC) grant to this requirement, so a
|
|
45
|
+
* substituted bundle whose DR does not pin (id, team) would silently revoke
|
|
46
|
+
* the user's grant on the next paste. Left undefined for the computer helper,
|
|
47
|
+
* which has no such upgrade-stable grant to protect (matching its behavior
|
|
48
|
+
* before this shared primitive existed).
|
|
49
|
+
*/
|
|
50
|
+
expectedBundleId?: string;
|
|
51
|
+
/** Local-build command named in the "asset missing" error, for a repo checkout. */
|
|
52
|
+
localBuildHint: string;
|
|
53
|
+
}
|
|
54
|
+
/** Cache dir for a downloaded helper, one subdir per release tag. */
|
|
55
|
+
export declare function helperCacheDir(spec: HelperSpec, version: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* Release-asset URLs for one helper build, at that HELPER's own tag.
|
|
58
|
+
*
|
|
59
|
+
* The tag is `<helper>/v<version>` (e.g. `menubar/v1.0.0`), never the CLI's
|
|
60
|
+
* `v<cliVersion>`. Keying these to the CLI tag is what forced every CLI release
|
|
61
|
+
* to re-stage every helper asset, and simultaneously made a helper fix
|
|
62
|
+
* unreachable without a CLI release — see `helper-versions.ts` for the full why.
|
|
63
|
+
*
|
|
64
|
+
* `version` is a HELPER version. Passing a CLI version here yields a tag that
|
|
65
|
+
* does not exist; callers default it from {@link helperFloor}.
|
|
66
|
+
*/
|
|
67
|
+
export declare function helperAssetUrls(spec: HelperSpec, version: string): {
|
|
68
|
+
zip: string;
|
|
69
|
+
sha256: string;
|
|
70
|
+
};
|
|
71
|
+
/** Extract `TeamIdentifier=XXXX` from `codesign -dv --verbose=4` output (which is
|
|
72
|
+
* emitted on stderr). Returns null when absent (ad-hoc / unsigned). */
|
|
73
|
+
export declare function parseTeamId(codesignInfo: string): string | null;
|
|
74
|
+
/** Read the bundle's designated requirement string (`codesign -d --requirements -`).
|
|
75
|
+
* codesign writes it to stdout, but capture both streams so a diagnostic on
|
|
76
|
+
* stderr can never make the pin-check read empty and falsely pass. */
|
|
77
|
+
export declare function readDesignatedRequirement(appPath: string): string;
|
|
78
|
+
/**
|
|
79
|
+
* Pure predicate (no I/O) behind the DR pin: does the requirement string pin
|
|
80
|
+
* BOTH the expected CFBundleIdentifier and the Developer ID Team? Returns null
|
|
81
|
+
* when it does, else an actionable error message naming the missing pin. Kept
|
|
82
|
+
* pure so the truth table is unit-testable without a signed bundle or codesign.
|
|
83
|
+
* Mirrors the release-time gate in `scripts/verify-menubar-helper.sh` (identifier
|
|
84
|
+
* + team substring check against the same requirement string).
|
|
85
|
+
*/
|
|
86
|
+
export declare function checkDesignatedRequirement(req: string, bundleId: string, teamId: string): string | null;
|
|
87
|
+
/**
|
|
88
|
+
* Verify the bundle's designated requirement pins the expected CFBundleIdentifier
|
|
89
|
+
* AND Developer ID Team. macOS re-validates each new version against this stored
|
|
90
|
+
* requirement to keep an Accessibility grant alive across upgrades; a downloaded
|
|
91
|
+
* bundle whose DR drops either pin would silently revoke that grant on the next
|
|
92
|
+
* paste, so refuse it.
|
|
93
|
+
*/
|
|
94
|
+
export declare function verifyDesignatedRequirement(appPath: string, bundleId: string, teamId: string): void;
|
|
95
|
+
/**
|
|
96
|
+
* Verify a helper `.app` bundle is intact, signed by the expected Developer ID
|
|
97
|
+
* Team, (for the menu-bar helper) pinned by its designated requirement, and
|
|
98
|
+
* notarized (Gatekeeper-accepted). Throws with an actionable message on any
|
|
99
|
+
* failure — a downloaded bundle is never trusted without this.
|
|
100
|
+
*/
|
|
101
|
+
export declare function verifyHelperApp(appPath: string, spec: HelperSpec): void;
|
|
102
|
+
/**
|
|
103
|
+
* Download a helper release asset for `version`, verify sha256, extract the
|
|
104
|
+
* `.app`, and verify its signature (+ DR pin when the spec requires one).
|
|
105
|
+
* Returns the path to the extracted bundle. A missing asset is a hard error
|
|
106
|
+
* naming the exact tag — never a silent fallback to another release.
|
|
107
|
+
*/
|
|
108
|
+
export declare function downloadHelperApp(spec: HelperSpec, version: string): Promise<string>;
|