@phnx-labs/agents-cli 1.19.1 → 1.20.0

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 (109) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +70 -10
  3. package/dist/commands/browser.js +88 -16
  4. package/dist/commands/cli.d.ts +14 -0
  5. package/dist/commands/cli.js +244 -0
  6. package/dist/commands/commands.js +3 -3
  7. package/dist/commands/computer.js +18 -1
  8. package/dist/commands/doctor.d.ts +1 -1
  9. package/dist/commands/doctor.js +2 -2
  10. package/dist/commands/exec.js +3 -3
  11. package/dist/commands/factory.d.ts +3 -14
  12. package/dist/commands/factory.js +3 -3
  13. package/dist/commands/hooks.js +3 -3
  14. package/dist/commands/mcp.js +29 -0
  15. package/dist/commands/plugins.js +11 -4
  16. package/dist/commands/profiles.js +1 -1
  17. package/dist/commands/prune.js +39 -160
  18. package/dist/commands/pull.js +56 -3
  19. package/dist/commands/routines.js +106 -13
  20. package/dist/commands/secrets.js +6 -8
  21. package/dist/commands/sessions.d.ts +36 -7
  22. package/dist/commands/sessions.js +130 -53
  23. package/dist/commands/setup.d.ts +1 -0
  24. package/dist/commands/setup.js +37 -28
  25. package/dist/commands/skills.js +3 -3
  26. package/dist/commands/teams.js +13 -0
  27. package/dist/commands/versions.d.ts +4 -3
  28. package/dist/commands/versions.js +147 -124
  29. package/dist/commands/view.js +12 -12
  30. package/dist/index.js +34 -6
  31. package/dist/lib/acp/harnesses.js +8 -0
  32. package/dist/lib/agents.js +162 -9
  33. package/dist/lib/browser/cdp.d.ts +8 -1
  34. package/dist/lib/browser/cdp.js +40 -3
  35. package/dist/lib/browser/chrome.d.ts +13 -0
  36. package/dist/lib/browser/chrome.js +42 -3
  37. package/dist/lib/browser/domain-skills.d.ts +51 -0
  38. package/dist/lib/browser/domain-skills.js +157 -0
  39. package/dist/lib/browser/drivers/local.js +45 -4
  40. package/dist/lib/browser/drivers/ssh.js +1 -1
  41. package/dist/lib/browser/ipc.d.ts +8 -1
  42. package/dist/lib/browser/ipc.js +37 -28
  43. package/dist/lib/browser/profiles.d.ts +13 -0
  44. package/dist/lib/browser/profiles.js +41 -1
  45. package/dist/lib/browser/service.d.ts +3 -0
  46. package/dist/lib/browser/service.js +21 -5
  47. package/dist/lib/browser/types.d.ts +7 -0
  48. package/dist/lib/cli-resources.d.ts +109 -0
  49. package/dist/lib/cli-resources.js +255 -0
  50. package/dist/lib/cloud/rush.js +5 -5
  51. package/dist/lib/command-skills.js +0 -2
  52. package/dist/lib/computer-rpc.d.ts +3 -0
  53. package/dist/lib/computer-rpc.js +53 -0
  54. package/dist/lib/daemon.js +20 -0
  55. package/dist/lib/exec.d.ts +3 -2
  56. package/dist/lib/exec.js +62 -6
  57. package/dist/lib/hooks.js +182 -0
  58. package/dist/lib/mcp.js +6 -0
  59. package/dist/lib/migrate.js +1 -1
  60. package/dist/lib/overdue.d.ts +26 -0
  61. package/dist/lib/overdue.js +101 -0
  62. package/dist/lib/permissions.js +5 -1
  63. package/dist/lib/plugin-marketplace.js +1 -1
  64. package/dist/lib/profiles-presets.js +37 -0
  65. package/dist/lib/registry.d.ts +18 -0
  66. package/dist/lib/registry.js +44 -0
  67. package/dist/lib/resources/mcp.js +43 -1
  68. package/dist/lib/resources/types.d.ts +1 -1
  69. package/dist/lib/resources.d.ts +1 -1
  70. package/dist/lib/rotate.js +10 -4
  71. package/dist/lib/routines-format.d.ts +35 -0
  72. package/dist/lib/routines-format.js +173 -0
  73. package/dist/lib/routines.d.ts +7 -1
  74. package/dist/lib/routines.js +32 -12
  75. package/dist/lib/runner.js +19 -5
  76. package/dist/lib/scheduler.js +8 -1
  77. package/dist/lib/secrets/{AgentsKeychain.app → Agents CLI.app}/Contents/CodeResources +0 -0
  78. package/dist/lib/secrets/{AgentsKeychain.app/Contents/Info.plist → Agents CLI.app/Contents/Info.plist } +4 -2
  79. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  80. package/dist/lib/secrets/bundles.d.ts +33 -2
  81. package/dist/lib/secrets/bundles.js +249 -26
  82. package/dist/lib/secrets/index.d.ts +10 -1
  83. package/dist/lib/secrets/index.js +143 -48
  84. package/dist/lib/session/active.d.ts +8 -0
  85. package/dist/lib/session/active.js +3 -2
  86. package/dist/lib/session/db.d.ts +10 -4
  87. package/dist/lib/session/db.js +16 -16
  88. package/dist/lib/session/parse.d.ts +1 -0
  89. package/dist/lib/session/parse.js +44 -0
  90. package/dist/lib/session/types.d.ts +1 -1
  91. package/dist/lib/session/types.js +1 -1
  92. package/dist/lib/shims.d.ts +6 -2
  93. package/dist/lib/shims.js +88 -10
  94. package/dist/lib/state.d.ts +0 -1
  95. package/dist/lib/state.js +2 -15
  96. package/dist/lib/teams/agents.js +1 -1
  97. package/dist/lib/teams/parsers.d.ts +1 -1
  98. package/dist/lib/teams/parsers.js +153 -3
  99. package/dist/lib/teams/summarizer.js +18 -2
  100. package/dist/lib/teams/worktree.js +14 -3
  101. package/dist/lib/types.d.ts +7 -4
  102. package/dist/lib/types.js +6 -3
  103. package/dist/lib/versions.d.ts +10 -2
  104. package/dist/lib/versions.js +227 -35
  105. package/package.json +9 -9
  106. package/dist/lib/secrets/AgentsKeychain.app/Contents/MacOS/AgentsKeychain +0 -0
  107. package/npm-shrinkwrap.json +0 -3162
  108. /package/dist/lib/secrets/{AgentsKeychain.app → Agents CLI.app}/Contents/_CodeSignature/CodeResources +0 -0
  109. /package/dist/lib/secrets/{AgentsKeychain.app → Agents CLI.app}/Contents/embedded.provisionprofile +0 -0
@@ -155,7 +155,13 @@ export declare function getRowCount(): {
155
155
  sessions: number;
156
156
  textRows: number;
157
157
  };
158
- /** Count sessions older than the given timestamp (for dry-run previews). */
159
- export declare function countSessionsOlderThan(cutoffMs: number): number;
160
- /** Delete sessions older than the given timestamp. Returns the number of rows deleted. */
161
- export declare function deleteSessionsOlderThan(cutoffMs: number): number;
158
+ /**
159
+ * Rewrite file_path for all sessions whose path starts with oldPrefix, replacing
160
+ * it with newPrefix + the unchanged suffix. Also clears the matching scan_ledger
161
+ * entries so they are re-indexed from the new location on the next scan.
162
+ *
163
+ * Used by removeVersion after soft-deleting a version directory to trash, so
164
+ * that session reads (transcript view, /continue) still work from the trash path.
165
+ * Returns the number of session rows updated.
166
+ */
167
+ export declare function updateSessionFilePaths(oldPrefix: string, newPrefix: string): number;
@@ -742,27 +742,27 @@ export function getRowCount() {
742
742
  const textRows = db.prepare(`SELECT COUNT(*) AS c FROM session_text`).get().c;
743
743
  return { sessions, textRows };
744
744
  }
745
- /** Count sessions older than the given timestamp (for dry-run previews). */
746
- export function countSessionsOlderThan(cutoffMs) {
747
- const db = getDB();
748
- const cutoffIso = new Date(cutoffMs).toISOString();
749
- const row = db.prepare(`SELECT COUNT(*) AS n FROM sessions WHERE timestamp < ?`).get(cutoffIso);
750
- return row.n;
751
- }
752
- /** Delete sessions older than the given timestamp. Returns the number of rows deleted. */
753
- export function deleteSessionsOlderThan(cutoffMs) {
745
+ /**
746
+ * Rewrite file_path for all sessions whose path starts with oldPrefix, replacing
747
+ * it with newPrefix + the unchanged suffix. Also clears the matching scan_ledger
748
+ * entries so they are re-indexed from the new location on the next scan.
749
+ *
750
+ * Used by removeVersion after soft-deleting a version directory to trash, so
751
+ * that session reads (transcript view, /continue) still work from the trash path.
752
+ * Returns the number of session rows updated.
753
+ */
754
+ export function updateSessionFilePaths(oldPrefix, newPrefix) {
754
755
  const db = getDB();
755
- const cutoffIso = new Date(cutoffMs).toISOString();
756
- const rows = db.prepare(`SELECT id, file_path FROM sessions WHERE timestamp < ?`).all(cutoffIso);
756
+ const rows = db
757
+ .prepare(`SELECT id, file_path FROM sessions WHERE file_path LIKE ?`)
758
+ .all(oldPrefix + '%');
757
759
  if (rows.length === 0)
758
760
  return 0;
759
761
  const txn = db.transaction(() => {
760
762
  for (const { id, file_path } of rows) {
761
- db.prepare(`DELETE FROM session_text WHERE session_id = ?`).run(id);
762
- db.prepare(`DELETE FROM sessions WHERE id = ?`).run(id);
763
- if (file_path) {
764
- db.prepare(`DELETE FROM scan_ledger WHERE file_path = ?`).run(canonicalLedgerKey(file_path));
765
- }
763
+ const newPath = newPrefix + file_path.slice(oldPrefix.length);
764
+ db.prepare(`UPDATE sessions SET file_path = ? WHERE id = ?`).run(newPath, id);
765
+ db.prepare(`DELETE FROM scan_ledger WHERE file_path = ?`).run(canonicalLedgerKey(file_path));
766
766
  }
767
767
  });
768
768
  txn();
@@ -43,6 +43,7 @@ export declare function parseGemini(filePath: string): SessionEvent[];
43
43
  * Messages have role (user/assistant) and metadata.
44
44
  * Parts contain the actual content: text, tool, reasoning, patch, step-start/finish.
45
45
  */
46
+ export declare function parseGrok(filePath: string): SessionEvent[];
46
47
  export declare function parseOpenCode(filePath: string): SessionEvent[];
47
48
  /** Parse a Rush JSONL session file into normalized events. */
48
49
  export declare function parseRush(filePath: string): SessionEvent[];
@@ -93,6 +93,9 @@ export function parseSession(filePath, agent) {
93
93
  case 'opencode':
94
94
  events = parseOpenCode(filePath);
95
95
  break;
96
+ case 'grok':
97
+ events = parseGrok(filePath);
98
+ break;
96
99
  case 'rush':
97
100
  events = parseRush(filePath);
98
101
  break;
@@ -118,6 +121,8 @@ export function detectAgent(filePath) {
118
121
  return 'codex';
119
122
  if (filePath.includes('/.gemini/') || filePath.includes('\\.gemini\\'))
120
123
  return 'gemini';
124
+ if (filePath.includes('/.grok/') || filePath.includes('\\.grok\\'))
125
+ return 'grok';
121
126
  if (filePath.includes('/.rush/') || filePath.includes('\\.rush\\'))
122
127
  return 'rush';
123
128
  if (filePath.includes('/.hermes/') || filePath.includes('\\.hermes\\'))
@@ -645,6 +650,45 @@ function extractGeminiContent(content) {
645
650
  * Messages have role (user/assistant) and metadata.
646
651
  * Parts contain the actual content: text, tool, reasoning, patch, step-start/finish.
647
652
  */
653
+ export function parseGrok(filePath) {
654
+ // Grok sessions are rich (summary.json + events.jsonl + chat_history.jsonl + updates.jsonl)
655
+ // This is a minimal stub for now so grok appears in `agents sessions`.
656
+ // Full parser (with subagents, tool calls, etc.) can be expanded later.
657
+ try {
658
+ const content = fs.readFileSync(filePath, 'utf-8');
659
+ // If it's a summary.json, create a basic event
660
+ if (filePath.endsWith('summary.json')) {
661
+ const summary = JSON.parse(content);
662
+ return [{
663
+ timestamp: summary.created_at || new Date().toISOString(),
664
+ type: 'session_start',
665
+ content: summary.session_summary || 'Grok session',
666
+ agent: 'grok',
667
+ metadata: { sessionId: summary.id, cwd: summary.cwd },
668
+ }];
669
+ }
670
+ // For JSONL files (events, chat_history, updates), return basic parsed lines
671
+ if (filePath.endsWith('.jsonl')) {
672
+ const lines = content.trim().split('\n').filter(Boolean);
673
+ return lines.slice(0, 50).map((line, i) => {
674
+ try {
675
+ const obj = JSON.parse(line);
676
+ return {
677
+ timestamp: obj.timestamp || obj.ts || new Date().toISOString(),
678
+ type: obj.type || obj.method || 'grok_event',
679
+ content: typeof obj.content === 'string' ? obj.content : JSON.stringify(obj).slice(0, 200),
680
+ agent: 'grok',
681
+ };
682
+ }
683
+ catch {
684
+ return { timestamp: new Date().toISOString(), type: 'raw', content: line.slice(0, 200), agent: 'grok' };
685
+ }
686
+ });
687
+ }
688
+ }
689
+ catch { }
690
+ return [];
691
+ }
648
692
  export function parseOpenCode(filePath) {
649
693
  const [dbPath, sessionId] = filePath.split('#');
650
694
  if (!dbPath || !sessionId)
@@ -7,7 +7,7 @@
7
7
  * speaks these types.
8
8
  */
9
9
  /** Agents that store session data on disk and can be discovered by `agents sessions`. */
10
- export type SessionAgentId = 'claude' | 'codex' | 'gemini' | 'opencode' | 'openclaw' | 'rush' | 'hermes';
10
+ export type SessionAgentId = 'claude' | 'codex' | 'gemini' | 'opencode' | 'openclaw' | 'rush' | 'hermes' | 'grok';
11
11
  /** All agents with session discovery support, in display order. */
12
12
  export declare const SESSION_AGENTS: SessionAgentId[];
13
13
  /** A single normalized event within a session (message, tool call, thinking, etc.). */
@@ -7,4 +7,4 @@
7
7
  * speaks these types.
8
8
  */
9
9
  /** All agents with session discovery support, in display order. */
10
- export const SESSION_AGENTS = ['claude', 'codex', 'gemini', 'opencode', 'openclaw', 'rush', 'hermes'];
10
+ export const SESSION_AGENTS = ['claude', 'codex', 'gemini', 'opencode', 'openclaw', 'rush', 'hermes', 'grok'];
@@ -55,8 +55,12 @@ export interface ConflictInfo {
55
55
  * v12 — helper calls inside generated shims use the absolute agents-cli
56
56
  * entrypoint instead of PATH-resolved `agents`.
57
57
  * v13 — validate agents.yaml version strings before constructing binary paths.
58
+ * v14 — derive `configDirName` from `agentConfig.configDir` relative to $HOME
59
+ * instead of hardcoding `.${agent}`. Backwards-compatible for every
60
+ * existing agent (their configDir is `~/.{agent}`); enables nested
61
+ * layouts like Antigravity's `~/.gemini/antigravity-cli/`.
58
62
  */
59
- export declare const SHIM_SCHEMA_VERSION = 13;
63
+ export declare const SHIM_SCHEMA_VERSION = 14;
60
64
  /**
61
65
  * Generate the full bash shim script for the given agent. The returned string
62
66
  * is written to ~/.agents/shims/{cliCommand} and made executable.
@@ -209,7 +213,7 @@ export declare function getPathShadowingExecutable(agent: AgentId): string | nul
209
213
  * Delete the legacy ~/.agents/shims/<cli> file if it exists, returning whether
210
214
  * anything was removed. Pre-split installs put shims under ~/.agents/shims/;
211
215
  * the new layout uses ~/.agents-system/shims/. The leftover file causes the
212
- * repair-prompt loop reported in EXAMPLE-664 — `getPathShadowingExecutable` flags
216
+ * repair-prompt loop reported in PROJ-789 — `getPathShadowingExecutable` flags
213
217
  * it as a shadow but `addShimsToPath` only edits rc files, never the file
214
218
  * itself. Removing it ends the loop.
215
219
  */
package/dist/lib/shims.js CHANGED
@@ -179,8 +179,12 @@ async function promptConflictStrategy(conflictInfos) {
179
179
  * v12 — helper calls inside generated shims use the absolute agents-cli
180
180
  * entrypoint instead of PATH-resolved `agents`.
181
181
  * v13 — validate agents.yaml version strings before constructing binary paths.
182
+ * v14 — derive `configDirName` from `agentConfig.configDir` relative to $HOME
183
+ * instead of hardcoding `.${agent}`. Backwards-compatible for every
184
+ * existing agent (their configDir is `~/.{agent}`); enables nested
185
+ * layouts like Antigravity's `~/.gemini/antigravity-cli/`.
182
186
  */
183
- export const SHIM_SCHEMA_VERSION = 13;
187
+ export const SHIM_SCHEMA_VERSION = 14;
184
188
  /** Internal marker string used to embed the schema version in shim scripts. */
185
189
  const SHIM_VERSION_MARKER = 'agents-shim-version:';
186
190
  function shellQuote(value) {
@@ -196,7 +200,11 @@ function getAgentsBinForGeneratedShim() {
196
200
  export function generateShimScript(agent) {
197
201
  const agentConfig = AGENTS[agent];
198
202
  const cliCommand = agentConfig.cliCommand;
199
- const configDirName = `.${agent}`;
203
+ // Derive the relative config-dir path from the registry. For most agents
204
+ // this is just `.${agent}` (e.g., `.claude`, `.codex`); for nested layouts
205
+ // like Antigravity (`~/.gemini/antigravity-cli`) it carries the full
206
+ // subpath so per-version HOME symlinks reach the right place.
207
+ const configDirName = path.relative(os.homedir(), agentConfig.configDir);
200
208
  const agentsBin = shellQuote(getAgentsBinForGeneratedShim());
201
209
  const managedEnv = agent === 'claude'
202
210
  ? `
@@ -218,7 +226,22 @@ fi
218
226
  # written by agents-cli actually take effect.
219
227
  export CODEX_HOME="$VERSION_DIR/home/${configDirName}"
220
228
  `
221
- : '';
229
+ : agent === 'copilot'
230
+ ? `
231
+ # GitHub Copilot CLI honors COPILOT_HOME to relocate its config and state
232
+ # (settings.json, mcp-config.json, session-state/, logs/, plugins/). Point
233
+ # it at the versioned home so MCP servers, custom agents, and session
234
+ # history are isolated per copilot version.
235
+ export COPILOT_HOME="$VERSION_DIR/home/${configDirName}"
236
+ `
237
+ : agent === 'grok'
238
+ ? `
239
+ # Grok Build uses GROK_HOME to isolate its entire configuration tree
240
+ # (skills, hooks, plugins, agents, memory, sessions, config.toml, MCP, etc.).
241
+ # This gives agents-cli full versioned isolation + resource sync for grok.
242
+ export GROK_HOME="$VERSION_DIR/home/.grok"
243
+ `
244
+ : '';
222
245
  // Agents that don't natively resolve @-imports in their rules file need
223
246
  // agents-cli to recompile when the user edits a rule/preset file. The
224
247
  // check is fast (sha256 of ~8 small files) and skips the recompile when
@@ -368,7 +391,27 @@ if [[ ! "$VERSION" =~ ^(latest|[A-Za-z0-9._+-]{1,64})$ || "$VERSION" == *..* ]];
368
391
  fi
369
392
 
370
393
  VERSION_DIR="$AGENTS_USER_DIR/.history/versions/$AGENT/$VERSION"
371
- BINARY="$VERSION_DIR/node_modules/.bin/$CLI_COMMAND"
394
+
395
+ # Grok special case: binary lives in ~/.grok/downloads/, not node_modules.
396
+ # We still use the agents-cli version dir purely for GROK_HOME isolation.
397
+ if [ "$AGENT" = "grok" ]; then
398
+ # Try to find a matching binary for the pinned version in the global grok downloads dir.
399
+ GROK_DOWNLOADS="$HOME/.grok/downloads"
400
+ if [ -d "$GROK_DOWNLOADS" ]; then
401
+ # Prefer a binary whose filename contains the exact version
402
+ BINARY=$(ls "$GROK_DOWNLOADS"/grok-* 2>/dev/null | grep -i "$VERSION" | head -1)
403
+ if [ -z "$BINARY" ]; then
404
+ # Fallback to the "current" grok binary (symlink or latest)
405
+ BINARY=$(ls "$GROK_DOWNLOADS"/grok-* 2>/dev/null | head -1)
406
+ fi
407
+ fi
408
+ if [ -z "$BINARY" ] || [ ! -x "$BINARY" ]; then
409
+ # Last resort: whatever is on PATH (user may have installed grok globally)
410
+ BINARY=$(command -v grok 2>/dev/null || echo "")
411
+ fi
412
+ else
413
+ BINARY="$VERSION_DIR/node_modules/.bin/$CLI_COMMAND"
414
+ fi
372
415
 
373
416
  # Auto-install if not present
374
417
  if [ ! -x "$BINARY" ]; then
@@ -504,7 +547,9 @@ function assertSafeVersion(version) {
504
547
  export function generateVersionedAliasScript(agent, version) {
505
548
  assertSafeVersion(version);
506
549
  const agentConfig = AGENTS[agent];
507
- const configDirName = `.${agent}`;
550
+ // Same derivation as `generateShimScript` so nested layouts (e.g.,
551
+ // Antigravity's `~/.gemini/antigravity-cli`) land in the right place.
552
+ const configDirName = path.relative(os.homedir(), agentConfig.configDir);
508
553
  const managedEnv = agent === 'claude'
509
554
  ? `
510
555
  # Claude stores OAuth credentials in the macOS keychain. Scope them to this
@@ -518,7 +563,14 @@ export CLAUDE_CONFIG_DIR="$HOME/.agents/.history/versions/${agent}/${version}/ho
518
563
  # and rules written by agents-cli actually take effect.
519
564
  export CODEX_HOME="$HOME/.agents/.history/versions/${agent}/${version}/home/${configDirName}"
520
565
  `
521
- : '';
566
+ : agent === 'copilot'
567
+ ? `
568
+ # Copilot honors COPILOT_HOME to relocate ~/.copilot (settings, mcp-config.json,
569
+ # session-state, logs). Point direct aliases at the versioned home so per-
570
+ # version MCP and session state are isolated.
571
+ export COPILOT_HOME="$HOME/.agents/.history/versions/${agent}/${version}/home/${configDirName}"
572
+ `
573
+ : '';
522
574
  const launchArgs = agent === 'codex' ? ' -c check_for_update_on_startup=false' : '';
523
575
  return `#!/bin/bash
524
576
  # Auto-generated by agents-cli - do not edit
@@ -637,7 +689,9 @@ function getAgentConfigPath(agent) {
637
689
  function getVersionConfigPath(agent, version) {
638
690
  const agentConfig = AGENTS[agent];
639
691
  const versionsDir = getVersionsDir();
640
- const configDirName = `.${agent}`; // .claude, .codex, etc.
692
+ // Carry the agent's full configDir subpath so nested layouts work.
693
+ // e.g., antigravity → `.gemini/antigravity-cli`, claude → `.claude`.
694
+ const configDirName = path.relative(os.homedir(), agentConfig.configDir);
641
695
  return path.join(versionsDir, agent, version, 'home', configDirName);
642
696
  }
643
697
  /**
@@ -707,8 +761,29 @@ export async function switchConfigSymlink(agent, version) {
707
761
  // Already pointing to correct target, no-op
708
762
  return { success: true };
709
763
  }
764
+ // openclaw mixes user data (openclaw.json, openclaw.db, per-agent
765
+ // workspaces under ~/.openclaw/{agentId}/, memory/) with the version
766
+ // home — silently swapping the symlink to a fresh version home strips
767
+ // every running agent's config + workspace + memory. Carry the user
768
+ // data forward into the new version home before flipping the symlink
769
+ // (keep-dest preserves anything the new version already shipped).
770
+ // Other agents (Claude, Codex, etc.) keep user data outside the
771
+ // version-home dir, so this is openclaw-only by design.
772
+ if (agent === 'openclaw') {
773
+ try {
774
+ if (fs.existsSync(resolvedCurrent) && fs.statSync(resolvedCurrent).isDirectory()) {
775
+ await copyDirContents(resolvedCurrent, versionConfigPath, 'keep-dest');
776
+ }
777
+ }
778
+ catch (migrationErr) {
779
+ console.error(`Warning: openclaw data migration from ${resolvedCurrent} -> ${versionConfigPath} ` +
780
+ `failed: ${migrationErr.message}. The previous version's data is intact ` +
781
+ `at the old path; you can copy it manually if needed.`);
782
+ }
783
+ }
710
784
  // Different target - update it
711
785
  fs.unlinkSync(configPath);
786
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
712
787
  fs.symlinkSync(versionConfigPath, configPath);
713
788
  return { success: true };
714
789
  }
@@ -721,7 +796,7 @@ export async function switchConfigSymlink(agent, version) {
721
796
  const finalBackupPath = path.join(agentBackupDir, String(timestamp));
722
797
  fs.mkdirSync(agentBackupDir, { recursive: true });
723
798
  fs.renameSync(configPath, finalBackupPath);
724
- // Create symlink
799
+ // Create symlink (parent already exists since the dir we just moved was here)
725
800
  fs.symlinkSync(versionConfigPath, configPath);
726
801
  return { success: true, backupPath: finalBackupPath };
727
802
  }
@@ -731,7 +806,10 @@ export async function switchConfigSymlink(agent, version) {
731
806
  }
732
807
  catch (err) {
733
808
  if (err.code === 'ENOENT') {
734
- // Config path doesn't exist - create symlink
809
+ // Config path doesn't exist - create symlink.
810
+ // For nested layouts (e.g., ~/.gemini/antigravity-cli) the parent dir
811
+ // may also be missing if the parent agent (Gemini) is not installed.
812
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
735
813
  fs.symlinkSync(versionConfigPath, configPath);
736
814
  return { success: true };
737
815
  }
@@ -1166,7 +1244,7 @@ export function getPathShadowingExecutable(agent) {
1166
1244
  * Delete the legacy ~/.agents/shims/<cli> file if it exists, returning whether
1167
1245
  * anything was removed. Pre-split installs put shims under ~/.agents/shims/;
1168
1246
  * the new layout uses ~/.agents-system/shims/. The leftover file causes the
1169
- * repair-prompt loop reported in EXAMPLE-664 — `getPathShadowingExecutable` flags
1247
+ * repair-prompt loop reported in PROJ-789 — `getPathShadowingExecutable` flags
1170
1248
  * it as a shadow but `addShimsToPath` only edits rc files, never the file
1171
1249
  * itself. Removing it ends the loop.
1172
1250
  */
@@ -212,7 +212,6 @@ export declare function recordVersionResources(_agent: AgentId, _version: string
212
212
  */
213
213
  export declare function ensureVersionResourcePatterns(agent: AgentId, version: string, updates: Partial<Record<Exclude<keyof VersionResources, 'rulesPreset'>, ResourcePattern[]>>): void;
214
214
  export declare function getVersionResources(agent: AgentId, version: string): VersionResources | null;
215
- export declare function clearVersionResources(agent: AgentId, version: string): void;
216
215
  /** Active rules preset for an agent@version. Defaults to "default" when unset. */
217
216
  export declare function getActiveRulesPreset(agent: AgentId, version: string): string;
218
217
  /** Persist the active rules preset for an agent@version. */
package/dist/lib/state.js CHANGED
@@ -74,7 +74,7 @@ const DRIVE_DIR = path.join(CACHE_DIR, 'drive');
74
74
  const TERMINALS_DIR = path.join(CACHE_DIR, 'terminals');
75
75
  const LOGS_DIR = path.join(CACHE_DIR, 'logs');
76
76
  const RUNTIME_STATE_DIR = path.join(CACHE_DIR, 'state');
77
- const SWARMIFY_DIR = path.join(CACHE_DIR, 'companion');
77
+ const COMPANION_CACHE_DIR = path.join(CACHE_DIR, 'companion');
78
78
  const BROWSER_RUNTIME_DIR = path.join(CACHE_DIR, 'browser');
79
79
  const HELPERS_DIR = path.join(CACHE_DIR, 'helpers');
80
80
  const DAEMON_DIR = path.join(HELPERS_DIR, 'daemon');
@@ -294,7 +294,7 @@ export function getLogsDir() { return LOGS_DIR; }
294
294
  /** Path to per-process runtime state (~/.agents/.cache/state/). */
295
295
  export function getRuntimeStateDir() { return RUNTIME_STATE_DIR; }
296
296
  /** Path to companion-extension scratch (~/.agents/.cache/companion/). */
297
- export function getCompanionDir() { return SWARMIFY_DIR; }
297
+ export function getCompanionDir() { return COMPANION_CACHE_DIR; }
298
298
  /** Path to browser runtime data — chrome-data, pids (~/.agents/.cache/browser/). */
299
299
  export function getBrowserRuntimeDir() { return BROWSER_RUNTIME_DIR; }
300
300
  /** Path to helper subprocess scratch (~/.agents/.cache/helpers/). */
@@ -626,19 +626,6 @@ export function getVersionResources(agent, version) {
626
626
  const meta = readMeta();
627
627
  return meta.versions?.[agent]?.[version] || null;
628
628
  }
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
629
  /** Active rules preset for an agent@version. Defaults to "default" when unset. */
643
630
  export function getActiveRulesPreset(agent, version) {
644
631
  const meta = readMeta();
@@ -171,7 +171,7 @@ export function captureProcessStartTime(pid) {
171
171
  }
172
172
  }
173
173
  /** Agent types the team runner supports. */
174
- const TEAM_AGENT_TYPES = ['codex', 'cursor', 'gemini', 'claude', 'opencode'];
174
+ const TEAM_AGENT_TYPES = ['codex', 'cursor', 'gemini', 'claude', 'opencode', 'grok', 'antigravity'];
175
175
  // Suffix appended to all prompts to ensure agents provide a summary
176
176
  const PROMPT_SUFFIX = `
177
177
 
@@ -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). */
@@ -2,9 +2,9 @@
2
2
  * Agent event stream parsers.
3
3
  *
4
4
  * Normalizes the heterogeneous JSON event formats emitted by each agent CLI
5
- * (Claude, Codex, Gemini, Cursor, OpenCode) into a unified event schema
6
- * with consistent types: init, message, tool_use, bash, file_read, file_write,
7
- * file_create, file_delete, result, error, and others.
5
+ * (Claude, Codex, Gemini, Cursor, OpenCode, Grok, Antigravity) into a unified
6
+ * event schema with consistent types: init, message, tool_use, bash,
7
+ * file_read, file_write, file_create, file_delete, result, error, and others.
8
8
  */
9
9
  import { extractFileOpsFromBash } from './file_ops.js';
10
10
  const claudeToolUseMap = new Map();
@@ -25,6 +25,12 @@ export function normalizeEvents(agentType, raw) {
25
25
  else if (agentType === 'opencode') {
26
26
  return normalizeOpencode(raw);
27
27
  }
28
+ else if (agentType === 'grok') {
29
+ return normalizeGrok(raw);
30
+ }
31
+ else if (agentType === 'antigravity') {
32
+ return normalizeAntigravity(raw);
33
+ }
28
34
  const timestamp = new Date().toISOString();
29
35
  return [{
30
36
  type: raw.type || 'unknown',
@@ -824,6 +830,150 @@ function normalizeOpencode(raw) {
824
830
  timestamp: timestamp,
825
831
  }];
826
832
  }
833
+ // --- Grok parsing ---
834
+ // Grok's streaming-json mode emits one JSON object per token, with three event
835
+ // types:
836
+ // {"type":"thought","data":"<chunk>"} — reasoning tokens (many, small)
837
+ // {"type":"text","data":"<chunk>"} — visible response tokens (many, small)
838
+ // {"type":"end","stopReason":"EndTurn","sessionId":"<uuid>","requestId":"<uuid>"}
839
+ //
840
+ // Tool calls are NOT exposed as separate events in this format; they appear
841
+ // inside the `thought` text as XML-like markup. Extracting them reliably would
842
+ // require running a streaming XML/markup parser over concatenated thought
843
+ // chunks, which is out of scope for v1. The teams summary will show grok
844
+ // teammates' bash/file ops as empty — known limitation, fixable later by
845
+ // switching to grok's `agent` subcommand (richer event stream) once stable.
846
+ //
847
+ // Tokens are emitted as `message` events with `complete: false` so the
848
+ // summarizer can concatenate them into a final message; `thinking` events are
849
+ // already collapsed by the summarizer's groupAndFlattenEvents pathway.
850
+ function normalizeGrok(raw) {
851
+ if (!raw || typeof raw !== 'object') {
852
+ return [{
853
+ type: 'unknown',
854
+ agent: 'grok',
855
+ raw: raw,
856
+ timestamp: new Date().toISOString(),
857
+ }];
858
+ }
859
+ const eventType = raw.type || 'unknown';
860
+ const timestamp = new Date().toISOString();
861
+ if (eventType === 'thought') {
862
+ const data = typeof raw.data === 'string' ? raw.data : '';
863
+ if (!data)
864
+ return [];
865
+ return [{
866
+ type: 'thinking',
867
+ agent: 'grok',
868
+ content: data,
869
+ timestamp: timestamp,
870
+ }];
871
+ }
872
+ if (eventType === 'text') {
873
+ const data = typeof raw.data === 'string' ? raw.data : '';
874
+ if (!data)
875
+ return [];
876
+ return [{
877
+ type: 'message',
878
+ agent: 'grok',
879
+ content: data,
880
+ complete: false,
881
+ timestamp: timestamp,
882
+ }];
883
+ }
884
+ if (eventType === 'end') {
885
+ const stopReason = typeof raw.stopReason === 'string' ? raw.stopReason : '';
886
+ const status = stopReason === 'EndTurn' || stopReason === 'StopSequence' || stopReason === ''
887
+ ? 'success'
888
+ : 'error';
889
+ return [{
890
+ type: 'result',
891
+ agent: 'grok',
892
+ status: status,
893
+ stop_reason: stopReason || null,
894
+ session_id: typeof raw.sessionId === 'string' ? raw.sessionId : null,
895
+ timestamp: timestamp,
896
+ }];
897
+ }
898
+ return [{
899
+ type: eventType,
900
+ agent: 'grok',
901
+ raw: raw,
902
+ timestamp: timestamp,
903
+ }];
904
+ }
905
+ // --- Antigravity parsing ---
906
+ // Intentionally conservative. Antigravity's `agy` binary advertises an
907
+ // `--output-format json` flag in its docs, but the released binary errors with
908
+ // `flags provided but not defined: -output-format` (tracked upstream as
909
+ // google-antigravity/antigravity-cli#7, open as of May 2026). Until JSON
910
+ // streaming stabilizes, this parser treats agy output as a black box:
911
+ // - non-object input (a plain string line, or null/number) becomes a single
912
+ // `message` event with the full content and complete:true so the
913
+ // summarizer captures it without token-level concatenation
914
+ // - objects with a recognizable `type` field (e.g. `init`, `message`,
915
+ // `result`) get a minimal shape-preserving normalization
916
+ // - everything else falls through to the generic unknown-event shape
917
+ // Once agy ships stable streaming JSON, replace this with proper event
918
+ // mapping mirroring normalizeGrok / normalizeClaude.
919
+ function normalizeAntigravity(raw) {
920
+ const timestamp = new Date().toISOString();
921
+ if (typeof raw === 'string') {
922
+ if (!raw)
923
+ return [];
924
+ return [{
925
+ type: 'message',
926
+ agent: 'antigravity',
927
+ content: raw,
928
+ complete: true,
929
+ timestamp: timestamp,
930
+ }];
931
+ }
932
+ if (!raw || typeof raw !== 'object') {
933
+ return [{
934
+ type: 'unknown',
935
+ agent: 'antigravity',
936
+ raw: raw,
937
+ timestamp: timestamp,
938
+ }];
939
+ }
940
+ const eventType = raw.type || 'unknown';
941
+ if (eventType === 'init') {
942
+ return [{
943
+ type: 'init',
944
+ agent: 'antigravity',
945
+ session_id: typeof raw.sessionId === 'string' ? raw.sessionId : null,
946
+ timestamp: timestamp,
947
+ }];
948
+ }
949
+ if (eventType === 'message') {
950
+ const content = typeof raw.content === 'string' ? raw.content : '';
951
+ if (!content)
952
+ return [];
953
+ return [{
954
+ type: 'message',
955
+ agent: 'antigravity',
956
+ content: content,
957
+ complete: raw.complete !== false,
958
+ timestamp: timestamp,
959
+ }];
960
+ }
961
+ if (eventType === 'result') {
962
+ return [{
963
+ type: 'result',
964
+ agent: 'antigravity',
965
+ status: raw.status === 'error' ? 'error' : 'success',
966
+ session_id: typeof raw.sessionId === 'string' ? raw.sessionId : null,
967
+ timestamp: timestamp,
968
+ }];
969
+ }
970
+ return [{
971
+ type: eventType,
972
+ agent: 'antigravity',
973
+ raw: raw,
974
+ timestamp: timestamp,
975
+ }];
976
+ }
827
977
  /** Parse a single JSONL line into normalized events. Returns null if the line is not valid JSON. */
828
978
  export function parseEvent(agentType, line) {
829
979
  try {
@@ -159,11 +159,18 @@ export function groupAndFlattenEvents(events) {
159
159
  if (eventType === 'message' || eventType === 'thinking') {
160
160
  let count = 1;
161
161
  let combinedContent = event.content || '';
162
+ // Streaming token events (complete:false) get concatenated without a
163
+ // separator so tokens reassemble into readable prose. Whole-turn events
164
+ // get joined with newlines so distinct turns/thoughts stay separated.
165
+ const isStreaming = event.complete === false;
162
166
  let j = i + 1;
163
167
  while (j < events.length && events[j].type === eventType) {
164
168
  count++;
165
169
  if (events[j].content) {
166
- combinedContent += (combinedContent ? '\n' : '') + events[j].content;
170
+ const sep = isStreaming || events[j].complete === false
171
+ ? ''
172
+ : (combinedContent ? '\n' : '');
173
+ combinedContent += sep + events[j].content;
167
174
  }
168
175
  j++;
169
176
  }
@@ -405,7 +412,16 @@ export function summarizeEvents(agentId, agentType, status, events, duration = n
405
412
  else if (eventType === 'message') {
406
413
  const content = event.content || '';
407
414
  if (content) {
408
- summary.finalMessage = content;
415
+ // Streaming token-by-token messages (e.g., grok) arrive as many small
416
+ // `message` events with complete:false; concatenate them so the final
417
+ // turn reads as one message. Whole-turn messages (claude, codex,
418
+ // gemini) keep their complete:true semantics — last one wins.
419
+ if (event.complete === false) {
420
+ summary.finalMessage = (summary.finalMessage || '') + content;
421
+ }
422
+ else {
423
+ summary.finalMessage = content;
424
+ }
409
425
  }
410
426
  }
411
427
  else if (eventType === 'error') {