@phnx-labs/agents-cli 1.22.42 → 1.22.44
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 +149 -0
- package/README.md +15 -13
- package/dist/bootstrap.js +33 -13
- package/dist/cli/command-registry.d.ts +0 -8
- package/dist/cli/command-registry.js +1 -25
- package/dist/commands/alias.d.ts +7 -4
- package/dist/commands/alias.js +33 -8
- package/dist/commands/apply.d.ts +7 -10
- package/dist/commands/apply.js +15 -35
- package/dist/commands/artifacts.js +4 -5
- package/dist/commands/audit.d.ts +6 -6
- package/dist/commands/audit.js +12 -12
- package/dist/commands/auth.js +14 -6
- package/dist/commands/beta.d.ts +7 -1
- package/dist/commands/beta.js +16 -9
- package/dist/commands/commands.js +1 -1
- package/dist/commands/computer.js +20 -6
- package/dist/commands/daemon.js +1 -1
- package/dist/commands/doctor.d.ts +4 -2
- package/dist/commands/doctor.js +21 -9
- package/dist/commands/events.d.ts +2 -2
- package/dist/commands/events.js +6 -5
- package/dist/commands/exec.d.ts +1 -1
- package/dist/commands/exec.js +11 -6
- package/dist/commands/factory.d.ts +1 -1
- package/dist/commands/feed.js +1 -30
- package/dist/commands/fleet-capture.d.ts +1 -1
- package/dist/commands/fleet-capture.js +4 -5
- package/dist/commands/harness.js +4 -52
- package/dist/commands/hooks.js +1 -1
- package/dist/commands/insights.d.ts +1 -1
- package/dist/commands/insights.js +3 -2
- package/dist/commands/menubar.js +58 -1
- package/dist/commands/monitors.js +1 -1
- package/dist/commands/org.d.ts +5 -0
- package/dist/commands/org.js +83 -29
- package/dist/commands/output.js +2 -2
- package/dist/commands/profiles.js +2 -2
- package/dist/commands/prune.js +5 -4
- package/dist/commands/routines.js +43 -25
- package/dist/commands/routines.test-fixture.d.ts +86 -0
- package/dist/commands/routines.test-fixture.js +345 -0
- package/dist/commands/sessions-picker.js +124 -122
- package/dist/commands/sessions.test-fixture.d.ts +24 -0
- package/dist/commands/sessions.test-fixture.js +224 -0
- package/dist/commands/setup.js +9 -1
- package/dist/commands/share.d.ts +9 -10
- package/dist/commands/share.js +68 -50
- package/dist/commands/skills.js +1 -1
- package/dist/commands/snapshot.d.ts +1 -1
- package/dist/commands/snapshot.js +4 -4
- package/dist/commands/ssh.js +15 -62
- package/dist/commands/status.d.ts +4 -2
- package/dist/commands/status.js +10 -7
- package/dist/commands/subagents.js +1 -1
- package/dist/commands/sync.d.ts +1 -0
- package/dist/commands/sync.js +29 -2
- package/dist/commands/view.js +11 -2
- package/dist/commands/webhook.js +1 -1
- package/dist/lib/accounting/rotate.d.ts +5 -3
- package/dist/lib/accounting/rotate.js +36 -7
- package/dist/lib/accounting/usage.d.ts +18 -4
- package/dist/lib/accounting/usage.js +138 -27
- package/dist/lib/agent-spec/agents.d.ts +7 -7
- package/dist/lib/agent-spec/agents.js +15 -15
- package/dist/lib/analytics/mix-commands.d.ts +6 -28
- package/dist/lib/analytics/mix-commands.js +14 -40
- package/dist/lib/audit/log.d.ts +2 -2
- package/dist/lib/audit/log.js +2 -2
- package/dist/lib/beta.js +1 -1
- package/dist/lib/browser/domain-skills.d.ts +26 -6
- package/dist/lib/browser/domain-skills.js +81 -43
- package/dist/lib/cloud/rush.d.ts +0 -15
- package/dist/lib/cloud/rush.js +0 -35
- package/dist/lib/crabbox/runtimes.d.ts +6 -2
- package/dist/lib/crabbox/runtimes.js +21 -5
- package/dist/lib/daemon/daemon.js +97 -58
- package/dist/lib/daemon/daemon.test-fixture.d.ts +24 -0
- package/dist/lib/daemon/daemon.test-fixture.js +71 -0
- package/dist/lib/daemon/runner.d.ts +18 -1
- package/dist/lib/daemon/runner.js +105 -11
- package/dist/lib/daemon-ticks.d.ts +38 -1
- package/dist/lib/daemon-ticks.js +52 -5
- package/dist/lib/device-config.js +1 -2
- package/dist/lib/devices/discovery-policy.d.ts +3 -3
- package/dist/lib/devices/discovery-policy.js +3 -3
- package/dist/lib/devices/doctor-findings.js +1 -1
- package/dist/lib/devices/fleet.d.ts +4 -8
- package/dist/lib/devices/fleet.js +3 -15
- package/dist/lib/devices/health-report.js +4 -4
- package/dist/lib/devices/pool.d.ts +0 -6
- package/dist/lib/devices/pool.js +0 -6
- package/dist/lib/devices/registry.d.ts +0 -15
- package/dist/lib/devices/registry.js +0 -9
- package/dist/lib/devices/stats-cache.d.ts +12 -0
- package/dist/lib/devices/stats-cache.js +24 -5
- package/dist/lib/drift-sync.d.ts +1 -1
- package/dist/lib/drift-sync.js +2 -2
- package/dist/lib/drift.d.ts +1 -1
- package/dist/lib/exec.js +13 -3
- package/dist/lib/fleet/capture.d.ts +1 -1
- package/dist/lib/fleet/manifest.js +1 -1
- package/dist/lib/fleet/remote-login.d.ts +2 -2
- package/dist/lib/fleet/remote-login.js +3 -4
- package/dist/lib/format.d.ts +9 -0
- package/dist/lib/format.js +16 -0
- package/dist/lib/github/pr-mergeable.d.ts +49 -0
- package/dist/lib/github/pr-mergeable.js +157 -0
- package/dist/lib/github/pr-verdict.d.ts +67 -0
- package/dist/lib/github/pr-verdict.js +72 -0
- package/dist/lib/harness/adapter.d.ts +6 -5
- package/dist/lib/hosts/dispatch.d.ts +2 -0
- package/dist/lib/hosts/dispatch.js +8 -1
- package/dist/lib/hosts/passthrough.d.ts +10 -0
- package/dist/lib/hosts/passthrough.js +28 -7
- package/dist/lib/hosts/providers/devices.js +1 -13
- package/dist/lib/hosts/ready.d.ts +12 -3
- package/dist/lib/hosts/ready.js +27 -12
- package/dist/lib/hosts/registry.d.ts +4 -5
- package/dist/lib/hosts/registry.js +3 -7
- package/dist/lib/hosts/session-index.js +1 -1
- package/dist/lib/installations/shims.js +1 -1
- package/dist/lib/installations/versions.d.ts +40 -0
- package/dist/lib/installations/versions.js +78 -4
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +1 -5
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +2 -15
- package/dist/lib/menubar/install-menubar.d.ts +109 -0
- package/dist/lib/menubar/install-menubar.js +218 -12
- package/dist/lib/monitors/config.d.ts +2 -2
- package/dist/lib/monitors/config.js +3 -2
- package/dist/lib/origin-machine.d.ts +18 -0
- package/dist/lib/origin-machine.js +34 -0
- package/dist/lib/perf/db.js +1 -1
- package/dist/lib/perf/spool.js +1 -1
- package/dist/lib/prix-account.d.ts +2 -1
- package/dist/lib/prix-account.js +2 -1
- package/dist/lib/profiles.d.ts +7 -0
- package/dist/lib/profiles.js +26 -1
- package/dist/lib/remote-agents-json.js +1 -7
- package/dist/lib/routines-placement.d.ts +1 -1
- package/dist/lib/routines-placement.js +1 -1
- package/dist/lib/sandbox.d.ts +24 -0
- package/dist/lib/sandbox.js +101 -0
- package/dist/lib/scheduling/routines.d.ts +18 -9
- package/dist/lib/scheduling/routines.js +16 -8
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -2
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -13
- package/dist/lib/secrets/agent.js +9 -1
- package/dist/lib/secrets/index.js +1 -1
- package/dist/lib/secrets/list-filter.js +1 -1
- package/dist/lib/service-manifest.d.ts +20 -0
- package/dist/lib/service-manifest.js +34 -0
- package/dist/lib/session/active.d.ts +4 -16
- package/dist/lib/session/active.js +2 -6
- package/dist/lib/session/cloud.js +1 -1
- package/dist/lib/session/db.d.ts +13 -98
- package/dist/lib/session/db.js +5 -12
- package/dist/lib/session/discover.d.ts +5 -81
- package/dist/lib/session/discover.js +5 -5
- package/dist/lib/session/fork.js +1 -1
- package/dist/lib/session/live-metadata.js +1 -1
- package/dist/lib/session/origin-machine.d.ts +1 -18
- package/dist/lib/session/origin-machine.js +4 -34
- package/dist/lib/session/relative-time.d.ts +1 -44
- package/dist/lib/session/relative-time.js +4 -120
- package/dist/lib/session/remote/remote-bundle.d.ts +12 -0
- package/dist/lib/session/remote/remote-bundle.js +61 -0
- package/dist/lib/session/remote/remote-list.d.ts +148 -0
- package/dist/lib/session/remote/remote-list.js +607 -0
- package/dist/lib/session/remote/remote.d.ts +132 -0
- package/dist/lib/session/remote/remote.js +314 -0
- package/dist/lib/session/remote/watch.d.ts +101 -0
- package/dist/lib/session/remote/watch.js +241 -0
- package/dist/lib/session/remote-bundle.d.ts +1 -12
- package/dist/lib/session/remote-bundle.js +3 -61
- package/dist/lib/session/remote-list.d.ts +1 -148
- package/dist/lib/session/remote-list.js +3 -607
- package/dist/lib/session/remote.d.ts +1 -132
- package/dist/lib/session/remote.js +3 -314
- package/dist/lib/session/short-id.d.ts +1 -17
- package/dist/lib/session/short-id.js +4 -20
- package/dist/lib/session/watch.d.ts +1 -101
- package/dist/lib/session/watch.js +3 -242
- package/dist/lib/session/width.d.ts +1 -29
- package/dist/lib/session/width.js +4 -101
- package/dist/lib/share/config.d.ts +12 -2
- package/dist/lib/share/config.js +47 -11
- package/dist/lib/share/delete.js +1 -1
- package/dist/lib/share/publish.js +1 -1
- package/dist/lib/share/worker-template.js +2 -2
- package/dist/lib/smart-launch.d.ts +4 -4
- package/dist/lib/smart-launch.js +8 -18
- package/dist/lib/snapshot.d.ts +2 -2
- package/dist/lib/snapshot.js +1 -1
- package/dist/lib/staleness/index.d.ts +3 -1
- package/dist/lib/staleness/index.js +21 -1
- package/dist/lib/staleness/types.d.ts +11 -0
- package/dist/lib/staleness/writers/commands.js +13 -4
- package/dist/lib/staleness/writers/hooks.js +3 -1
- package/dist/lib/staleness/writers/mcp.d.ts +0 -7
- package/dist/lib/staleness/writers/mcp.js +16 -1
- package/dist/lib/staleness/writers/rules.js +1 -1
- package/dist/lib/staleness/writers/skills.js +3 -1
- package/dist/lib/staleness/writers/subagents.d.ts +0 -9
- package/dist/lib/staleness/writers/subagents.js +18 -1
- package/dist/lib/staleness/writers/types.d.ts +10 -0
- package/dist/lib/startup/command-registry.d.ts +10 -1
- package/dist/lib/startup/command-registry.js +26 -7
- package/dist/lib/startup/root-command.d.ts +18 -1
- package/dist/lib/startup/root-command.js +18 -1
- package/dist/lib/state.js +7 -0
- package/dist/lib/teams/placement-probe.js +28 -13
- package/dist/lib/teams/scheduler.d.ts +3 -1
- package/dist/lib/teams/scheduler.js +1 -1
- package/dist/lib/text/relative-time.d.ts +44 -0
- package/dist/lib/text/relative-time.js +120 -0
- package/dist/lib/text/short-id.d.ts +17 -0
- package/dist/lib/text/short-id.js +20 -0
- package/dist/lib/text/width.d.ts +29 -0
- package/dist/lib/text/width.js +101 -0
- package/dist/lib/triggers/handlers.d.ts +7 -0
- package/dist/lib/triggers/handlers.js +8 -0
- package/dist/lib/triggers/webhook.d.ts +1 -0
- package/dist/lib/triggers/webhook.js +2 -1
- package/dist/lib/types.d.ts +1 -1
- package/dist/lib/view-types.d.ts +7 -0
- package/package.json +2 -3
- package/dist/bin/agents +0 -0
- package/dist/commands/serve.d.ts +0 -10
- package/dist/commands/serve.js +0 -68
- package/dist/commands/tickets.d.ts +0 -2
- package/dist/commands/tickets.js +0 -43
- package/dist/commands/trends.d.ts +0 -10
- package/dist/commands/trends.js +0 -12
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/observe-aliases.d.ts +0 -30
- package/dist/lib/observe-aliases.js +0 -38
- package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/serve/control.d.ts +0 -95
- package/dist/lib/serve/control.js +0 -260
- package/dist/lib/serve/data.d.ts +0 -81
- package/dist/lib/serve/data.js +0 -91
- package/dist/lib/serve/page.d.ts +0 -7
- package/dist/lib/serve/page.js +0 -140
- package/dist/lib/serve/server.d.ts +0 -80
- package/dist/lib/serve/server.js +0 -145
- package/dist/lib/serve/stream.d.ts +0 -43
- package/dist/lib/serve/stream.js +0 -116
- package/dist/lib/serve/token.d.ts +0 -35
- package/dist/lib/serve/token.js +0 -85
- package/dist/lib/tickets/list.d.ts +0 -53
- package/dist/lib/tickets/list.js +0 -153
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List this user's mergeable PRs across registered project repos.
|
|
3
|
+
*
|
|
4
|
+
* The built-in `pr-merge-on-green` monitor used to run `gh pr list --author @me`
|
|
5
|
+
* with no `--repo`. `gh` then infers the repository from the working directory,
|
|
6
|
+
* and the daemon's cwd is not a git repo, so every poll returned empty.
|
|
7
|
+
*
|
|
8
|
+
* This helper:
|
|
9
|
+
* 1. Collects GitHub slugs from `~/.agents/projects/*.yaml` (`repo` / `repos[].slug`).
|
|
10
|
+
* 2. Canonicalizes each slug (`gh repo view` — `phnx-labs/agents-cli` lists
|
|
11
|
+
* nothing; the live name is `phnx-labs/agi-cli`).
|
|
12
|
+
* 3. Runs `gh pr list --repo <slug> --author @me` (cwd-independent).
|
|
13
|
+
* 4. Keeps CI-green PRs with a merge-guard verdict (formal APPROVED review
|
|
14
|
+
* OR an APPROVE comment on THIS PR).
|
|
15
|
+
*
|
|
16
|
+
* Stdout is a single line of `owner/repo#n` refs (or empty). Empty is a silent
|
|
17
|
+
* observation under `condition.mode: every` — do not write to stderr on the
|
|
18
|
+
* no-match path, or command.ts will treat stderr as the observation and fire.
|
|
19
|
+
*/
|
|
20
|
+
import { type ProjectDef } from '../projects.js';
|
|
21
|
+
import { type MergeablePrInput, type StatusCheck } from './pr-verdict.js';
|
|
22
|
+
export type GhExec = (args: string[]) => Promise<string>;
|
|
23
|
+
/** Default runner: `gh` with a 30s timeout. Non-zero exit throws. */
|
|
24
|
+
export declare function ghExec(args: string[]): Promise<string>;
|
|
25
|
+
/** Unique GitHub slugs declared on project definitions. */
|
|
26
|
+
export declare function projectRepoSlugs(defs: readonly ProjectDef[]): string[];
|
|
27
|
+
/**
|
|
28
|
+
* Resolve a slug to GitHub's current `nameWithOwner`. A renamed repo
|
|
29
|
+
* (`phnx-labs/agents-cli` → `phnx-labs/agi-cli`) lists zero PRs under the old
|
|
30
|
+
* name; `gh repo view` returns the live one. On error, keep the input slug.
|
|
31
|
+
*/
|
|
32
|
+
export declare function canonicalizeRepo(slug: string, gh: GhExec): Promise<string>;
|
|
33
|
+
interface ListedPr {
|
|
34
|
+
number: number;
|
|
35
|
+
reviewDecision?: string | null;
|
|
36
|
+
statusCheckRollup?: StatusCheck[] | null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Given PRs already listed for one repo, attach verdict payloads only for the
|
|
40
|
+
* CI-green ones that lack a formal APPROVED reviewDecision, then select.
|
|
41
|
+
*/
|
|
42
|
+
export declare function selectListedMergeable(repo: string, listed: readonly ListedPr[], gh: GhExec): Promise<MergeablePrInput[]>;
|
|
43
|
+
/** List mergeable `owner/repo#n` refs, space-separated. Empty string if none. */
|
|
44
|
+
export declare function listMergeableRefs(opts?: {
|
|
45
|
+
gh?: GhExec;
|
|
46
|
+
defs?: ProjectDef[];
|
|
47
|
+
repos?: string[];
|
|
48
|
+
}): Promise<string>;
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List this user's mergeable PRs across registered project repos.
|
|
3
|
+
*
|
|
4
|
+
* The built-in `pr-merge-on-green` monitor used to run `gh pr list --author @me`
|
|
5
|
+
* with no `--repo`. `gh` then infers the repository from the working directory,
|
|
6
|
+
* and the daemon's cwd is not a git repo, so every poll returned empty.
|
|
7
|
+
*
|
|
8
|
+
* This helper:
|
|
9
|
+
* 1. Collects GitHub slugs from `~/.agents/projects/*.yaml` (`repo` / `repos[].slug`).
|
|
10
|
+
* 2. Canonicalizes each slug (`gh repo view` — `phnx-labs/agents-cli` lists
|
|
11
|
+
* nothing; the live name is `phnx-labs/agi-cli`).
|
|
12
|
+
* 3. Runs `gh pr list --repo <slug> --author @me` (cwd-independent).
|
|
13
|
+
* 4. Keeps CI-green PRs with a merge-guard verdict (formal APPROVED review
|
|
14
|
+
* OR an APPROVE comment on THIS PR).
|
|
15
|
+
*
|
|
16
|
+
* Stdout is a single line of `owner/repo#n` refs (or empty). Empty is a silent
|
|
17
|
+
* observation under `condition.mode: every` — do not write to stderr on the
|
|
18
|
+
* no-match path, or command.ts will treat stderr as the observation and fire.
|
|
19
|
+
*/
|
|
20
|
+
import { execFile } from 'child_process';
|
|
21
|
+
import { promisify } from 'util';
|
|
22
|
+
import { listProjectDefs } from '../projects.js';
|
|
23
|
+
import { formatMergeableRef, hasApproveVerdict, isCiGreen, } from './pr-verdict.js';
|
|
24
|
+
const execFileAsync = promisify(execFile);
|
|
25
|
+
/**
|
|
26
|
+
* `gh --json` is not color-safe when this fleet exports FORCE_COLOR /
|
|
27
|
+
* CLICOLOR_FORCE: gh paints the payload and JSON.parse / jq die on the ANSI
|
|
28
|
+
* prefix (live miss on the sibling poll script). Strip the force vars and
|
|
29
|
+
* pin GH_NO_COLOR for every gh spawn.
|
|
30
|
+
*/
|
|
31
|
+
function ghEnv() {
|
|
32
|
+
const env = { ...process.env };
|
|
33
|
+
env.CLICOLOR = '0';
|
|
34
|
+
env.NO_COLOR = '1';
|
|
35
|
+
env.GH_NO_COLOR = '1';
|
|
36
|
+
env.GH_PAGER = 'cat';
|
|
37
|
+
delete env.CLICOLOR_FORCE;
|
|
38
|
+
delete env.FORCE_COLOR;
|
|
39
|
+
delete env.GH_FORCE_TTY;
|
|
40
|
+
return env;
|
|
41
|
+
}
|
|
42
|
+
/** Default runner: `gh` with a 30s timeout. Non-zero exit throws. */
|
|
43
|
+
export async function ghExec(args) {
|
|
44
|
+
const { stdout } = await execFileAsync('gh', args, {
|
|
45
|
+
timeout: 30_000,
|
|
46
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
47
|
+
encoding: 'utf-8',
|
|
48
|
+
env: ghEnv(),
|
|
49
|
+
});
|
|
50
|
+
return String(stdout ?? '');
|
|
51
|
+
}
|
|
52
|
+
/** Unique GitHub slugs declared on project definitions. */
|
|
53
|
+
export function projectRepoSlugs(defs) {
|
|
54
|
+
const slugs = new Set();
|
|
55
|
+
for (const d of defs) {
|
|
56
|
+
if (d.repo)
|
|
57
|
+
slugs.add(d.repo);
|
|
58
|
+
for (const r of d.repos ?? []) {
|
|
59
|
+
if (r.slug)
|
|
60
|
+
slugs.add(r.slug);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return [...slugs].sort();
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Resolve a slug to GitHub's current `nameWithOwner`. A renamed repo
|
|
67
|
+
* (`phnx-labs/agents-cli` → `phnx-labs/agi-cli`) lists zero PRs under the old
|
|
68
|
+
* name; `gh repo view` returns the live one. On error, keep the input slug.
|
|
69
|
+
*/
|
|
70
|
+
export async function canonicalizeRepo(slug, gh) {
|
|
71
|
+
try {
|
|
72
|
+
const out = (await gh([
|
|
73
|
+
'repo', 'view', slug, '--json', 'nameWithOwner', '--jq', '.nameWithOwner',
|
|
74
|
+
])).trim();
|
|
75
|
+
return out || slug;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return slug;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function fetchVerdict(repo, number, gh) {
|
|
82
|
+
const [reviewsRaw, commentsRaw] = await Promise.all([
|
|
83
|
+
gh(['api', `repos/${repo}/pulls/${number}/reviews`, '--cache', '60s']),
|
|
84
|
+
gh(['api', `repos/${repo}/issues/${number}/comments`, '--cache', '60s']),
|
|
85
|
+
]);
|
|
86
|
+
const reviews = JSON.parse(reviewsRaw);
|
|
87
|
+
const comments = JSON.parse(commentsRaw);
|
|
88
|
+
return {
|
|
89
|
+
reviews: Array.isArray(reviews) ? reviews : [],
|
|
90
|
+
comments: Array.isArray(comments) ? comments : [],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Given PRs already listed for one repo, attach verdict payloads only for the
|
|
95
|
+
* CI-green ones that lack a formal APPROVED reviewDecision, then select.
|
|
96
|
+
*/
|
|
97
|
+
export async function selectListedMergeable(repo, listed, gh) {
|
|
98
|
+
const candidates = [];
|
|
99
|
+
for (const row of listed) {
|
|
100
|
+
if (!isCiGreen(row.statusCheckRollup))
|
|
101
|
+
continue;
|
|
102
|
+
const base = {
|
|
103
|
+
number: row.number,
|
|
104
|
+
repo,
|
|
105
|
+
reviewDecision: row.reviewDecision ?? '',
|
|
106
|
+
statusCheckRollup: row.statusCheckRollup,
|
|
107
|
+
reviews: [],
|
|
108
|
+
comments: [],
|
|
109
|
+
};
|
|
110
|
+
if (base.reviewDecision === 'APPROVED') {
|
|
111
|
+
candidates.push(base);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
const extra = await fetchVerdict(repo, row.number, gh);
|
|
116
|
+
candidates.push({ ...base, ...extra });
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// A single PR's reviews/comments probe failed — skip it, keep the rest.
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return candidates.filter((pr) => pr.reviewDecision === 'APPROVED' || hasApproveVerdict(pr.reviews, pr.comments));
|
|
124
|
+
}
|
|
125
|
+
/** List mergeable `owner/repo#n` refs, space-separated. Empty string if none. */
|
|
126
|
+
export async function listMergeableRefs(opts) {
|
|
127
|
+
const gh = opts?.gh ?? ghExec;
|
|
128
|
+
const slugs = opts?.repos ?? projectRepoSlugs(opts?.defs ?? listProjectDefs());
|
|
129
|
+
const seen = new Set();
|
|
130
|
+
const refs = [];
|
|
131
|
+
for (const raw of slugs) {
|
|
132
|
+
const repo = await canonicalizeRepo(raw, gh);
|
|
133
|
+
if (seen.has(repo))
|
|
134
|
+
continue;
|
|
135
|
+
seen.add(repo);
|
|
136
|
+
let listed = [];
|
|
137
|
+
try {
|
|
138
|
+
const rawList = await gh([
|
|
139
|
+
'pr', 'list',
|
|
140
|
+
'--repo', repo,
|
|
141
|
+
'--author', '@me',
|
|
142
|
+
'--state', 'open',
|
|
143
|
+
'--limit', '50',
|
|
144
|
+
'--json', 'number,reviewDecision,statusCheckRollup',
|
|
145
|
+
]);
|
|
146
|
+
const parsed = JSON.parse(rawList);
|
|
147
|
+
listed = Array.isArray(parsed) ? parsed : [];
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const mergeable = await selectListedMergeable(repo, listed, gh);
|
|
153
|
+
for (const pr of mergeable)
|
|
154
|
+
refs.push(formatMergeableRef(pr));
|
|
155
|
+
}
|
|
156
|
+
return refs.join(' ');
|
|
157
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-author PR approval + CI-green checks for merge-on-green.
|
|
3
|
+
*
|
|
4
|
+
* Verdict rules are copied from merge-guard.sh (the PreToolUse hook in
|
|
5
|
+
* phnx-labs/.agents-system `rules/subrules/gh-merge-guard/merge-guard.sh`).
|
|
6
|
+
* That hook accepts EITHER a formal GitHub `APPROVED` review OR an APPROVE
|
|
7
|
+
* verdict comment on THIS PR, and rejects a comment that only cites another
|
|
8
|
+
* PR ("carried from #N" / "APPROVE on #N") — the #2736 laundering pattern.
|
|
9
|
+
*
|
|
10
|
+
* Do not invent a third rule. If the hook's regexes change, change these in
|
|
11
|
+
* the same delivery.
|
|
12
|
+
*/
|
|
13
|
+
/** A GitHub pull-request review, shaped after `GET .../pulls/{n}/reviews`. */
|
|
14
|
+
export interface PrReview {
|
|
15
|
+
state?: string;
|
|
16
|
+
}
|
|
17
|
+
/** An issue comment on a PR, shaped after `GET .../issues/{n}/comments`. */
|
|
18
|
+
export interface PrComment {
|
|
19
|
+
body?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* One status-check rollup item from `gh pr list --json statusCheckRollup`.
|
|
23
|
+
* `gh` exposes conclusion/state/status under those names; empty fields are
|
|
24
|
+
* pending/in-progress.
|
|
25
|
+
*/
|
|
26
|
+
export interface StatusCheck {
|
|
27
|
+
conclusion?: string;
|
|
28
|
+
state?: string;
|
|
29
|
+
status?: string;
|
|
30
|
+
}
|
|
31
|
+
/** One open PR with the fields the mergeable selector reads. */
|
|
32
|
+
export interface MergeablePrInput {
|
|
33
|
+
number: number;
|
|
34
|
+
/** `owner/repo`. Required so a daemon with no git cwd can still merge. */
|
|
35
|
+
repo: string;
|
|
36
|
+
reviewDecision?: string | null;
|
|
37
|
+
statusCheckRollup?: StatusCheck[] | null;
|
|
38
|
+
reviews?: PrReview[] | null;
|
|
39
|
+
comments?: PrComment[] | null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* True when a non-author verdict exists ON this PR.
|
|
43
|
+
*
|
|
44
|
+
* Mirrors merge-guard.sh: a GitHub review with `state == "APPROVED"`, or an
|
|
45
|
+
* issue comment whose body contains `\bAPPROVE\b` and is not a carried-from
|
|
46
|
+
* citation (`\bcarried\s+(?:over\s+)?from\b` or `\bAPPROVE\s+(?:on|from)\s+#\d+`).
|
|
47
|
+
*/
|
|
48
|
+
export declare function hasApproveVerdict(reviews: readonly PrReview[] | null | undefined, comments: readonly PrComment[] | null | undefined): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* True when every check is a terminal success-class conclusion.
|
|
51
|
+
*
|
|
52
|
+
* Matches the built-in monitor's original jq: SUCCESS / NEUTRAL / SKIPPED
|
|
53
|
+
* (ascii-uppercased conclusion, else state, else status). An empty rollup
|
|
54
|
+
* is green — jq `all([])` is true, and a PR with no checks is mergeable
|
|
55
|
+
* from this filter's point of view.
|
|
56
|
+
*/
|
|
57
|
+
export declare function isCiGreen(rollup: readonly StatusCheck[] | null | undefined): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Keep PRs that are CI-green AND non-author-approved.
|
|
60
|
+
*
|
|
61
|
+
* `reviewDecision == "APPROVED"` is sufficient (a formal review). When that
|
|
62
|
+
* field is empty — the fleet convention: reviewers post an APPROVE *comment*
|
|
63
|
+
* rather than a GitHub review — fall through to {@link hasApproveVerdict}.
|
|
64
|
+
*/
|
|
65
|
+
export declare function selectMergeablePrs(prs: readonly MergeablePrInput[]): MergeablePrInput[];
|
|
66
|
+
/** `owner/repo#n` — the poll observation the merge action can parse without a cwd. */
|
|
67
|
+
export declare function formatMergeableRef(pr: Pick<MergeablePrInput, 'repo' | 'number'>): string;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-author PR approval + CI-green checks for merge-on-green.
|
|
3
|
+
*
|
|
4
|
+
* Verdict rules are copied from merge-guard.sh (the PreToolUse hook in
|
|
5
|
+
* phnx-labs/.agents-system `rules/subrules/gh-merge-guard/merge-guard.sh`).
|
|
6
|
+
* That hook accepts EITHER a formal GitHub `APPROVED` review OR an APPROVE
|
|
7
|
+
* verdict comment on THIS PR, and rejects a comment that only cites another
|
|
8
|
+
* PR ("carried from #N" / "APPROVE on #N") — the #2736 laundering pattern.
|
|
9
|
+
*
|
|
10
|
+
* Do not invent a third rule. If the hook's regexes change, change these in
|
|
11
|
+
* the same delivery.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* True when a non-author verdict exists ON this PR.
|
|
15
|
+
*
|
|
16
|
+
* Mirrors merge-guard.sh: a GitHub review with `state == "APPROVED"`, or an
|
|
17
|
+
* issue comment whose body contains `\bAPPROVE\b` and is not a carried-from
|
|
18
|
+
* citation (`\bcarried\s+(?:over\s+)?from\b` or `\bAPPROVE\s+(?:on|from)\s+#\d+`).
|
|
19
|
+
*/
|
|
20
|
+
export function hasApproveVerdict(reviews, comments) {
|
|
21
|
+
if (Array.isArray(reviews) && reviews.some((r) => r?.state === 'APPROVED')) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
if (!Array.isArray(comments))
|
|
25
|
+
return false;
|
|
26
|
+
for (const c of comments) {
|
|
27
|
+
const body = c?.body ?? '';
|
|
28
|
+
if (!/\bAPPROVE\b/.test(body))
|
|
29
|
+
continue;
|
|
30
|
+
// A verdict that only points at another PR is laundering, not review.
|
|
31
|
+
if (/\bcarried\s+(?:over\s+)?from\b|\bAPPROVE\s+(?:on|from)\s+#\d+/.test(body)) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* True when every check is a terminal success-class conclusion.
|
|
40
|
+
*
|
|
41
|
+
* Matches the built-in monitor's original jq: SUCCESS / NEUTRAL / SKIPPED
|
|
42
|
+
* (ascii-uppercased conclusion, else state, else status). An empty rollup
|
|
43
|
+
* is green — jq `all([])` is true, and a PR with no checks is mergeable
|
|
44
|
+
* from this filter's point of view.
|
|
45
|
+
*/
|
|
46
|
+
export function isCiGreen(rollup) {
|
|
47
|
+
const items = rollup ?? [];
|
|
48
|
+
return items.every((c) => {
|
|
49
|
+
const v = (c.conclusion || c.state || c.status || '').toUpperCase();
|
|
50
|
+
return v === 'SUCCESS' || v === 'NEUTRAL' || v === 'SKIPPED';
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Keep PRs that are CI-green AND non-author-approved.
|
|
55
|
+
*
|
|
56
|
+
* `reviewDecision == "APPROVED"` is sufficient (a formal review). When that
|
|
57
|
+
* field is empty — the fleet convention: reviewers post an APPROVE *comment*
|
|
58
|
+
* rather than a GitHub review — fall through to {@link hasApproveVerdict}.
|
|
59
|
+
*/
|
|
60
|
+
export function selectMergeablePrs(prs) {
|
|
61
|
+
return prs.filter((pr) => {
|
|
62
|
+
if (!isCiGreen(pr.statusCheckRollup))
|
|
63
|
+
return false;
|
|
64
|
+
if (pr.reviewDecision === 'APPROVED')
|
|
65
|
+
return true;
|
|
66
|
+
return hasApproveVerdict(pr.reviews, pr.comments);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/** `owner/repo#n` — the poll observation the merge action can parse without a cwd. */
|
|
70
|
+
export function formatMergeableRef(pr) {
|
|
71
|
+
return `${pr.repo}#${pr.number}`;
|
|
72
|
+
}
|
|
@@ -73,9 +73,10 @@ export interface ExecLaunchArgsCtx {
|
|
|
73
73
|
}
|
|
74
74
|
/**
|
|
75
75
|
* Context for the routine (daemon-job) launch-arg quirks (mapping B, runner.ts
|
|
76
|
-
* side). This idiom mutates a
|
|
77
|
-
* distinct from buildExecCommand's declarative modeFlags —
|
|
78
|
-
* method on the same adapter, one source of truth per
|
|
76
|
+
* side). This idiom mutates a token array baked from AGENT_COMMANDS
|
|
77
|
+
* (bakeRoutineArgv), distinct from buildExecCommand's declarative modeFlags —
|
|
78
|
+
* so it is a separate method on the same adapter, one source of truth per
|
|
79
|
+
* harness across both.
|
|
79
80
|
*/
|
|
80
81
|
export interface RoutineLaunchCtx {
|
|
81
82
|
/** normalizeMode(config.mode) — the canonicalized mode. */
|
|
@@ -131,8 +132,8 @@ export interface HarnessAdapter {
|
|
|
131
132
|
execModeArgs?(ctx: ExecLaunchArgsCtx): string[] | undefined;
|
|
132
133
|
/**
|
|
133
134
|
* This harness's routine (daemon-job) launch-arg quirks for buildJobCommand
|
|
134
|
-
* (runner.ts). Mutates the
|
|
135
|
-
*
|
|
135
|
+
* (runner.ts). Mutates the bakeRoutineArgv token array in place, exactly as
|
|
136
|
+
* the old per-agent arm did; runner appends model/reasoning flags after.
|
|
136
137
|
* Omitted for a harness with no routine launch quirks.
|
|
137
138
|
*/
|
|
138
139
|
routineModeArgs?(cmd: string[], ctx: RoutineLaunchCtx): void;
|
|
@@ -168,6 +168,8 @@ export interface InteractiveDispatchOptions {
|
|
|
168
168
|
agent: string;
|
|
169
169
|
/** Explicit agent version pin (e.g. "2.1.207") to forward as `agent@version`. */
|
|
170
170
|
version?: string;
|
|
171
|
+
/** Preserve the trailing-@ account picker so selection happens on the execution host. */
|
|
172
|
+
accountPicker?: boolean;
|
|
171
173
|
/** Explicit run strategy (e.g. "balanced") to forward as `--strategy <strategy>`. */
|
|
172
174
|
strategy?: string;
|
|
173
175
|
/** Named provider account to resolve on the remote host. */
|
|
@@ -417,7 +417,14 @@ export function buildRunForwardedArgs(opts) {
|
|
|
417
417
|
* infer headless from the prompt).
|
|
418
418
|
*/
|
|
419
419
|
export function buildInteractiveRunForwardedArgs(opts) {
|
|
420
|
-
|
|
420
|
+
if (opts.version && opts.accountPicker) {
|
|
421
|
+
throw new Error('Interactive host dispatch cannot combine an account picker with a version pin');
|
|
422
|
+
}
|
|
423
|
+
const agentArg = opts.accountPicker
|
|
424
|
+
? `${opts.agent}@`
|
|
425
|
+
: opts.version
|
|
426
|
+
? `${opts.agent}@${opts.version}`
|
|
427
|
+
: opts.agent;
|
|
421
428
|
const args = ['run', agentArg];
|
|
422
429
|
if (opts.prompt && opts.forceInteractive)
|
|
423
430
|
args.push(opts.prompt);
|
|
@@ -48,6 +48,16 @@ export declare const REMOTE_PASSTHROUGH: Record<string, RemoteSpec>;
|
|
|
48
48
|
* these to {@link REMOTE_PASSTHROUGH}.
|
|
49
49
|
*/
|
|
50
50
|
export declare const OWN_HOST_COMMANDS: Set<string>;
|
|
51
|
+
/**
|
|
52
|
+
* Argv forwarded over SSH for a `--device` passthrough.
|
|
53
|
+
*
|
|
54
|
+
* `sync`'s umbrella spec appends `--yes` when there is no TTY so a remote
|
|
55
|
+
* reconcile does not hang on a picker. `sync status` is inspect-only unless the
|
|
56
|
+
* caller typed `--yes` themselves: `--yes` on that command is reconcile, not
|
|
57
|
+
* "don't prompt". Nesting it under `sync` must not inherit the umbrella flag
|
|
58
|
+
* (RUSH-2864).
|
|
59
|
+
*/
|
|
60
|
+
export declare function buildPassthroughForwardedArgs(command: string, allArgs: string[], interactive: boolean): string[];
|
|
51
61
|
/** Injectable dependencies for {@link runFleetPassthrough} — used by tests. */
|
|
52
62
|
export interface FleetPassthroughOptions {
|
|
53
63
|
/** Override the device registry loader (tests). */
|
|
@@ -52,11 +52,10 @@ export { flagValue, hasHostRoutingFlag } from './routing-flag.js';
|
|
|
52
52
|
* `add`/`use`/`list`, and none) and were removed.
|
|
53
53
|
*/
|
|
54
54
|
export const REMOTE_PASSTHROUGH = {
|
|
55
|
-
//
|
|
55
|
+
// inspect
|
|
56
56
|
view: {},
|
|
57
57
|
inspect: {},
|
|
58
58
|
doctor: {},
|
|
59
|
-
status: {},
|
|
60
59
|
check: {},
|
|
61
60
|
list: {},
|
|
62
61
|
usage: {},
|
|
@@ -81,7 +80,6 @@ export const REMOTE_PASSTHROUGH = {
|
|
|
81
80
|
workflows: {},
|
|
82
81
|
models: {},
|
|
83
82
|
defaults: {},
|
|
84
|
-
alias: {},
|
|
85
83
|
// Installations are per-machine, so updating one on a peer means running it
|
|
86
84
|
// there — the same local/remote shape `add` would need.
|
|
87
85
|
update: {},
|
|
@@ -97,7 +95,6 @@ export const REMOTE_PASSTHROUGH = {
|
|
|
97
95
|
restore: {},
|
|
98
96
|
worktree: {},
|
|
99
97
|
events: {},
|
|
100
|
-
audit: {},
|
|
101
98
|
feedback: {},
|
|
102
99
|
pty: {},
|
|
103
100
|
tmux: {},
|
|
@@ -137,6 +134,32 @@ const STRIP_SPECS = [
|
|
|
137
134
|
{ long: 'hosts', takesValue: true },
|
|
138
135
|
{ long: 'devices', takesValue: true },
|
|
139
136
|
];
|
|
137
|
+
/** First non-flag token after `group` in argv (the subcommand, robust to leading flags). */
|
|
138
|
+
function firstSubcommand(allArgs, group) {
|
|
139
|
+
const idx = allArgs.indexOf(group);
|
|
140
|
+
return idx >= 0 ? allArgs.slice(idx + 1).find((a) => !a.startsWith('-')) : undefined;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Argv forwarded over SSH for a `--device` passthrough.
|
|
144
|
+
*
|
|
145
|
+
* `sync`'s umbrella spec appends `--yes` when there is no TTY so a remote
|
|
146
|
+
* reconcile does not hang on a picker. `sync status` is inspect-only unless the
|
|
147
|
+
* caller typed `--yes` themselves: `--yes` on that command is reconcile, not
|
|
148
|
+
* "don't prompt". Nesting it under `sync` must not inherit the umbrella flag
|
|
149
|
+
* (RUSH-2864).
|
|
150
|
+
*/
|
|
151
|
+
export function buildPassthroughForwardedArgs(command, allArgs, interactive) {
|
|
152
|
+
const spec = REMOTE_PASSTHROUGH[command];
|
|
153
|
+
let forwarded = stripRoutingFlags(allArgs, STRIP_SPECS);
|
|
154
|
+
// Detect the subcommand on the *stripped* argv. On the raw argv,
|
|
155
|
+
// `sync --device peer status` would treat `peer` as the first non-flag
|
|
156
|
+
// token and inherit umbrella `--yes` (RUSH-2864 review).
|
|
157
|
+
const skipInheritedYes = command === 'sync' && firstSubcommand(forwarded, 'sync') === 'status';
|
|
158
|
+
if (!interactive && spec?.nonInteractive && !skipInheritedYes) {
|
|
159
|
+
forwarded = [...forwarded, ...spec.nonInteractive];
|
|
160
|
+
}
|
|
161
|
+
return forwarded;
|
|
162
|
+
}
|
|
140
163
|
/** Synthesize a `Host` for a raw `user@host` / bare-alias target (not enrolled). */
|
|
141
164
|
function syntheticHost(target) {
|
|
142
165
|
const at = target.indexOf('@');
|
|
@@ -531,9 +554,7 @@ export async function maybeRunOnHost(command, allArgs, opts) {
|
|
|
531
554
|
// out — otherwise force the command's non-interactive path so no half-drawn
|
|
532
555
|
// picker is piped into a file or another program.
|
|
533
556
|
const interactive = !!process.stdout.isTTY && !allArgs.includes('--no-tty');
|
|
534
|
-
|
|
535
|
-
if (!interactive && spec.nonInteractive)
|
|
536
|
-
forwarded = [...forwarded, ...spec.nonInteractive];
|
|
557
|
+
const forwarded = buildPassthroughForwardedArgs(command, allArgs, interactive);
|
|
537
558
|
// The one long-running case: keep the remote team supervisor alive past a
|
|
538
559
|
// disconnect by dispatching it detached (nohup), still streaming live.
|
|
539
560
|
const isWatchedTeamStart = command === 'teams' && forwarded[1] === 'start' && forwarded.includes('--watch');
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* enrolled host shadows a same-name device in both list dedup and resolve
|
|
18
18
|
* order, exactly like the old tier-2 devices fall-through in resolveHost.
|
|
19
19
|
*/
|
|
20
|
-
import { loadDevices, getDevice
|
|
20
|
+
import { loadDevices, getDevice } from '../../devices/registry.js';
|
|
21
21
|
import { resolveDeviceProfile } from '../../devices/resolve-profile.js';
|
|
22
22
|
import { DeviceOffloadUnsupportedError } from '../types.js';
|
|
23
23
|
/** Tailscale's own presence bit, when the sync captured one. */
|
|
@@ -34,12 +34,6 @@ function statusOf(device) {
|
|
|
34
34
|
* precedence, carrying the caps.
|
|
35
35
|
*/
|
|
36
36
|
function deviceToPoolHost(rawDevice) {
|
|
37
|
-
// A control device (a cockpit, e.g. a paired iPhone) drives the fleet but
|
|
38
|
-
// never runs agents — it must never enter the host pool or be resolvable as a
|
|
39
|
-
// dispatch target, whatever platform it reports (an iPhone syncs as `unknown`,
|
|
40
|
-
// which remoteShellFor would otherwise default to POSIX and try to SSH).
|
|
41
|
-
if (isControlDevice(rawDevice))
|
|
42
|
-
return null;
|
|
43
37
|
// Effective profile: central config (ssh.*/platform) overlays discovery.
|
|
44
38
|
const device = resolveDeviceProfile(rawDevice);
|
|
45
39
|
const address = device.address.dnsName ?? device.address.ip;
|
|
@@ -78,12 +72,6 @@ export class DevicesHostProvider {
|
|
|
78
72
|
const raw = await getDevice(name);
|
|
79
73
|
if (!raw)
|
|
80
74
|
return null;
|
|
81
|
-
// Resolving is asking to dispatch. A control device can't run agents — fail
|
|
82
|
-
// loud with a clear message instead of attempting an SSH dispatch onto a
|
|
83
|
-
// phone (which remoteShellFor would treat as a POSIX host).
|
|
84
|
-
if (isControlDevice(raw)) {
|
|
85
|
-
throw new Error(`Device "${raw.name}" is a control device (a cockpit), not an executor — it can't run agents. Dispatch to a worker device instead.`);
|
|
86
|
-
}
|
|
87
75
|
// Effective profile: central config (ssh.*/platform) overlays discovery —
|
|
88
76
|
// the password-auth refusal and the dial shape both follow the config.
|
|
89
77
|
const device = resolveDeviceProfile(raw);
|
|
@@ -88,11 +88,20 @@ export declare function viewAgentVersions(view: string, agent: string): string[]
|
|
|
88
88
|
export declare function viewHasAgentVersion(view: string, agent: string, version: string): boolean | undefined;
|
|
89
89
|
/** Actionable fail-loud message for a missing remote pin (RUSH-2313). */
|
|
90
90
|
export declare function missingPinnedVersionMessage(hostName: string, agent: string, version: string, installed?: string[]): string;
|
|
91
|
+
/** Account eligibility extracted from one device's `agents view --json`. */
|
|
92
|
+
export interface ViewAgentAccountEligibility {
|
|
93
|
+
/** At least one account can launch immediately. */
|
|
94
|
+
signedIn: boolean | undefined;
|
|
95
|
+
/** At least one account can launch immediately or enter the harness login flow. */
|
|
96
|
+
pickerEligible: boolean | undefined;
|
|
97
|
+
}
|
|
91
98
|
/**
|
|
92
|
-
* Read
|
|
93
|
-
*
|
|
94
|
-
*
|
|
99
|
+
* Read the two account gates automatic placement needs from `agents view
|
|
100
|
+
* --json`. A picker may route to a signed-out version because launching it is
|
|
101
|
+
* the login flow, but it must not route to a device whose every signed-in
|
|
102
|
+
* account is throttled.
|
|
95
103
|
*/
|
|
104
|
+
export declare function viewAgentAccountEligibility(view: string, agent: string): ViewAgentAccountEligibility;
|
|
96
105
|
export declare function viewAgentSignedIn(view: string, agent: string): boolean | undefined;
|
|
97
106
|
export interface EnsureReadyOptions {
|
|
98
107
|
agent: string;
|
package/dist/lib/hosts/ready.js
CHANGED
|
@@ -12,6 +12,7 @@ import { sshExec, shellQuote } from '../ssh-exec.js';
|
|
|
12
12
|
import { hostIdentityArgs, sshTargetFor } from './types.js';
|
|
13
13
|
import { remoteShellFor, buildWindowsAgentsCommand, encodePowershell, powershellQuote, POWERSHELL_PROGRESS_SILENCE } from './remote-cmd.js';
|
|
14
14
|
import { resolveRemoteOsSync } from './remote-os.js';
|
|
15
|
+
import { isDeadVerdict } from '../auth-health.js';
|
|
15
16
|
/** Resolve this CLI's own version by walking up to the nearest package.json. */
|
|
16
17
|
export function localCliVersion() {
|
|
17
18
|
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -208,27 +209,41 @@ export function missingPinnedVersionMessage(hostName, agent, version, installed)
|
|
|
208
209
|
`Install it on that box: agents ssh ${hostName} -- agents add ${agent}@${version}`);
|
|
209
210
|
}
|
|
210
211
|
/**
|
|
211
|
-
* Read
|
|
212
|
-
*
|
|
213
|
-
*
|
|
212
|
+
* Read the two account gates automatic placement needs from `agents view
|
|
213
|
+
* --json`. A picker may route to a signed-out version because launching it is
|
|
214
|
+
* the login flow, but it must not route to a device whose every signed-in
|
|
215
|
+
* account is throttled.
|
|
214
216
|
*/
|
|
215
|
-
export function
|
|
217
|
+
export function viewAgentAccountEligibility(view, agent) {
|
|
216
218
|
try {
|
|
217
219
|
const rows = JSON.parse(view);
|
|
218
220
|
const row = rows.find((candidate) => candidate.agent?.toLowerCase() === agent.toLowerCase());
|
|
219
221
|
if (!row)
|
|
220
|
-
return undefined;
|
|
221
|
-
const verdicts = (row.versions ?? [])
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
222
|
+
return { signedIn: undefined, pickerEligible: undefined };
|
|
223
|
+
const verdicts = (row.versions ?? []).flatMap((version) => {
|
|
224
|
+
if (typeof version.signedIn !== 'boolean')
|
|
225
|
+
return [];
|
|
226
|
+
const throttled = version.usageStatus === 'rate_limited' || version.usageStatus === 'out_of_credits';
|
|
227
|
+
const authBlocked = version.authVerdict !== null
|
|
228
|
+
&& version.authVerdict !== undefined
|
|
229
|
+
&& isDeadVerdict(version.authVerdict);
|
|
230
|
+
const ready = version.signedIn && !authBlocked && !throttled;
|
|
231
|
+
return [{ ready, pickerEligible: ready || !version.signedIn || authBlocked }];
|
|
232
|
+
});
|
|
233
|
+
if (verdicts.length === 0)
|
|
234
|
+
return { signedIn: undefined, pickerEligible: undefined };
|
|
235
|
+
return {
|
|
236
|
+
signedIn: verdicts.some((verdict) => verdict.ready),
|
|
237
|
+
pickerEligible: verdicts.some((verdict) => verdict.pickerEligible),
|
|
238
|
+
};
|
|
227
239
|
}
|
|
228
240
|
catch {
|
|
229
|
-
return undefined;
|
|
241
|
+
return { signedIn: undefined, pickerEligible: undefined };
|
|
230
242
|
}
|
|
231
243
|
}
|
|
244
|
+
export function viewAgentSignedIn(view, agent) {
|
|
245
|
+
return viewAgentAccountEligibility(view, agent).signedIn;
|
|
246
|
+
}
|
|
232
247
|
/**
|
|
233
248
|
* Pure readiness verdict for the agent/version half of {@link ensureHostReady}
|
|
234
249
|
* (unit-tested without SSH). Returns warnings for soft agent-missing cases, or
|
|
@@ -27,8 +27,8 @@ export declare function getAllProviders(): HostProvider[];
|
|
|
27
27
|
* A resolved host, carrying the live {@link DeviceProfile} when the token
|
|
28
28
|
* matched a registered device (by normalized name). The device reference lets
|
|
29
29
|
* the `agents ssh` wrapper reach auth/shell/tailscale metadata a plain `Host`
|
|
30
|
-
* doesn't hold, and lets dispatch callers apply the device-only
|
|
31
|
-
* (
|
|
30
|
+
* doesn't hold, and lets dispatch callers apply the device-only refusal
|
|
31
|
+
* (password auth) without re-reading the registry.
|
|
32
32
|
*/
|
|
33
33
|
export interface ResolvedHost extends Host {
|
|
34
34
|
device?: DeviceProfile;
|
|
@@ -65,7 +65,7 @@ export interface MatchHostOptions {
|
|
|
65
65
|
* all resolve the same way regardless of which subcommand called.
|
|
66
66
|
*
|
|
67
67
|
* Non-throwing: returns null on an injection-guard failure or an unresolved bare
|
|
68
|
-
* token.
|
|
68
|
+
* token. The device-only refusal (password auth) is NOT applied
|
|
69
69
|
* here — that is the dispatch wrapper's job ({@link resolveHost}), so the fan-out
|
|
70
70
|
* and `agents ssh` paths, which handle those cases differently, aren't forced
|
|
71
71
|
* into a dispatch verdict.
|
|
@@ -81,8 +81,7 @@ export declare function listAllHosts(): Promise<Host[]>;
|
|
|
81
81
|
* caller consumes (`run --device`, the generic passthrough, teams placement, the
|
|
82
82
|
* cloud host provider, doctor, funnel, remote secrets). Grammar and merge come
|
|
83
83
|
* from {@link matchHost}; on top, this layer applies the device-only dispatch
|
|
84
|
-
*
|
|
85
|
-
* - a control device (a paired iPhone) can never be a dispatch target;
|
|
84
|
+
* refusal so it holds even when an inline overlay shadows the device:
|
|
86
85
|
* - a password-auth device throws the typed {@link DeviceOffloadUnsupportedError}
|
|
87
86
|
* (offload rides `BatchMode=yes` ssh, which can't answer a prompt);
|
|
88
87
|
* - an addressless device has nothing to dial.
|