@phnx-labs/agents-cli 1.20.51 → 1.20.53
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 +71 -0
- package/README.md +12 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/browser.js +215 -7
- package/dist/commands/cloud.d.ts +3 -0
- package/dist/commands/cloud.js +8 -1
- package/dist/commands/events.d.ts +1 -1
- package/dist/commands/events.js +2 -3
- package/dist/commands/exec.js +82 -3
- package/dist/commands/factory.js +8 -0
- package/dist/commands/feed.d.ts +38 -0
- package/dist/commands/feed.js +274 -0
- package/dist/commands/logs.d.ts +5 -1
- package/dist/commands/logs.js +248 -3
- package/dist/commands/mcp.js +7 -0
- package/dist/commands/memory.d.ts +9 -0
- package/dist/commands/memory.js +164 -0
- package/dist/commands/message.d.ts +11 -6
- package/dist/commands/message.js +140 -5
- package/dist/commands/routines.js +12 -0
- package/dist/commands/secrets-migrate.d.ts +2 -1
- package/dist/commands/secrets-migrate.js +88 -13
- package/dist/commands/secrets.d.ts +22 -0
- package/dist/commands/secrets.js +176 -42
- package/dist/commands/sessions.js +10 -1
- package/dist/commands/teams.js +4 -0
- package/dist/commands/worktree.js +4 -2
- package/dist/index.js +21 -22
- package/dist/lib/agents.js +249 -17
- package/dist/lib/answer-router.d.ts +75 -0
- package/dist/lib/answer-router.js +149 -0
- package/dist/lib/ask-classifier.d.ts +71 -0
- package/dist/lib/ask-classifier.js +197 -0
- package/dist/lib/browser/login-detection.d.ts +94 -0
- package/dist/lib/browser/login-detection.js +274 -0
- package/dist/lib/browser/profiles.d.ts +17 -8
- package/dist/lib/browser/profiles.js +27 -8
- package/dist/lib/browser/secret-ref.d.ts +10 -0
- package/dist/lib/browser/secret-ref.js +14 -0
- package/dist/lib/browser/service.js +14 -12
- package/dist/lib/cloud/antigravity.d.ts +0 -2
- package/dist/lib/cloud/antigravity.js +2 -17
- package/dist/lib/cloud/codex.js +3 -18
- package/dist/lib/cloud/rush.d.ts +15 -0
- package/dist/lib/cloud/rush.js +10 -16
- package/dist/lib/cloud/stream.js +2 -0
- package/dist/lib/cloud/types.d.ts +21 -0
- package/dist/lib/cloud/types.js +81 -0
- package/dist/lib/crabbox/cli.d.ts +1 -1
- package/dist/lib/crabbox/cli.js +12 -2
- package/dist/lib/crabbox/lease.d.ts +19 -0
- package/dist/lib/crabbox/lease.js +22 -11
- package/dist/lib/crabbox/progress.d.ts +62 -0
- package/dist/lib/crabbox/progress.js +129 -0
- package/dist/lib/crabbox/runtimes.d.ts +38 -1
- package/dist/lib/crabbox/runtimes.js +98 -5
- package/dist/lib/daemon.d.ts +12 -9
- package/dist/lib/daemon.js +32 -17
- package/dist/lib/events.d.ts +31 -5
- package/dist/lib/events.js +291 -101
- package/dist/lib/exec.js +20 -1
- package/dist/lib/feed-outcome.d.ts +101 -0
- package/dist/lib/feed-outcome.js +244 -0
- package/dist/lib/feed-policy.d.ts +30 -0
- package/dist/lib/feed-policy.js +133 -0
- package/dist/lib/feed.d.ts +180 -0
- package/dist/lib/feed.js +627 -0
- package/dist/lib/git.d.ts +17 -1
- package/dist/lib/git.js +20 -1
- package/dist/lib/hooks.js +529 -14
- package/dist/lib/hosts/passthrough.d.ts +3 -3
- package/dist/lib/hosts/passthrough.js +3 -3
- package/dist/lib/mailbox-gc.d.ts +22 -0
- package/dist/lib/mailbox-gc.js +161 -0
- package/dist/lib/mailbox.d.ts +26 -2
- package/dist/lib/mailbox.js +80 -5
- package/dist/lib/mcp.js +82 -0
- package/dist/lib/memory.d.ts +55 -0
- package/dist/lib/memory.js +274 -0
- package/dist/lib/notify.d.ts +16 -0
- package/dist/lib/notify.js +61 -0
- package/dist/lib/operator.d.ts +26 -0
- package/dist/lib/operator.js +107 -0
- package/dist/lib/plugins.d.ts +35 -0
- package/dist/lib/plugins.js +217 -0
- package/dist/lib/remote-agents-json.d.ts +14 -0
- package/dist/lib/remote-agents-json.js +94 -0
- package/dist/lib/resources/mcp.js +44 -0
- package/dist/lib/resources/memory.d.ts +15 -0
- package/dist/lib/resources/memory.js +46 -0
- package/dist/lib/resources/types.d.ts +2 -2
- package/dist/lib/rotate.js +2 -0
- package/dist/lib/runner.d.ts +43 -0
- package/dist/lib/runner.js +323 -74
- package/dist/lib/sandbox.js +6 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/agent.d.ts +21 -0
- package/dist/lib/secrets/agent.js +63 -1
- package/dist/lib/secrets/bundles.d.ts +33 -1
- package/dist/lib/secrets/bundles.js +76 -21
- package/dist/lib/secrets/icloud-import.d.ts +79 -0
- package/dist/lib/secrets/icloud-import.js +203 -0
- package/dist/lib/secrets/index.d.ts +142 -2
- package/dist/lib/secrets/index.js +695 -30
- package/dist/lib/secrets/remote.js +1 -1
- package/dist/lib/secrets/sync.js +1 -1
- package/dist/lib/session/active.d.ts +18 -0
- package/dist/lib/session/active.js +47 -17
- package/dist/lib/session/db.d.ts +9 -1
- package/dist/lib/session/db.js +18 -3
- package/dist/lib/session/discover.d.ts +13 -0
- package/dist/lib/session/discover.js +32 -2
- package/dist/lib/session/parse.d.ts +8 -0
- package/dist/lib/session/parse.js +42 -21
- package/dist/lib/session/remote-active.js +8 -89
- package/dist/lib/session/state.d.ts +11 -0
- package/dist/lib/session/state.js +50 -1
- package/dist/lib/session/tail.d.ts +23 -4
- package/dist/lib/session/tail.js +34 -16
- package/dist/lib/session/throughput.d.ts +30 -0
- package/dist/lib/session/throughput.js +86 -0
- package/dist/lib/shim-heal.d.ts +12 -3
- package/dist/lib/shim-heal.js +12 -6
- package/dist/lib/staleness/detectors/subagents.js +57 -3
- package/dist/lib/staleness/writers/hooks.js +7 -3
- package/dist/lib/staleness/writers/subagents.js +37 -6
- package/dist/lib/startup/command-registry.d.ts +2 -0
- package/dist/lib/startup/command-registry.js +4 -0
- package/dist/lib/state.d.ts +2 -0
- package/dist/lib/state.js +25 -8
- package/dist/lib/subagents.d.ts +52 -0
- package/dist/lib/subagents.js +315 -12
- package/dist/lib/teams/agents.js +6 -3
- package/dist/lib/teams/worktree.d.ts +8 -0
- package/dist/lib/teams/worktree.js +8 -0
- package/dist/lib/types.d.ts +20 -2
- package/dist/lib/versions.js +38 -48
- package/dist/lib/whats-new.d.ts +5 -3
- package/dist/lib/whats-new.js +25 -5
- package/package.json +4 -3
- package/scripts/postinstall.js +61 -1
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort login-state detection for browser profiles.
|
|
3
|
+
*
|
|
4
|
+
* Reads a profile's Chromium cookie store (presence only — never decrypts the
|
|
5
|
+
* Keychain-encrypted values) to tell which login-gated services have a live
|
|
6
|
+
* session in that profile. Powers the `agents browser start` guardrail (warn
|
|
7
|
+
* when an agent opens a logged-out profile for a login-gated URL) and the
|
|
8
|
+
* `agents browser profiles logins` view.
|
|
9
|
+
*
|
|
10
|
+
* Everything here is advisory: any failure (missing DB, locked file, unknown
|
|
11
|
+
* schema) degrades to "no known session" and NEVER throws to callers — browser
|
|
12
|
+
* start must not slow down or break because cookie inspection hiccuped.
|
|
13
|
+
*/
|
|
14
|
+
import * as fs from 'fs';
|
|
15
|
+
import * as os from 'os';
|
|
16
|
+
import * as path from 'path';
|
|
17
|
+
import Database from '../sqlite.js';
|
|
18
|
+
import { getBrowserRuntimeDir, listProfiles } from './profiles.js';
|
|
19
|
+
/**
|
|
20
|
+
* Known login-gated services. Presence of ANY listed cookie on a matching host
|
|
21
|
+
* (unexpired, or a session cookie) = logged in. Deliberately conservative: a
|
|
22
|
+
* service absent from this map simply never triggers a warning — we do not
|
|
23
|
+
* guess. Each cookie name here is the definitive authenticated-session token for
|
|
24
|
+
* that service (e.g. LinkedIn's `li_at` is only set for a signed-in member;
|
|
25
|
+
* visitor cookies like `bcookie`/`JSESSIONID` are NOT auth). Services whose
|
|
26
|
+
* session is not reliably expressed as a recognizable cookie (e.g. Attio, whose
|
|
27
|
+
* only same-site cookies are third-party analytics) are deliberately omitted so
|
|
28
|
+
* we never emit a false "logged out" warning. Extend as needed.
|
|
29
|
+
*/
|
|
30
|
+
export const AUTH_SIGNATURES = {
|
|
31
|
+
linkedin: {
|
|
32
|
+
hosts: ['linkedin.com'],
|
|
33
|
+
cookies: ['li_at'],
|
|
34
|
+
login: { loginUrl: 'https://www.linkedin.com/login', keyPrefix: 'LINKEDIN' },
|
|
35
|
+
},
|
|
36
|
+
google: {
|
|
37
|
+
hosts: ['google.com'],
|
|
38
|
+
cookies: ['SID', 'SAPISID', '__Secure-1PSID', '__Secure-3PSID'],
|
|
39
|
+
login: { loginUrl: 'https://accounts.google.com/', keyPrefix: 'GOOGLE' },
|
|
40
|
+
},
|
|
41
|
+
x: {
|
|
42
|
+
hosts: ['x.com', 'twitter.com'],
|
|
43
|
+
cookies: ['auth_token'],
|
|
44
|
+
login: { loginUrl: 'https://x.com/login', keyPrefix: 'X' },
|
|
45
|
+
},
|
|
46
|
+
reddit: {
|
|
47
|
+
hosts: ['reddit.com'],
|
|
48
|
+
cookies: ['reddit_session', 'token_v2'],
|
|
49
|
+
login: { loginUrl: 'https://www.reddit.com/login', keyPrefix: 'REDDIT' },
|
|
50
|
+
},
|
|
51
|
+
github: {
|
|
52
|
+
hosts: ['github.com'],
|
|
53
|
+
cookies: ['user_session'],
|
|
54
|
+
login: { loginUrl: 'https://github.com/login', keyPrefix: 'GITHUB' },
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* The bundle keys a profile's `--secrets` bundle should hold to log into
|
|
59
|
+
* `service`: `<PREFIX>_USERNAME` / `<PREFIX>_PASSWORD`. Null for services with no
|
|
60
|
+
* login metadata. Feed a resolved value to the page with
|
|
61
|
+
* `agents browser type <ref> --secret <bundle>/<KEY>` (never printed).
|
|
62
|
+
*/
|
|
63
|
+
export function credKeysForService(service) {
|
|
64
|
+
const login = AUTH_SIGNATURES[service]?.login;
|
|
65
|
+
if (!login)
|
|
66
|
+
return null;
|
|
67
|
+
return { user: `${login.keyPrefix}_USERNAME`, pass: `${login.keyPrefix}_PASSWORD` };
|
|
68
|
+
}
|
|
69
|
+
/** The interactive login URL for a service, or null. */
|
|
70
|
+
export function loginUrlForService(service) {
|
|
71
|
+
return AUTH_SIGNATURES[service]?.login?.loginUrl ?? null;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Current time as microseconds since 1601-01-01 (Chromium's `expires_utc`
|
|
75
|
+
* epoch), as a BigInt. Chromium expiries routinely exceed 2^53, so this is
|
|
76
|
+
* bound into SQL as a BigInt and the comparison happens IN SQLite — the huge
|
|
77
|
+
* integer never marshals back to JS (node:sqlite throws RangeError if it does).
|
|
78
|
+
* 11644473600000 = ms between 1601-01-01 and 1970-01-01.
|
|
79
|
+
*/
|
|
80
|
+
function chromeNowMicrosBigInt() {
|
|
81
|
+
return (BigInt(Date.now()) + 11644473600000n) * 1000n;
|
|
82
|
+
}
|
|
83
|
+
function hostMatches(hostKey, host) {
|
|
84
|
+
return hostKey === host || hostKey === '.' + host || hostKey.endsWith('.' + host);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Pure core: given a profile's (already expiry-filtered) cookie rows, return the
|
|
88
|
+
* services with a live session. Exported for direct unit testing without disk.
|
|
89
|
+
*/
|
|
90
|
+
export function detectServices(rows) {
|
|
91
|
+
const services = [];
|
|
92
|
+
for (const [service, sig] of Object.entries(AUTH_SIGNATURES)) {
|
|
93
|
+
const found = rows.some((r) => sig.cookies.includes(r.name) && sig.hosts.some((h) => hostMatches(r.host_key, h)));
|
|
94
|
+
if (found)
|
|
95
|
+
services.push(service);
|
|
96
|
+
}
|
|
97
|
+
return services;
|
|
98
|
+
}
|
|
99
|
+
/** Map a URL to a known login-gated service key, or null. */
|
|
100
|
+
export function serviceForUrl(url) {
|
|
101
|
+
let host;
|
|
102
|
+
try {
|
|
103
|
+
host = new URL(url).hostname;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
for (const [service, sig] of Object.entries(AUTH_SIGNATURES)) {
|
|
109
|
+
if (sig.hosts.some((h) => host === h || host.endsWith('.' + h)))
|
|
110
|
+
return service;
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Locate candidate Chromium DBs for a profile at the given relative paths. The
|
|
116
|
+
* live runtime dir is keyed by the composite `<profile>@<endpoint>` name, so we
|
|
117
|
+
* scan the browser cache root for `<profile>` and `<profile>@*` dirs and return
|
|
118
|
+
* every matching DB found, most-recently-modified first.
|
|
119
|
+
*/
|
|
120
|
+
function profileDbCandidates(profileName, relPaths) {
|
|
121
|
+
const root = getBrowserRuntimeDir();
|
|
122
|
+
let entries;
|
|
123
|
+
try {
|
|
124
|
+
entries = fs.readdirSync(root);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
const dirs = entries.filter((e) => e === profileName || e.startsWith(profileName + '@'));
|
|
130
|
+
const found = [];
|
|
131
|
+
for (const d of dirs) {
|
|
132
|
+
for (const rel of relPaths) {
|
|
133
|
+
const p = path.join(root, d, ...rel);
|
|
134
|
+
try {
|
|
135
|
+
found.push({ p, mtime: fs.statSync(p).mtimeMs });
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
/* not present */
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return found.sort((a, b) => b.mtime - a.mtime).map((f) => f.p);
|
|
143
|
+
}
|
|
144
|
+
function cookieDbCandidates(profileName) {
|
|
145
|
+
return profileDbCandidates(profileName, [
|
|
146
|
+
['chrome-data', 'Default', 'Cookies'],
|
|
147
|
+
['chrome-data', 'Default', 'Network', 'Cookies'],
|
|
148
|
+
]);
|
|
149
|
+
}
|
|
150
|
+
function loginDbCandidates(profileName) {
|
|
151
|
+
return profileDbCandidates(profileName, [['chrome-data', 'Default', 'Login Data']]);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Open a Chromium SQLite store safely: copy the DB (and any WAL/SHM sidecars) to
|
|
155
|
+
* a temp dir first so a running browser's lock never blocks us and we never
|
|
156
|
+
* touch the live file, run `fn`, then clean up. Returns `fallback` on any error.
|
|
157
|
+
*/
|
|
158
|
+
function withCopiedDb(dbPath, fn, fallback) {
|
|
159
|
+
let tmpDir = null;
|
|
160
|
+
try {
|
|
161
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agents-browserdb-'));
|
|
162
|
+
const tmpDb = path.join(tmpDir, 'db');
|
|
163
|
+
fs.copyFileSync(dbPath, tmpDb);
|
|
164
|
+
for (const suffix of ['-wal', '-shm']) {
|
|
165
|
+
if (fs.existsSync(dbPath + suffix))
|
|
166
|
+
fs.copyFileSync(dbPath + suffix, tmpDb + suffix);
|
|
167
|
+
}
|
|
168
|
+
const db = new Database(tmpDb);
|
|
169
|
+
try {
|
|
170
|
+
return fn(db);
|
|
171
|
+
}
|
|
172
|
+
finally {
|
|
173
|
+
db.close();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return fallback;
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
if (tmpDir) {
|
|
181
|
+
try {
|
|
182
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
/* temp cleanup best-effort */
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/** Read (expiry-filtered) cookie rows from a Chromium cookie DB. [] on failure. */
|
|
191
|
+
function readCookieRows(dbPath) {
|
|
192
|
+
return withCopiedDb(dbPath, (db) => {
|
|
193
|
+
// Filter expiry in SQL (session cookies have expires_utc = 0; otherwise it
|
|
194
|
+
// must be in the future) and select only text columns, so the huge
|
|
195
|
+
// microsecond integer never crosses into JS.
|
|
196
|
+
const rows = db
|
|
197
|
+
.prepare('SELECT host_key, name FROM cookies WHERE expires_utc = 0 OR expires_utc > ?')
|
|
198
|
+
.all(chromeNowMicrosBigInt());
|
|
199
|
+
return rows.map((r) => ({
|
|
200
|
+
host_key: String(r.host_key ?? ''),
|
|
201
|
+
name: String(r.name ?? ''),
|
|
202
|
+
}));
|
|
203
|
+
}, []);
|
|
204
|
+
}
|
|
205
|
+
/** Read saved-login rows from a Chromium `Login Data` DB. Skips
|
|
206
|
+
* user-blacklisted origins ("never save"). Selects only text columns —
|
|
207
|
+
* `password_value` (encrypted) is never touched. [] on failure. */
|
|
208
|
+
function readLoginRows(dbPath) {
|
|
209
|
+
return withCopiedDb(dbPath, (db) => {
|
|
210
|
+
const rows = db
|
|
211
|
+
.prepare('SELECT origin_url, username_value, signon_realm FROM logins WHERE blacklisted_by_user = 0')
|
|
212
|
+
.all();
|
|
213
|
+
return rows.map((r) => ({
|
|
214
|
+
origin_url: String(r.origin_url ?? ''),
|
|
215
|
+
username_value: String(r.username_value ?? ''),
|
|
216
|
+
signon_realm: String(r.signon_realm ?? ''),
|
|
217
|
+
}));
|
|
218
|
+
}, []);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Map a profile's saved logins to service → account username (identity, not
|
|
222
|
+
* session). Best-effort; `{}` if `Login Data` is unreadable. The first non-empty
|
|
223
|
+
* username per service wins.
|
|
224
|
+
*/
|
|
225
|
+
export async function accountsForProfile(profileName) {
|
|
226
|
+
const candidates = loginDbCandidates(profileName);
|
|
227
|
+
if (candidates.length === 0)
|
|
228
|
+
return {};
|
|
229
|
+
const rows = readLoginRows(candidates[0]);
|
|
230
|
+
const out = {};
|
|
231
|
+
for (const r of rows) {
|
|
232
|
+
if (!r.username_value)
|
|
233
|
+
continue;
|
|
234
|
+
const service = serviceForUrl(r.origin_url) ?? serviceForUrl(r.signon_realm);
|
|
235
|
+
if (service && !out[service])
|
|
236
|
+
out[service] = r.username_value;
|
|
237
|
+
}
|
|
238
|
+
return out;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* For each service with a LIVE session in the profile (cookie-gated), the
|
|
242
|
+
* account it is signed in as (from saved logins). Intersecting with live
|
|
243
|
+
* sessions means a stale saved-login for a service you've since logged out of
|
|
244
|
+
* never shows.
|
|
245
|
+
*/
|
|
246
|
+
export async function loginsWithAccountsForProfile(profileName) {
|
|
247
|
+
const active = await loginsForProfile(profileName);
|
|
248
|
+
if (active.length === 0)
|
|
249
|
+
return [];
|
|
250
|
+
const accounts = await accountsForProfile(profileName);
|
|
251
|
+
return active.map((service) => ({ service, username: accounts[service] }));
|
|
252
|
+
}
|
|
253
|
+
/** Services with a live session in the given profile (best-effort; [] if none
|
|
254
|
+
* detected or the cookie store is unreadable). */
|
|
255
|
+
export async function loginsForProfile(profileName) {
|
|
256
|
+
const candidates = cookieDbCandidates(profileName);
|
|
257
|
+
if (candidates.length === 0)
|
|
258
|
+
return [];
|
|
259
|
+
const rows = readCookieRows(candidates[0]);
|
|
260
|
+
if (rows.length === 0)
|
|
261
|
+
return [];
|
|
262
|
+
return detectServices(rows);
|
|
263
|
+
}
|
|
264
|
+
/** Profile names that have a live session for the given service. */
|
|
265
|
+
export async function profilesLoggedInto(service) {
|
|
266
|
+
const profiles = await listProfiles();
|
|
267
|
+
const out = [];
|
|
268
|
+
for (const p of profiles) {
|
|
269
|
+
const logins = await loginsForProfile(p.name);
|
|
270
|
+
if (logins.includes(service))
|
|
271
|
+
out.push(p.name);
|
|
272
|
+
}
|
|
273
|
+
return out;
|
|
274
|
+
}
|
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
import type { BrowserProfile } from './types.js';
|
|
2
2
|
export type { BrowserProfile } from './types.js';
|
|
3
3
|
export declare const DEFAULT_BROWSER_PROFILE_NAME = "default";
|
|
4
|
+
/**
|
|
5
|
+
* The device-local configured default profile name (set via
|
|
6
|
+
* `agents browser profiles set-default`), or undefined when unset. When set, it
|
|
7
|
+
* is the profile `agents browser start` resolves to for BOTH the no-`--profile`
|
|
8
|
+
* path and an explicit `--profile default`. Stored per-machine — see
|
|
9
|
+
* `Meta.defaultBrowserProfile`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getConfiguredDefaultProfileName(): string | undefined;
|
|
4
12
|
export declare function getBrowserRuntimeDir(): string;
|
|
5
13
|
export declare function getProfileRuntimeDir(name: string): string;
|
|
6
14
|
export declare function listProfiles(): Promise<BrowserProfile[]>;
|
|
7
15
|
export declare function getProfile(name: string): Promise<BrowserProfile | null>;
|
|
8
16
|
/**
|
|
9
|
-
*
|
|
10
|
-
* Chromium-family browser per the platform priority list in chrome.ts.
|
|
17
|
+
* Resolve the profile `agents browser start` uses when no `--profile` is given.
|
|
11
18
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
19
|
+
* Order: (1) the device-local configured default (`agents browser profiles
|
|
20
|
+
* set-default <name>`) when it names an existing profile; (2) an existing
|
|
21
|
+
* `default` profile as-is; (3) auto-pick the first installed Chromium-family
|
|
22
|
+
* browser per the platform priority list in chrome.ts (macOS: chrome > brave >
|
|
23
|
+
* edge > chromium > comet; Linux: chrome > chromium > brave > edge; Windows:
|
|
24
|
+
* edge > chrome > brave > comet) and pin a new `default` profile to it. Throws an
|
|
25
|
+
* actionable error if none of those binaries are installed. A configured default
|
|
26
|
+
* that no longer exists warns and falls through to (2)/(3) — never a hard fail.
|
|
18
27
|
*/
|
|
19
28
|
export declare function ensureDefaultBrowserProfile(): Promise<BrowserProfile>;
|
|
20
29
|
/**
|
|
@@ -3,6 +3,16 @@ import { getBrowserRuntimeDir as getBrowserRuntimeDirRoot, readMeta, writeMeta,
|
|
|
3
3
|
import { findBrowserPath, findFirstInstalledBrowser, isPortInUse } from './chrome.js';
|
|
4
4
|
import { DEFAULT_VIEWPORT } from './devices.js';
|
|
5
5
|
export const DEFAULT_BROWSER_PROFILE_NAME = 'default';
|
|
6
|
+
/**
|
|
7
|
+
* The device-local configured default profile name (set via
|
|
8
|
+
* `agents browser profiles set-default`), or undefined when unset. When set, it
|
|
9
|
+
* is the profile `agents browser start` resolves to for BOTH the no-`--profile`
|
|
10
|
+
* path and an explicit `--profile default`. Stored per-machine — see
|
|
11
|
+
* `Meta.defaultBrowserProfile`.
|
|
12
|
+
*/
|
|
13
|
+
export function getConfiguredDefaultProfileName() {
|
|
14
|
+
return readMeta().defaultBrowserProfile || undefined;
|
|
15
|
+
}
|
|
6
16
|
export function getBrowserRuntimeDir() {
|
|
7
17
|
return getBrowserRuntimeDirRoot();
|
|
8
18
|
}
|
|
@@ -69,17 +79,26 @@ export async function getProfile(name) {
|
|
|
69
79
|
return configToProfile(name, config);
|
|
70
80
|
}
|
|
71
81
|
/**
|
|
72
|
-
*
|
|
73
|
-
* Chromium-family browser per the platform priority list in chrome.ts.
|
|
82
|
+
* Resolve the profile `agents browser start` uses when no `--profile` is given.
|
|
74
83
|
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
84
|
+
* Order: (1) the device-local configured default (`agents browser profiles
|
|
85
|
+
* set-default <name>`) when it names an existing profile; (2) an existing
|
|
86
|
+
* `default` profile as-is; (3) auto-pick the first installed Chromium-family
|
|
87
|
+
* browser per the platform priority list in chrome.ts (macOS: chrome > brave >
|
|
88
|
+
* edge > chromium > comet; Linux: chrome > chromium > brave > edge; Windows:
|
|
89
|
+
* edge > chrome > brave > comet) and pin a new `default` profile to it. Throws an
|
|
90
|
+
* actionable error if none of those binaries are installed. A configured default
|
|
91
|
+
* that no longer exists warns and falls through to (2)/(3) — never a hard fail.
|
|
81
92
|
*/
|
|
82
93
|
export async function ensureDefaultBrowserProfile() {
|
|
94
|
+
const configured = getConfiguredDefaultProfileName();
|
|
95
|
+
if (configured) {
|
|
96
|
+
const chosen = await getProfile(configured);
|
|
97
|
+
if (chosen)
|
|
98
|
+
return chosen;
|
|
99
|
+
console.warn(`warning: configured default browser profile "${configured}" no longer exists; ` +
|
|
100
|
+
`falling back to auto-detect. Fix with: agents browser profiles set-default <name> (or --unset)`);
|
|
101
|
+
}
|
|
83
102
|
const existing = await getProfile(DEFAULT_BROWSER_PROFILE_NAME);
|
|
84
103
|
if (existing)
|
|
85
104
|
return existing;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a `<bundle>/<KEY>` reference (optionally `secret:<bundle>/<KEY>`) used by
|
|
3
|
+
* `agents browser type --secret` to feed a credential from an `agents secrets`
|
|
4
|
+
* bundle into a page WITHOUT the value ever crossing stdout or the transcript.
|
|
5
|
+
* Returns null on a malformed ref.
|
|
6
|
+
*/
|
|
7
|
+
export declare function parseSecretRef(ref: string): {
|
|
8
|
+
bundle: string;
|
|
9
|
+
key: string;
|
|
10
|
+
} | null;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a `<bundle>/<KEY>` reference (optionally `secret:<bundle>/<KEY>`) used by
|
|
3
|
+
* `agents browser type --secret` to feed a credential from an `agents secrets`
|
|
4
|
+
* bundle into a page WITHOUT the value ever crossing stdout or the transcript.
|
|
5
|
+
* Returns null on a malformed ref.
|
|
6
|
+
*/
|
|
7
|
+
export function parseSecretRef(ref) {
|
|
8
|
+
const body = ref.startsWith('secret:') ? ref.slice('secret:'.length) : ref;
|
|
9
|
+
const slash = body.indexOf('/');
|
|
10
|
+
// Need a non-empty bundle before the slash and a non-empty key after it.
|
|
11
|
+
if (slash <= 0 || slash >= body.length - 1)
|
|
12
|
+
return null;
|
|
13
|
+
return { bundle: body.slice(0, slash), key: body.slice(slash + 1) };
|
|
14
|
+
}
|
|
@@ -8,7 +8,7 @@ import { getProfile, getProfileRuntimeDir, getBrowserRuntimeDir, listProfiles, e
|
|
|
8
8
|
import { killChrome, getRunningChromeInfo, launchBrowser, allocatePort } from './chrome.js';
|
|
9
9
|
import { connectLocal } from './drivers/local.js';
|
|
10
10
|
import { connectSSH, shellQuote } from './drivers/ssh.js';
|
|
11
|
-
import { clearProfileRuntime } from './runtime-state.js';
|
|
11
|
+
import { clearProfileRuntime, listProfileCacheDirs, readProfileRuntimeMeta, isProcessAlive } from './runtime-state.js';
|
|
12
12
|
import { resolveDomainSkill } from './domain-skills.js';
|
|
13
13
|
import { generateTaskId, generateShortId, generateTaskName, } from './types.js';
|
|
14
14
|
import { getRefs, resolveRefToCoords, describeRefs, healRef } from './refs.js';
|
|
@@ -415,6 +415,7 @@ export class BrowserService {
|
|
|
415
415
|
killChrome(conn.pid);
|
|
416
416
|
conn.cleanup?.();
|
|
417
417
|
this.connections.delete(profileName);
|
|
418
|
+
clearProfileRuntime(profileName);
|
|
418
419
|
}
|
|
419
420
|
return { ok: true, profile: profileName };
|
|
420
421
|
}
|
|
@@ -442,17 +443,18 @@ export class BrowserService {
|
|
|
442
443
|
killChrome(conn.pid);
|
|
443
444
|
conn.cleanup?.();
|
|
444
445
|
this.connections.delete(key);
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
446
|
+
clearProfileRuntime(key);
|
|
447
|
+
}
|
|
448
|
+
// Kill stale processes and clean runtime dirs for every composite and
|
|
449
|
+
// fork entry belonging to this profile (including `.N` forks left by
|
|
450
|
+
// earlier daemon sessions that the connection loop above didn't cover).
|
|
451
|
+
for (const dir of listProfileCacheDirs(profileName)) {
|
|
452
|
+
const dirName = path.basename(dir);
|
|
453
|
+
const meta = readProfileRuntimeMeta(dirName);
|
|
454
|
+
if (meta?.pid && meta.pid !== 0 && isProcessAlive(meta.pid, meta.command)) {
|
|
455
|
+
killChrome(meta.pid);
|
|
456
|
+
}
|
|
457
|
+
clearProfileRuntime(dirName);
|
|
456
458
|
}
|
|
457
459
|
}
|
|
458
460
|
async navigate(taskId, url, profileName) {
|
|
@@ -31,8 +31,6 @@ interface InteractionResponse {
|
|
|
31
31
|
message?: string;
|
|
32
32
|
} | string;
|
|
33
33
|
}
|
|
34
|
-
/** Map an Interactions API status string to the canonical CloudTaskStatus. */
|
|
35
|
-
export declare function mapStatus(s: string | undefined): CloudTaskStatus;
|
|
36
34
|
/** Build the Interactions API request body for a fresh dispatch. */
|
|
37
35
|
export declare function buildInteractionBody(prompt: string, model: string): Record<string, unknown>;
|
|
38
36
|
/** Parse an Interactions API response into a CloudTask (minus prompt/timestamps). */
|
|
@@ -19,26 +19,11 @@
|
|
|
19
19
|
* `cloud.providers.antigravity.secretsBundle` (never from agents.yaml), falling
|
|
20
20
|
* back to GEMINI_API_KEY / GOOGLE_API_KEY in the environment.
|
|
21
21
|
*/
|
|
22
|
-
import { resolveDispatchRepos } from './types.js';
|
|
22
|
+
import { resolveDispatchRepos, normalizeProviderStatus } from './types.js';
|
|
23
23
|
import { readAndResolveBundleEnv } from '../secrets/bundles.js';
|
|
24
24
|
const INTERACTIONS_URL = 'https://generativelanguage.googleapis.com/v1beta/interactions';
|
|
25
25
|
const DEFAULT_MODEL = 'antigravity-preview-05-2026';
|
|
26
26
|
const KEY_NAMES = ['GEMINI_API_KEY', 'GOOGLE_API_KEY'];
|
|
27
|
-
/** Map an Interactions API status string to the canonical CloudTaskStatus. */
|
|
28
|
-
export function mapStatus(s) {
|
|
29
|
-
const lower = (s ?? '').toLowerCase();
|
|
30
|
-
if (lower.includes('queue') || lower.includes('pending'))
|
|
31
|
-
return 'queued';
|
|
32
|
-
if (lower.includes('run') || lower.includes('progress'))
|
|
33
|
-
return 'running';
|
|
34
|
-
if (lower.includes('complete') || lower.includes('success'))
|
|
35
|
-
return 'completed';
|
|
36
|
-
if (lower.includes('fail') || lower.includes('error'))
|
|
37
|
-
return 'failed';
|
|
38
|
-
if (lower.includes('cancel'))
|
|
39
|
-
return 'cancelled';
|
|
40
|
-
return 'completed';
|
|
41
|
-
}
|
|
42
27
|
/** Build the Interactions API request body for a fresh dispatch. */
|
|
43
28
|
export function buildInteractionBody(prompt, model) {
|
|
44
29
|
return {
|
|
@@ -52,7 +37,7 @@ export function parseInteraction(resp) {
|
|
|
52
37
|
const id = resp.id ?? resp.interaction_id ?? `antigravity-${Date.now()}`;
|
|
53
38
|
return {
|
|
54
39
|
id,
|
|
55
|
-
status:
|
|
40
|
+
status: normalizeProviderStatus('antigravity', resp.status),
|
|
56
41
|
summary: resp.output_text,
|
|
57
42
|
environmentId: resp.environment_id,
|
|
58
43
|
};
|
package/dist/lib/cloud/codex.js
CHANGED
|
@@ -8,24 +8,9 @@
|
|
|
8
8
|
import { spawn, execFileSync } from 'child_process';
|
|
9
9
|
import * as fs from 'fs';
|
|
10
10
|
import * as path from 'path';
|
|
11
|
-
import { resolveDispatchRepos, MissingTargetError } from './types.js';
|
|
11
|
+
import { resolveDispatchRepos, normalizeProviderStatus, MissingTargetError } from './types.js';
|
|
12
12
|
import { getShimsDir } from '../state.js';
|
|
13
13
|
const SHIMS_DIR = getShimsDir();
|
|
14
|
-
/** Map a Codex Cloud status string to the canonical CloudTaskStatus enum. */
|
|
15
|
-
function mapStatus(s) {
|
|
16
|
-
const lower = s.toLowerCase();
|
|
17
|
-
if (lower.includes('queued') || lower.includes('pending'))
|
|
18
|
-
return 'queued';
|
|
19
|
-
if (lower.includes('running') || lower.includes('in_progress'))
|
|
20
|
-
return 'running';
|
|
21
|
-
if (lower.includes('completed') || lower.includes('succeeded') || lower.includes('success'))
|
|
22
|
-
return 'completed';
|
|
23
|
-
if (lower.includes('failed') || lower.includes('error'))
|
|
24
|
-
return 'failed';
|
|
25
|
-
if (lower.includes('cancelled') || lower.includes('canceled'))
|
|
26
|
-
return 'cancelled';
|
|
27
|
-
return 'running';
|
|
28
|
-
}
|
|
29
14
|
/** Locate the codex binary, checking agents-cli shims first then PATH. */
|
|
30
15
|
function findCodexBinary() {
|
|
31
16
|
// Check agents-cli shims first
|
|
@@ -77,7 +62,7 @@ function parseTaskFromText(text) {
|
|
|
77
62
|
}
|
|
78
63
|
return {
|
|
79
64
|
id: result.id || result.task_id,
|
|
80
|
-
status: result.status ?
|
|
65
|
+
status: result.status ? normalizeProviderStatus('codex', result.status) : undefined,
|
|
81
66
|
summary: result.summary || result.output,
|
|
82
67
|
};
|
|
83
68
|
}
|
|
@@ -186,7 +171,7 @@ export class CodexCloudProvider {
|
|
|
186
171
|
const tasks = (data.tasks ?? data ?? []).map((t) => ({
|
|
187
172
|
id: (t.id || t.task_id),
|
|
188
173
|
provider: 'codex',
|
|
189
|
-
status:
|
|
174
|
+
status: normalizeProviderStatus('codex', t.status ?? ''),
|
|
190
175
|
agent: 'codex',
|
|
191
176
|
prompt: (t.prompt || t.query || ''),
|
|
192
177
|
branch: t.branch || undefined,
|
package/dist/lib/cloud/rush.d.ts
CHANGED
|
@@ -29,6 +29,21 @@ export interface AccountTokenEntry {
|
|
|
29
29
|
/** Stringified OAuth credentials JSON (Mac: keychain blob; Linux: .credentials.json). */
|
|
30
30
|
credentials_json: string;
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Read the raw OAuth credentials for one Claude version. On Mac, prefer the
|
|
34
|
+
* Keychain blob (canonical). On Linux/CI, fall back to `.claude/.credentials.json`
|
|
35
|
+
* inside the version home (where the Linux Claude CLI stores its OAuth).
|
|
36
|
+
*
|
|
37
|
+
* Returns null when no credentials are findable — caller treats as "version
|
|
38
|
+
* is installed but not signed in" and skips it from the manifest.
|
|
39
|
+
*
|
|
40
|
+
* NOTE: the darwin branch returns the UNWRAPPED oauth sub-object (what the Rush
|
|
41
|
+
* server re-wraps). It is NOT the shape Claude Code reads back from
|
|
42
|
+
* `.credentials.json`. The lease exporter (crabbox/runtimes.ts) therefore only
|
|
43
|
+
* reuses the Linux `.credentials.json` branch here and reads the wrapped raw
|
|
44
|
+
* Keychain payload itself on darwin.
|
|
45
|
+
*/
|
|
46
|
+
export declare function readClaudeCredentialsBlob(home: string): Promise<string | null>;
|
|
32
47
|
/**
|
|
33
48
|
* Build a manifest of the user's local Claude installations to send on every
|
|
34
49
|
* cloud dispatch. The manifest is the contract the server uses to detect when
|
package/dist/lib/cloud/rush.js
CHANGED
|
@@ -10,7 +10,7 @@ import * as os from 'os';
|
|
|
10
10
|
import * as crypto from 'crypto';
|
|
11
11
|
import * as yaml from 'yaml';
|
|
12
12
|
import { getCloudDir } from '../state.js';
|
|
13
|
-
import { resolveDispatchRepos, MAX_IMAGES_PER_DISPATCH } from './types.js';
|
|
13
|
+
import { resolveDispatchRepos, normalizeProviderStatus, MAX_IMAGES_PER_DISPATCH } from './types.js';
|
|
14
14
|
import { parseSSE } from './stream.js';
|
|
15
15
|
import { listInstalledVersions, getVersionHomePath } from '../versions.js';
|
|
16
16
|
import { getAccountInfo } from '../agents.js';
|
|
@@ -56,18 +56,6 @@ function recordRushUploadConsent(grantedBy, accountFingerprint) {
|
|
|
56
56
|
// boundary. Worst case, the user is asked to consent again next dispatch.
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
-
/** Map a Factory Floor status string to the canonical CloudTaskStatus enum. */
|
|
60
|
-
function mapStatus(s) {
|
|
61
|
-
switch (s) {
|
|
62
|
-
case 'allocating': return 'allocating';
|
|
63
|
-
case 'running': return 'running';
|
|
64
|
-
case 'needs_review': return 'input_required';
|
|
65
|
-
case 'completed': return 'completed';
|
|
66
|
-
case 'failed': return 'failed';
|
|
67
|
-
case 'cancelled': return 'cancelled';
|
|
68
|
-
default: return 'running';
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
59
|
/** Read the Rush session access token from ~/.rush/user.yaml. */
|
|
72
60
|
function readToken() {
|
|
73
61
|
if (!fs.existsSync(USER_YAML)) {
|
|
@@ -170,8 +158,14 @@ function parsePromptCode(body) {
|
|
|
170
158
|
*
|
|
171
159
|
* Returns null when no credentials are findable — caller treats as "version
|
|
172
160
|
* is installed but not signed in" and skips it from the manifest.
|
|
161
|
+
*
|
|
162
|
+
* NOTE: the darwin branch returns the UNWRAPPED oauth sub-object (what the Rush
|
|
163
|
+
* server re-wraps). It is NOT the shape Claude Code reads back from
|
|
164
|
+
* `.credentials.json`. The lease exporter (crabbox/runtimes.ts) therefore only
|
|
165
|
+
* reuses the Linux `.credentials.json` branch here and reads the wrapped raw
|
|
166
|
+
* Keychain payload itself on darwin.
|
|
173
167
|
*/
|
|
174
|
-
async function readClaudeCredentialsBlob(home) {
|
|
168
|
+
export async function readClaudeCredentialsBlob(home) {
|
|
175
169
|
if (process.platform === 'darwin') {
|
|
176
170
|
const oauth = await loadClaudeOauth(home);
|
|
177
171
|
if (oauth && oauth.accessToken) {
|
|
@@ -446,7 +440,7 @@ export class RushCloudProvider {
|
|
|
446
440
|
return {
|
|
447
441
|
id: taskId,
|
|
448
442
|
provider: 'rush',
|
|
449
|
-
status:
|
|
443
|
+
status: normalizeProviderStatus('rush', data.status),
|
|
450
444
|
agent: data.agent || undefined,
|
|
451
445
|
prompt: data.prompt || '',
|
|
452
446
|
repo: data.repo_owner && data.repo_name ? `${data.repo_owner}/${data.repo_name}` : undefined,
|
|
@@ -471,7 +465,7 @@ export class RushCloudProvider {
|
|
|
471
465
|
return (data.executions ?? []).map((e) => ({
|
|
472
466
|
id: e.execution_id,
|
|
473
467
|
provider: 'rush',
|
|
474
|
-
status:
|
|
468
|
+
status: normalizeProviderStatus('rush', e.status),
|
|
475
469
|
agent: e.agent || undefined,
|
|
476
470
|
prompt: e.prompt || '',
|
|
477
471
|
repo: e.repo_owner && e.repo_name ? `${e.repo_owner}/${e.repo_name}` : undefined,
|
package/dist/lib/cloud/stream.js
CHANGED
|
@@ -179,6 +179,8 @@ function statusLabel(status) {
|
|
|
179
179
|
return chalk.blue(`[${status}]`);
|
|
180
180
|
case 'running':
|
|
181
181
|
return chalk.yellow(`[${status}]`);
|
|
182
|
+
case 'idle':
|
|
183
|
+
return chalk.dim('[idle]');
|
|
182
184
|
case 'completed':
|
|
183
185
|
return chalk.green('[completed]');
|
|
184
186
|
case 'needs_review':
|
|
@@ -27,6 +27,27 @@ export type CloudProviderId = 'rush' | 'codex' | 'factory' | 'antigravity';
|
|
|
27
27
|
* failed | cancelled` states, which cannot transition back to `running`.
|
|
28
28
|
*/
|
|
29
29
|
export type CloudTaskStatus = 'queued' | 'allocating' | 'running' | 'idle' | 'input_required' | 'completed' | 'failed' | 'cancelled';
|
|
30
|
+
/** Cloud backends whose wire status is normalized by `normalizeProviderStatus`. */
|
|
31
|
+
export type StatusNormalizingProvider = 'rush' | 'codex' | 'antigravity';
|
|
32
|
+
/**
|
|
33
|
+
* Normalize a provider's raw wire status into the canonical `CloudTaskStatus`.
|
|
34
|
+
*
|
|
35
|
+
* Each cloud backend speaks its own status vocabulary and had its own copy of
|
|
36
|
+
* this mapping, which had drifted. This dispatches per provider while keeping
|
|
37
|
+
* provider-specific defaults explicit:
|
|
38
|
+
* - `rush` — switch over the Factory Floor's known strings; includes
|
|
39
|
+
* `allocating` and stopped/resumable `idle` states, has no
|
|
40
|
+
* `queued`, default `running`.
|
|
41
|
+
* - `codex` — substring match on the lowercased CLI status; default
|
|
42
|
+
* `running`.
|
|
43
|
+
* - `antigravity` — substring match on the (possibly `undefined`) Interactions
|
|
44
|
+
* API status; default `completed` (its synchronous response
|
|
45
|
+
* is terminal), and `undefined`-safe.
|
|
46
|
+
*
|
|
47
|
+
* Factory's `mapResultStatus` is structurally different (it maps a droid *exit*
|
|
48
|
+
* result, not a lifecycle string) and deliberately stays in `factory.ts`.
|
|
49
|
+
*/
|
|
50
|
+
export declare function normalizeProviderStatus(provider: StatusNormalizingProvider, wireStatus: string | undefined): CloudTaskStatus;
|
|
30
51
|
/** Snapshot of a dispatched task, stored locally and refreshed from the provider. */
|
|
31
52
|
export interface CloudTask {
|
|
32
53
|
id: string;
|