@phnx-labs/agents-cli 1.22.95 → 1.22.96

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 CHANGED
@@ -1,6 +1,8 @@
1
1
  # Changelog
2
2
 
3
- ## 1.22.95
3
+ ## 1.22.96
4
+
5
+ - **`agents browser tabs --all` shows every tab open in the profile browser, yours included (PHNX-4043).** Until now `tabs` listed only the task's own tabs, so an agent sharing an Arc Space, a Comet store, or a Firefox profile with you could not see what else was open. `--all` lists every top-level tab: OWNER names the agents-cli task that owns it, or `you` for a tab no task owns; `*` still marks the task's current tab. Read-only on every backend (Chromium `Target.getTargets`, the Arc Space's tabs, Firefox `browsingContext.getTree`). Source: `cli/src/lib/browser/service.ts` `profileTabs`, `cli/src/commands/browser.ts`.
4
6
 
5
7
  - **Firefox automation over WebDriver BiDi (PHNX-4043).** `agents browser` now drives
6
8
  Firefox, which dropped the Chrome DevTools Protocol in 129. Every Firefox profile in
@@ -2235,15 +2235,17 @@ function registerTaskCommands(browser) {
2235
2235
  });
2236
2236
  browser
2237
2237
  .command('tabs')
2238
- .description('List tabs open for the current task')
2238
+ .description('List tabs open for the current task; --all shows every tab open in the profile browser, yours included')
2239
2239
  .option(TASK_OPTION_FLAG, TASK_OPTION_DESC)
2240
2240
  .option(DEVICE_ON_PAGE_VERB_FLAG, DEVICE_ON_PAGE_VERB_DESC)
2241
+ .option('--all', "Every tab open in the profile browser, not only this task's: OWNER names the owning task, or \"you\" for a tab no task owns")
2241
2242
  .option('--json', 'Output machine-readable JSON')
2242
2243
  .action(async (opts) => {
2243
2244
  const task = resolveTaskName(opts);
2244
2245
  const response = await sendIPCRequest({
2245
2246
  action: 'tab-list',
2246
2247
  task,
2248
+ ...(opts.all ? { all: true } : {}),
2247
2249
  });
2248
2250
  if (!response.ok) {
2249
2251
  if (opts.json) {
@@ -2262,6 +2264,16 @@ function registerTaskCommands(browser) {
2262
2264
  console.log('No tabs open');
2263
2265
  return;
2264
2266
  }
2267
+ if (opts.all) {
2268
+ console.log('TAB'.padEnd(12) + 'OWNER'.padEnd(18) + 'URL');
2269
+ console.log('-'.repeat(88));
2270
+ for (const t of response.tabs) {
2271
+ const owner = t.task ?? 'you';
2272
+ const current = t.current ? ' *' : '';
2273
+ console.log(t.id.slice(0, 11).padEnd(12) + owner.slice(0, 17).padEnd(18) + t.url.slice(0, 55) + current);
2274
+ }
2275
+ return;
2276
+ }
2265
2277
  console.log('TAB'.padEnd(12) + 'URL');
2266
2278
  console.log('-'.repeat(70));
2267
2279
  for (const t of response.tabs) {
@@ -935,6 +935,10 @@ export class BrowserIPCServer {
935
935
  if (!request.task) {
936
936
  return { ok: false, error: 'Task required' };
937
937
  }
938
+ if (request.all) {
939
+ const tabs = await this.service.profileTabs(request.task);
940
+ return { ok: true, tabs: tabs.map(t => ({ id: t.id, url: t.url, title: t.title, current: t.current, task: t.task })) };
941
+ }
938
942
  const tabs = await this.service.tabList(request.task);
939
943
  // Carry `current` through — it is the one field a caller needs to show
940
944
  // which tab URL-less verbs act on, and dropping it here left `agents
@@ -1,7 +1,7 @@
1
1
  import { CDPClient } from './cdp.js';
2
2
  import { type DeviceProbe } from './resolve-target.js';
3
3
  import { type ResolvedDomainSkill } from './domain-skills.js';
4
- import { type Task, type TabInfo, type ProfileStatus, type BrowserType, type HistoricalTask, type ReapResult, type ProfileName, type ConnectionKey } from './types.js';
4
+ import { type Task, type TabInfo, type ProfileTabInfo, type ProfileStatus, type BrowserType, type HistoricalTask, type ReapResult, type ProfileName, type ConnectionKey } from './types.js';
5
5
  import { type ReapOptions } from './hygiene.js';
6
6
  import { type RefOpts, type RefNode } from './refs.js';
7
7
  import type { TargetFilter, PageOpenResult, ArcNativeProfileIdentity, FirefoxProfileIdentity } from './types.js';
@@ -380,6 +380,14 @@ export declare class BrowserService {
380
380
  title: string;
381
381
  current: boolean;
382
382
  }>>;
383
+ /**
384
+ * Every top-level tab open in the task's profile browser, the owner's tabs
385
+ * included (`agents browser tabs --all`). Read-only: nothing is focused,
386
+ * navigated, or closed. A tab an agents-cli task owns carries that task's
387
+ * name and short id (so it can be addressed); any other tab carries the
388
+ * browser's own id and no task. Same rows on every backend.
389
+ */
390
+ profileTabs(taskId: string): Promise<ProfileTabInfo[]>;
383
391
  private resolveTabHint;
384
392
  private resolveCurrentTab;
385
393
  private getCdpTargetId;
@@ -1242,6 +1242,64 @@ export class BrowserService {
1242
1242
  }
1243
1243
  return tabs;
1244
1244
  }
1245
+ /**
1246
+ * Every top-level tab open in the task's profile browser, the owner's tabs
1247
+ * included (`agents browser tabs --all`). Read-only: nothing is focused,
1248
+ * navigated, or closed. A tab an agents-cli task owns carries that task's
1249
+ * name and short id (so it can be addressed); any other tab carries the
1250
+ * browser's own id and no task. Same rows on every backend.
1251
+ */
1252
+ async profileTabs(taskId) {
1253
+ const { conn, task } = await this.findTask(taskId);
1254
+ const owners = new Map();
1255
+ for (const [, owner] of conn.tasks) {
1256
+ const entries = conn.backend === 'arc-native'
1257
+ ? Object.entries(owner.arcNative?.tabs ?? {}).map(([shortId, ref]) => [shortId, ref.tabId])
1258
+ : Object.entries(owner.tabs);
1259
+ for (const [shortId, nativeId] of entries)
1260
+ owners.set(nativeId, { owner, shortId });
1261
+ }
1262
+ const row = (nativeId, url, title) => {
1263
+ const owned = owners.get(nativeId);
1264
+ if (!owned)
1265
+ return { id: nativeId, url, title };
1266
+ return {
1267
+ id: owned.shortId,
1268
+ url,
1269
+ title,
1270
+ task: owned.owner.name,
1271
+ current: owned.owner === task && owned.shortId === task.currentTabId,
1272
+ };
1273
+ };
1274
+ if (conn.backend === 'arc-native') {
1275
+ const native = this.requireArcTask(task);
1276
+ const space = (await enumerateArcSpaces()).find((candidate) => candidate.windowId === native.windowId && candidate.spaceId === native.spaceId);
1277
+ if (!space) {
1278
+ throw new Error(`Arc Space "${native.spaceTitle}" is not open in its original window (${native.windowId}); ` +
1279
+ 'open it in Arc and retry.');
1280
+ }
1281
+ return space.tabs.map((tab) => row(tab.tabId, tab.url, tab.title));
1282
+ }
1283
+ if (conn.backend === 'bidi') {
1284
+ const rows = [];
1285
+ for (const context of await bidiTopLevelContexts(conn.bidi)) {
1286
+ let title = '';
1287
+ try {
1288
+ title = String(await bidiEvaluate(conn.bidi, context.context, 'document.title'));
1289
+ }
1290
+ catch {
1291
+ /* about: pages or a mid-navigation context — url is enough */
1292
+ }
1293
+ rows.push(row(context.context, context.url, title));
1294
+ }
1295
+ return rows;
1296
+ }
1297
+ requireCdp(conn, 'enumerate');
1298
+ const { targetInfos } = (await conn.cdp.send('Target.getTargets'));
1299
+ return targetInfos
1300
+ .filter((target) => target.type === 'page')
1301
+ .map((target) => row(target.targetId, target.url, target.title));
1302
+ }
1245
1303
  async resolveTabHint(conn, task, hint) {
1246
1304
  // Exact match
1247
1305
  if (task.tabs[hint])
@@ -327,6 +327,20 @@ export interface TabInfo {
327
327
  /** Whether this is the task's current tab (the one URL-less verbs act on). */
328
328
  current?: boolean;
329
329
  }
330
+ /**
331
+ * One row of `agents browser tabs --all`: a top-level tab open in the profile's
332
+ * browser. Owned by an agents-cli task → `task` names it and `id` is that
333
+ * task's short tab id; otherwise (the owner's own tab, or a tab from another
334
+ * tool) `task` is absent and `id` is the browser's own tab id.
335
+ */
336
+ export interface ProfileTabInfo {
337
+ id: string;
338
+ url: string;
339
+ title: string;
340
+ task?: string;
341
+ /** The queried task's current tab. */
342
+ current?: boolean;
343
+ }
330
344
  export interface ProfileStatus {
331
345
  /**
332
346
  * The BARE profile name — what the user passed to `--profile` and what
@@ -360,6 +374,7 @@ export interface TaskStatus {
360
374
  url: string;
361
375
  title?: string;
362
376
  current?: boolean;
377
+ task?: string;
363
378
  }>;
364
379
  }
365
380
  export interface HistoricalTask {
@@ -395,6 +410,8 @@ export interface IPCRequest {
395
410
  action: IPCAction;
396
411
  task?: string;
397
412
  taskName?: string;
413
+ /** tab-list: every tab open in the profile browser, not only the task's own. */
414
+ all?: boolean;
398
415
  profile?: string;
399
416
  url?: string;
400
417
  tabId?: string;
@@ -543,7 +560,8 @@ export interface IPCResponse {
543
560
  */
544
561
  reused?: boolean;
545
562
  windowTargetId?: string;
546
- tabs?: TabInfo[];
563
+ /** `tab-list` rows; `task` is absent for a tab no agents-cli task owns (`--all`). */
564
+ tabs?: ProfileTabInfo[];
547
565
  profiles?: ProfileStatus[];
548
566
  history?: HistoricalTask[];
549
567
  /** `gc`: what the abandoned-task reaper closed (or would close under dryRun). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phnx-labs/agents-cli",
3
- "version": "1.22.95",
3
+ "version": "1.22.96",
4
4
  "description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",