@phnx-labs/agents-cli 1.19.2 → 1.20.3

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.
Files changed (156) hide show
  1. package/CHANGELOG.md +140 -0
  2. package/README.md +72 -12
  3. package/dist/browser.js +0 -0
  4. package/dist/commands/browser.js +88 -16
  5. package/dist/commands/cli.d.ts +14 -0
  6. package/dist/commands/cli.js +244 -0
  7. package/dist/commands/cloud.js +1 -1
  8. package/dist/commands/commands.js +27 -10
  9. package/dist/commands/computer.js +18 -1
  10. package/dist/commands/doctor.d.ts +1 -1
  11. package/dist/commands/doctor.js +2 -2
  12. package/dist/commands/exec.js +38 -18
  13. package/dist/commands/factory.d.ts +3 -14
  14. package/dist/commands/factory.js +3 -3
  15. package/dist/commands/feedback.d.ts +7 -0
  16. package/dist/commands/feedback.js +89 -0
  17. package/dist/commands/helper.d.ts +12 -0
  18. package/dist/commands/helper.js +87 -0
  19. package/dist/commands/hooks.js +89 -10
  20. package/dist/commands/mcp.js +166 -10
  21. package/dist/commands/packages.js +196 -27
  22. package/dist/commands/permissions.js +21 -6
  23. package/dist/commands/plugins.js +11 -4
  24. package/dist/commands/profiles.d.ts +8 -0
  25. package/dist/commands/profiles.js +118 -5
  26. package/dist/commands/prune.js +39 -160
  27. package/dist/commands/pull.js +58 -5
  28. package/dist/commands/routines.js +107 -14
  29. package/dist/commands/rules.js +8 -4
  30. package/dist/commands/secrets-migrate.d.ts +24 -0
  31. package/dist/commands/secrets-migrate.js +198 -0
  32. package/dist/commands/secrets-sync.d.ts +11 -0
  33. package/dist/commands/secrets-sync.js +155 -0
  34. package/dist/commands/secrets.js +79 -46
  35. package/dist/commands/sessions.d.ts +28 -0
  36. package/dist/commands/sessions.js +98 -33
  37. package/dist/commands/setup.d.ts +1 -0
  38. package/dist/commands/setup.js +37 -28
  39. package/dist/commands/skills.js +25 -8
  40. package/dist/commands/subagents.js +69 -49
  41. package/dist/commands/teams.js +61 -10
  42. package/dist/commands/utils.d.ts +33 -0
  43. package/dist/commands/utils.js +139 -0
  44. package/dist/commands/versions.d.ts +4 -3
  45. package/dist/commands/versions.js +134 -130
  46. package/dist/commands/view.d.ts +6 -0
  47. package/dist/commands/view.js +175 -19
  48. package/dist/commands/workflows.js +29 -6
  49. package/dist/computer.js +0 -0
  50. package/dist/index.js +38 -6
  51. package/dist/lib/acp/client.js +6 -1
  52. package/dist/lib/acp/harnesses.js +8 -0
  53. package/dist/lib/agents.d.ts +4 -0
  54. package/dist/lib/agents.js +125 -34
  55. package/dist/lib/auto-pull-worker.js +18 -1
  56. package/dist/lib/browser/cdp.d.ts +8 -1
  57. package/dist/lib/browser/cdp.js +40 -3
  58. package/dist/lib/browser/chrome.d.ts +13 -0
  59. package/dist/lib/browser/chrome.js +46 -3
  60. package/dist/lib/browser/domain-skills.d.ts +51 -0
  61. package/dist/lib/browser/domain-skills.js +157 -0
  62. package/dist/lib/browser/drivers/local.js +45 -4
  63. package/dist/lib/browser/drivers/ssh.js +2 -2
  64. package/dist/lib/browser/ipc.d.ts +8 -1
  65. package/dist/lib/browser/ipc.js +37 -28
  66. package/dist/lib/browser/profiles.d.ts +16 -3
  67. package/dist/lib/browser/profiles.js +44 -4
  68. package/dist/lib/browser/service.d.ts +3 -0
  69. package/dist/lib/browser/service.js +40 -5
  70. package/dist/lib/browser/types.d.ts +11 -4
  71. package/dist/lib/cli-resources.d.ts +137 -0
  72. package/dist/lib/cli-resources.js +477 -0
  73. package/dist/lib/cloud/factory.d.ts +1 -1
  74. package/dist/lib/cloud/factory.js +1 -1
  75. package/dist/lib/cloud/rush.js +5 -5
  76. package/dist/lib/command-skills.js +0 -2
  77. package/dist/lib/computer-rpc.d.ts +3 -0
  78. package/dist/lib/computer-rpc.js +53 -0
  79. package/dist/lib/daemon.js +20 -0
  80. package/dist/lib/events.d.ts +16 -2
  81. package/dist/lib/events.js +33 -2
  82. package/dist/lib/exec.d.ts +42 -13
  83. package/dist/lib/exec.js +127 -33
  84. package/dist/lib/help.js +11 -5
  85. package/dist/lib/hooks/cache.d.ts +38 -0
  86. package/dist/lib/hooks/cache.js +242 -0
  87. package/dist/lib/hooks/profile.d.ts +33 -0
  88. package/dist/lib/hooks/profile.js +129 -0
  89. package/dist/lib/hooks.d.ts +0 -10
  90. package/dist/lib/hooks.js +246 -11
  91. package/dist/lib/mcp.d.ts +15 -0
  92. package/dist/lib/mcp.js +46 -0
  93. package/dist/lib/migrate.js +1 -1
  94. package/dist/lib/overdue.d.ts +26 -0
  95. package/dist/lib/overdue.js +101 -0
  96. package/dist/lib/permissions.d.ts +13 -0
  97. package/dist/lib/permissions.js +55 -1
  98. package/dist/lib/plugin-marketplace.js +1 -1
  99. package/dist/lib/plugins.js +15 -1
  100. package/dist/lib/profiles-presets.d.ts +26 -0
  101. package/dist/lib/profiles-presets.js +216 -0
  102. package/dist/lib/profiles.d.ts +34 -0
  103. package/dist/lib/profiles.js +112 -1
  104. package/dist/lib/resources/mcp.js +37 -0
  105. package/dist/lib/resources.d.ts +1 -1
  106. package/dist/lib/rotate.js +10 -4
  107. package/dist/lib/routines-format.d.ts +47 -0
  108. package/dist/lib/routines-format.js +194 -0
  109. package/dist/lib/routines.d.ts +8 -2
  110. package/dist/lib/routines.js +34 -14
  111. package/dist/lib/runner.js +83 -15
  112. package/dist/lib/scheduler.js +8 -1
  113. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  114. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  115. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -9
  116. package/dist/lib/secrets/bundles.d.ts +34 -17
  117. package/dist/lib/secrets/bundles.js +210 -36
  118. package/dist/lib/secrets/index.d.ts +49 -30
  119. package/dist/lib/secrets/index.js +126 -115
  120. package/dist/lib/secrets/install-helper.d.ts +45 -0
  121. package/dist/lib/secrets/install-helper.js +165 -0
  122. package/dist/lib/secrets/linux.js +4 -4
  123. package/dist/lib/secrets/sync.d.ts +56 -0
  124. package/dist/lib/secrets/sync.js +180 -0
  125. package/dist/lib/session/active.d.ts +8 -0
  126. package/dist/lib/session/active.js +3 -2
  127. package/dist/lib/session/db.d.ts +0 -4
  128. package/dist/lib/session/db.js +0 -26
  129. package/dist/lib/session/parse.d.ts +1 -0
  130. package/dist/lib/session/parse.js +44 -0
  131. package/dist/lib/session/render.js +4 -4
  132. package/dist/lib/session/types.d.ts +2 -2
  133. package/dist/lib/session/types.js +1 -1
  134. package/dist/lib/shims.d.ts +5 -2
  135. package/dist/lib/shims.js +70 -38
  136. package/dist/lib/state.d.ts +14 -2
  137. package/dist/lib/state.js +51 -20
  138. package/dist/lib/teams/agents.d.ts +5 -4
  139. package/dist/lib/teams/agents.js +48 -22
  140. package/dist/lib/teams/api.d.ts +2 -1
  141. package/dist/lib/teams/api.js +4 -3
  142. package/dist/lib/teams/parsers.d.ts +1 -1
  143. package/dist/lib/teams/parsers.js +153 -3
  144. package/dist/lib/teams/summarizer.js +18 -2
  145. package/dist/lib/teams/worktree.js +14 -3
  146. package/dist/lib/types.d.ts +63 -4
  147. package/dist/lib/types.js +8 -3
  148. package/dist/lib/usage.d.ts +27 -2
  149. package/dist/lib/usage.js +100 -17
  150. package/dist/lib/versions.d.ts +45 -3
  151. package/dist/lib/versions.js +455 -60
  152. package/package.json +15 -14
  153. package/scripts/install-helper.js +97 -0
  154. package/scripts/postinstall.js +16 -0
  155. package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
  156. package/npm-shrinkwrap.json +0 -3162
@@ -107,6 +107,10 @@ export declare function getRunsDir(): string;
107
107
  export declare function getVersionsDir(): string;
108
108
  /** Path to version-switching shim scripts (~/.agents/.cache/shims/). */
109
109
  export declare function getShimsDir(): string;
110
+ /** Path to generated per-hook caching/timing shims (~/.agents/.cache/shims/hooks/). */
111
+ export declare function getHookShimsDir(): string;
112
+ /** Path to per-hook stdout cache files (~/.agents/.cache/state/hooks/). */
113
+ export declare function getHookCacheDir(): string;
110
114
  /** Path to per-agent installed CLI binaries (~/.agents/.cache/bin/). */
111
115
  export declare function getBinDir(): string;
112
116
  /** Path to config backups (~/.agents/.history/backups/). */
@@ -191,7 +195,16 @@ export declare function getEnabledExtraRepos(): Array<{
191
195
  export declare function ensureAgentsDir(): void;
192
196
  /** Return an empty Meta object used when no agents.yaml exists yet. */
193
197
  export declare function createDefaultMeta(): Meta;
194
- /** Read and cache ~/.agents/agents.yaml, migrating from legacy locations if needed. */
198
+ /**
199
+ * Read and cache ~/.agents/agents.yaml, migrating from legacy locations if needed.
200
+ *
201
+ * Cache invariants:
202
+ * - Cache key is the mtime of the user agents.yaml.
203
+ * - `writeMetaUnlocked` clears the cache; in-process callers always see fresh state.
204
+ * - If the file is mutated by ANOTHER process while we hold a stale cache, the
205
+ * mtime check below catches it on the next read (assuming the mtime advanced).
206
+ * - The cache stores the merged system+user meta; both files' mtimes contribute.
207
+ */
195
208
  export declare function readMeta(): Meta;
196
209
  /** Serialize and write agents.yaml to the user repo, invalidating the in-memory cache. */
197
210
  export declare function writeMeta(meta: Meta): void;
@@ -212,7 +225,6 @@ export declare function recordVersionResources(_agent: AgentId, _version: string
212
225
  */
213
226
  export declare function ensureVersionResourcePatterns(agent: AgentId, version: string, updates: Partial<Record<Exclude<keyof VersionResources, 'rulesPreset'>, ResourcePattern[]>>): void;
214
227
  export declare function getVersionResources(agent: AgentId, version: string): VersionResources | null;
215
- export declare function clearVersionResources(agent: AgentId, version: string): void;
216
228
  /** Active rules preset for an agent@version. Defaults to "default" when unset. */
217
229
  export declare function getActiveRulesPreset(agent: AgentId, version: string): string;
218
230
  /** Persist the active rules preset for an agent@version. */
package/dist/lib/state.js CHANGED
@@ -64,6 +64,8 @@ const BACKUPS_DIR = path.join(HISTORY_DIR, 'backups');
64
64
  const TRASH_DIR = path.join(HISTORY_DIR, 'trash');
65
65
  // Cache bucket (regenerable).
66
66
  const SHIMS_DIR = path.join(CACHE_DIR, 'shims');
67
+ const HOOK_SHIMS_DIR = path.join(SHIMS_DIR, 'hooks');
68
+ const HOOK_CACHE_DIR = path.join(CACHE_DIR, 'state', 'hooks');
67
69
  const BIN_DIR = path.join(CACHE_DIR, 'bin');
68
70
  const PACKAGES_DIR = path.join(CACHE_DIR, 'packages');
69
71
  // Plugins are user-authored resources, alongside skills/, commands/, hooks/.
@@ -74,7 +76,7 @@ const DRIVE_DIR = path.join(CACHE_DIR, 'drive');
74
76
  const TERMINALS_DIR = path.join(CACHE_DIR, 'terminals');
75
77
  const LOGS_DIR = path.join(CACHE_DIR, 'logs');
76
78
  const RUNTIME_STATE_DIR = path.join(CACHE_DIR, 'state');
77
- const SWARMIFY_DIR = path.join(CACHE_DIR, 'companion');
79
+ const COMPANION_CACHE_DIR = path.join(CACHE_DIR, 'companion');
78
80
  const BROWSER_RUNTIME_DIR = path.join(CACHE_DIR, 'browser');
79
81
  const HELPERS_DIR = path.join(CACHE_DIR, 'helpers');
80
82
  const DAEMON_DIR = path.join(HELPERS_DIR, 'daemon');
@@ -265,6 +267,10 @@ export function getRunsDir() { return RUNS_DIR; }
265
267
  export function getVersionsDir() { return VERSIONS_DIR; }
266
268
  /** Path to version-switching shim scripts (~/.agents/.cache/shims/). */
267
269
  export function getShimsDir() { return SHIMS_DIR; }
270
+ /** Path to generated per-hook caching/timing shims (~/.agents/.cache/shims/hooks/). */
271
+ export function getHookShimsDir() { return HOOK_SHIMS_DIR; }
272
+ /** Path to per-hook stdout cache files (~/.agents/.cache/state/hooks/). */
273
+ export function getHookCacheDir() { return HOOK_CACHE_DIR; }
268
274
  /** Path to per-agent installed CLI binaries (~/.agents/.cache/bin/). */
269
275
  export function getBinDir() { return BIN_DIR; }
270
276
  /** Path to config backups (~/.agents/.history/backups/). */
@@ -294,7 +300,7 @@ export function getLogsDir() { return LOGS_DIR; }
294
300
  /** Path to per-process runtime state (~/.agents/.cache/state/). */
295
301
  export function getRuntimeStateDir() { return RUNTIME_STATE_DIR; }
296
302
  /** Path to companion-extension scratch (~/.agents/.cache/companion/). */
297
- export function getCompanionDir() { return SWARMIFY_DIR; }
303
+ export function getCompanionDir() { return COMPANION_CACHE_DIR; }
298
304
  /** Path to browser runtime data — chrome-data, pids (~/.agents/.cache/browser/). */
299
305
  export function getBrowserRuntimeDir() { return BROWSER_RUNTIME_DIR; }
300
306
  /** Path to helper subprocess scratch (~/.agents/.cache/helpers/). */
@@ -413,6 +419,24 @@ export function createDefaultMeta() {
413
419
  }
414
420
  let metaCache = null;
415
421
  let metaLockDepth = 0;
422
+ /** Return mtimeMs for a file path, or 0 if the file is absent or unreadable. */
423
+ function safeMtimeMs(filePath) {
424
+ try {
425
+ return fs.statSync(filePath).mtimeMs;
426
+ }
427
+ catch {
428
+ return 0;
429
+ }
430
+ }
431
+ /** Compute the combined cache stamp for the user + system agents.yaml files. */
432
+ function currentMetaStamp() {
433
+ return safeMtimeMs(META_FILE) + safeMtimeMs(SYSTEM_META_FILE) * 1e-3;
434
+ }
435
+ /** Memoize a parsed Meta against the current file mtimes. */
436
+ function rememberMeta(meta) {
437
+ metaCache = { mtime: currentMetaStamp(), meta };
438
+ return meta;
439
+ }
416
440
  function withMetaLock(fn) {
417
441
  ensureAgentsDir();
418
442
  if (metaLockDepth > 0) {
@@ -483,9 +507,29 @@ function migrateSystemMetaToUser() {
483
507
  // Best-effort; proceed with fresh state if it fails.
484
508
  }
485
509
  }
486
- /** Read and cache ~/.agents/agents.yaml, migrating from legacy locations if needed. */
510
+ /**
511
+ * Read and cache ~/.agents/agents.yaml, migrating from legacy locations if needed.
512
+ *
513
+ * Cache invariants:
514
+ * - Cache key is the mtime of the user agents.yaml.
515
+ * - `writeMetaUnlocked` clears the cache; in-process callers always see fresh state.
516
+ * - If the file is mutated by ANOTHER process while we hold a stale cache, the
517
+ * mtime check below catches it on the next read (assuming the mtime advanced).
518
+ * - The cache stores the merged system+user meta; both files' mtimes contribute.
519
+ */
487
520
  export function readMeta() {
488
521
  ensureAgentsDir();
522
+ // Fast path: serve from cache when both source files are byte-identical to
523
+ // what we last parsed. Reduces N readMeta calls per CLI invocation to ~2 stat
524
+ // syscalls plus an in-memory object spread.
525
+ if (metaCache) {
526
+ const userMtime = safeMtimeMs(META_FILE);
527
+ const systemMtime = safeMtimeMs(SYSTEM_META_FILE);
528
+ const stamp = userMtime + systemMtime * 1e-3;
529
+ if (stamp === metaCache.mtime) {
530
+ return metaCache.meta;
531
+ }
532
+ }
489
533
  // NOTE: agents.yaml migration from ~/.agents-system/ to ~/.agents/ is handled
490
534
  // exclusively by runMigration() in migrate.ts, called from postinstall and
491
535
  // from a one-shot bootstrap step in src/index.ts. Calling it here would
@@ -515,7 +559,7 @@ export function readMeta() {
515
559
  fs.unlinkSync(oldMetaFile);
516
560
  }
517
561
  catch { /* non-critical */ }
518
- return meta;
562
+ return rememberMeta(meta);
519
563
  }
520
564
  catch {
521
565
  /* meta.yaml migration failed */
@@ -557,15 +601,15 @@ export function readMeta() {
557
601
  }
558
602
  if (applyRegistrySeeds(meta)) {
559
603
  writeMeta(meta);
560
- return meta;
604
+ return rememberMeta(meta);
561
605
  }
562
- return meta;
606
+ return rememberMeta(meta);
563
607
  }
564
608
  const meta = createDefaultMeta();
565
609
  if (applyRegistrySeeds(meta)) {
566
610
  writeMeta(meta);
567
611
  }
568
- return meta;
612
+ return rememberMeta(meta);
569
613
  }
570
614
  /** Serialize and write agents.yaml to the user repo, invalidating the in-memory cache. */
571
615
  export function writeMeta(meta) {
@@ -626,19 +670,6 @@ export function getVersionResources(agent, version) {
626
670
  const meta = readMeta();
627
671
  return meta.versions?.[agent]?.[version] || null;
628
672
  }
629
- export function clearVersionResources(agent, version) {
630
- const meta = readMeta();
631
- if (meta.versions?.[agent]?.[version]) {
632
- delete meta.versions[agent][version];
633
- if (Object.keys(meta.versions[agent]).length === 0) {
634
- delete meta.versions[agent];
635
- }
636
- if (Object.keys(meta.versions).length === 0) {
637
- delete meta.versions;
638
- }
639
- writeMeta(meta);
640
- }
641
- }
642
673
  /** Active rules preset for an agent@version. Defaults to "default" when unset. */
643
674
  export function getActiveRulesPreset(agent, version) {
644
675
  const meta = readMeta();
@@ -36,8 +36,8 @@ export declare function captureProcessStartTime(pid: number): string | null;
36
36
  * model_reasoning_effort override). Mode (plan/edit/full) is a separate knob.
37
37
  */
38
38
  export type EffortLevel = 'low' | 'medium' | 'high' | 'xhigh' | 'max' | 'auto';
39
- declare const VALID_MODES: readonly ["plan", "edit", "full", "auto"];
40
- type Mode = typeof VALID_MODES[number];
39
+ export declare const VALID_MODES: readonly ["plan", "edit", "auto", "skip", "full"];
40
+ type Mode = 'plan' | 'edit' | 'auto' | 'skip';
41
41
  /** Resolve a mode string to a validated Mode, falling back to the given default. */
42
42
  export declare function resolveMode(requestedMode: string | null | undefined, defaultMode?: Mode): Mode;
43
43
  /** Ensure Gemini's settings.json has experimental.plan enabled for headless plan mode. */
@@ -82,6 +82,7 @@ export declare class AgentProcess {
82
82
  after: string[];
83
83
  effort: EffortLevel | null;
84
84
  model: string | null;
85
+ profileName: string | null;
85
86
  envOverrides: Record<string, string> | null;
86
87
  taskType: TaskType | null;
87
88
  cloudRepo: string | null;
@@ -91,7 +92,7 @@ export declare class AgentProcess {
91
92
  private eventsCache;
92
93
  private lastReadPos;
93
94
  private baseDir;
94
- constructor(agentId: string, taskName: string, agentType: AgentType, prompt: string, cwd?: string | null, mode?: Mode, pid?: number | null, status?: AgentStatus, startedAt?: Date, completedAt?: Date | null, baseDir?: string | null, parentSessionId?: string | null, workspaceDir?: string | null, cloudSessionId?: string | null, cloudProvider?: string | null, prUrl?: string | null, version?: string | null, remoteSessionId?: string | null, name?: string | null, after?: string[], effort?: EffortLevel | null, model?: string | null, envOverrides?: Record<string, string> | null, taskType?: TaskType | null, cloudRepo?: string | null, cloudBranch?: string | null, worktreeName?: string | null, worktreePath?: string | null);
95
+ constructor(agentId: string, taskName: string, agentType: AgentType, prompt: string, cwd?: string | null, mode?: Mode, pid?: number | null, status?: AgentStatus, startedAt?: Date, completedAt?: Date | null, baseDir?: string | null, parentSessionId?: string | null, workspaceDir?: string | null, cloudSessionId?: string | null, cloudProvider?: string | null, prUrl?: string | null, version?: string | null, remoteSessionId?: string | null, name?: string | null, after?: string[], effort?: EffortLevel | null, model?: string | null, envOverrides?: Record<string, string> | null, taskType?: TaskType | null, cloudRepo?: string | null, cloudBranch?: string | null, worktreeName?: string | null, worktreePath?: string | null, profileName?: string | null);
95
96
  get isEditMode(): boolean;
96
97
  getAgentDir(): Promise<string>;
97
98
  /**
@@ -179,7 +180,7 @@ export declare class AgentManager {
179
180
  */
180
181
  rescanFromDisk(): Promise<number>;
181
182
  private loadExistingAgents;
182
- spawn(taskName: string, agentType: AgentType, prompt: string, cwd?: string | null, mode?: Mode | null, effort?: EffortLevel, parentSessionId?: string | null, workspaceDir?: string | null, version?: string | null, name?: string | null, after?: string[], model?: string | null, envOverrides?: Record<string, string> | null, taskType?: TaskType | null, cloudProvider?: string | null, cloudSessionId?: string | null, cloudRepo?: string | null, cloudBranch?: string | null, worktreeName?: string | null, worktreePath?: string | null): Promise<AgentProcess>;
183
+ spawn(taskName: string, agentType: AgentType, prompt: string, cwd?: string | null, mode?: Mode | null, effort?: EffortLevel, parentSessionId?: string | null, workspaceDir?: string | null, version?: string | null, name?: string | null, after?: string[], model?: string | null, envOverrides?: Record<string, string> | null, taskType?: TaskType | null, cloudProvider?: string | null, cloudSessionId?: string | null, cloudRepo?: string | null, cloudBranch?: string | null, worktreeName?: string | null, worktreePath?: string | null, profileName?: string | null): Promise<AgentProcess>;
183
184
  /**
184
185
  * Actually spawn the OS process for a teammate. Extracted from spawn() so
185
186
  * staged teammates can be launched later by startReady().
@@ -19,6 +19,7 @@ import { debug } from './debug.js';
19
19
  import { setGeminiAutoUpdateDisabled, updateGeminiSettings } from '../gemini-settings.js';
20
20
  import { getAgentsDir as getSystemAgentsDir } from '../state.js';
21
21
  import { AGENTS } from '../agents.js';
22
+ import { sanitizeProcessEnv } from '../secrets/bundles.js';
22
23
  let lastMemoryWarnAt = 0;
23
24
  // On macOS, os.freemem() returns only the truly-free pool and ignores the
24
25
  // large inactive+purgeable cache the kernel will reclaim under pressure, so
@@ -171,7 +172,7 @@ export function captureProcessStartTime(pid) {
171
172
  }
172
173
  }
173
174
  /** Agent types the team runner supports. */
174
- const TEAM_AGENT_TYPES = ['codex', 'cursor', 'gemini', 'claude', 'opencode'];
175
+ const TEAM_AGENT_TYPES = ['codex', 'cursor', 'gemini', 'claude', 'opencode', 'grok', 'antigravity'];
175
176
  // Suffix appended to all prompts to ensure agents provide a summary
176
177
  const PROMPT_SUFFIX = `
177
178
 
@@ -183,12 +184,17 @@ When you're done, provide a brief summary of:
183
184
  const CLAUDE_PLAN_MODE_PREFIX = `You are running in HEADLESS PLAN MODE. This mode works like normal plan mode with one exception: you cannot write to ~/.claude/plans/ directory. Instead of writing a plan file, output your complete plan/response as your final message.
184
185
 
185
186
  `;
186
- const VALID_MODES = ['plan', 'edit', 'full', 'auto'];
187
+ // Canonical modes plus the historical `full` alias (rewritten to `skip` by
188
+ // normalizeModeValue). Keep `full` listed so user-typed CLI flags and stored
189
+ // metadata that pre-date the rename continue to parse.
190
+ export const VALID_MODES = ['plan', 'edit', 'auto', 'skip', 'full'];
187
191
  function normalizeModeValue(modeValue) {
188
192
  if (!modeValue)
189
193
  return null;
190
194
  const normalized = modeValue.trim().toLowerCase();
191
- if (VALID_MODES.includes(normalized)) {
195
+ if (normalized === 'full')
196
+ return 'skip';
197
+ if (['plan', 'edit', 'auto', 'skip'].includes(normalized)) {
192
198
  return normalized;
193
199
  }
194
200
  return null;
@@ -201,7 +207,7 @@ function defaultModeFromEnv() {
201
207
  return parsed;
202
208
  }
203
209
  if (rawValue) {
204
- console.warn(`Invalid ${envVar}='${rawValue}'. Use 'plan' or 'edit'. Falling back to plan mode.`);
210
+ console.warn(`Invalid ${envVar}='${rawValue}'. Use plan, edit, auto, or skip. Falling back to plan mode.`);
205
211
  }
206
212
  }
207
213
  return 'plan';
@@ -253,12 +259,12 @@ function extractTimestamp(raw) {
253
259
  export function resolveMode(requestedMode, defaultMode = 'plan') {
254
260
  const normalizedDefault = normalizeModeValue(defaultMode);
255
261
  if (!normalizedDefault) {
256
- throw new Error(`Invalid default mode '${defaultMode}'. Use 'plan' or 'edit'.`);
262
+ throw new Error(`Invalid default mode '${defaultMode}'. Use plan, edit, auto, or skip.`);
257
263
  }
258
264
  if (requestedMode !== null && requestedMode !== undefined) {
259
265
  const normalizedMode = normalizeModeValue(requestedMode);
260
266
  if (!normalizedMode) {
261
- throw new Error(`Invalid mode '${requestedMode}'. Valid modes: 'plan' (read-only) or 'edit' (can write).`);
267
+ throw new Error(`Invalid mode '${requestedMode}'. Valid modes: plan (read-only), edit (can write), auto (smart classifier), skip (bypass all permissions). 'full' is accepted as alias for skip.`);
262
268
  }
263
269
  return normalizedMode;
264
270
  }
@@ -363,6 +369,11 @@ export class AgentProcess {
363
369
  // Pinned model for this teammate. When null, the agent's CLI picks its
364
370
  // own default (no --model forwarded).
365
371
  model = null;
372
+ // Profile target name when the teammate was added via `agents teams add
373
+ // <team> <profile>`. The launcher targets the profile name so env/keychain
374
+ // injection happens; agentType stays the underlying harness so event
375
+ // parsers and CLI availability checks keep working.
376
+ profileName = null;
366
377
  // Extra env vars passed through to the child process (from --env KEY=VALUE).
367
378
  envOverrides = null;
368
379
  // Factory task-type label. Drives planner fan-out. Null for plain teammates — no behavioral change.
@@ -378,13 +389,14 @@ export class AgentProcess {
378
389
  eventsCache = [];
379
390
  lastReadPos = 0;
380
391
  baseDir = null;
381
- constructor(agentId, taskName, agentType, prompt, cwd = null, mode = 'plan', pid = null, status = AgentStatus.RUNNING, startedAt = new Date(), completedAt = null, baseDir = null, parentSessionId = null, workspaceDir = null, cloudSessionId = null, cloudProvider = null, prUrl = null, version = null, remoteSessionId = null, name = null, after = [], effort = null, model = null, envOverrides = null, taskType = null, cloudRepo = null, cloudBranch = null, worktreeName = null, worktreePath = null) {
392
+ constructor(agentId, taskName, agentType, prompt, cwd = null, mode = 'plan', pid = null, status = AgentStatus.RUNNING, startedAt = new Date(), completedAt = null, baseDir = null, parentSessionId = null, workspaceDir = null, cloudSessionId = null, cloudProvider = null, prUrl = null, version = null, remoteSessionId = null, name = null, after = [], effort = null, model = null, envOverrides = null, taskType = null, cloudRepo = null, cloudBranch = null, worktreeName = null, worktreePath = null, profileName = null) {
382
393
  this.agentId = agentId;
383
394
  this.remoteSessionId = remoteSessionId;
384
395
  this.name = name;
385
396
  this.after = after;
386
397
  this.effort = effort;
387
398
  this.model = model;
399
+ this.profileName = profileName;
388
400
  this.envOverrides = envOverrides;
389
401
  this.taskType = taskType;
390
402
  this.cloudRepo = cloudRepo;
@@ -409,7 +421,9 @@ export class AgentProcess {
409
421
  this.version = version;
410
422
  }
411
423
  get isEditMode() {
412
- return this.mode === 'edit' || this.mode === 'full';
424
+ // Any mode that can mutate the workspace counts as "edit mode" for the
425
+ // purposes of guarding read-only flows (plan-mode teammates).
426
+ return this.mode === 'edit' || this.mode === 'auto' || this.mode === 'skip';
413
427
  }
414
428
  async getAgentDir() {
415
429
  const base = this.baseDir || await getAgentsDir();
@@ -466,6 +480,7 @@ export class AgentProcess {
466
480
  after: this.after,
467
481
  effort: this.effort,
468
482
  model: this.model,
483
+ profile_name: this.profileName,
469
484
  env_overrides: this.envOverrides,
470
485
  task_type: this.taskType,
471
486
  cloud_repo: this.cloudRepo,
@@ -596,6 +611,7 @@ export class AgentProcess {
596
611
  after: this.after,
597
612
  effort: this.effort,
598
613
  model: this.model,
614
+ profile_name: this.profileName,
599
615
  env_overrides: this.envOverrides,
600
616
  task_type: this.taskType,
601
617
  cloud_repo: this.cloudRepo,
@@ -619,12 +635,16 @@ export class AgentProcess {
619
635
  try {
620
636
  const metaContent = await fs.readFile(metaPath, 'utf-8');
621
637
  const meta = JSON.parse(metaContent);
622
- // Legacy teammates may have mode='ralph' or 'cloud' from before modes
623
- // were narrowed. Coerce to the closest current mode so they still load.
638
+ // Legacy teammates may have mode='ralph', 'cloud', or 'full' from before
639
+ // modes were narrowed/renamed. Coerce to the closest current mode so they
640
+ // still load.
624
641
  const modeMap = {
642
+ plan: 'plan',
625
643
  edit: 'edit',
626
- full: 'full',
627
- ralph: 'full', // ralph used the same "no-permission" flags as full
644
+ auto: 'auto',
645
+ skip: 'skip',
646
+ full: 'skip', // historical alias — `full` is the old name for `skip`
647
+ ralph: 'skip', // ralph used the same "no-permission" flags as full
628
648
  cloud: 'edit', // cloud teammates had edit-level write access
629
649
  };
630
650
  const resolvedMode = modeMap[meta.mode] || 'plan';
@@ -637,7 +657,7 @@ export class AgentProcess {
637
657
  : AgentStatus.RUNNING;
638
658
  const agent = new AgentProcess(meta.agent_id, meta.task_name || 'default', meta.agent_type, meta.prompt, meta.cwd || null, resolvedMode, meta.pid || null, resolvedStatus, new Date(meta.started_at), meta.completed_at ? new Date(meta.completed_at) : null, baseDir, meta.parent_session_id || null, meta.workspace_dir || null, meta.cloud_session_id || null, meta.cloud_provider || null, meta.pr_url || null, meta.version || null, meta.remote_session_id || null, meta.name || null, Array.isArray(meta.after) ? meta.after : [], meta.effort || null, meta.model || null, meta.env_overrides || null, meta.task_type && VALID_TASK_TYPES.includes(meta.task_type)
639
659
  ? meta.task_type
640
- : null, meta.cloud_repo || null, meta.cloud_branch || null, meta.worktree_name || null, meta.worktree_path || null);
660
+ : null, meta.cloud_repo || null, meta.cloud_branch || null, meta.worktree_name || null, meta.worktree_path || null, meta.profile_name || null);
641
661
  agent.startTime = typeof meta.start_time === 'string' ? meta.start_time : null;
642
662
  return agent;
643
663
  }
@@ -759,7 +779,7 @@ export class AgentManager {
759
779
  this.cleanupAgeDays = cleanupAgeDays;
760
780
  const resolvedDefaultMode = defaultMode ? normalizeModeValue(defaultMode) : defaultModeFromEnv();
761
781
  if (!resolvedDefaultMode) {
762
- throw new Error(`Invalid default_mode '${defaultMode}'. Use 'plan' or 'edit'.`);
782
+ throw new Error(`Invalid default_mode '${defaultMode}'. Use plan, edit, auto, or skip.`);
763
783
  }
764
784
  this.defaultMode = resolvedDefaultMode;
765
785
  this.initPromise = this.doInitialize();
@@ -875,7 +895,7 @@ export class AgentManager {
875
895
  }
876
896
  debug(`Loaded ${loadedCount} agents from disk`);
877
897
  }
878
- async spawn(taskName, agentType, prompt, cwd = null, mode = null, effort = 'medium', parentSessionId = null, workspaceDir = null, version = null, name = null, after = [], model = null, envOverrides = null, taskType = null, cloudProvider = null, cloudSessionId = null, cloudRepo = null, cloudBranch = null, worktreeName = null, worktreePath = null) {
898
+ async spawn(taskName, agentType, prompt, cwd = null, mode = null, effort = 'medium', parentSessionId = null, workspaceDir = null, version = null, name = null, after = [], model = null, envOverrides = null, taskType = null, cloudProvider = null, cloudSessionId = null, cloudRepo = null, cloudBranch = null, worktreeName = null, worktreePath = null, profileName = null) {
879
899
  await this.initialize();
880
900
  const resolvedMode = resolveMode(mode, this.defaultMode);
881
901
  // Enforce: teammate names are unique within a team.
@@ -922,6 +942,9 @@ export class AgentManager {
922
942
  // dispatched via the cloud provider and passed us the provider + session.
923
943
  const isCloudBacked = Boolean(cloudProvider);
924
944
  if (!isCloudBacked) {
945
+ // Profile-backed teammates still spawn through `agents run`, which
946
+ // resolves the profile to its host harness — so the CLI we need to be
947
+ // present is the underlying agentType, not the profile name.
925
948
  const [available, pathOrError] = checkCliAvailable(agentType);
926
949
  if (!available) {
927
950
  throw new Error(pathOrError || 'CLI tool not available');
@@ -934,7 +957,7 @@ export class AgentManager {
934
957
  const initialStatus = isStaged || !isCloudBacked
935
958
  ? AgentStatus.PENDING
936
959
  : AgentStatus.RUNNING;
937
- const agent = new AgentProcess(agentId, taskName, agentType, prompt, resolvedCwd, resolvedMode, null, initialStatus, new Date(), null, this.agentsDir, parentSessionId, workspaceDir, cloudSessionId, cloudProvider, null, version, null, name, cleanAfter, effort, model, envOverrides && Object.keys(envOverrides).length > 0 ? envOverrides : null, taskType, cloudRepo, cloudBranch, worktreeName, worktreePath);
960
+ const agent = new AgentProcess(agentId, taskName, agentType, prompt, resolvedCwd, resolvedMode, null, initialStatus, new Date(), null, this.agentsDir, parentSessionId, workspaceDir, cloudSessionId, cloudProvider, null, version, null, name, cleanAfter, effort, model, envOverrides && Object.keys(envOverrides).length > 0 ? envOverrides : null, taskType, cloudRepo, cloudBranch, worktreeName, worktreePath, profileName);
938
961
  const agentDir = await agent.getAgentDir();
939
962
  try {
940
963
  await fs.mkdir(agentDir, { recursive: true });
@@ -971,7 +994,7 @@ export class AgentManager {
971
994
  // forwarded). Effort is a separate knob wired into buildReasoningFlags
972
995
  // inside buildCommand.
973
996
  const resolvedModel = agent.model ?? null;
974
- const cmd = this.buildCommand(agent.agentType, agent.prompt, agent.mode, resolvedModel, agent.cwd, agent.agentId, effort, agent.version);
997
+ const cmd = this.buildCommand(agent.agentType, agent.prompt, agent.mode, resolvedModel, agent.cwd, agent.agentId, effort, agent.version, agent.profileName);
975
998
  debug(`Launching ${agent.agentType} agent ${agent.agentId} [${agent.mode}]: ${cmd.slice(0, 3).join(' ')}...`);
976
999
  try {
977
1000
  const stdoutPath = await agent.getStdoutPath();
@@ -982,8 +1005,8 @@ export class AgentManager {
982
1005
  cwd: agent.cwd || undefined,
983
1006
  detached: true,
984
1007
  env: agent.envOverrides
985
- ? { ...process.env, ...agent.envOverrides }
986
- : process.env,
1008
+ ? { ...sanitizeProcessEnv(process.env), ...agent.envOverrides }
1009
+ : sanitizeProcessEnv(process.env),
987
1010
  });
988
1011
  childProcess.unref();
989
1012
  stdoutFile.close().catch(() => { });
@@ -1055,15 +1078,18 @@ export class AgentManager {
1055
1078
  * exec path (src/lib/exec.ts). The team runner just supplies prompt + mode
1056
1079
  * and reads stream-json events off stdout.
1057
1080
  */
1058
- buildCommand(agentType, prompt, mode, model, cwd = null, sessionId = null, effort = 'medium', version = null) {
1081
+ buildCommand(agentType, prompt, mode, model, cwd = null, sessionId = null, effort = 'medium', version = null, profileName = null) {
1059
1082
  // Compose the prompt: a plan-mode prefix for Claude (clarifying headless
1060
1083
  // plan-mode restrictions) and a universal summary suffix. These are
1061
1084
  // team-specific prompt scaffolding — `agents run` does not apply them.
1062
1085
  let fullPrompt = prompt + PROMPT_SUFFIX;
1063
- if (agentType === 'claude' && mode !== 'edit') {
1086
+ if (agentType === 'claude' && mode === 'plan') {
1064
1087
  fullPrompt = CLAUDE_PLAN_MODE_PREFIX + fullPrompt;
1065
1088
  }
1066
- const target = version ? `${agentType}@${version}` : agentType;
1089
+ // Profile target takes precedence `agents run <profile>` resolves the
1090
+ // host harness, version pin, and env injection in one place. Plain
1091
+ // version pins only apply when no profile is selected.
1092
+ const target = profileName ?? (version ? `${agentType}@${version}` : agentType);
1067
1093
  const agentsCli = process.argv[1];
1068
1094
  const cmd = [
1069
1095
  process.execPath,
@@ -13,6 +13,7 @@ export interface SpawnResult {
13
13
  status: string;
14
14
  started_at: string;
15
15
  version?: string | null;
16
+ profile_name?: string | null;
16
17
  remote_session_id?: string | null;
17
18
  name?: string | null;
18
19
  after?: string[];
@@ -88,7 +89,7 @@ export interface TasksResult {
88
89
  tasks: TaskInfo[];
89
90
  }
90
91
  /** Spawn a new teammate in a task and return its initial metadata. */
91
- export declare function handleSpawn(manager: AgentManager, taskName: string, agentType: AgentType, prompt: string, cwd: string | null, mode: string | null, effort?: 'low' | 'medium' | 'high' | 'xhigh' | 'max' | 'auto' | null, parentSessionId?: string | null, workspaceDir?: string | null, version?: string | null, name?: string | null, after?: string[], model?: string | null, envOverrides?: Record<string, string> | null, taskType?: TaskType | null, cloudProvider?: string | null, cloudSessionId?: string | null, cloudRepo?: string | null, cloudBranch?: string | null, worktreeName?: string | null, worktreePath?: string | null): Promise<SpawnResult>;
92
+ export declare function handleSpawn(manager: AgentManager, taskName: string, agentType: AgentType, prompt: string, cwd: string | null, mode: string | null, effort?: 'low' | 'medium' | 'high' | 'xhigh' | 'max' | 'auto' | null, parentSessionId?: string | null, workspaceDir?: string | null, version?: string | null, name?: string | null, after?: string[], model?: string | null, envOverrides?: Record<string, string> | null, taskType?: TaskType | null, cloudProvider?: string | null, cloudSessionId?: string | null, cloudRepo?: string | null, cloudBranch?: string | null, worktreeName?: string | null, worktreePath?: string | null, profileName?: string | null): Promise<SpawnResult>;
92
93
  /** Retrieve the current status of all teammates in a task, with optional timestamp-based delta filtering. */
93
94
  export declare function handleStatus(manager: AgentManager, taskName: string | null | undefined, filter?: string, since?: string, // Optional ISO timestamp - return only events after this time
94
95
  parentSessionId?: string | null): Promise<TaskStatusResult>;
@@ -56,12 +56,12 @@ function recentToolCalls(events, max = 10) {
56
56
  }));
57
57
  }
58
58
  /** Spawn a new teammate in a task and return its initial metadata. */
59
- export async function handleSpawn(manager, taskName, agentType, prompt, cwd, mode, effort = 'medium', parentSessionId = null, workspaceDir = null, version = null, name = null, after = [], model = null, envOverrides = null, taskType = null, cloudProvider = null, cloudSessionId = null, cloudRepo = null, cloudBranch = null, worktreeName = null, worktreePath = null) {
59
+ export async function handleSpawn(manager, taskName, agentType, prompt, cwd, mode, effort = 'medium', parentSessionId = null, workspaceDir = null, version = null, name = null, after = [], model = null, envOverrides = null, taskType = null, cloudProvider = null, cloudSessionId = null, cloudRepo = null, cloudBranch = null, worktreeName = null, worktreePath = null, profileName = null) {
60
60
  const defaultMode = manager.getDefaultMode();
61
61
  const resolvedMode = resolveMode(mode, defaultMode);
62
62
  const resolvedEffort = effort ?? 'medium';
63
- debug(`[spawn] Spawning ${agentType} agent for task "${taskName}" [${resolvedMode}] effort=${resolvedEffort}...`);
64
- const agent = await manager.spawn(taskName, agentType, prompt, cwd, resolvedMode, resolvedEffort, parentSessionId, workspaceDir, version, name, after, model, envOverrides, taskType, cloudProvider, cloudSessionId, cloudRepo, cloudBranch, worktreeName, worktreePath);
63
+ debug(`[spawn] Spawning ${agentType} agent for task "${taskName}" [${resolvedMode}] effort=${resolvedEffort}${profileName ? ` profile=${profileName}` : ''}...`);
64
+ const agent = await manager.spawn(taskName, agentType, prompt, cwd, resolvedMode, resolvedEffort, parentSessionId, workspaceDir, version, name, after, model, envOverrides, taskType, cloudProvider, cloudSessionId, cloudRepo, cloudBranch, worktreeName, worktreePath, profileName);
65
65
  debug(`[spawn] Spawned ${agentType} agent ${agent.agentId} for task "${taskName}"`);
66
66
  return {
67
67
  task_name: taskName,
@@ -70,6 +70,7 @@ export async function handleSpawn(manager, taskName, agentType, prompt, cwd, mod
70
70
  status: agent.status,
71
71
  started_at: agent.startedAt.toISOString(),
72
72
  version: agent.version,
73
+ profile_name: agent.profileName,
73
74
  remote_session_id: agent.remoteSessionId,
74
75
  name: agent.name,
75
76
  after: agent.after,
@@ -1,5 +1,5 @@
1
1
  /** Supported agent CLI types for team spawning. */
2
- export type AgentType = 'codex' | 'gemini' | 'cursor' | 'claude' | 'opencode';
2
+ export type AgentType = 'codex' | 'gemini' | 'cursor' | 'claude' | 'opencode' | 'grok' | 'antigravity';
3
3
  /** Normalize a raw JSON event from any agent type into an array of unified event objects. */
4
4
  export declare function normalizeEvents(agentType: AgentType, raw: any): any[];
5
5
  /** Normalize a raw JSON event, returning only the first unified event (convenience wrapper). */