@phnx-labs/agents-cli 1.22.115 → 1.22.117
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 +161 -14
- package/README.md +1 -1
- package/dist/commands/browser.js +11 -0
- package/dist/commands/exec.d.ts +18 -0
- package/dist/commands/exec.js +263 -152
- package/dist/commands/feed.js +65 -14
- package/dist/commands/sessions-picker.d.ts +11 -0
- package/dist/commands/sessions-picker.js +88 -7
- package/dist/commands/sessions.d.ts +21 -2
- package/dist/commands/sessions.js +157 -3
- package/dist/commands/setup-secrets.d.ts +2 -2
- package/dist/commands/setup-term.d.ts +25 -0
- package/dist/commands/setup-term.js +71 -0
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +12 -4
- package/dist/commands/ssh.js +1 -69
- package/dist/lib/accounting/rotate.d.ts +63 -1
- package/dist/lib/accounting/rotate.js +56 -0
- package/dist/lib/accounts/add.js +2 -2
- package/dist/lib/accounts/slots.js +32 -2
- package/dist/lib/answer-router.d.ts +11 -2
- package/dist/lib/answer-router.js +26 -2
- package/dist/lib/auth-mint.d.ts +5 -4
- package/dist/lib/auth-mint.js +4 -3
- package/dist/lib/browser/drivers/arc.d.ts +1 -1
- package/dist/lib/browser/service.d.ts +10 -0
- package/dist/lib/browser/service.js +208 -36
- package/dist/lib/browser/types.d.ts +18 -0
- package/dist/lib/config-keys.d.ts +1 -1
- package/dist/lib/config-keys.js +5 -0
- package/dist/lib/device-config.js +61 -0
- package/dist/lib/devices/doctor-findings.js +2 -6
- package/dist/lib/feed/answer.d.ts +153 -4
- package/dist/lib/feed/answer.js +716 -105
- package/dist/lib/feed/feed.d.ts +61 -1
- package/dist/lib/feed/feed.js +226 -14
- package/dist/lib/feed/hub-server.d.ts +58 -3
- package/dist/lib/feed/hub-server.js +306 -54
- package/dist/lib/feed/pr-status.d.ts +8 -0
- package/dist/lib/feed/pr-status.js +9 -1
- package/dist/lib/feed-outcome.d.ts +1 -1
- package/dist/lib/feed-outcome.js +9 -2
- package/dist/lib/feed-policy.js +9 -3
- package/dist/lib/fleet/auth-sync.d.ts +2 -55
- package/dist/lib/fleet/auth-sync.js +2 -89
- package/dist/lib/harness-auth-capabilities.js +7 -2
- package/dist/lib/hosts/dispatch.d.ts +20 -1
- package/dist/lib/hosts/dispatch.js +52 -30
- package/dist/lib/hosts/remote-cmd.d.ts +21 -0
- package/dist/lib/hosts/remote-cmd.js +27 -2
- package/dist/lib/mailbox.d.ts +12 -0
- package/dist/lib/mailbox.js +16 -2
- package/dist/lib/menubar/snapshot.d.ts +51 -0
- package/dist/lib/menubar/snapshot.js +42 -3
- package/dist/lib/open-url.js +2 -2
- package/dist/lib/placement.d.ts +2 -0
- package/dist/lib/placement.js +5 -0
- package/dist/lib/projects.d.ts +23 -0
- package/dist/lib/projects.js +78 -0
- package/dist/lib/secrets-cli.d.ts +3 -3
- package/dist/lib/secrets-cli.js +1 -1
- package/dist/lib/session/active.d.ts +1 -0
- package/dist/lib/session/active.js +8 -0
- package/dist/lib/session/db.d.ts +67 -3
- package/dist/lib/session/db.js +381 -126
- package/dist/lib/session/prompt.d.ts +23 -7
- package/dist/lib/session/prompt.js +46 -8
- package/dist/lib/session/remote/remote-list.d.ts +20 -0
- package/dist/lib/session/remote/remote-list.js +22 -6
- package/dist/lib/session/remote/watch.d.ts +12 -0
- package/dist/lib/session/remote/watch.js +9 -0
- package/dist/lib/session/remote-preview-cache.d.ts +29 -0
- package/dist/lib/session/remote-preview-cache.js +373 -0
- package/dist/lib/session/tail.d.ts +50 -0
- package/dist/lib/session/tail.js +219 -0
- package/dist/lib/setup-tool-install.js +2 -1
- package/dist/lib/setup-tool-status.d.ts +1 -1
- package/dist/lib/setup-tool-status.js +7 -1
- package/dist/lib/signin-badge.d.ts +19 -4
- package/dist/lib/signin-badge.js +29 -11
- package/dist/lib/term-driver.d.ts +24 -0
- package/dist/lib/term-driver.js +36 -0
- package/dist/lib/terminal/index.d.ts +1 -1
- package/dist/lib/terminal/index.js +1 -1
- package/dist/lib/terminal/inject.d.ts +38 -0
- package/dist/lib/terminal/inject.js +55 -9
- package/dist/lib/terminal/transport.d.ts +15 -5
- package/dist/lib/terminal/transport.js +61 -11
- package/package.json +1 -1
- package/dist/lib/fleet/remote-login.d.ts +0 -170
- package/dist/lib/fleet/remote-login.js +0 -568
|
@@ -333,6 +333,11 @@ export function arcCreateMarker() {
|
|
|
333
333
|
export function isArcCreateMarker(url) {
|
|
334
334
|
return url.startsWith(ARC_CREATE_MARKER_PREFIX);
|
|
335
335
|
}
|
|
336
|
+
/** Run `fn` once per pass and share its promise, rejections included. */
|
|
337
|
+
function onceAsync(fn) {
|
|
338
|
+
let pending;
|
|
339
|
+
return () => (pending ??= fn());
|
|
340
|
+
}
|
|
336
341
|
export class BrowserService {
|
|
337
342
|
static SOURCE_PREFIX = {
|
|
338
343
|
'rush-app': 'rush-app-',
|
|
@@ -2198,9 +2203,20 @@ export class BrowserService {
|
|
|
2198
2203
|
* it used to return (RUSH-2709).
|
|
2199
2204
|
*/
|
|
2200
2205
|
async status(profileRef) {
|
|
2206
|
+
// Built before rehydration: a cold pass rehydrates every saved runtime dir
|
|
2207
|
+
// first, and each Arc one probed Arc on its own, spending the budget before
|
|
2208
|
+
// the snapshot existed. One enumeration and one liveness read cover the pass.
|
|
2209
|
+
const arc = {
|
|
2210
|
+
spaces: onceAsync(() => enumerateArcSpaces()),
|
|
2211
|
+
running: onceAsync(() => isArcRunning()),
|
|
2212
|
+
};
|
|
2201
2213
|
// Reconnect live browsers after a daemon restart so status is not empty
|
|
2202
|
-
// while tasks.json still holds open work.
|
|
2203
|
-
|
|
2214
|
+
// while tasks.json still holds open work. Rehydration is per runtime dir:
|
|
2215
|
+
// one unreadable saved profile is reported below, never fatal to the pass.
|
|
2216
|
+
const rehydrateErrors = new Map();
|
|
2217
|
+
await this.rehydrateAllFromDisk(arc.running, (key, err) => {
|
|
2218
|
+
rehydrateErrors.set(key, err instanceof Error ? err.message : String(err));
|
|
2219
|
+
});
|
|
2204
2220
|
// A caller who pasted a runtime key out of an older listing still gets the
|
|
2205
2221
|
// profile it belongs to, rather than an empty result.
|
|
2206
2222
|
const profileName = profileRef ? parseConnectionKey(profileRef).profile : undefined;
|
|
@@ -2210,25 +2226,99 @@ export class BrowserService {
|
|
|
2210
2226
|
? [...this.connections.keys()].filter((k) => keyBelongsToProfile(k, profileName))
|
|
2211
2227
|
: Array.from(this.connections.keys());
|
|
2212
2228
|
for (const key of candidates) {
|
|
2213
|
-
|
|
2229
|
+
// Read-only: a profile that cannot be read is reported as unreadable, not
|
|
2230
|
+
// allowed to abort the listing. Before this, a single stale Arc task made
|
|
2231
|
+
// `status` throw and EVERY profile vanished, including healthy ones.
|
|
2232
|
+
let status;
|
|
2233
|
+
try {
|
|
2234
|
+
status = await this.getProfileStatus(key, arc);
|
|
2235
|
+
}
|
|
2236
|
+
catch (err) {
|
|
2237
|
+
const parsed = parseConnectionKey(key);
|
|
2238
|
+
statuses.push({
|
|
2239
|
+
name: this.connections.get(key)?.profile ?? parsed.profile,
|
|
2240
|
+
endpoint: parsed.endpoint,
|
|
2241
|
+
key,
|
|
2242
|
+
running: false,
|
|
2243
|
+
tasks: [],
|
|
2244
|
+
unavailable: err instanceof Error ? err.message : String(err),
|
|
2245
|
+
});
|
|
2246
|
+
seenProfiles.add(parsed.profile);
|
|
2247
|
+
continue;
|
|
2248
|
+
}
|
|
2214
2249
|
if (status) {
|
|
2215
2250
|
statuses.push(status);
|
|
2216
2251
|
seenProfiles.add(status.name);
|
|
2217
2252
|
}
|
|
2218
2253
|
}
|
|
2254
|
+
// Reported BEFORE the disk reconcile, and keyed by the runtime dir that
|
|
2255
|
+
// actually failed. Reconcile reads the same `tasks.json`, so without this
|
|
2256
|
+
// ordering one corrupt runtime produced two rows for the same profile: one
|
|
2257
|
+
// keyed here and one unkeyed from reconcile, which the dedupe then missed.
|
|
2258
|
+
for (const [key, message] of rehydrateErrors) {
|
|
2259
|
+
const parsed = parseConnectionKey(key);
|
|
2260
|
+
if (profileName && parsed.profile !== profileName)
|
|
2261
|
+
continue;
|
|
2262
|
+
if (statuses.some((status) => status.key === key))
|
|
2263
|
+
continue;
|
|
2264
|
+
statuses.push({
|
|
2265
|
+
name: parsed.profile,
|
|
2266
|
+
endpoint: parsed.endpoint,
|
|
2267
|
+
key,
|
|
2268
|
+
running: false,
|
|
2269
|
+
tasks: [],
|
|
2270
|
+
unavailable: message,
|
|
2271
|
+
});
|
|
2272
|
+
seenProfiles.add(parsed.profile);
|
|
2273
|
+
}
|
|
2274
|
+
// Disk reconcile reads `tasks.json` too, so it fails on exactly the same
|
|
2275
|
+
// corrupt file — report that profile rather than let it empty the table.
|
|
2276
|
+
const reconcile = async (name) => {
|
|
2277
|
+
const failures = new Map();
|
|
2278
|
+
let reconciled = null;
|
|
2279
|
+
try {
|
|
2280
|
+
reconciled = await this.reconcileFromDisk(name, (key, err) => {
|
|
2281
|
+
failures.set(key, err instanceof Error ? err.message : String(err));
|
|
2282
|
+
});
|
|
2283
|
+
}
|
|
2284
|
+
catch (err) {
|
|
2285
|
+
// Listing the profile's runtime dirs failed, so no single runtime is at
|
|
2286
|
+
// fault and none is invented: this is reported at profile level.
|
|
2287
|
+
statuses.push({
|
|
2288
|
+
name,
|
|
2289
|
+
running: false,
|
|
2290
|
+
tasks: [],
|
|
2291
|
+
unavailable: err instanceof Error ? err.message : String(err),
|
|
2292
|
+
});
|
|
2293
|
+
return;
|
|
2294
|
+
}
|
|
2295
|
+
if (reconciled)
|
|
2296
|
+
statuses.push(reconciled);
|
|
2297
|
+
// Keyed by the runtime dir that actually failed, so a profile with several
|
|
2298
|
+
// runtimes names the unreadable one rather than guessing at the first.
|
|
2299
|
+
for (const [key, message] of failures) {
|
|
2300
|
+
if (statuses.some((status) => status.key === key))
|
|
2301
|
+
continue;
|
|
2302
|
+
const parsed = parseConnectionKey(key);
|
|
2303
|
+
statuses.push({
|
|
2304
|
+
name,
|
|
2305
|
+
endpoint: parsed.endpoint,
|
|
2306
|
+
key,
|
|
2307
|
+
running: false,
|
|
2308
|
+
tasks: [],
|
|
2309
|
+
unavailable: message,
|
|
2310
|
+
});
|
|
2311
|
+
}
|
|
2312
|
+
};
|
|
2219
2313
|
if (!profileName) {
|
|
2220
2314
|
for (const profile of await listProfiles()) {
|
|
2221
2315
|
if (seenProfiles.has(profile.name))
|
|
2222
2316
|
continue;
|
|
2223
|
-
|
|
2224
|
-
if (reconciled)
|
|
2225
|
-
statuses.push(reconciled);
|
|
2317
|
+
await reconcile(profile.name);
|
|
2226
2318
|
}
|
|
2227
2319
|
}
|
|
2228
2320
|
else if (statuses.length === 0) {
|
|
2229
|
-
|
|
2230
|
-
if (reconciled)
|
|
2231
|
-
statuses.push(reconciled);
|
|
2321
|
+
await reconcile(profileName);
|
|
2232
2322
|
}
|
|
2233
2323
|
return statuses;
|
|
2234
2324
|
}
|
|
@@ -2241,14 +2331,27 @@ export class BrowserService {
|
|
|
2241
2331
|
* ({@link listProfileCacheDirs}), including the legacy pre-composite dir named
|
|
2242
2332
|
* exactly the profile.
|
|
2243
2333
|
*/
|
|
2244
|
-
async reconcileFromDisk(profileName
|
|
2334
|
+
async reconcileFromDisk(profileName,
|
|
2335
|
+
/**
|
|
2336
|
+
* Read-only callers pass a sink so an unreadable runtime dir is attributed
|
|
2337
|
+
* to the dir that actually failed, and the remaining dirs are still tried.
|
|
2338
|
+
* Callers without a sink keep the strict behaviour.
|
|
2339
|
+
*/
|
|
2340
|
+
onError) {
|
|
2245
2341
|
const dirs = listProfileCacheDirs(profileName);
|
|
2246
2342
|
for (const dir of dirs) {
|
|
2247
2343
|
const key = asConnectionKey(path.basename(dir));
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2344
|
+
try {
|
|
2345
|
+
const info = getRunningChromeInfo(key);
|
|
2346
|
+
if (!info)
|
|
2347
|
+
continue;
|
|
2348
|
+
return await this.profileStatusFromDisk(profileName, key, info.port, info.pid);
|
|
2349
|
+
}
|
|
2350
|
+
catch (err) {
|
|
2351
|
+
if (!onError)
|
|
2352
|
+
throw err;
|
|
2353
|
+
onError(key, err);
|
|
2354
|
+
}
|
|
2252
2355
|
}
|
|
2253
2356
|
return null;
|
|
2254
2357
|
}
|
|
@@ -3347,7 +3450,13 @@ export class BrowserService {
|
|
|
3347
3450
|
* Returns null when nothing is speaking CDP so callers can fall through
|
|
3348
3451
|
* to disk reconcile.
|
|
3349
3452
|
*/
|
|
3350
|
-
async attachRunningProfile(key, diskTasks
|
|
3453
|
+
async attachRunningProfile(key, diskTasks,
|
|
3454
|
+
/**
|
|
3455
|
+
* Shared Arc liveness for one status pass; each saved Arc runtime used to
|
|
3456
|
+
* probe on its own. Callers with no pass to share pass nothing and probe
|
|
3457
|
+
* directly, as before.
|
|
3458
|
+
*/
|
|
3459
|
+
arcRunning) {
|
|
3351
3460
|
const { profile: bare, endpoint: endpointFromKey } = parseConnectionKey(key);
|
|
3352
3461
|
const profile = await getProfile(bare);
|
|
3353
3462
|
if (!profile)
|
|
@@ -3371,7 +3480,7 @@ export class BrowserService {
|
|
|
3371
3480
|
// is still running. No port, no CDP — just verify Arc is alive and restore
|
|
3372
3481
|
// the task state.
|
|
3373
3482
|
if (resolved.target.startsWith('arc-native:')) {
|
|
3374
|
-
if (process.platform !== 'darwin' || !(await isArcRunning()))
|
|
3483
|
+
if (process.platform !== 'darwin' || !(await (arcRunning ?? isArcRunning)()))
|
|
3375
3484
|
return null;
|
|
3376
3485
|
const tasks = this.loadTaskState(key);
|
|
3377
3486
|
for (const [k, t] of diskTasks) {
|
|
@@ -3575,7 +3684,14 @@ export class BrowserService {
|
|
|
3575
3684
|
}
|
|
3576
3685
|
return existing;
|
|
3577
3686
|
}
|
|
3578
|
-
async rehydrateAllFromDisk(
|
|
3687
|
+
async rehydrateAllFromDisk(arcRunning,
|
|
3688
|
+
/**
|
|
3689
|
+
* Read-only callers pass a sink to make rehydration per-runtime: one saved
|
|
3690
|
+
* dir that cannot be read (corrupt `tasks.json`, an Arc Apple Event that
|
|
3691
|
+
* times out) is reported through this instead of aborting the pass. Callers
|
|
3692
|
+
* without a sink keep the strict behaviour and the error propagates.
|
|
3693
|
+
*/
|
|
3694
|
+
onError) {
|
|
3579
3695
|
const runtimeRoot = getBrowserRuntimeDir();
|
|
3580
3696
|
let dirNames = [];
|
|
3581
3697
|
try {
|
|
@@ -3599,24 +3715,31 @@ export class BrowserService {
|
|
|
3599
3715
|
// Skip non-runtime dirs (e.g. profiles/)
|
|
3600
3716
|
if (dirName === 'profiles' || dirName === 'sessions' || dirName === 'exports')
|
|
3601
3717
|
continue;
|
|
3602
|
-
const tasks = this.loadTaskState(key);
|
|
3603
|
-
if (tasks.size === 0)
|
|
3604
|
-
continue;
|
|
3605
|
-
// Soft attach only — never launch / never clear pid files.
|
|
3606
|
-
const conn = await this.attachRunningProfile(key, tasks);
|
|
3607
|
-
if (!conn)
|
|
3608
|
-
continue;
|
|
3609
|
-
// A concurrent rehydrate may have registered one for this key while we
|
|
3610
|
-
// awaited the attach; keep the winner and tear our loser's tunnel down.
|
|
3611
|
-
const registered = this.registerRehydratedConnection(key, conn);
|
|
3612
|
-
if (registered !== conn)
|
|
3613
|
-
continue;
|
|
3614
3718
|
try {
|
|
3615
|
-
|
|
3616
|
-
|
|
3719
|
+
const tasks = this.loadTaskState(key);
|
|
3720
|
+
if (tasks.size === 0)
|
|
3721
|
+
continue;
|
|
3722
|
+
// Soft attach only — never launch / never clear pid files.
|
|
3723
|
+
const conn = await this.attachRunningProfile(key, tasks, arcRunning);
|
|
3724
|
+
if (!conn)
|
|
3725
|
+
continue;
|
|
3726
|
+
// A concurrent rehydrate may have registered one for this key while we
|
|
3727
|
+
// awaited the attach; keep the winner and tear our loser's tunnel down.
|
|
3728
|
+
const registered = this.registerRehydratedConnection(key, conn);
|
|
3729
|
+
if (registered !== conn)
|
|
3730
|
+
continue;
|
|
3731
|
+
try {
|
|
3732
|
+
if (conn.backend !== 'arc-native' && conn.backend !== 'bidi')
|
|
3733
|
+
await this.applyDefaultDownloadBehavior(conn, key);
|
|
3734
|
+
}
|
|
3735
|
+
catch {
|
|
3736
|
+
// Non-fatal for rehydrate.
|
|
3737
|
+
}
|
|
3617
3738
|
}
|
|
3618
|
-
catch {
|
|
3619
|
-
|
|
3739
|
+
catch (err) {
|
|
3740
|
+
if (!onError)
|
|
3741
|
+
throw err;
|
|
3742
|
+
onError(key, err);
|
|
3620
3743
|
}
|
|
3621
3744
|
}
|
|
3622
3745
|
}
|
|
@@ -3708,14 +3831,29 @@ export class BrowserService {
|
|
|
3708
3831
|
return tabs;
|
|
3709
3832
|
}
|
|
3710
3833
|
/** Live status for ONE registered connection, addressed by its runtime key. */
|
|
3711
|
-
async getProfileStatus(key) {
|
|
3834
|
+
async getProfileStatus(key, arc) {
|
|
3712
3835
|
const conn = this.connections.get(key);
|
|
3713
3836
|
if (!conn)
|
|
3714
3837
|
return null;
|
|
3715
3838
|
if (conn.backend === 'arc-native') {
|
|
3839
|
+
// One snapshot and one liveness read for the whole pass, shared across
|
|
3840
|
+
// every task and every Arc profile (built in `status`).
|
|
3841
|
+
const spaces = arc?.spaces ?? onceAsync(() => enumerateArcSpaces());
|
|
3842
|
+
const running = arc?.running ?? onceAsync(() => isArcRunning());
|
|
3716
3843
|
const tasks = [];
|
|
3717
3844
|
for (const task of conn.tasks.values()) {
|
|
3718
|
-
|
|
3845
|
+
// The moved-tab refusal is the ownership guarantee actions rely on and
|
|
3846
|
+
// stays strict. `status` only reads, so one stale task must not take down
|
|
3847
|
+
// its neighbours: report what disk knows plus the reason, and leave
|
|
3848
|
+
// `tabs` ABSENT rather than empty so nothing claims a moved tab is live.
|
|
3849
|
+
let tabs = [];
|
|
3850
|
+
let unavailable;
|
|
3851
|
+
try {
|
|
3852
|
+
tabs = await this.listArcTaskTabsFromSnapshot(task, spaces);
|
|
3853
|
+
}
|
|
3854
|
+
catch (err) {
|
|
3855
|
+
unavailable = err instanceof Error ? err.message : String(err);
|
|
3856
|
+
}
|
|
3719
3857
|
const domains = tabs.flatMap((tab) => {
|
|
3720
3858
|
try {
|
|
3721
3859
|
const domain = new URL(tab.url).hostname.replace(/^www\./, '');
|
|
@@ -3734,6 +3872,7 @@ export class BrowserService {
|
|
|
3734
3872
|
createdAt: task.createdAt,
|
|
3735
3873
|
tabs: tabs.length ? tabs : undefined,
|
|
3736
3874
|
domains: [...new Set(domains)],
|
|
3875
|
+
unavailable,
|
|
3737
3876
|
});
|
|
3738
3877
|
}
|
|
3739
3878
|
const parsed = parseConnectionKey(key);
|
|
@@ -3741,7 +3880,7 @@ export class BrowserService {
|
|
|
3741
3880
|
name: conn.profile ?? parsed.profile,
|
|
3742
3881
|
endpoint: parsed.endpoint,
|
|
3743
3882
|
key,
|
|
3744
|
-
running: await
|
|
3883
|
+
running: await running(),
|
|
3745
3884
|
port: 0,
|
|
3746
3885
|
pid: 0,
|
|
3747
3886
|
tasks,
|
|
@@ -4217,6 +4356,39 @@ export class BrowserService {
|
|
|
4217
4356
|
}
|
|
4218
4357
|
return executeJavaScript(ref, expression);
|
|
4219
4358
|
}
|
|
4359
|
+
/**
|
|
4360
|
+
* Resolve a task's owned Arc tabs against one shared snapshot of every
|
|
4361
|
+
* window/space/tab, rather than asking Arc per tab as `listArcTaskTabs` does.
|
|
4362
|
+
*
|
|
4363
|
+
* Same ownership rule as the live path — `requireArcTask`, then the recorded
|
|
4364
|
+
* ref must be present in its original window and Space. The snapshot changes
|
|
4365
|
+
* how often Arc is asked, never what counts as owned. Actions keep the live
|
|
4366
|
+
* path, because a snapshot can go stale before it is acted on.
|
|
4367
|
+
*/
|
|
4368
|
+
async listArcTaskTabsFromSnapshot(task, spaces) {
|
|
4369
|
+
// Before the snapshot on purpose: a ref naming a window its task does not
|
|
4370
|
+
// own is a moved tab on the record alone, so it needs no round trip and
|
|
4371
|
+
// reports the right reason even when Arc is unreachable.
|
|
4372
|
+
const native = this.requireArcTask(task);
|
|
4373
|
+
const snapshot = await spaces();
|
|
4374
|
+
const tabs = [];
|
|
4375
|
+
for (const [shortId, ref] of Object.entries(native.tabs)) {
|
|
4376
|
+
const live = snapshot
|
|
4377
|
+
.find((space) => space.windowId === ref.windowId && space.spaceId === ref.spaceId)
|
|
4378
|
+
?.tabs.find((tab) => tab.tabId === ref.tabId);
|
|
4379
|
+
if (!live) {
|
|
4380
|
+
throw new Error(`Owned Arc tab ${shortId} is missing from its original window/Space; refusing to adopt a moved tab.`);
|
|
4381
|
+
}
|
|
4382
|
+
tabs.push({
|
|
4383
|
+
id: shortId,
|
|
4384
|
+
url: live.url,
|
|
4385
|
+
title: live.title,
|
|
4386
|
+
task: task.name,
|
|
4387
|
+
current: shortId === task.currentTabId,
|
|
4388
|
+
});
|
|
4389
|
+
}
|
|
4390
|
+
return tabs;
|
|
4391
|
+
}
|
|
4220
4392
|
async listArcTaskTabs(task) {
|
|
4221
4393
|
const native = this.requireArcTask(task);
|
|
4222
4394
|
const tabs = [];
|
|
@@ -358,6 +358,15 @@ export interface ProfileStatus {
|
|
|
358
358
|
/** The port declared in the profile's first endpoint, when it differs from the running port. */
|
|
359
359
|
configuredPort?: number;
|
|
360
360
|
tasks: TaskStatus[];
|
|
361
|
+
/**
|
|
362
|
+
* Why this profile's live state could not be read, when it could not be.
|
|
363
|
+
*
|
|
364
|
+
* `status` is read-only, so one unreadable profile must not hide the others:
|
|
365
|
+
* the profile is still listed, with what is known from disk, and this says
|
|
366
|
+
* what is missing. Absent means the read succeeded — it is never set to paper
|
|
367
|
+
* over a failure that did not happen.
|
|
368
|
+
*/
|
|
369
|
+
unavailable?: string;
|
|
361
370
|
}
|
|
362
371
|
export interface TaskStatus {
|
|
363
372
|
id: string;
|
|
@@ -376,6 +385,15 @@ export interface TaskStatus {
|
|
|
376
385
|
current?: boolean;
|
|
377
386
|
task?: string;
|
|
378
387
|
}>;
|
|
388
|
+
/**
|
|
389
|
+
* Why this task's live tabs could not be enumerated, when they could not be.
|
|
390
|
+
*
|
|
391
|
+
* Set together with an ABSENT {@link tabs} — the recorded {@link tabCount}
|
|
392
|
+
* still reports what the task owns on disk, but nothing claims those tabs are
|
|
393
|
+
* live, because that is exactly what could not be verified. An Arc tab moved
|
|
394
|
+
* to another window or Space is the case this exists for.
|
|
395
|
+
*/
|
|
396
|
+
unavailable?: string;
|
|
379
397
|
}
|
|
380
398
|
export interface HistoricalTask {
|
|
381
399
|
id: string;
|
|
@@ -16,7 +16,7 @@ export type ConfigScope = 'run' | 'interactive' | 'auto' | 'browser' | 'project'
|
|
|
16
16
|
* CONFIG_KEYS (user scope); this list is only the parser's allow-set so an
|
|
17
17
|
* unknown `menubar.menu.<x>` fails loud here instead of at the store.
|
|
18
18
|
*/
|
|
19
|
-
export declare const MENUBAR_MENU_PROPERTIES: readonly ["defaultProject", "workingRowsShown", "showPreviews", "projectPriorityFilter", "hideCompletedMilestones", "bannerWhenNeedsYou", "includeOtherDeviceRequests", "groupBy", "thenBy", "projectScope", "projectSort", "ticketSort", "showPullRequests"];
|
|
19
|
+
export declare const MENUBAR_MENU_PROPERTIES: readonly ["defaultProject", "workingRowsShown", "showPreviews", "projectPriorityFilter", "hideCompletedMilestones", "bannerWhenNeedsYou", "includeOtherDeviceRequests", "groupBy", "thenBy", "projectScope", "projectSort", "ticketSort", "showPullRequests", "sessionUpdates", "deviceSort", "headlessAgent", "headlessFallbackAgent", "headlessPlacement"];
|
|
20
20
|
/** A run-time default key: model, mode, effort, or tier override. */
|
|
21
21
|
export interface ParsedRunConfigKey {
|
|
22
22
|
scope: 'run';
|
package/dist/lib/config-keys.js
CHANGED
|
@@ -29,6 +29,11 @@ export const MENUBAR_MENU_PROPERTIES = [
|
|
|
29
29
|
'projectSort',
|
|
30
30
|
'ticketSort',
|
|
31
31
|
'showPullRequests',
|
|
32
|
+
'sessionUpdates',
|
|
33
|
+
'deviceSort',
|
|
34
|
+
'headlessAgent',
|
|
35
|
+
'headlessFallbackAgent',
|
|
36
|
+
'headlessPlacement',
|
|
32
37
|
];
|
|
33
38
|
const DEVICE_CONFIG_PROPERTIES = [
|
|
34
39
|
'browser.viewer',
|
|
@@ -505,6 +505,67 @@ export const CONFIG_KEYS = [
|
|
|
505
505
|
defaultValue: true,
|
|
506
506
|
description: 'AGI Menu: show the open pull requests section for each project.',
|
|
507
507
|
},
|
|
508
|
+
{
|
|
509
|
+
name: 'menubar.menu.sessionUpdates',
|
|
510
|
+
yamlKey: 'menubarMenuSessionUpdates',
|
|
511
|
+
scope: 'user',
|
|
512
|
+
type: 'bool',
|
|
513
|
+
defaultValue: true,
|
|
514
|
+
description: 'AGI Menu: show the session-updates section on Home. Off leaves Home to requests and progress ' +
|
|
515
|
+
'only; the full list stays on the Sessions tab either way.',
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
name: 'menubar.menu.deviceSort',
|
|
519
|
+
yamlKey: 'menubarMenuDeviceSort',
|
|
520
|
+
scope: 'user',
|
|
521
|
+
type: 'string',
|
|
522
|
+
defaultValue: 'name',
|
|
523
|
+
description: 'AGI Menu: how the device list is ordered — name, role, load, memory, or disk. The spec orders read ' +
|
|
524
|
+
'the same cached fleet stats the rows render, so a device with no observation sorts last rather than ' +
|
|
525
|
+
'as zero.',
|
|
526
|
+
validate: oneOf('menubar.menu.deviceSort', ['name', 'role', 'load', 'memory', 'disk']),
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
name: 'menubar.menu.headlessAgent',
|
|
530
|
+
yamlKey: 'menubarMenuHeadlessAgent',
|
|
531
|
+
scope: 'user',
|
|
532
|
+
type: 'string',
|
|
533
|
+
description: 'AGI Menu: the harness a Headless run dispatches to by default (an agent id, e.g. claude). Unset ' +
|
|
534
|
+
'leaves the choice to the menu\'s own default — this key never invents a primary harness.',
|
|
535
|
+
validate: (v) => isAgentId(v) ? null : `menubar.menu.headlessAgent must be an agent id (e.g. claude, codex), got ${JSON.stringify(v)}.`,
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
name: 'menubar.menu.headlessFallbackAgent',
|
|
539
|
+
yamlKey: 'menubarMenuHeadlessFallbackAgent',
|
|
540
|
+
scope: 'user',
|
|
541
|
+
type: 'string',
|
|
542
|
+
description: 'AGI Menu: the permitted alternate harness for a Headless run, dispatched as `agents run --fallback ' +
|
|
543
|
+
'<agent>`. It is reached only after every usable account of the primary harness is exhausted (the ' +
|
|
544
|
+
'run\'s own same-agent account failover runs first). Unset sends no --fallback.',
|
|
545
|
+
validate: (v) => isAgentId(v) ? null : `menubar.menu.headlessFallbackAgent must be an agent id (e.g. codex, grok), got ${JSON.stringify(v)}.`,
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
name: 'menubar.menu.headlessPlacement',
|
|
549
|
+
yamlKey: 'menubarMenuHeadlessPlacement',
|
|
550
|
+
scope: 'user',
|
|
551
|
+
type: 'string',
|
|
552
|
+
defaultValue: 'auto',
|
|
553
|
+
description: 'AGI Menu: where a Headless run executes — `auto` (the CLI picks an eligible worker; `agents run ' +
|
|
554
|
+
'--device auto` never picks a personal or desktop box), `local` (this machine, no --device), ' +
|
|
555
|
+
'`interactive` (the box pinned as interactive.host), or a device name to pin one explicitly.',
|
|
556
|
+
validate: (v) => {
|
|
557
|
+
const value = v;
|
|
558
|
+
if (value === 'auto' || value === 'local' || value === 'interactive')
|
|
559
|
+
return null;
|
|
560
|
+
try {
|
|
561
|
+
assertValidDeviceName(value);
|
|
562
|
+
return null;
|
|
563
|
+
}
|
|
564
|
+
catch {
|
|
565
|
+
return 'menubar.menu.headlessPlacement must be auto | local | interactive | a device name.';
|
|
566
|
+
}
|
|
567
|
+
},
|
|
568
|
+
},
|
|
508
569
|
];
|
|
509
570
|
/** Look up a key spec by CLI dotted name, or throw listing the known keys. */
|
|
510
571
|
/**
|
|
@@ -41,7 +41,7 @@ import chalk from 'chalk';
|
|
|
41
41
|
import * as path from 'path';
|
|
42
42
|
import { AGENTS, ALL_AGENT_IDS, supportsAccountInspection } from '../agents.js';
|
|
43
43
|
import { blocksLocalScripts } from '../platform/winpath.js';
|
|
44
|
-
import { loginHint } from '../signin-badge.js';
|
|
44
|
+
import { loginHint, loginSubcommand, SUBCOMMAND_LOGIN_AGENTS } from '../signin-badge.js';
|
|
45
45
|
import { CONFIG_ENV_ISOLATED_AGENTS } from '../installations/shims.js';
|
|
46
46
|
import { padToWidth, stringWidth } from '../text/width.js';
|
|
47
47
|
import { windowsSshEnrollmentProblem } from './windows-ssh-enrollment.js';
|
|
@@ -78,11 +78,7 @@ const NO_PER_VERSION_LOGIN = new Set(ALL_AGENT_IDS.filter((a) => !ISOLATED_LOGIN
|
|
|
78
78
|
* `subcommand` — `<cli> login` / `<cli> auth login`, runnable via `--`
|
|
79
79
|
* `in-tui` — claude only: a `/login` slash command inside its own TUI
|
|
80
80
|
* `on-launch` — the device/oauth flow starts when the agent launches */
|
|
81
|
-
const LOGIN_SUBCOMMAND =
|
|
82
|
-
codex: 'login',
|
|
83
|
-
grok: 'login',
|
|
84
|
-
opencode: 'auth login',
|
|
85
|
-
};
|
|
81
|
+
const LOGIN_SUBCOMMAND = Object.fromEntries(SUBCOMMAND_LOGIN_AGENTS.map((agent) => [agent, loginSubcommand(agent)]));
|
|
86
82
|
/** Kinds whose fix is inherently PER VERSION, so a row collapsed across versions
|
|
87
83
|
* could not carry a correct remediation. A login is the whole set: there is no
|
|
88
84
|
* `@all` selector for it, and dropping the version falls back to the bare native
|