@phnx-labs/agents-cli 1.20.83 → 1.20.84

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,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.20.84
4
+
5
+ - **Agent onboarding cheat sheet and docs drift guard.** Added
6
+ `apps/cli/docs/AGENT-CHEATSHEET.md` as a one-page on-ramp for agents, wired it
7
+ from `apps/cli/AGENTS.md` and `apps/cli/docs/README.md`, and added
8
+ `scripts/verify-docs.sh` (plus a `verify-docs` npm script and CI job) to catch
9
+ broken relative links and missing entry-point wiring before merge.
10
+
11
+ - **Codex can now build, test, and install without escalating to YOLO.** Codex's
12
+ `workspace-write` sandbox blocks `$HOME`, so `cargo build`, `go build`, `npm/bun install`,
13
+ `pip install` etc. failed on their out-of-workspace cache writes (`~/.cargo`, `GOCACHE`,
14
+ `~/.npm`, `~/.cache`, …) — which is what pushed people to `--mode full`
15
+ (`--dangerously-bypass-approvals-and-sandbox`). agents-cli now writes a platform-resolved
16
+ baseline of **regenerable toolchain caches** into Codex's `config.toml`
17
+ (`[sandbox_workspace_write].writable_roots`) on permission sync — `~/.cargo`, `~/.rustup`,
18
+ `~/.npm`, `~/.bun`, `~/go`, `~/.deno`, `~/.gradle`, `~/.m2`, `~/.gem`, plus `~/Library/Caches`
19
+ + `~/Library/pnpm` on macOS or `~/.cache` + `~/.local/{share,state}` on Linux. Credential dirs
20
+ (`~/.ssh`, `~/.aws`, `~/.gnupg`, `~/.config`, `~/.netrc`) are deliberately excluded, so
21
+ `--mode auto` stays a real sandbox — far narrower than danger-full-access. Any
22
+ `writable_roots` you set yourself are preserved (unioned, never clobbered). Source:
23
+ `apps/cli/src/lib/permissions.ts` (`codexDefaultWritableRoots`, `mergeCodexSandboxWrite`).
24
+
25
+ - **`agents sessions` team rows now show the team's target and teammate, not just
26
+ the slug.** Each teammate row reads `<team> · <teammate> · by <orchestrator> ·
27
+ <live turn | mission>`, where the mission is a one-line summary of the teammate's
28
+ spawn prompt (`assignedTask`, shown even before it has a transcript). Several
29
+ teams from one orchestrator stay legible (distinct team names) and each says what
30
+ it is for. `--active --json` carries `assignedTask`. Source:
31
+ `apps/cli/src/lib/session/active.ts`, `apps/cli/src/commands/sessions.ts`.
32
+
3
33
  ## 1.20.83
4
34
 
5
35
  - **Routines now treat date-specific cron schedules as one-shot jobs (RUSH-2074).**
package/dist/bin/agents CHANGED
Binary file
@@ -247,7 +247,13 @@ export function buildSessionDescription(s) {
247
247
  return cleanPreview([todo, base].filter(Boolean).join(' · '));
248
248
  }
249
249
  if (s.context === 'teams') {
250
+ // A teams row identifies its TEAM, then the teammate within it, then who
251
+ // spun it up, then what it's working on — so several teams from one
252
+ // orchestrator stay distinct and each shows its target, not just its slug.
250
253
  const parts = [s.teamName];
254
+ // Teammate name (distinct from the team slug) — which member this row is.
255
+ if (s.label && s.label !== s.teamName)
256
+ parts.push(s.label);
251
257
  // Lineage: which orchestrator spun up this team. Prefer the resolved label,
252
258
  // else the short session id, so "by whom" is answerable at a glance.
253
259
  const orch = s.orchestratorLabel || (s.orchestratorSessionId ? s.orchestratorSessionId.slice(0, 8) : '');
@@ -255,12 +261,12 @@ export function buildSessionDescription(s) {
255
261
  parts.push(`by ${orch}`);
256
262
  if (todo)
257
263
  parts.push(todo);
258
- if (s.preview)
259
- parts.push(s.preview);
260
- else if (s.label)
261
- parts.push(s.label);
262
- else if (s.topic)
263
- parts.push(s.topic);
264
+ // Target: the live latest turn if working, else the assigned mission (the
265
+ // team's task/target, shown even before the teammate has a transcript), else
266
+ // the transcript topic.
267
+ const target = s.preview || s.assignedTask || s.topic;
268
+ if (target)
269
+ parts.push(target);
264
270
  return cleanPreview(parts.filter(Boolean).join(' · '));
265
271
  }
266
272
  // Terminal, headless, or sub-agent: todos + live preview, then label, then topic.
@@ -233,6 +233,27 @@ export declare function convertToKimiFormat(set: PermissionSet): {
233
233
  * OpenCode uses: { permission: { bash: { "git *": "allow", "rm *": "deny" } } }
234
234
  */
235
235
  export declare function convertToOpenCodeFormat(set: PermissionSet): OpenCodePermissions;
236
+ /**
237
+ * Default extra writable roots for Codex's `workspace-write` sandbox: the
238
+ * regenerable package/toolchain caches that build/test/install write OUTSIDE the
239
+ * workspace (cargo registry, npm/bun/pnpm caches, GOPATH/GOCACHE, the OS cache
240
+ * root, …). Without these, a sandboxed `cargo build` / `go build` / `npm install`
241
+ * fails on its cache write, which is what pushes users to `--mode full` (YOLO).
242
+ *
243
+ * Credential dirs (`~/.ssh`, `~/.aws`, `~/.gnupg`, `~/.config`, `~/.netrc`) are
244
+ * deliberately EXCLUDED so the sandbox stays meaningful — this is far from
245
+ * danger-full-access. Resolved per platform + home; each box regenerates its own
246
+ * Codex config on sync, so the paths always match the box Codex runs on.
247
+ */
248
+ export declare function codexDefaultWritableRoots(home?: string, platform?: NodeJS.Platform): string[];
249
+ /**
250
+ * Merge Codex `[sandbox_workspace_write]` config, UNIONing `writable_roots` so a
251
+ * baseline cache root never clobbers a root the user configured directly (a
252
+ * plain object spread would overwrite the whole array), while merging scalar
253
+ * keys (`network_access`) normally. Shared by the user- and version-scoped Codex
254
+ * config writers so the two can't drift.
255
+ */
256
+ export declare function mergeCodexSandboxWrite(existing: Record<string, unknown> | undefined, incoming: NonNullable<CodexPermissions['sandbox_workspace_write']>): Record<string, unknown>;
236
257
  /**
237
258
  * Convert canonical permission set to Codex format.
238
259
  * Codex uses coarse-grained modes, so we infer the best fit.
@@ -1007,6 +1007,48 @@ export function convertToOpenCodeFormat(set) {
1007
1007
  },
1008
1008
  };
1009
1009
  }
1010
+ /**
1011
+ * Default extra writable roots for Codex's `workspace-write` sandbox: the
1012
+ * regenerable package/toolchain caches that build/test/install write OUTSIDE the
1013
+ * workspace (cargo registry, npm/bun/pnpm caches, GOPATH/GOCACHE, the OS cache
1014
+ * root, …). Without these, a sandboxed `cargo build` / `go build` / `npm install`
1015
+ * fails on its cache write, which is what pushes users to `--mode full` (YOLO).
1016
+ *
1017
+ * Credential dirs (`~/.ssh`, `~/.aws`, `~/.gnupg`, `~/.config`, `~/.netrc`) are
1018
+ * deliberately EXCLUDED so the sandbox stays meaningful — this is far from
1019
+ * danger-full-access. Resolved per platform + home; each box regenerates its own
1020
+ * Codex config on sync, so the paths always match the box Codex runs on.
1021
+ */
1022
+ export function codexDefaultWritableRoots(home = HOME, platform = process.platform) {
1023
+ const shared = ['.cargo', '.rustup', '.npm', '.bun', 'go', '.deno', '.gradle', '.m2', '.gem'];
1024
+ const roots = shared.map((d) => path.join(home, d));
1025
+ if (platform === 'darwin') {
1026
+ roots.push(path.join(home, 'Library', 'Caches'), path.join(home, 'Library', 'pnpm'));
1027
+ }
1028
+ else {
1029
+ // Linux/XDG: ~/.cache covers pip, uv, go-build, ms-playwright, etc.
1030
+ roots.push(path.join(home, '.cache'), path.join(home, '.local', 'share'), path.join(home, '.local', 'state'));
1031
+ }
1032
+ return roots;
1033
+ }
1034
+ /**
1035
+ * Merge Codex `[sandbox_workspace_write]` config, UNIONing `writable_roots` so a
1036
+ * baseline cache root never clobbers a root the user configured directly (a
1037
+ * plain object spread would overwrite the whole array), while merging scalar
1038
+ * keys (`network_access`) normally. Shared by the user- and version-scoped Codex
1039
+ * config writers so the two can't drift.
1040
+ */
1041
+ export function mergeCodexSandboxWrite(existing, incoming) {
1042
+ const existingRoots = Array.isArray(existing?.writable_roots)
1043
+ ? existing.writable_roots
1044
+ : [];
1045
+ const unionRoots = [...new Set([...existingRoots, ...(incoming.writable_roots ?? [])])];
1046
+ return {
1047
+ ...existing,
1048
+ ...incoming,
1049
+ ...(unionRoots.length > 0 ? { writable_roots: unionRoots } : {}),
1050
+ };
1051
+ }
1010
1052
  /**
1011
1053
  * Convert canonical permission set to Codex format.
1012
1054
  * Codex uses coarse-grained modes, so we infer the best fit.
@@ -1042,6 +1084,15 @@ export function convertToCodexFormat(set, cwd) {
1042
1084
  network_access: true,
1043
1085
  };
1044
1086
  }
1087
+ // Baseline (unconditional): always grant the regenerable build/test/install
1088
+ // caches as writable roots so `agents run codex` in workspace-write can build,
1089
+ // test, and install without escalating to danger-full-access. Merged with
1090
+ // network_access above when set; applyCodexPermissions unions these with any
1091
+ // writable_roots the user configured directly.
1092
+ result.sandbox_workspace_write = {
1093
+ ...result.sandbox_workspace_write,
1094
+ writable_roots: codexDefaultWritableRoots(),
1095
+ };
1045
1096
  return result;
1046
1097
  }
1047
1098
  // ============================================================================
@@ -1333,8 +1384,10 @@ function applyCodexPermissions(set, scope = 'user', cwd, merge = true) {
1333
1384
  }
1334
1385
  if (newPermissions.sandbox_workspace_write) {
1335
1386
  const existing = config.sandbox_workspace_write;
1387
+ // merge=false is a deliberate full replace (drops any user-custom roots);
1388
+ // production sync always passes merge=true, taking the union path.
1336
1389
  config.sandbox_workspace_write = merge
1337
- ? { ...existing, ...newPermissions.sandbox_workspace_write }
1390
+ ? mergeCodexSandboxWrite(existing, newPermissions.sandbox_workspace_write)
1338
1391
  : newPermissions.sandbox_workspace_write;
1339
1392
  }
1340
1393
  fs.writeFileSync(configPath, TOML.stringify(config), 'utf-8');
@@ -1450,8 +1503,10 @@ export function applyPermissionsToVersion(agentId, set, versionHome, merge = tru
1450
1503
  }
1451
1504
  if (newPermissions.sandbox_workspace_write) {
1452
1505
  const existing = config.sandbox_workspace_write;
1506
+ // merge=false is a deliberate full replace (drops any user-custom roots);
1507
+ // production sync always passes merge=true, taking the union path.
1453
1508
  config.sandbox_workspace_write = merge
1454
- ? { ...existing, ...newPermissions.sandbox_workspace_write }
1509
+ ? mergeCodexSandboxWrite(existing, newPermissions.sandbox_workspace_write)
1455
1510
  : newPermissions.sandbox_workspace_write;
1456
1511
  }
1457
1512
  fs.writeFileSync(configPath, TOML.stringify(config), 'utf-8');
@@ -158,6 +158,15 @@ export interface ActiveSession {
158
158
  /** Display label for the orchestrator (its topic/label), resolved when the
159
159
  * orchestrator is itself present in the active set. Display-only. */
160
160
  orchestratorLabel?: string;
161
+ /**
162
+ * For a teams teammate: a one-line summary of the mission it was spawned with
163
+ * (the `prompt` stored on the teammate record — the team's task/target), so the
164
+ * listing answers "what is this team working on", not just its name. Survives
165
+ * before the teammate has produced any transcript (a pending/staged teammate
166
+ * still shows its target). Distinct from `topic`, which is derived from the
167
+ * teammate's own transcript once it starts.
168
+ */
169
+ assignedTask?: string;
161
170
  agentId?: string;
162
171
  cloudProvider?: string;
163
172
  cloudTaskId?: string;
@@ -338,6 +347,12 @@ interface LiveSignals {
338
347
  * {@link resolveFallbackStatus} reports the honest live floor (`running`).
339
348
  */
340
349
  export declare function computeLiveSignals(kind: string, sessionFile: string | undefined, cwd: string | undefined, pidAlive: boolean): LiveSignals;
350
+ /**
351
+ * One-line summary of a teammate's spawn prompt — the team's task/target. Takes
352
+ * the first non-empty line, strips a leading `MISSION:`/`CONTEXT:`/`TASK:` label,
353
+ * and truncates. Exported for tests.
354
+ */
355
+ export declare function summarizeMission(prompt: string | null | undefined): string | undefined;
341
356
  /** Live teams teammates. Reuses AgentManager which already polls PIDs via `kill -0`. */
342
357
  export declare function listTeamsActive(): Promise<ActiveSession[]>;
343
358
  /** Live editor-terminal agents across every IDE window. */
@@ -576,6 +576,22 @@ function quickExtractTopic(sessionFile) {
576
576
  }
577
577
  return undefined;
578
578
  }
579
+ /**
580
+ * One-line summary of a teammate's spawn prompt — the team's task/target. Takes
581
+ * the first non-empty line, strips a leading `MISSION:`/`CONTEXT:`/`TASK:` label,
582
+ * and truncates. Exported for tests.
583
+ */
584
+ export function summarizeMission(prompt) {
585
+ if (!prompt)
586
+ return undefined;
587
+ const firstLine = prompt.split('\n').map((l) => l.trim()).find(Boolean);
588
+ if (!firstLine)
589
+ return undefined;
590
+ const cleaned = firstLine.replace(/^(MISSION|CONTEXT|TASK|GOAL|OBJECTIVE)\s*[:\-—]\s*/i, '').trim();
591
+ if (!cleaned)
592
+ return undefined;
593
+ return cleaned.length > 80 ? `${cleaned.slice(0, 79)}…` : cleaned;
594
+ }
579
595
  /** Live teams teammates. Reuses AgentManager which already polls PIDs via `kill -0`. */
580
596
  export async function listTeamsActive() {
581
597
  const mgr = new AgentManager();
@@ -607,6 +623,7 @@ export async function listTeamsActive() {
607
623
  startedAtMs: a.startedAt.getTime(),
608
624
  lastActivityMs: sessionFileTimes(sessionFile).mtimeMs,
609
625
  teamName: a.taskName,
626
+ assignedTask: summarizeMission(a.prompt),
610
627
  agentId: a.agentId,
611
628
  // The frozen actor stamped on the teammate record (RUSH-2028) — who ran
612
629
  // this teammate, surfaced as the owner in --active (RUSH-2018); sidecar
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phnx-labs/agents-cli",
3
- "version": "1.20.83",
3
+ "version": "1.20.84",
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",
@@ -55,6 +55,7 @@
55
55
  "start": "node dist/index.js",
56
56
  "test": "node ./node_modules/vitest/vitest.mjs run",
57
57
  "test:remote": "scripts/sandbox.sh 'bun install && bun run build && bun run test'",
58
+ "verify-docs": "scripts/verify-docs.sh",
58
59
  "test:watch": "node ./node_modules/vitest/vitest.mjs"
59
60
  },
60
61
  "keywords": [