@phnx-labs/agents-cli 1.22.41 → 1.22.43
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 +86 -0
- package/README.md +10 -4
- package/dist/bin/agents +0 -0
- package/dist/bootstrap.js +25 -7
- package/dist/cli/command-registry.d.ts +2 -3
- package/dist/cli/command-registry.js +4 -8
- package/dist/commands/accounts.d.ts +29 -0
- package/dist/commands/accounts.js +220 -29
- package/dist/commands/alias.d.ts +7 -4
- package/dist/commands/alias.js +34 -9
- package/dist/commands/auth.d.ts +9 -0
- package/dist/commands/auth.js +108 -0
- package/dist/commands/browser-sessions-picker.js +42 -62
- package/dist/commands/browser.js +30 -38
- package/dist/commands/computer-actions.d.ts +1 -1
- package/dist/commands/computer-actions.js +1 -1
- package/dist/commands/computer-sessions-picker.js +32 -55
- package/dist/commands/computer.d.ts +1 -1
- package/dist/commands/computer.js +22 -8
- package/dist/commands/daemon.js +1 -1
- package/dist/commands/doctor.d.ts +5 -3
- package/dist/commands/doctor.js +23 -8
- package/dist/commands/exec.js +3 -2
- package/dist/commands/feed.js +2 -31
- package/dist/commands/harness.js +3 -3
- package/dist/commands/hooks.js +1 -1
- package/dist/commands/import.js +1 -1
- package/dist/commands/insights.js +163 -85
- package/dist/commands/inspect.js +2 -2
- package/dist/commands/mine.js +1 -1
- package/dist/commands/monitors.js +1 -1
- package/dist/commands/org.d.ts +6 -0
- package/dist/commands/org.js +175 -0
- package/dist/commands/packages.js +1 -1
- package/dist/commands/profiles.js +2 -2
- package/dist/commands/prune.js +1 -1
- package/dist/commands/routines.js +44 -26
- package/dist/commands/run-account-picker.d.ts +19 -0
- package/dist/commands/run-account-picker.js +81 -0
- package/dist/commands/sessions-picker-factory.d.ts +40 -0
- package/dist/commands/sessions-picker-factory.js +67 -0
- package/dist/commands/sessions-picker.js +124 -122
- package/dist/commands/setup-browser.js +4 -4
- package/dist/commands/setup-mine.js +1 -1
- package/dist/commands/setup-preferences.js +2 -2
- package/dist/commands/setup.js +10 -4
- package/dist/commands/snapshot.d.ts +1 -1
- package/dist/commands/snapshot.js +4 -4
- package/dist/commands/status.d.ts +4 -2
- package/dist/commands/status.js +10 -7
- package/dist/commands/sync.d.ts +1 -0
- package/dist/commands/sync.js +30 -3
- package/dist/commands/versions.js +1 -1
- package/dist/commands/view.js +7 -1
- package/dist/commands/webhook.js +1 -1
- package/dist/lib/account-capabilities.d.ts +10 -0
- package/dist/lib/account-capabilities.js +34 -0
- 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 +584 -0
- package/dist/lib/agent-spec/agents.js +3127 -0
- package/dist/lib/agent-spec/provider.js +1 -1
- package/dist/lib/agents.d.ts +2 -584
- package/dist/lib/agents.js +2 -3127
- package/dist/lib/browser/drivers/local.d.ts +8 -2
- package/dist/lib/browser/drivers/local.js +8 -2
- package/dist/lib/browser/drivers/ssh.d.ts +8 -2
- package/dist/lib/browser/drivers/ssh.js +10 -4
- package/dist/lib/browser/hygiene.d.ts +1 -1
- package/dist/lib/browser/hygiene.js +5 -1
- package/dist/lib/browser/profiles.d.ts +74 -1
- package/dist/lib/browser/profiles.js +114 -5
- package/dist/lib/browser/runtime-state.d.ts +11 -5
- package/dist/lib/browser/runtime-state.js +13 -9
- package/dist/lib/browser/service.d.ts +68 -30
- package/dist/lib/browser/service.js +305 -194
- package/dist/lib/browser/types.d.ts +95 -3
- package/dist/lib/browser/types.js +51 -0
- package/dist/lib/catchup.js +1 -1
- package/dist/lib/cloud/rush.d.ts +0 -15
- package/dist/lib/cloud/rush.js +0 -35
- package/dist/lib/computer/actions.d.ts +1 -1
- package/dist/lib/computer/actions.js +1 -1
- package/dist/lib/{computer-rpc.js → computer/computer-rpc.js} +5 -5
- package/dist/lib/computer/dispatch.d.ts +1 -1
- package/dist/lib/computer/download.d.ts +1 -1
- package/dist/lib/computer/download.js +3 -3
- package/dist/lib/{ssh-tunnel.d.ts → computer/ssh-tunnel.d.ts} +1 -1
- package/dist/lib/{ssh-tunnel.js → computer/ssh-tunnel.js} +12 -12
- package/dist/lib/crabbox/runtimes.d.ts +6 -2
- package/dist/lib/crabbox/runtimes.js +21 -5
- package/dist/lib/daemon/daemon.js +87 -59
- package/dist/lib/{runner.d.ts → daemon/runner.d.ts} +28 -11
- package/dist/lib/{runner.js → daemon/runner.js} +143 -123
- package/dist/lib/devices/doctor-findings.d.ts +1 -1
- package/dist/lib/devices/doctor-findings.js +4 -4
- package/dist/lib/devices/fleet-inventory.js +1 -1
- package/dist/lib/devices/harness-inventory.js +1 -1
- package/dist/lib/devices/health-report.js +1 -1
- package/dist/lib/devices/registry.js +4 -22
- package/dist/lib/doctor-diff.d.ts +1 -1
- package/dist/lib/doctor-diff.js +1 -1
- 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/drift.js +1 -1
- package/dist/lib/entitlement.d.ts +31 -0
- package/dist/lib/entitlement.js +137 -0
- package/dist/lib/exec.js +52 -205
- package/dist/lib/feed/feed.js +2 -8
- package/dist/lib/format.d.ts +9 -0
- package/dist/lib/format.js +16 -0
- package/dist/lib/fs-atomic.d.ts +7 -0
- package/dist/lib/fs-atomic.js +9 -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 +161 -0
- package/dist/lib/harness/adapter.js +47 -0
- package/dist/lib/harness/adapters/claude.d.ts +2 -0
- package/dist/lib/harness/adapters/claude.js +110 -0
- package/dist/lib/harness/adapters/codex.d.ts +2 -0
- package/dist/lib/harness/adapters/codex.js +83 -0
- package/dist/lib/harness/adapters/copilot.d.ts +2 -0
- package/dist/lib/harness/adapters/copilot.js +23 -0
- package/dist/lib/harness/adapters/cursor.d.ts +2 -0
- package/dist/lib/harness/adapters/cursor.js +38 -0
- package/dist/lib/harness/adapters/droid.d.ts +2 -0
- package/dist/lib/harness/adapters/droid.js +17 -0
- package/dist/lib/harness/adapters/grok.d.ts +2 -0
- package/dist/lib/harness/adapters/grok.js +14 -0
- package/dist/lib/harness/adapters/kimi.d.ts +2 -0
- package/dist/lib/harness/adapters/kimi.js +49 -0
- package/dist/lib/harness/adapters/muse.d.ts +2 -0
- package/dist/lib/harness/adapters/muse.js +44 -0
- package/dist/lib/harness/adapters/opencode.d.ts +2 -0
- package/dist/lib/harness/adapters/opencode.js +14 -0
- package/dist/lib/harness/exec-config-version.d.ts +8 -0
- package/dist/lib/harness/exec-config-version.js +22 -0
- package/dist/lib/harness/index.d.ts +1 -0
- package/dist/lib/harness/index.js +34 -0
- package/dist/lib/{hooks.d.ts → hooks/install.d.ts} +1 -1
- package/dist/lib/{hooks.js → hooks/install.js} +11 -11
- package/dist/lib/hosts/passthrough.d.ts +10 -0
- package/dist/lib/hosts/passthrough.js +28 -6
- package/dist/lib/hosts/session-index.js +1 -1
- package/dist/lib/import.js +1 -1
- package/dist/lib/installations/index.d.ts +4 -4
- package/dist/lib/installations/index.js +3 -4
- package/dist/lib/installations/migrate.js +6 -6
- package/dist/lib/{shims.d.ts → installations/shims.d.ts} +2 -2
- package/dist/lib/{shims.js → installations/shims.js} +13 -115
- package/dist/lib/installations/store.d.ts +141 -3
- package/dist/lib/installations/store.js +475 -5
- package/dist/lib/installations/versions.d.ts +40 -125
- package/dist/lib/installations/versions.js +57 -462
- package/dist/lib/isolation-boundary-report.d.ts +1 -1
- package/dist/lib/isolation-boundary-report.js +1 -1
- package/dist/lib/mailbox-gc.js +2 -3
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/install-menubar.js +19 -8
- package/dist/lib/monitors/config.d.ts +2 -2
- package/dist/lib/monitors/config.js +3 -2
- package/dist/lib/monitors/dispatch.d.ts +1 -1
- package/dist/lib/monitors/dispatch.js +2 -2
- package/dist/lib/monitors/state.d.ts +1 -1
- 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/plugins/plugins.js +1 -1
- package/dist/lib/plugins/skills.js +1 -1
- package/dist/lib/prix-account.d.ts +158 -0
- package/dist/lib/prix-account.js +214 -0
- package/dist/lib/profiles.d.ts +7 -0
- package/dist/lib/profiles.js +26 -1
- package/dist/lib/project-key.js +2 -2
- package/dist/lib/project-launch.js +1 -1
- package/dist/lib/refresh.js +2 -2
- package/dist/lib/resource-inventory.d.ts +1 -1
- package/dist/lib/resource-inventory.js +1 -1
- package/dist/lib/rules/rules.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 +17 -9
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/agent.js +9 -1
- package/dist/lib/secrets/index.js +1 -1
- package/dist/lib/secrets/list-filter.js +1 -1
- package/dist/lib/self-heal/checks/hook-runtime.js +1 -1
- package/dist/lib/self-heal/checks/path.js +1 -1
- package/dist/lib/self-heal/checks/shadowing.js +1 -1
- package/dist/lib/self-heal/checks/shims.js +1 -1
- package/dist/lib/service-manifest.d.ts +20 -0
- package/dist/lib/service-manifest.js +34 -0
- package/dist/lib/session/cloud.js +1 -1
- package/dist/lib/session/db.js +1 -1
- package/dist/lib/session/discover.d.ts +39 -0
- package/dist/lib/session/discover.js +3 -3
- 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/parse.js +24 -42
- package/dist/lib/session/recovery.js +1 -1
- package/dist/lib/session/relative-time.d.ts +1 -44
- package/dist/lib/session/relative-time.js +4 -120
- package/dist/lib/session/remote-list.js +1 -1
- package/dist/lib/session/remote.js +2 -2
- package/dist/lib/session/short-id.d.ts +1 -17
- package/dist/lib/session/short-id.js +4 -20
- package/dist/lib/session/width.d.ts +1 -29
- package/dist/lib/session/width.js +4 -101
- package/dist/lib/snapshot.d.ts +2 -2
- package/dist/lib/snapshot.js +1 -1
- package/dist/lib/staleness/prune.d.ts +1 -1
- package/dist/lib/staleness/prune.js +1 -1
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/startup/command-registry.d.ts +4 -1
- package/dist/lib/startup/command-registry.js +12 -5
- package/dist/lib/teams/agents.js +2 -19
- package/dist/lib/teams/parsers.js +20 -26
- package/dist/lib/teams/placement-probe.js +1 -1
- package/dist/lib/teams/registry.js +2 -21
- 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 +9 -1
- package/dist/lib/triggers/webhook.d.ts +1 -0
- package/dist/lib/triggers/webhook.js +3 -2
- package/dist/lib/uninstall.js +1 -1
- package/dist/lib/version.d.ts +9 -2
- package/dist/lib/version.js +11 -9
- package/dist/lib/view-types.d.ts +4 -0
- package/package.json +1 -1
- package/dist/commands/tickets.d.ts +0 -2
- package/dist/commands/tickets.js +0 -43
- package/dist/lib/observe-aliases.d.ts +0 -30
- package/dist/lib/observe-aliases.js +0 -38
- package/dist/lib/tickets/list.d.ts +0 -53
- package/dist/lib/tickets/list.js +0 -153
- /package/dist/lib/{computer-rpc.d.ts → computer/computer-rpc.d.ts} +0 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harness adapter registry — the behavior axis of a supported harness.
|
|
3
|
+
*
|
|
4
|
+
* The declarative FLAG table lives in `AGENTS` (lib/agents.ts): what a harness
|
|
5
|
+
* *is* (cliCommand, configDir, capabilities). A HarnessAdapter carries what a
|
|
6
|
+
* harness *does* at the execution boundary — the per-harness quirks that four
|
|
7
|
+
* call sites otherwise each re-express as a `if (agent === 'x') … else if …`
|
|
8
|
+
* name-chain:
|
|
9
|
+
*
|
|
10
|
+
* - config-dir env pins → exec.ts buildExecEnv + shims.ts shim script
|
|
11
|
+
* - launch-arg quirks → exec.ts buildExecCommand + runner.ts buildJobCommand
|
|
12
|
+
*
|
|
13
|
+
* One adapter per harness is the single source of truth for that harness's
|
|
14
|
+
* behavior across every consuming site, mirroring the ChannelProvider
|
|
15
|
+
* (lib/channels/registry.ts), HostProvider (lib/hosts/types.ts), and
|
|
16
|
+
* AccountProviderAdapter (lib/account-provider-registry.ts) registries this repo
|
|
17
|
+
* already proves.
|
|
18
|
+
*
|
|
19
|
+
* NOT here: transcript parsing and live team-event normalization. Those are
|
|
20
|
+
* registry-dispatched too — a `Record` table in `session/parse.ts`
|
|
21
|
+
* (`TRANSCRIPT_PARSERS`) and one in `teams/parsers.ts` (`TEAM_EVENT_NORMALIZERS`)
|
|
22
|
+
* — but kept beside the parser functions they dispatch (which live in those
|
|
23
|
+
* files) rather than on this `AgentId` registry, because their id domains differ:
|
|
24
|
+
* the offline transcript reader keys on `SessionAgentId` (which includes `rush`,
|
|
25
|
+
* not an `AgentId`), and the live team-event normalizer keys on the teams
|
|
26
|
+
* `AgentType`. They stay separate functions — offline transcript vs live team
|
|
27
|
+
* events — deliberately.
|
|
28
|
+
*/
|
|
29
|
+
import type { AgentId, Mode } from '../types.js';
|
|
30
|
+
import type { JobConfig } from '../scheduling/routines.js';
|
|
31
|
+
/**
|
|
32
|
+
* Context for the exec-time config-env pin (mapping A, exec.ts side). The caller
|
|
33
|
+
* resolves the interactive/version facts once; the adapter only expresses the
|
|
34
|
+
* harness-specific env manipulation, operating on the accumulating `result` env
|
|
35
|
+
* exactly as the old per-agent branch did.
|
|
36
|
+
*/
|
|
37
|
+
export interface ExecConfigEnvCtx {
|
|
38
|
+
agent: AgentId;
|
|
39
|
+
/** The version to pin, pre-resolved by the caller (null = unresolved/not installed). */
|
|
40
|
+
version: string | null;
|
|
41
|
+
/** That version's home, pre-resolved by the caller (null when version is null). */
|
|
42
|
+
versionHome: string | null;
|
|
43
|
+
/** resolveInteractive(options) — computed once by the caller. */
|
|
44
|
+
interactive: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* claude-account-token's resolveClaudeSetupToken, injected. The adapters MUST
|
|
47
|
+
* stay import-leaf: claude-account-token pulls in the secrets stack, which
|
|
48
|
+
* transitively imports sqlite.ts (top-level await). Importing it inside an
|
|
49
|
+
* adapter drags that into shims.ts's module graph (shims imports the harness
|
|
50
|
+
* barrel), and subprocess-spawning tests that load shims via tsx then fail the
|
|
51
|
+
* cjs transform. Only claude uses it.
|
|
52
|
+
*/
|
|
53
|
+
resolveClaudeSetupToken: (versionHome: string) => string | null;
|
|
54
|
+
}
|
|
55
|
+
/** Context for the shim-script config-env block (mapping A, shims.ts side). */
|
|
56
|
+
export interface ShimConfigEnvCtx {
|
|
57
|
+
/**
|
|
58
|
+
* The config-dir path relative to `$HOME` (e.g. `.claude`, or the nested
|
|
59
|
+
* `.gemini/antigravity-cli`), derived by the caller from the AGENTS registry.
|
|
60
|
+
*/
|
|
61
|
+
configDirName: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Context for the exec-time launch-arg quirks (mapping B, exec.ts side). Mirrors
|
|
65
|
+
* the locals buildExecCommand already computed at the emission point.
|
|
66
|
+
*/
|
|
67
|
+
export interface ExecLaunchArgsCtx {
|
|
68
|
+
resolvedMode: Mode;
|
|
69
|
+
interactive: boolean;
|
|
70
|
+
cwd: string;
|
|
71
|
+
/** Extra writable roots (`--add-dir`) requested for this run, home-expanded. */
|
|
72
|
+
addDirs: string[];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Context for the routine (daemon-job) launch-arg quirks (mapping B, runner.ts
|
|
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.
|
|
80
|
+
*/
|
|
81
|
+
export interface RoutineLaunchCtx {
|
|
82
|
+
/** normalizeMode(config.mode) — the canonicalized mode. */
|
|
83
|
+
mode: Mode;
|
|
84
|
+
config: JobConfig;
|
|
85
|
+
/**
|
|
86
|
+
* exec.ts resolveHeadlessMode, injected to avoid an import cycle (exec.ts
|
|
87
|
+
* imports this registry). Kimi calls it for its plan→auto downgrade warning.
|
|
88
|
+
*/
|
|
89
|
+
resolveHeadlessMode: (agent: AgentId, mode: Mode, interactive: boolean) => void;
|
|
90
|
+
}
|
|
91
|
+
export interface HarnessAdapter {
|
|
92
|
+
id: AgentId;
|
|
93
|
+
/**
|
|
94
|
+
* Apply this harness's config-dir env pins to the live process env for an
|
|
95
|
+
* `agents run` invocation (exec.ts buildExecEnv). Mutates `result` in place,
|
|
96
|
+
* exactly as the old per-agent branch did. The caller has already stripped the
|
|
97
|
+
* shared config-dir keys (CONFIG_DIR_ENV_KEYS), so an adapter only sets its own
|
|
98
|
+
* vars (plus any harness-specific extra it must delete, e.g. Claude's inherited
|
|
99
|
+
* OAuth token). Omitted for a harness with no config-dir env (the old `else`).
|
|
100
|
+
*/
|
|
101
|
+
applyExecConfigEnv?(result: NodeJS.ProcessEnv, ctx: ExecConfigEnvCtx): void;
|
|
102
|
+
/**
|
|
103
|
+
* The config-env bash block for this harness's generated shim (shims.ts). The
|
|
104
|
+
* returned string is spliced verbatim into the shim script. Omitted (⇒ '') for
|
|
105
|
+
* a harness with no managed config-dir env.
|
|
106
|
+
*/
|
|
107
|
+
shimConfigEnvBash?(ctx: ShimConfigEnvCtx): string;
|
|
108
|
+
/**
|
|
109
|
+
* Launch args appended to this harness's shim `exec` line (shims.ts). Codex
|
|
110
|
+
* pins `check_for_update_on_startup=false` + its edit-profile policy args here.
|
|
111
|
+
* Returns '' for a harness with no shim launch args.
|
|
112
|
+
*/
|
|
113
|
+
shimLaunchArgs?(): string;
|
|
114
|
+
/**
|
|
115
|
+
* The shim's `exec` tail for this harness (shims.ts). Codex resolves the repo's
|
|
116
|
+
* `.agents` dir from `$PWD` at run time and appends `--add-dir`. Omitted ⇒ the
|
|
117
|
+
* default `exec "$BINARY"<launchArgs> "$@"`.
|
|
118
|
+
*/
|
|
119
|
+
shimExecTail?(launchArgs: string): string;
|
|
120
|
+
/**
|
|
121
|
+
* Additive launch args emitted BEFORE mode-flag resolution in buildExecCommand
|
|
122
|
+
* (exec.ts). Cursor's `--trust` for a configured headless edit lives here.
|
|
123
|
+
* Returns undefined when this harness adds nothing.
|
|
124
|
+
*/
|
|
125
|
+
execPreModeArgs?(ctx: ExecLaunchArgsCtx): string[] | undefined;
|
|
126
|
+
/**
|
|
127
|
+
* This harness's mode-flag emission for buildExecCommand (exec.ts), overriding
|
|
128
|
+
* the generic `template.modeFlags` / resume-subcommand path. Codex returns its
|
|
129
|
+
* policy args; Kimi returns [] for a headless run (and throws on an invariant
|
|
130
|
+
* violation). Returns undefined to defer to the generic path.
|
|
131
|
+
*/
|
|
132
|
+
execModeArgs?(ctx: ExecLaunchArgsCtx): string[] | undefined;
|
|
133
|
+
/**
|
|
134
|
+
* This harness's routine (daemon-job) launch-arg quirks for buildJobCommand
|
|
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.
|
|
137
|
+
* Omitted for a harness with no routine launch quirks.
|
|
138
|
+
*/
|
|
139
|
+
routineModeArgs?(cmd: string[], ctx: RoutineLaunchCtx): void;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The four config-dir env keys a harness pins to its version home. Every
|
|
143
|
+
* per-harness branch in the old buildExecEnv deleted the ones it does NOT set,
|
|
144
|
+
* so its own config pointer never leaks into a foreign harness's invocation.
|
|
145
|
+
* (XDG_CONFIG_HOME / XDG_DATA_HOME — Muse/Cursor — are deliberately absent: the
|
|
146
|
+
* old code sets them but never strips them, and this preserves that exactly.)
|
|
147
|
+
*/
|
|
148
|
+
export declare const CONFIG_DIR_ENV_KEYS: readonly ["CLAUDE_CONFIG_DIR", "CODEX_HOME", "COPILOT_HOME", "KIMI_CODE_HOME"];
|
|
149
|
+
/**
|
|
150
|
+
* Strip every config-dir env key except the one(s) this harness sets. `keep=[]`
|
|
151
|
+
* (the default / no-config-dir harness) deletes all four — the old `else` arm.
|
|
152
|
+
*/
|
|
153
|
+
export declare function stripForeignConfigDir(result: NodeJS.ProcessEnv, keep?: readonly string[]): void;
|
|
154
|
+
export declare function registerHarnessAdapter(adapter: HarnessAdapter): void;
|
|
155
|
+
/**
|
|
156
|
+
* The behavior adapter for a harness. Every `AgentId` resolves to an adapter —
|
|
157
|
+
* one with real behavior when registered, else the id-only default (the old
|
|
158
|
+
* `else` arm). Callers therefore never name-check a harness.
|
|
159
|
+
*/
|
|
160
|
+
export declare function resolveHarnessAdapter(id: AgentId): HarnessAdapter;
|
|
161
|
+
export declare function listHarnessAdapters(): AgentId[];
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The four config-dir env keys a harness pins to its version home. Every
|
|
3
|
+
* per-harness branch in the old buildExecEnv deleted the ones it does NOT set,
|
|
4
|
+
* so its own config pointer never leaks into a foreign harness's invocation.
|
|
5
|
+
* (XDG_CONFIG_HOME / XDG_DATA_HOME — Muse/Cursor — are deliberately absent: the
|
|
6
|
+
* old code sets them but never strips them, and this preserves that exactly.)
|
|
7
|
+
*/
|
|
8
|
+
export const CONFIG_DIR_ENV_KEYS = [
|
|
9
|
+
'CLAUDE_CONFIG_DIR',
|
|
10
|
+
'CODEX_HOME',
|
|
11
|
+
'COPILOT_HOME',
|
|
12
|
+
'KIMI_CODE_HOME',
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* Strip every config-dir env key except the one(s) this harness sets. `keep=[]`
|
|
16
|
+
* (the default / no-config-dir harness) deletes all four — the old `else` arm.
|
|
17
|
+
*/
|
|
18
|
+
export function stripForeignConfigDir(result, keep = []) {
|
|
19
|
+
for (const key of CONFIG_DIR_ENV_KEYS) {
|
|
20
|
+
if (!keep.includes(key))
|
|
21
|
+
delete result[key];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const REGISTRY = new Map();
|
|
25
|
+
/**
|
|
26
|
+
* The no-behavior adapter — a harness with no managed config-dir env and no
|
|
27
|
+
* launch-arg quirks. buildExecEnv strips all four config-dir keys for it (the old
|
|
28
|
+
* `else` arm) by falling back to {@link stripForeignConfigDir} when an adapter
|
|
29
|
+
* omits `applyExecConfigEnv`.
|
|
30
|
+
*/
|
|
31
|
+
function defaultAdapter(id) {
|
|
32
|
+
return { id };
|
|
33
|
+
}
|
|
34
|
+
export function registerHarnessAdapter(adapter) {
|
|
35
|
+
REGISTRY.set(adapter.id, adapter);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The behavior adapter for a harness. Every `AgentId` resolves to an adapter —
|
|
39
|
+
* one with real behavior when registered, else the id-only default (the old
|
|
40
|
+
* `else` arm). Callers therefore never name-check a harness.
|
|
41
|
+
*/
|
|
42
|
+
export function resolveHarnessAdapter(id) {
|
|
43
|
+
return REGISTRY.get(id) ?? defaultAdapter(id);
|
|
44
|
+
}
|
|
45
|
+
export function listHarnessAdapters() {
|
|
46
|
+
return [...REGISTRY.keys()].sort();
|
|
47
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import { stripForeignConfigDir } from '../adapter.js';
|
|
3
|
+
export const claudeAdapter = {
|
|
4
|
+
id: 'claude',
|
|
5
|
+
applyExecConfigEnv(result, ctx) {
|
|
6
|
+
const { versionHome } = ctx;
|
|
7
|
+
// The per-account `claude setup-token` only resolves when there is a version
|
|
8
|
+
// home to key it to; version===null (claude unresolved / not installed) yields
|
|
9
|
+
// null, exactly as the routines path treats it (`runner.ts:1017-1021`). The
|
|
10
|
+
// token decision below runs even then, so an ambient inherited value is stripped
|
|
11
|
+
// on the routines/provisioned path regardless of whether a version resolved.
|
|
12
|
+
// resolveClaudeSetupToken is injected (see ExecConfigEnvCtx) to keep this
|
|
13
|
+
// adapter import-leaf — importing claude-account-token here would drag the
|
|
14
|
+
// secrets/sqlite graph into shims.ts.
|
|
15
|
+
const setupToken = versionHome ? ctx.resolveClaudeSetupToken(versionHome) : null;
|
|
16
|
+
if (versionHome) {
|
|
17
|
+
result.CLAUDE_CONFIG_DIR = path.join(versionHome, '.claude');
|
|
18
|
+
// A managed pin lives in a per-version dir; Claude Code's own background
|
|
19
|
+
// auto-updater would rewrite that pinned binary in place (and has left it
|
|
20
|
+
// half-swapped and broken). Disable it so a pin stays a pin. Honor an
|
|
21
|
+
// explicit user value — from process.env (already in result) or from
|
|
22
|
+
// options.env (spread over result below).
|
|
23
|
+
if (result.DISABLE_AUTOUPDATER === undefined) {
|
|
24
|
+
result.DISABLE_AUTOUPDATER = '1';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// The `auth` bundle's setup-token exists so a run with NO human present
|
|
28
|
+
// authenticates without the Touch-ID-gated login item — usage probes,
|
|
29
|
+
// routines, dispatched runs (claude-account-token.ts). An interactive run
|
|
30
|
+
// has a human at the TTY, and their own per-version login is the credential
|
|
31
|
+
// they established and expect; overriding it made `/status` report
|
|
32
|
+
// `Auth token: CLAUDE_CODE_OAUTH_TOKEN` on a personal machine and took every
|
|
33
|
+
// hand-driven session off that login. macOS cannot cheaply confirm a home's
|
|
34
|
+
// login first (probing the Keychain raises an authorization sheet per
|
|
35
|
+
// installed version on the `agents run` hot path — agents.ts
|
|
36
|
+
// `isClaudeCredentialFileBlank`), so interactive simply defers to Claude
|
|
37
|
+
// Code, which prompts a present human to log in if the login is missing.
|
|
38
|
+
if (ctx.interactive) {
|
|
39
|
+
// Drop an INHERITED copy of OUR OWN setup-token: an interactive launch from
|
|
40
|
+
// inside a headless agent's shell inherits that agent's injected value via
|
|
41
|
+
// sanitizeProcessEnv(process.env) and would keep authenticating as it.
|
|
42
|
+
// Matched by VALUE, so a token the user exported deliberately is a different
|
|
43
|
+
// string and is left alone (#2383). This is NARROWER than the non-interactive
|
|
44
|
+
// path below, which overwrites-or-deletes unconditionally and never inspects
|
|
45
|
+
// the inherited value — a DIFFERENT account's inherited setup-token passing
|
|
46
|
+
// through this equality check is the adjacent hole RUSH-2360 leaves as
|
|
47
|
+
// follow-up (it does not silently run on a *shared, rotating* token, which is
|
|
48
|
+
// what caused the RUSH-1822 logout storm).
|
|
49
|
+
if (setupToken && result.CLAUDE_CODE_OAUTH_TOKEN === setupToken) {
|
|
50
|
+
delete result.CLAUDE_CODE_OAUTH_TOKEN;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
// Non-interactive (routines, dispatched, provisioned box): mirror the routines
|
|
55
|
+
// path (`runner.ts:1017-1021`) UNCONDITIONALLY. Inject the per-account
|
|
56
|
+
// setup-token when one resolves — it replaces any ambient shared value
|
|
57
|
+
// inherited from the launcher. When NONE resolves, STRIP the ambient
|
|
58
|
+
// CLAUDE_CODE_OAUTH_TOKEN so a run on a provisioned box can never silently
|
|
59
|
+
// authenticate as the shared, rotating token an earlier version of this path
|
|
60
|
+
// let through — the RUSH-1822 fleet-wide-logout hazard, tracked by RUSH-2360.
|
|
61
|
+
// A missing login then fails loud (401) against this home's own credential
|
|
62
|
+
// instead of quietly borrowing another's. options.env still wins below for an
|
|
63
|
+
// explicit caller override.
|
|
64
|
+
if (setupToken) {
|
|
65
|
+
result.CLAUDE_CODE_OAUTH_TOKEN = setupToken;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
delete result.CLAUDE_CODE_OAUTH_TOKEN;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
stripForeignConfigDir(result, ['CLAUDE_CONFIG_DIR']);
|
|
72
|
+
},
|
|
73
|
+
shimConfigEnvBash(ctx) {
|
|
74
|
+
return `
|
|
75
|
+
# Claude stores OAuth credentials in the macOS keychain. Scope them to the
|
|
76
|
+
# selected version's config directory so switching versions also switches the
|
|
77
|
+
# live Claude account.
|
|
78
|
+
export CLAUDE_CONFIG_DIR="$VERSION_DIR/home/${ctx.configDirName}"
|
|
79
|
+
# Managed installs are pinned in a per-version dir; Claude Code's background
|
|
80
|
+
# auto-updater would rewrite the pinned binary in place. Disable it so a pin
|
|
81
|
+
# stays a pin. An explicit user value always wins.
|
|
82
|
+
export DISABLE_AUTOUPDATER="\${DISABLE_AUTOUPDATER:-1}"
|
|
83
|
+
# On Linux sandboxes (no keychain), fall back to a per-version token file.
|
|
84
|
+
# The env var always wins if already set; no-op on macOS.
|
|
85
|
+
if [ "\$(uname -s)" = "Linux" ] && [ -z "\${CLAUDE_CODE_OAUTH_TOKEN:-}" ] && [ -f "\$CLAUDE_CONFIG_DIR/.oauth_token" ]; then
|
|
86
|
+
CLAUDE_CODE_OAUTH_TOKEN=\$(cat "\$CLAUDE_CONFIG_DIR/.oauth_token")
|
|
87
|
+
export CLAUDE_CODE_OAUTH_TOKEN
|
|
88
|
+
fi
|
|
89
|
+
`;
|
|
90
|
+
},
|
|
91
|
+
routineModeArgs(cmd, ctx) {
|
|
92
|
+
const mode = ctx.mode;
|
|
93
|
+
if (mode === 'edit') {
|
|
94
|
+
const planIndex = cmd.indexOf('plan');
|
|
95
|
+
if (planIndex !== -1)
|
|
96
|
+
cmd[planIndex] = 'acceptEdits';
|
|
97
|
+
}
|
|
98
|
+
else if (mode === 'auto') {
|
|
99
|
+
const planIndex = cmd.indexOf('plan');
|
|
100
|
+
if (planIndex !== -1)
|
|
101
|
+
cmd[planIndex] = 'auto';
|
|
102
|
+
}
|
|
103
|
+
else if (mode === 'skip') {
|
|
104
|
+
// Replace --permission-mode plan with --dangerously-skip-permissions
|
|
105
|
+
const pmIndex = cmd.indexOf('--permission-mode');
|
|
106
|
+
if (pmIndex !== -1)
|
|
107
|
+
cmd.splice(pmIndex, 2, '--dangerously-skip-permissions');
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
};
|