@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.
- package/CHANGELOG.md +67 -0
- package/README.md +70 -10
- package/dist/commands/browser.js +88 -16
- package/dist/commands/cli.d.ts +14 -0
- package/dist/commands/cli.js +244 -0
- package/dist/commands/commands.js +3 -3
- package/dist/commands/computer.js +18 -1
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/doctor.js +2 -2
- package/dist/commands/exec.js +3 -3
- package/dist/commands/factory.d.ts +3 -14
- package/dist/commands/factory.js +3 -3
- package/dist/commands/hooks.js +3 -3
- package/dist/commands/mcp.js +29 -0
- package/dist/commands/plugins.js +11 -4
- package/dist/commands/profiles.js +1 -1
- package/dist/commands/prune.js +39 -160
- package/dist/commands/pull.js +56 -3
- package/dist/commands/routines.js +106 -13
- package/dist/commands/secrets.js +6 -8
- package/dist/commands/sessions.d.ts +36 -7
- package/dist/commands/sessions.js +130 -53
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.js +37 -28
- package/dist/commands/skills.js +3 -3
- package/dist/commands/teams.js +13 -0
- package/dist/commands/versions.d.ts +4 -3
- package/dist/commands/versions.js +147 -124
- package/dist/commands/view.js +12 -12
- package/dist/index.js +34 -6
- package/dist/lib/acp/harnesses.js +8 -0
- package/dist/lib/agents.js +162 -9
- package/dist/lib/browser/cdp.d.ts +8 -1
- package/dist/lib/browser/cdp.js +40 -3
- package/dist/lib/browser/chrome.d.ts +13 -0
- package/dist/lib/browser/chrome.js +42 -3
- package/dist/lib/browser/domain-skills.d.ts +51 -0
- package/dist/lib/browser/domain-skills.js +157 -0
- package/dist/lib/browser/drivers/local.js +45 -4
- package/dist/lib/browser/drivers/ssh.js +1 -1
- package/dist/lib/browser/ipc.d.ts +8 -1
- package/dist/lib/browser/ipc.js +37 -28
- package/dist/lib/browser/profiles.d.ts +13 -0
- package/dist/lib/browser/profiles.js +41 -1
- package/dist/lib/browser/service.d.ts +3 -0
- package/dist/lib/browser/service.js +21 -5
- package/dist/lib/browser/types.d.ts +7 -0
- package/dist/lib/cli-resources.d.ts +109 -0
- package/dist/lib/cli-resources.js +255 -0
- package/dist/lib/cloud/rush.js +5 -5
- package/dist/lib/command-skills.js +0 -2
- package/dist/lib/computer-rpc.d.ts +3 -0
- package/dist/lib/computer-rpc.js +53 -0
- package/dist/lib/daemon.js +20 -0
- package/dist/lib/exec.d.ts +3 -2
- package/dist/lib/exec.js +62 -6
- package/dist/lib/hooks.js +182 -0
- package/dist/lib/mcp.js +6 -0
- package/dist/lib/migrate.js +1 -1
- package/dist/lib/overdue.d.ts +26 -0
- package/dist/lib/overdue.js +101 -0
- package/dist/lib/permissions.js +5 -1
- package/dist/lib/plugin-marketplace.js +1 -1
- package/dist/lib/profiles-presets.js +37 -0
- package/dist/lib/registry.d.ts +18 -0
- package/dist/lib/registry.js +44 -0
- package/dist/lib/resources/mcp.js +43 -1
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/resources.d.ts +1 -1
- package/dist/lib/rotate.js +10 -4
- package/dist/lib/routines-format.d.ts +35 -0
- package/dist/lib/routines-format.js +173 -0
- package/dist/lib/routines.d.ts +7 -1
- package/dist/lib/routines.js +32 -12
- package/dist/lib/runner.js +19 -5
- package/dist/lib/scheduler.js +8 -1
- package/dist/lib/secrets/{AgentsKeychain.app → Agents CLI.app}/Contents/CodeResources +0 -0
- package/dist/lib/secrets/{AgentsKeychain.app/Contents/Info.plist → Agents CLI.app/Contents/Info.plist } +4 -2
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/bundles.d.ts +33 -2
- package/dist/lib/secrets/bundles.js +249 -26
- package/dist/lib/secrets/index.d.ts +10 -1
- package/dist/lib/secrets/index.js +143 -48
- package/dist/lib/session/active.d.ts +8 -0
- package/dist/lib/session/active.js +3 -2
- package/dist/lib/session/db.d.ts +10 -4
- package/dist/lib/session/db.js +16 -16
- package/dist/lib/session/parse.d.ts +1 -0
- package/dist/lib/session/parse.js +44 -0
- package/dist/lib/session/types.d.ts +1 -1
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/shims.d.ts +6 -2
- package/dist/lib/shims.js +88 -10
- package/dist/lib/state.d.ts +0 -1
- package/dist/lib/state.js +2 -15
- package/dist/lib/teams/agents.js +1 -1
- package/dist/lib/teams/parsers.d.ts +1 -1
- package/dist/lib/teams/parsers.js +153 -3
- package/dist/lib/teams/summarizer.js +18 -2
- package/dist/lib/teams/worktree.js +14 -3
- package/dist/lib/types.d.ts +7 -4
- package/dist/lib/types.js +6 -3
- package/dist/lib/versions.d.ts +10 -2
- package/dist/lib/versions.js +227 -35
- package/package.json +9 -9
- package/dist/lib/secrets/AgentsKeychain.app/Contents/MacOS/AgentsKeychain +0 -0
- package/npm-shrinkwrap.json +0 -3162
- /package/dist/lib/secrets/{AgentsKeychain.app → Agents CLI.app}/Contents/_CodeSignature/CodeResources +0 -0
- /package/dist/lib/secrets/{AgentsKeychain.app → Agents CLI.app}/Contents/embedded.provisionprofile +0 -0
package/dist/lib/session/db.d.ts
CHANGED
|
@@ -155,7 +155,13 @@ export declare function getRowCount(): {
|
|
|
155
155
|
sessions: number;
|
|
156
156
|
textRows: number;
|
|
157
157
|
};
|
|
158
|
-
/**
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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;
|
package/dist/lib/session/db.js
CHANGED
|
@@ -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
|
-
/**
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
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
|
|
756
|
-
|
|
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
|
-
|
|
762
|
-
db.prepare(`
|
|
763
|
-
|
|
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'];
|
package/dist/lib/shims.d.ts
CHANGED
|
@@ -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 =
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
*/
|
package/dist/lib/state.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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();
|
package/dist/lib/teams/agents.js
CHANGED
|
@@ -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
|
|
6
|
-
* with consistent types: init, message, tool_use, bash,
|
|
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
|
-
|
|
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
|
-
|
|
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') {
|