@phnx-labs/agents-cli 1.20.52 → 1.20.53
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 +57 -2
- package/README.md +12 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/cloud.d.ts +3 -0
- package/dist/commands/cloud.js +2 -1
- package/dist/commands/exec.js +65 -1
- package/dist/commands/feed.d.ts +29 -0
- package/dist/commands/feed.js +237 -32
- package/dist/commands/memory.d.ts +9 -0
- package/dist/commands/memory.js +164 -0
- package/dist/commands/message.d.ts +11 -6
- package/dist/commands/message.js +140 -5
- package/dist/commands/routines.js +12 -0
- package/dist/commands/secrets-migrate.d.ts +2 -1
- package/dist/commands/secrets-migrate.js +88 -13
- package/dist/commands/secrets.js +4 -1
- package/dist/commands/sessions.js +10 -1
- package/dist/commands/worktree.js +4 -2
- package/dist/index.js +16 -21
- package/dist/lib/agents.js +249 -17
- package/dist/lib/answer-router.d.ts +75 -0
- package/dist/lib/answer-router.js +149 -0
- package/dist/lib/ask-classifier.d.ts +71 -0
- package/dist/lib/ask-classifier.js +197 -0
- package/dist/lib/cloud/antigravity.d.ts +0 -2
- package/dist/lib/cloud/antigravity.js +2 -17
- package/dist/lib/cloud/codex.js +3 -18
- package/dist/lib/cloud/rush.js +3 -15
- package/dist/lib/cloud/stream.js +2 -0
- package/dist/lib/cloud/types.d.ts +21 -0
- package/dist/lib/cloud/types.js +81 -0
- package/dist/lib/crabbox/cli.d.ts +1 -1
- package/dist/lib/crabbox/cli.js +12 -2
- package/dist/lib/crabbox/lease.d.ts +13 -0
- package/dist/lib/crabbox/lease.js +11 -2
- package/dist/lib/crabbox/progress.d.ts +62 -0
- package/dist/lib/crabbox/progress.js +129 -0
- package/dist/lib/events.js +4 -1
- package/dist/lib/exec.js +19 -1
- package/dist/lib/feed-outcome.d.ts +101 -0
- package/dist/lib/feed-outcome.js +244 -0
- package/dist/lib/feed-policy.d.ts +30 -0
- package/dist/lib/feed-policy.js +133 -0
- package/dist/lib/feed.d.ts +127 -3
- package/dist/lib/feed.js +416 -40
- package/dist/lib/git.d.ts +17 -1
- package/dist/lib/git.js +20 -1
- package/dist/lib/hooks.js +522 -12
- package/dist/lib/hosts/passthrough.d.ts +3 -3
- package/dist/lib/hosts/passthrough.js +3 -4
- package/dist/lib/mailbox-gc.d.ts +22 -0
- package/dist/lib/mailbox-gc.js +161 -0
- package/dist/lib/mailbox.d.ts +26 -2
- package/dist/lib/mailbox.js +80 -5
- package/dist/lib/mcp.js +82 -0
- package/dist/lib/memory.d.ts +55 -0
- package/dist/lib/memory.js +274 -0
- package/dist/lib/notify.d.ts +16 -0
- package/dist/lib/notify.js +61 -0
- package/dist/lib/operator.d.ts +26 -0
- package/dist/lib/operator.js +107 -0
- package/dist/lib/plugins.d.ts +35 -0
- package/dist/lib/plugins.js +217 -0
- package/dist/lib/remote-agents-json.d.ts +14 -0
- package/dist/lib/remote-agents-json.js +94 -0
- package/dist/lib/resources/mcp.js +44 -0
- package/dist/lib/resources/memory.d.ts +15 -0
- package/dist/lib/resources/memory.js +46 -0
- package/dist/lib/resources/types.d.ts +2 -2
- package/dist/lib/runner.d.ts +43 -0
- package/dist/lib/runner.js +323 -74
- package/dist/lib/sandbox.js +6 -0
- package/dist/lib/secrets/bundles.js +38 -13
- package/dist/lib/secrets/icloud-import.d.ts +12 -3
- package/dist/lib/secrets/icloud-import.js +37 -7
- package/dist/lib/secrets/index.d.ts +106 -2
- package/dist/lib/secrets/index.js +603 -28
- package/dist/lib/session/active.d.ts +18 -0
- package/dist/lib/session/active.js +47 -17
- package/dist/lib/session/db.d.ts +9 -1
- package/dist/lib/session/db.js +18 -3
- package/dist/lib/session/discover.d.ts +13 -0
- package/dist/lib/session/discover.js +31 -0
- package/dist/lib/session/parse.d.ts +8 -0
- package/dist/lib/session/parse.js +42 -21
- package/dist/lib/session/remote-active.js +8 -89
- package/dist/lib/session/state.d.ts +11 -0
- package/dist/lib/session/state.js +37 -0
- package/dist/lib/session/tail.d.ts +23 -4
- package/dist/lib/session/tail.js +34 -16
- package/dist/lib/session/throughput.d.ts +30 -0
- package/dist/lib/session/throughput.js +86 -0
- package/dist/lib/shim-heal.d.ts +12 -3
- package/dist/lib/shim-heal.js +12 -6
- package/dist/lib/staleness/detectors/subagents.js +57 -3
- package/dist/lib/staleness/writers/hooks.js +7 -3
- package/dist/lib/staleness/writers/subagents.js +37 -6
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +2 -0
- package/dist/lib/subagents.d.ts +52 -0
- package/dist/lib/subagents.js +315 -12
- package/dist/lib/teams/worktree.d.ts +8 -0
- package/dist/lib/teams/worktree.js +8 -0
- package/dist/lib/types.d.ts +10 -2
- package/dist/lib/versions.js +38 -48
- package/package.json +4 -3
- package/scripts/postinstall.js +61 -1
package/dist/lib/agents.js
CHANGED
|
@@ -15,6 +15,7 @@ import * as fs from 'fs';
|
|
|
15
15
|
import * as path from 'path';
|
|
16
16
|
import * as os from 'os';
|
|
17
17
|
import * as TOML from 'smol-toml';
|
|
18
|
+
import * as yaml from 'yaml';
|
|
18
19
|
import chalk from 'chalk';
|
|
19
20
|
import { needsWindowsShell } from './platform/index.js';
|
|
20
21
|
import { latestFileMtimeMs } from './fs-walk.js';
|
|
@@ -213,7 +214,7 @@ export const AGENTS = {
|
|
|
213
214
|
// Claude Code has no headless Anthropic-hosted dispatch CLI (only
|
|
214
215
|
// --remote-control, which bridges a *local* session). Its cloud is Rush.
|
|
215
216
|
cloudProvider: 'rush',
|
|
216
|
-
capabilities: { hooks: true, mcp: true, mcpHttp: true, mcpHeaders: true, allowlist: true, skills: true, commands: true, plugins: true, subagents: true, rules: { file: 'CLAUDE.md' }, workflows: true, modes: ['plan', 'edit', 'auto', 'skip'], rulesImports: true },
|
|
217
|
+
capabilities: { hooks: true, mcp: true, mcpHttp: true, mcpHeaders: true, allowlist: true, skills: true, commands: true, plugins: true, subagents: true, rules: { file: 'CLAUDE.md' }, workflows: true, memory: true, modes: ['plan', 'edit', 'auto', 'skip'], rulesImports: true },
|
|
217
218
|
},
|
|
218
219
|
// codex hooks: gated to >= 0.116.0 (introduced [features] codex_hooks flag).
|
|
219
220
|
codex: {
|
|
@@ -233,7 +234,9 @@ export const AGENTS = {
|
|
|
233
234
|
variableSyntax: '$ARGUMENTS',
|
|
234
235
|
supportsHooks: true,
|
|
235
236
|
cloudProvider: 'codex',
|
|
236
|
-
|
|
237
|
+
// Subagents: multi-agent plumbing since 0.117.0; custom agents as
|
|
238
|
+
// ~/.codex/agents/*.toml (name, description, developer_instructions).
|
|
239
|
+
capabilities: { hooks: { since: '0.116.0' }, mcp: true, mcpHttp: true, mcpHeaders: false, allowlist: false, skills: true, commands: { until: '0.117.0' }, plugins: { since: '0.128.0' }, subagents: { since: '0.117.0' }, rules: { file: 'AGENTS.md' }, workflows: false, memory: true, modes: ['plan', 'edit', 'skip'] },
|
|
237
240
|
},
|
|
238
241
|
gemini: {
|
|
239
242
|
id: 'gemini',
|
|
@@ -262,7 +265,7 @@ export const AGENTS = {
|
|
|
262
265
|
url: 'https://developers.googleblog.com/an-important-update-transitioning-gemini-cli-to-antigravity-cli/',
|
|
263
266
|
},
|
|
264
267
|
// gemini hooks: shipped in v0.26.0 (Jan 2026); older binaries silently ignore the `hooks` key.
|
|
265
|
-
capabilities: { hooks: { since: '0.26.0' }, mcp: true, mcpHttp: true, mcpHeaders: false, allowlist: false, skills: true, commands: true, plugins: false, subagents: false, rules: { file: 'GEMINI.md' }, workflows: false, modes: ['plan', 'edit', 'skip'], rulesImports: true },
|
|
268
|
+
capabilities: { hooks: { since: '0.26.0' }, mcp: true, mcpHttp: true, mcpHeaders: false, allowlist: false, skills: true, commands: true, plugins: false, subagents: false, rules: { file: 'GEMINI.md' }, workflows: false, memory: false, modes: ['plan', 'edit', 'skip'], rulesImports: true },
|
|
266
269
|
},
|
|
267
270
|
cursor: {
|
|
268
271
|
id: 'cursor',
|
|
@@ -275,12 +278,19 @@ export const AGENTS = {
|
|
|
275
278
|
commandsDir: path.join(HOME, '.cursor', 'commands'),
|
|
276
279
|
commandsSubdir: 'commands',
|
|
277
280
|
skillsDir: path.join(HOME, '.cursor', 'skills'),
|
|
281
|
+
// Hooks: ~/.cursor/hooks.json (`{ "version": 1, "hooks": { event: [{ command }] } }`).
|
|
282
|
+
// CLI hooks since 2026-01-16. See registerHooksForCursor — only CLI-fired events.
|
|
278
283
|
hooksDir: 'hooks',
|
|
284
|
+
// Plugins: `.cursor-plugin/plugin.json` (re-enabled in CLI 2026-05). Mirror the
|
|
285
|
+
// Claude marketplace layout into ~/.cursor/plugins/ and copy the manifest into
|
|
286
|
+
// pluginManifestDir so Cursor's native loader sees it (same pattern as droid/
|
|
287
|
+
// codex).
|
|
288
|
+
pluginManifestDir: '.cursor-plugin',
|
|
279
289
|
instructionsFile: '.cursorrules',
|
|
280
290
|
format: 'markdown',
|
|
281
291
|
variableSyntax: '$ARGUMENTS',
|
|
282
|
-
supportsHooks:
|
|
283
|
-
capabilities: { hooks:
|
|
292
|
+
supportsHooks: true,
|
|
293
|
+
capabilities: { hooks: true, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: false, skills: true, commands: true, plugins: true, subagents: false, rules: { file: '.cursorrules' }, workflows: false, memory: false, modes: ['edit', 'skip'] },
|
|
284
294
|
},
|
|
285
295
|
opencode: {
|
|
286
296
|
id: 'opencode',
|
|
@@ -292,12 +302,15 @@ export const AGENTS = {
|
|
|
292
302
|
commandsDir: path.join(HOME, '.opencode', 'commands'),
|
|
293
303
|
commandsSubdir: 'commands',
|
|
294
304
|
skillsDir: path.join(HOME, '.opencode', 'skills'),
|
|
305
|
+
// Plugins: TS/JS modules auto-loaded from ~/.config/opencode/plugins/ (global)
|
|
306
|
+
// and .opencode/plugins/ (project). Not Claude marketplace format — see
|
|
307
|
+
// installOpenCodePlugin in plugins.ts. No native shell hooks (plugins only).
|
|
295
308
|
hooksDir: 'hooks',
|
|
296
309
|
instructionsFile: 'AGENTS.md',
|
|
297
310
|
format: 'markdown',
|
|
298
311
|
variableSyntax: '$ARGUMENTS',
|
|
299
312
|
supportsHooks: false,
|
|
300
|
-
capabilities: { hooks: false, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist:
|
|
313
|
+
capabilities: { hooks: false, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: { since: '1.1.1' }, skills: true, commands: true, plugins: true, subagents: true, rules: { file: 'AGENTS.md' }, workflows: false, memory: false, modes: ['plan', 'edit'] }, // subagents: ~/.config/opencode/agents/*.md
|
|
301
314
|
},
|
|
302
315
|
openclaw: {
|
|
303
316
|
id: 'openclaw',
|
|
@@ -315,7 +328,7 @@ export const AGENTS = {
|
|
|
315
328
|
format: 'markdown',
|
|
316
329
|
variableSyntax: '{{ARGUMENTS}}',
|
|
317
330
|
supportsHooks: true,
|
|
318
|
-
capabilities: { hooks: true, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: false, skills: true, commands: false, plugins: true, subagents: true, rules: { file: 'workspace/AGENTS.md' }, workflows: false, modes: ['plan', 'edit', 'skip'] },
|
|
331
|
+
capabilities: { hooks: true, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: false, skills: true, commands: false, plugins: true, subagents: true, rules: { file: 'workspace/AGENTS.md' }, workflows: false, memory: true, modes: ['plan', 'edit', 'skip'] },
|
|
319
332
|
},
|
|
320
333
|
copilot: {
|
|
321
334
|
id: 'copilot',
|
|
@@ -327,6 +340,9 @@ export const AGENTS = {
|
|
|
327
340
|
commandsDir: path.join(HOME, '.copilot', 'commands'),
|
|
328
341
|
commandsSubdir: 'commands',
|
|
329
342
|
skillsDir: path.join(HOME, '.copilot', 'skills'),
|
|
343
|
+
// Hooks: user-level `~/.copilot/hooks/*.json` (GA @github/copilot, every
|
|
344
|
+
// 1.x). Schema `{ "version": 1, "hooks": { event: [...] } }` with camelCase
|
|
345
|
+
// event names (sessionStart, preToolUse, …). See registerHooksForCopilot.
|
|
330
346
|
hooksDir: 'hooks',
|
|
331
347
|
// Copilot reads a plugin's manifest from the plugin ROOT (plugin.json),
|
|
332
348
|
// not `.claude-plugin/plugin.json`. Mirror it there. Verified against the
|
|
@@ -336,8 +352,8 @@ export const AGENTS = {
|
|
|
336
352
|
instructionsFile: 'AGENTS.md',
|
|
337
353
|
format: 'markdown',
|
|
338
354
|
variableSyntax: '$ARGUMENTS',
|
|
339
|
-
supportsHooks:
|
|
340
|
-
capabilities: { hooks:
|
|
355
|
+
supportsHooks: true,
|
|
356
|
+
capabilities: { hooks: true, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: false, skills: true, commands: true, plugins: true, subagents: false, rules: { file: 'AGENTS.md' }, workflows: false, memory: false, modes: ['plan', 'edit', 'auto', 'skip'] },
|
|
341
357
|
},
|
|
342
358
|
amp: {
|
|
343
359
|
id: 'amp',
|
|
@@ -354,7 +370,7 @@ export const AGENTS = {
|
|
|
354
370
|
format: 'markdown',
|
|
355
371
|
variableSyntax: '$ARGUMENTS',
|
|
356
372
|
supportsHooks: false,
|
|
357
|
-
capabilities: { hooks: false, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: false, skills: true, commands: true, plugins: false, subagents: false, rules: { file: 'AGENTS.md' }, workflows: false, modes: ['plan', 'edit'] },
|
|
373
|
+
capabilities: { hooks: false, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: false, skills: true, commands: true, plugins: false, subagents: false, rules: { file: 'AGENTS.md' }, workflows: false, memory: false, modes: ['plan', 'edit'] },
|
|
358
374
|
},
|
|
359
375
|
kiro: {
|
|
360
376
|
id: 'kiro',
|
|
@@ -367,12 +383,17 @@ export const AGENTS = {
|
|
|
367
383
|
commandsDir: path.join(HOME, '.kiro', 'commands'),
|
|
368
384
|
commandsSubdir: 'commands',
|
|
369
385
|
skillsDir: path.join(HOME, '.kiro', 'skills'),
|
|
386
|
+
// Hooks: v3 standalone files under ~/.kiro/hooks/*.json
|
|
387
|
+
// (`{ "version": "v1", "hooks": [...] }`). Fixed PreToolUse/PostToolUse
|
|
388
|
+
// firing in kiro-cli 0.10; fully stable by 2.6.1. Launch always passes
|
|
389
|
+
// --v3 (see AGENT_COMMANDS.kiro) so the standalone files actually load.
|
|
390
|
+
// See registerHooksForKiro.
|
|
370
391
|
hooksDir: 'hooks',
|
|
371
392
|
instructionsFile: 'AGENTS.md',
|
|
372
393
|
format: 'markdown',
|
|
373
394
|
variableSyntax: '$ARGUMENTS',
|
|
374
|
-
supportsHooks:
|
|
375
|
-
capabilities: { hooks:
|
|
395
|
+
supportsHooks: true,
|
|
396
|
+
capabilities: { hooks: { since: '0.10.0' }, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: false, skills: true, commands: true, plugins: false, subagents: false, rules: { file: 'AGENTS.md' }, workflows: false, memory: false, modes: ['edit'] },
|
|
376
397
|
},
|
|
377
398
|
goose: {
|
|
378
399
|
id: 'goose',
|
|
@@ -385,12 +406,17 @@ export const AGENTS = {
|
|
|
385
406
|
commandsDir: path.join(HOME, '.config', 'goose', 'commands'),
|
|
386
407
|
commandsSubdir: 'commands',
|
|
387
408
|
skillsDir: path.join(HOME, '.config', 'goose', 'skills'),
|
|
409
|
+
// Hooks: Open Plugins format — auto-discovered from
|
|
410
|
+
// ~/.agents/plugins/<name>/hooks/hooks.json (shipped block-goose-cli
|
|
411
|
+
// ≥ 1.34.0). See registerHooksForGoose.
|
|
388
412
|
hooksDir: 'hooks',
|
|
389
413
|
instructionsFile: 'AGENTS.md',
|
|
390
414
|
format: 'markdown',
|
|
391
415
|
variableSyntax: '$ARGUMENTS',
|
|
392
|
-
supportsHooks:
|
|
393
|
-
|
|
416
|
+
supportsHooks: true,
|
|
417
|
+
// Plugins: Open Plugins under ~/.agents/plugins/<name>/ (same layout as
|
|
418
|
+
// agents-cli source). Version isolation copies into versionHome/.agents/plugins/.
|
|
419
|
+
capabilities: { hooks: { since: '1.34.0' }, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: false, skills: false, commands: false, plugins: true, subagents: false, rules: { file: 'AGENTS.md' }, workflows: false, memory: false, modes: ['edit'] },
|
|
394
420
|
},
|
|
395
421
|
// Google Antigravity CLI (`agy`) — official replacement for Gemini CLI as of IO 2026.
|
|
396
422
|
// configDir nests inside `~/.gemini/` since agy shares the parent dir with the Gemini
|
|
@@ -419,7 +445,7 @@ export const AGENTS = {
|
|
|
419
445
|
variableSyntax: '{{args}}',
|
|
420
446
|
supportsHooks: true,
|
|
421
447
|
cloudProvider: 'antigravity',
|
|
422
|
-
capabilities: { hooks: true, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: true, skills: true, commands: true, plugins: true, subagents: false, rules: { file: 'AGENTS.md' }, workflows: false, modes: ['edit', 'skip'], rulesImports: false },
|
|
448
|
+
capabilities: { hooks: true, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: true, skills: true, commands: true, plugins: true, subagents: false, rules: { file: 'AGENTS.md' }, workflows: false, memory: false, modes: ['edit', 'skip'], rulesImports: false },
|
|
423
449
|
},
|
|
424
450
|
// xAI Grok Build CLI (`grok`) — early beta, SuperGrok Heavy. Auth via OAuth on
|
|
425
451
|
// first launch, or XAI_API_KEY env var for headless. MCP servers configured inline
|
|
@@ -452,9 +478,10 @@ export const AGENTS = {
|
|
|
452
478
|
skills: true,
|
|
453
479
|
commands: false, // covered by skills
|
|
454
480
|
plugins: true,
|
|
455
|
-
subagents:
|
|
481
|
+
subagents: true, // ~/.grok/agents/*.md (Claude-compatible agent defs)
|
|
456
482
|
rules: { file: 'AGENTS.md' },
|
|
457
483
|
workflows: false,
|
|
484
|
+
memory: true,
|
|
458
485
|
modes: ['plan', 'edit', 'skip'],
|
|
459
486
|
rulesImports: true,
|
|
460
487
|
},
|
|
@@ -490,9 +517,10 @@ export const AGENTS = {
|
|
|
490
517
|
skills: true,
|
|
491
518
|
commands: false,
|
|
492
519
|
plugins: true,
|
|
493
|
-
subagents:
|
|
520
|
+
subagents: true, // YAML agent files under ~/.kimi-code/agents/ (see transformSubagentForKimi)
|
|
494
521
|
rules: { file: 'AGENTS.md' },
|
|
495
522
|
workflows: false,
|
|
523
|
+
memory: false,
|
|
496
524
|
modes: ['plan', 'edit', 'auto', 'skip'],
|
|
497
525
|
rulesImports: false,
|
|
498
526
|
},
|
|
@@ -554,10 +582,82 @@ export const AGENTS = {
|
|
|
554
582
|
subagents: true,
|
|
555
583
|
rules: { file: 'AGENTS.md' },
|
|
556
584
|
workflows: false,
|
|
585
|
+
memory: false,
|
|
557
586
|
modes: ['plan', 'edit', 'auto', 'skip'],
|
|
558
587
|
rulesImports: false,
|
|
559
588
|
},
|
|
560
589
|
},
|
|
590
|
+
// Nous Hermes Agent. Config lives under ~/.hermes/config.yaml; MCP servers
|
|
591
|
+
// are YAML `mcp_servers`, skills are local SKILL.md directories, and durable
|
|
592
|
+
// memory is file-backed.
|
|
593
|
+
hermes: {
|
|
594
|
+
id: 'hermes',
|
|
595
|
+
name: 'Hermes',
|
|
596
|
+
color: 'cyanBright',
|
|
597
|
+
cliCommand: 'hermes',
|
|
598
|
+
npmPackage: '',
|
|
599
|
+
installScript: 'curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash',
|
|
600
|
+
configDir: path.join(HOME, '.hermes'),
|
|
601
|
+
commandsDir: '',
|
|
602
|
+
commandsSubdir: '',
|
|
603
|
+
skillsDir: path.join(HOME, '.hermes', 'skills'),
|
|
604
|
+
hooksDir: 'hooks',
|
|
605
|
+
instructionsFile: 'MEMORY.md',
|
|
606
|
+
format: 'markdown',
|
|
607
|
+
variableSyntax: '$ARGUMENTS',
|
|
608
|
+
supportsHooks: false,
|
|
609
|
+
capabilities: {
|
|
610
|
+
hooks: false,
|
|
611
|
+
mcp: true,
|
|
612
|
+
mcpHttp: true,
|
|
613
|
+
mcpHeaders: false,
|
|
614
|
+
allowlist: false,
|
|
615
|
+
skills: true,
|
|
616
|
+
commands: false,
|
|
617
|
+
plugins: false,
|
|
618
|
+
subagents: false,
|
|
619
|
+
rules: { file: 'MEMORY.md' },
|
|
620
|
+
workflows: false,
|
|
621
|
+
memory: true,
|
|
622
|
+
modes: ['edit'],
|
|
623
|
+
rulesImports: false,
|
|
624
|
+
},
|
|
625
|
+
},
|
|
626
|
+
// ForgeCode (`forge`) from Tailcall. It reads AGENTS.md project rules,
|
|
627
|
+
// SKILL.md directories, and MCP servers from `.mcp.json` files.
|
|
628
|
+
forge: {
|
|
629
|
+
id: 'forge',
|
|
630
|
+
name: 'ForgeCode',
|
|
631
|
+
color: 'greenBright',
|
|
632
|
+
cliCommand: 'forge',
|
|
633
|
+
npmPackage: '',
|
|
634
|
+
installScript: 'curl -fsSL https://forgecode.dev/cli | sh',
|
|
635
|
+
configDir: path.join(HOME, '.forge'),
|
|
636
|
+
commandsDir: '',
|
|
637
|
+
commandsSubdir: '',
|
|
638
|
+
skillsDir: path.join(HOME, '.forge', 'skills'),
|
|
639
|
+
hooksDir: 'hooks',
|
|
640
|
+
instructionsFile: 'AGENTS.md',
|
|
641
|
+
format: 'markdown',
|
|
642
|
+
variableSyntax: '$ARGUMENTS',
|
|
643
|
+
supportsHooks: false,
|
|
644
|
+
capabilities: {
|
|
645
|
+
hooks: false,
|
|
646
|
+
mcp: true,
|
|
647
|
+
mcpHttp: true,
|
|
648
|
+
mcpHeaders: false,
|
|
649
|
+
allowlist: false,
|
|
650
|
+
skills: true,
|
|
651
|
+
commands: false,
|
|
652
|
+
plugins: false,
|
|
653
|
+
subagents: false,
|
|
654
|
+
rules: { file: 'AGENTS.md' },
|
|
655
|
+
workflows: false,
|
|
656
|
+
memory: false,
|
|
657
|
+
modes: ['edit'],
|
|
658
|
+
rulesImports: false,
|
|
659
|
+
},
|
|
660
|
+
},
|
|
561
661
|
};
|
|
562
662
|
/** All registered agent IDs derived from the AGENTS registry. */
|
|
563
663
|
export const ALL_AGENT_IDS = Object.keys(AGENTS);
|
|
@@ -1324,6 +1424,15 @@ export async function registerMcp(agentId, name, command, scope = 'user', transp
|
|
|
1324
1424
|
if (transport === 'http' && options?.headers && Object.keys(options.headers).length > 0 && !supports(agentId, 'mcpHeaders').ok) {
|
|
1325
1425
|
return { success: false, error: 'skipped: HTTP MCP headers are only supported for Claude registration' };
|
|
1326
1426
|
}
|
|
1427
|
+
if (agentId === 'hermes' || agentId === 'forge') {
|
|
1428
|
+
try {
|
|
1429
|
+
writeMcpToConfig(agentId, name, command, scope, transport, options?.home);
|
|
1430
|
+
return { success: true };
|
|
1431
|
+
}
|
|
1432
|
+
catch (err) {
|
|
1433
|
+
return { success: false, error: err.message };
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1327
1436
|
if (!options?.binary && !(await isCliInstalled(agentId))) {
|
|
1328
1437
|
return { success: false, error: 'CLI not installed' };
|
|
1329
1438
|
}
|
|
@@ -1366,6 +1475,15 @@ export async function unregisterMcp(agentId, name, options) {
|
|
|
1366
1475
|
if (!agent.capabilities.mcp) {
|
|
1367
1476
|
return { success: false, error: 'Agent does not support MCP' };
|
|
1368
1477
|
}
|
|
1478
|
+
if (agentId === 'hermes' || agentId === 'forge') {
|
|
1479
|
+
try {
|
|
1480
|
+
removeMcpFromConfig(agentId, name, options?.home);
|
|
1481
|
+
return { success: true };
|
|
1482
|
+
}
|
|
1483
|
+
catch (err) {
|
|
1484
|
+
return { success: false, error: err.message };
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1369
1487
|
if (!options?.binary && !(await isCliInstalled(agentId))) {
|
|
1370
1488
|
return { success: false, error: 'CLI not installed' };
|
|
1371
1489
|
}
|
|
@@ -1422,6 +1540,86 @@ export async function unregisterMcpFromTargets(targets, name) {
|
|
|
1422
1540
|
}
|
|
1423
1541
|
return results;
|
|
1424
1542
|
}
|
|
1543
|
+
function userMcpConfigPath(agentId, home) {
|
|
1544
|
+
if (home)
|
|
1545
|
+
return getMcpConfigPathForHome(agentId, home);
|
|
1546
|
+
return getUserMcpConfigPath(agentId);
|
|
1547
|
+
}
|
|
1548
|
+
function scopedMcpConfigPath(agentId, scope, home) {
|
|
1549
|
+
if (scope === 'project')
|
|
1550
|
+
return getProjectMcpConfigPath(agentId);
|
|
1551
|
+
return userMcpConfigPath(agentId, home);
|
|
1552
|
+
}
|
|
1553
|
+
function mcpEntryFromCommand(command, transport) {
|
|
1554
|
+
if (transport === 'http') {
|
|
1555
|
+
return { url: command };
|
|
1556
|
+
}
|
|
1557
|
+
const commandArgs = splitCommandLine(command);
|
|
1558
|
+
return {
|
|
1559
|
+
command: commandArgs[0],
|
|
1560
|
+
args: commandArgs.slice(1),
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
function readYamlConfig(configPath) {
|
|
1564
|
+
if (!fs.existsSync(configPath))
|
|
1565
|
+
return {};
|
|
1566
|
+
const parsed = yaml.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
1567
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
1568
|
+
? parsed
|
|
1569
|
+
: {};
|
|
1570
|
+
}
|
|
1571
|
+
function readJsonConfig(configPath) {
|
|
1572
|
+
if (!fs.existsSync(configPath))
|
|
1573
|
+
return {};
|
|
1574
|
+
const content = configPath.endsWith('.jsonc')
|
|
1575
|
+
? stripJsonComments(fs.readFileSync(configPath, 'utf-8'))
|
|
1576
|
+
: fs.readFileSync(configPath, 'utf-8');
|
|
1577
|
+
const parsed = JSON.parse(content);
|
|
1578
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
1579
|
+
? parsed
|
|
1580
|
+
: {};
|
|
1581
|
+
}
|
|
1582
|
+
function writeMcpToConfig(agentId, name, command, scope, transport, home) {
|
|
1583
|
+
const configPath = scopedMcpConfigPath(agentId, scope, home);
|
|
1584
|
+
const entry = mcpEntryFromCommand(command, transport);
|
|
1585
|
+
if (agentId === 'hermes') {
|
|
1586
|
+
const config = readYamlConfig(configPath);
|
|
1587
|
+
if (!config.mcp_servers || typeof config.mcp_servers !== 'object' || Array.isArray(config.mcp_servers)) {
|
|
1588
|
+
config.mcp_servers = {};
|
|
1589
|
+
}
|
|
1590
|
+
config.mcp_servers[name] = entry;
|
|
1591
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
1592
|
+
fs.writeFileSync(configPath, yaml.stringify(config), 'utf-8');
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1595
|
+
const config = readJsonConfig(configPath);
|
|
1596
|
+
if (!config.mcpServers || typeof config.mcpServers !== 'object' || Array.isArray(config.mcpServers)) {
|
|
1597
|
+
config.mcpServers = {};
|
|
1598
|
+
}
|
|
1599
|
+
config.mcpServers[name] = entry;
|
|
1600
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
1601
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
1602
|
+
}
|
|
1603
|
+
function removeMcpFromConfig(agentId, name, home) {
|
|
1604
|
+
const configPath = userMcpConfigPath(agentId, home);
|
|
1605
|
+
if (!fs.existsSync(configPath))
|
|
1606
|
+
return;
|
|
1607
|
+
if (agentId === 'hermes') {
|
|
1608
|
+
const config = readYamlConfig(configPath);
|
|
1609
|
+
const servers = config.mcp_servers;
|
|
1610
|
+
if (servers && typeof servers === 'object' && !Array.isArray(servers)) {
|
|
1611
|
+
delete servers[name];
|
|
1612
|
+
fs.writeFileSync(configPath, yaml.stringify(config), 'utf-8');
|
|
1613
|
+
}
|
|
1614
|
+
return;
|
|
1615
|
+
}
|
|
1616
|
+
const config = readJsonConfig(configPath);
|
|
1617
|
+
const servers = config.mcpServers;
|
|
1618
|
+
if (servers && typeof servers === 'object' && !Array.isArray(servers)) {
|
|
1619
|
+
delete servers[name];
|
|
1620
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1425
1623
|
/**
|
|
1426
1624
|
* Extract version from npm package specification.
|
|
1427
1625
|
* Examples: @scope/package@latest -> latest
|
|
@@ -1539,6 +1737,20 @@ function parseMcpFromTomlConfig(configPath) {
|
|
|
1539
1737
|
return {};
|
|
1540
1738
|
}
|
|
1541
1739
|
}
|
|
1740
|
+
function parseMcpFromYamlConfig(configPath) {
|
|
1741
|
+
if (!fs.existsSync(configPath)) {
|
|
1742
|
+
return {};
|
|
1743
|
+
}
|
|
1744
|
+
try {
|
|
1745
|
+
const config = readYamlConfig(configPath);
|
|
1746
|
+
const mcpServers = config.mcp_servers;
|
|
1747
|
+
return mcpServers || {};
|
|
1748
|
+
}
|
|
1749
|
+
catch {
|
|
1750
|
+
/* YAML config corrupt or unreadable */
|
|
1751
|
+
return {};
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1542
1754
|
/**
|
|
1543
1755
|
* Parse MCP servers from OpenCode's JSONC config.
|
|
1544
1756
|
* OpenCode stores MCPs in the "mcp" object with different structure.
|
|
@@ -1610,6 +1822,10 @@ export function getUserMcpConfigPath(agentId) {
|
|
|
1610
1822
|
case 'droid':
|
|
1611
1823
|
// Factory AI Droid stores MCPs in ~/.factory/mcp.json
|
|
1612
1824
|
return path.join(agent.configDir, 'mcp.json');
|
|
1825
|
+
case 'hermes':
|
|
1826
|
+
return path.join(agent.configDir, 'config.yaml');
|
|
1827
|
+
case 'forge':
|
|
1828
|
+
return path.join(agent.configDir, '.mcp.json');
|
|
1613
1829
|
default:
|
|
1614
1830
|
// Gemini and others use settings.json
|
|
1615
1831
|
return path.join(agent.configDir, 'settings.json');
|
|
@@ -1644,6 +1860,10 @@ export function getMcpConfigPathForHome(agentId, home) {
|
|
|
1644
1860
|
return path.join(home, '.grok', 'config.toml');
|
|
1645
1861
|
case 'droid':
|
|
1646
1862
|
return path.join(home, '.factory', 'mcp.json');
|
|
1863
|
+
case 'hermes':
|
|
1864
|
+
return path.join(home, '.hermes', 'config.yaml');
|
|
1865
|
+
case 'forge':
|
|
1866
|
+
return path.join(home, '.forge', '.mcp.json');
|
|
1647
1867
|
default:
|
|
1648
1868
|
return path.join(home, agentConfigDirName(agentId), 'settings.json');
|
|
1649
1869
|
}
|
|
@@ -1680,6 +1900,10 @@ function getProjectMcpConfigPath(agentId, cwd = process.cwd()) {
|
|
|
1680
1900
|
return path.join(cwd, '.grok', 'config.toml');
|
|
1681
1901
|
case 'droid':
|
|
1682
1902
|
return path.join(cwd, '.factory', 'mcp.json');
|
|
1903
|
+
case 'hermes':
|
|
1904
|
+
return path.join(cwd, '.hermes', 'config.yaml');
|
|
1905
|
+
case 'forge':
|
|
1906
|
+
return path.join(cwd, '.mcp.json');
|
|
1683
1907
|
default:
|
|
1684
1908
|
return path.join(cwd, `.${agentId}`, 'settings.json');
|
|
1685
1909
|
}
|
|
@@ -1733,6 +1957,8 @@ export function parseMcpConfig(agentId, configPath) {
|
|
|
1733
1957
|
return parseMcpFromOpenCodeConfig(configPath);
|
|
1734
1958
|
case 'openclaw':
|
|
1735
1959
|
return parseMcpFromOpenClawConfig(configPath);
|
|
1960
|
+
case 'hermes':
|
|
1961
|
+
return parseMcpFromYamlConfig(configPath);
|
|
1736
1962
|
default:
|
|
1737
1963
|
return parseMcpFromJsonConfig(configPath);
|
|
1738
1964
|
}
|
|
@@ -1818,6 +2044,12 @@ export const AGENT_NAME_ALIASES = {
|
|
|
1818
2044
|
'kimi-code': 'kimi',
|
|
1819
2045
|
factory: 'droid',
|
|
1820
2046
|
'factory-ai': 'droid',
|
|
2047
|
+
droid: 'droid',
|
|
2048
|
+
hermes: 'hermes',
|
|
2049
|
+
'hermes-agent': 'hermes',
|
|
2050
|
+
forge: 'forge',
|
|
2051
|
+
forgecode: 'forge',
|
|
2052
|
+
'forge-code': 'forge',
|
|
1821
2053
|
};
|
|
1822
2054
|
/**
|
|
1823
2055
|
* Resolve a user-provided agent name (alias, shorthand, or canonical) to its AgentId.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parked-agent answer router (RUSH-1474).
|
|
3
|
+
*
|
|
4
|
+
* `agents message` historically only enqueued into the mailbox (delivered at the
|
|
5
|
+
* next PreToolUse). That steers a *running* agent between tool calls but never
|
|
6
|
+
* unblocks a *parked* agent:
|
|
7
|
+
* - interactive TUI open on AskUserQuestion (needs keystrokes, not context)
|
|
8
|
+
* - headless run waiting on input (no next tool call; needs resume)
|
|
9
|
+
*
|
|
10
|
+
* This module picks the delivery mechanism from (open feed block × session
|
|
11
|
+
* liveness × runtime rail). Pure — unit-testable without a live PTY.
|
|
12
|
+
*/
|
|
13
|
+
import type { OpenBlock, BlockOption } from './feed.js';
|
|
14
|
+
import type { ActiveSession } from './session/active.js';
|
|
15
|
+
import type { InjectTarget } from './terminal/inject.js';
|
|
16
|
+
export type AnswerRouteKind = 'mailbox' | 'pty' | 'tmux' | 'iterm' | 'resume' | 'refuse';
|
|
17
|
+
export interface AnswerRoute {
|
|
18
|
+
kind: AnswerRouteKind;
|
|
19
|
+
/** Human reason shown in CLI output / refused errors. */
|
|
20
|
+
reason: string;
|
|
21
|
+
/**
|
|
22
|
+
* Keystrokes / payload for the chosen path.
|
|
23
|
+
* - mailbox: unused (text is enqueued as-is)
|
|
24
|
+
* - pty/tmux/iterm: the digit or free-text to inject
|
|
25
|
+
* - resume: the free-text prompt to pass on re-entry
|
|
26
|
+
*/
|
|
27
|
+
payload?: string;
|
|
28
|
+
/** Inject target when kind is pty/tmux/iterm. */
|
|
29
|
+
inject?: InjectTarget;
|
|
30
|
+
/** Session id + agent kind for resume. */
|
|
31
|
+
resume?: {
|
|
32
|
+
sessionId: string;
|
|
33
|
+
agent: string;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export interface AnswerRouterInput {
|
|
37
|
+
/** Resolved mailbox / session id. */
|
|
38
|
+
mailboxId: string;
|
|
39
|
+
/** Answer text (option label or free text). */
|
|
40
|
+
answer: string;
|
|
41
|
+
/** Open feed block for this agent, if any. */
|
|
42
|
+
block?: OpenBlock | null;
|
|
43
|
+
/** Live session row matching the mailbox, if any. */
|
|
44
|
+
session?: ActiveSession | null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Match free-text answer against question options.
|
|
48
|
+
* Exact (case-insensitive) > startsWith > includes. Returns 0-based index or -1.
|
|
49
|
+
*/
|
|
50
|
+
export declare function matchOptionIndex(answer: string, options: Array<Pick<BlockOption, 'label'>> | undefined): number;
|
|
51
|
+
/**
|
|
52
|
+
* Build the keystroke payload that closes an AskUserQuestion TUI.
|
|
53
|
+
* Numbered options are selected by digit (1-based) + Enter. Free text with an
|
|
54
|
+
* "Other" option selects Other then types; without Other, types the answer.
|
|
55
|
+
*/
|
|
56
|
+
export declare function keystrokesForAnswer(answer: string, options?: Array<Pick<BlockOption, 'label'>>): {
|
|
57
|
+
payload: string;
|
|
58
|
+
matched: 'option' | 'free-text' | 'other';
|
|
59
|
+
};
|
|
60
|
+
/** True when the session is waiting on user input (parked on a question/plan). */
|
|
61
|
+
export declare function isParkedOnInput(session: ActiveSession | null | undefined): boolean;
|
|
62
|
+
/** True when an open feed block still needs an answer. */
|
|
63
|
+
export declare function isOpenQuestionBlock(block: OpenBlock | null | undefined): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Pick the delivery mechanism for one answer.
|
|
66
|
+
*
|
|
67
|
+
* Precedence:
|
|
68
|
+
* 1. Parked on open question + injectable rail (tmux/iterm/pty) → keystroke
|
|
69
|
+
* 2. Parked on open question + headless (no rail) → resume with answer
|
|
70
|
+
* 3. Parked on open question + no rail + interactive → refuse (don't mailbox-drop)
|
|
71
|
+
* 4. Otherwise → mailbox (running agent between tool calls)
|
|
72
|
+
*/
|
|
73
|
+
export declare function resolveAnswerRoute(input: AnswerRouterInput): AnswerRoute;
|
|
74
|
+
/** Build argv for a headless resume that continues with the answer text. */
|
|
75
|
+
export declare function resumeArgv(route: AnswerRoute): string[];
|