brainclaw 1.6.0 → 1.7.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/README.md +5 -4
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/commands/mcp-schemas.generated.js +33 -0
- package/dist/commands/mcp.js +66 -31
- package/dist/commands/setup.js +64 -13
- package/dist/core/agent-capability.js +19 -0
- package/dist/core/agent-files.js +86 -0
- package/dist/core/agent-integrations.js +34 -0
- package/dist/core/agent-inventory.js +27 -0
- package/dist/core/agentrun-reconciler.js +80 -15
- package/dist/core/ai-agent-detection.js +17 -1
- package/dist/core/claims.js +25 -3
- package/dist/core/dirty-scope.js +242 -0
- package/dist/core/facade-schema.js +17 -1
- package/dist/core/schema.js +2 -0
- package/dist/core/worktree.js +58 -0
- package/dist/facts.js +356 -3
- package/dist/facts.json +355 -2
- package/docs/cli.md +10 -7
- package/docs/concepts/troubleshooting.md +26 -0
- package/docs/index.md +4 -3
- package/docs/integrations/hermes.md +78 -0
- package/docs/integrations/overview.md +9 -7
- package/docs/mcp-schema-changelog.md +8 -1
- package/docs/quickstart-existing-project.md +1 -1
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ If you've ever:
|
|
|
14
14
|
- watched two coworkers (or two agents) **edit the same files** without knowing it,
|
|
15
15
|
- or **gave up running multiple agents in parallel** because keeping them in sync was a pain,
|
|
16
16
|
|
|
17
|
-
brainclaw gives you durable shared state across sessions, agents, and teammates. Plans, claims, handoffs, decisions, and traps live in `.brainclaw/`, work identically across any compatible agent (Claude Code, Codex, Copilot, Cline, OpenCode, Cursor, Windsurf, Kilocode, Roo Code, Continue, Mistral Vibe, Antigravity/Gemini CLI, …), and stay accessible whether you orchestrate them in parallel or pick them up one after another.
|
|
17
|
+
brainclaw gives you durable shared state across sessions, agents, and teammates. Plans, claims, handoffs, decisions, and traps live in `.brainclaw/`, work identically across any compatible agent (Claude Code, Codex, Copilot, Cline, OpenCode, Cursor, Windsurf, Kilocode, Roo Code, Continue, Mistral Vibe, Hermes, Antigravity/Gemini CLI, …), and stay accessible whether you orchestrate them in parallel or pick them up one after another.
|
|
18
18
|
|
|
19
19
|
Use it two ways — **together or separately**:
|
|
20
20
|
|
|
@@ -81,7 +81,8 @@ brainclaw is designed to sit alongside the coding agents teams are already using
|
|
|
81
81
|
|
|
82
82
|
| Logo | Agent | Tier | What brainclaw configures |
|
|
83
83
|
|---|---|---|---|
|
|
84
|
-
| [](https://github.com/openclaw/openclaw) | **[OpenClaw](https://github.com/openclaw/openclaw)** | B | MCP + brainclaw skill (SKILL.md) for structured project memory |
|
|
84
|
+
| [](https://github.com/openclaw/openclaw) | **[OpenClaw](https://github.com/openclaw/openclaw)** | B | MCP + brainclaw skill (SKILL.md) for structured project memory |
|
|
85
|
+
| [](https://github.com/NousResearch/hermes-agent) | **[Hermes Agent](https://github.com/NousResearch/hermes-agent)** | B | MCP + universal `.agents/skills/brainclaw/SKILL.md` |
|
|
85
86
|
| [](https://github.com/qwibitai/nanoclaw) | **[NanoClaw](https://github.com/qwibitai/nanoclaw)** | C | brainclaw skill — messaging agent (WhatsApp, Telegram, Slack) |
|
|
86
87
|
| [](https://github.com/NVIDIA/NemoClaw) | **[NemoClaw](https://github.com/NVIDIA/NemoClaw)** | C | brainclaw skill — NVIDIA enterprise agent stack |
|
|
87
88
|
| [](https://github.com/sipeed/picoclaw) | **[PicoClaw](https://github.com/sipeed/picoclaw)** | C | brainclaw skill — edge/IoT agent (Go, <10MB RAM) |
|
|
@@ -132,7 +133,7 @@ npm install -g brainclaw
|
|
|
132
133
|
brainclaw setup-machine --yes
|
|
133
134
|
```
|
|
134
135
|
|
|
135
|
-
This detects the installed agents on the current machine, writes the machine-level MCP and user config Brainclaw manages, and does **not** scan or initialize repositories.
|
|
136
|
+
This detects the installed coding agents on the current machine, writes the machine-level MCP and user config Brainclaw manages for that detected set, and does **not** scan or initialize repositories.
|
|
136
137
|
|
|
137
138
|
### 4. Initialize or refresh the current project
|
|
138
139
|
|
|
@@ -231,7 +232,7 @@ Still sharp:
|
|
|
231
232
|
1. **Same-checkout concurrent edits** — running two agents in the *same* working tree (no per-claim worktree) is still the wrong answer. Use the dispatch path (auto-worktree per claim) instead of raw concurrent CLI sessions.
|
|
232
233
|
2. **Cross-machine sync** — federation across machines is on the roadmap, not in v1.x. Today brainclaw's store is local and one-machine-per-project.
|
|
233
234
|
3. **Spawn-and-forget assumptions** — spawned workers don't always commit their work cleanly. The brief-ack file confirms the spawn started; in the worst case the coordinator harvests open changes.
|
|
234
|
-
4. **Live state for hook-less agents** — Tier B/C agents without lifecycle hooks (Cursor, Cline, Windsurf, Copilot, Continue, Kilocode, Mistral Vibe) get live context via `.live.md` companions regenerated on session-end and handoff, not via real-time push.
|
|
235
|
+
4. **Live state for hook-less agents** — Tier B/C agents without lifecycle hooks (Cursor, Cline, Windsurf, Copilot, Continue, Kilocode, Mistral Vibe, Hermes) get live context via `.live.md` companions regenerated on session-end and handoff, not via real-time push.
|
|
235
236
|
|
|
236
237
|
Recommended use today:
|
|
237
238
|
|
|
Binary file
|
|
@@ -106,6 +106,25 @@ export const generatedSchemas = {
|
|
|
106
106
|
],
|
|
107
107
|
"additionalProperties": false
|
|
108
108
|
},
|
|
109
|
+
{
|
|
110
|
+
"type": "object",
|
|
111
|
+
"properties": {
|
|
112
|
+
"kind": {
|
|
113
|
+
"type": "string",
|
|
114
|
+
"const": "min_iterations"
|
|
115
|
+
},
|
|
116
|
+
"n": {
|
|
117
|
+
"type": "integer",
|
|
118
|
+
"exclusiveMinimum": 0,
|
|
119
|
+
"maximum": 9007199254740991
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"required": [
|
|
123
|
+
"kind",
|
|
124
|
+
"n"
|
|
125
|
+
],
|
|
126
|
+
"additionalProperties": false
|
|
127
|
+
},
|
|
109
128
|
{
|
|
110
129
|
"type": "object",
|
|
111
130
|
"properties": {
|
|
@@ -161,6 +180,19 @@ export const generatedSchemas = {
|
|
|
161
180
|
],
|
|
162
181
|
"additionalProperties": false
|
|
163
182
|
},
|
|
183
|
+
{
|
|
184
|
+
"type": "object",
|
|
185
|
+
"properties": {
|
|
186
|
+
"kind": {
|
|
187
|
+
"type": "string",
|
|
188
|
+
"const": "no_open_questions"
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
"required": [
|
|
192
|
+
"kind"
|
|
193
|
+
],
|
|
194
|
+
"additionalProperties": false
|
|
195
|
+
},
|
|
164
196
|
{
|
|
165
197
|
"type": "object",
|
|
166
198
|
"properties": {
|
|
@@ -254,6 +286,7 @@ export const generatedSchemas = {
|
|
|
254
286
|
"open",
|
|
255
287
|
"assigned",
|
|
256
288
|
"working",
|
|
289
|
+
"waiting_input",
|
|
257
290
|
"done",
|
|
258
291
|
"failed",
|
|
259
292
|
"cancelled"
|
package/dist/commands/mcp.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { buildClaimEnvPrefix } from '../core/execution-profile.js';
|
|
5
6
|
import { fileURLToPath } from 'node:url';
|
|
@@ -35,10 +36,11 @@ import { buildOperationalIdentity, loadAllSessions, loadSessionById } from '../c
|
|
|
35
36
|
import { validateMcpInput, validateMcpField } from '../core/input-validation.js';
|
|
36
37
|
import { createCapability, createTool as createRegistryTool } from '../core/registries.js';
|
|
37
38
|
import { detectAiAgent } from '../core/ai-agent-detection.js';
|
|
38
|
-
import { checkGitPresence, scanGitRepos, parseRoots, parseRepoSelection, parseAgentSelection, runGlobalInstall, initReposAndConfigureAgents, readSetupState, ALL_KNOWN_AGENTS, } from './setup.js';
|
|
39
|
+
import { checkGitPresence, scanGitRepos, parseRoots, parseRepoSelection, parseAgentSelection, getDetectedSetupAgentNames, getInstalledAgentNames, runGlobalInstall, initReposAndConfigureAgents, readSetupState, ALL_KNOWN_AGENTS, } from './setup.js';
|
|
40
|
+
import { buildAgentInventory } from '../core/agent-inventory.js';
|
|
39
41
|
import { resolveEffectiveCwd, resolveProjectRef, resolveTargetStore } from '../core/store-resolution.js';
|
|
40
42
|
import { probeForQuickSetup, buildQuickSetupProbeResponse, buildOnboardingPreview } from '../core/setup-flow.js';
|
|
41
|
-
import { ensureUserStore } from '../core/setup-state.js';
|
|
43
|
+
import { ensureUserStore, resolveHomeDir } from '../core/setup-state.js';
|
|
42
44
|
import { createPlan, addStep as addStepOp, completeStep as completeStepOp, updateStep as updateStepOp, deleteStep as deleteStepOp, deletePlan as deletePlanOp } from '../core/operations/plan.js';
|
|
43
45
|
import { sendMessage, ackMessage, countActionable, getThread, hasActiveAssignment } from '../core/messaging.js';
|
|
44
46
|
import { dispatch, dispatchReview, generateDispatchBrief } from '../core/dispatcher.js';
|
|
@@ -421,7 +423,7 @@ export const MCP_READ_TOOLS = [
|
|
|
421
423
|
const MCP_WRITE_TOOLS = [
|
|
422
424
|
{
|
|
423
425
|
name: 'bclaw_dispatch',
|
|
424
|
-
description: 'Unified dispatch entry for sequence-lane parallelization (parallelize plans across lanes). To open a NEW review of a commit/branch, use `bclaw_coordinate(intent="review", open_loop=true, targetAgents=[…])` instead — bclaw_dispatch is for sequence-driven execution, NOT for opening reviews. `intent` discriminator: analysis (sequence lane status, read-only), execute (default — analyze + generate briefs + send to agents), review (routes an EXISTING already-reflected handoff to a reviewer — only for handoffs produced by `session-end --reflect-handoff` or similar). Consolidates the legacy bclaw_dispatch_analysis / bclaw_dispatch / bclaw_dispatch_review. Returns FacadeResponse; for verification semantics see the same response-validation guidance documented on `bclaw_coordinate`.',
|
|
426
|
+
description: 'Unified dispatch entry for sequence-lane parallelization (parallelize plans across lanes). To open a NEW review of a commit/branch, use `bclaw_coordinate(intent="review", open_loop=true, targetAgents=[…])` instead — bclaw_dispatch is for sequence-driven execution, NOT for opening new reviews. `intent` discriminator: analysis (sequence lane status, read-only), execute (default — analyze + generate briefs + send to agents), review (routes an EXISTING already-reflected handoff to a reviewer — only for handoffs produced by `session-end --reflect-handoff` or similar). Consolidates the legacy bclaw_dispatch_analysis / bclaw_dispatch / bclaw_dispatch_review. Returns FacadeResponse; for verification semantics see the same response-validation guidance documented on `bclaw_coordinate`.',
|
|
425
427
|
annotations: { tier: 'facade', category: 'coordination', headlessApproval: 'prompt' },
|
|
426
428
|
inputSchema: {
|
|
427
429
|
type: 'object',
|
|
@@ -947,6 +949,8 @@ const MCP_WRITE_TOOLS = [
|
|
|
947
949
|
agent: { type: 'string', description: 'Caller agent name.' },
|
|
948
950
|
agentId: { type: 'string', description: 'Caller registered agent id.' },
|
|
949
951
|
project: { type: 'string', description: 'Optional (pln#359 phase 1b): name of a linked project to dispatch into. When set, claim/assignment/message all land in the target project — the target agent picks the brief up async via its own bclaw_work. Auto-spawn is disabled in cross-project mode. Accepts cross_project_links and workspace store-chain children (see `brainclaw link list`).' },
|
|
952
|
+
allow_dirty: { type: 'boolean', description: 'Override the scope-aware dirty-working-tree guard (trp#371 Tier 2). The guard runs only for worktree-spawning intents (assign/review/reroute) and blocks only when uncommitted files overlap — or cannot be proven disjoint from — the dispatch scope (the worker spawns from HEAD and will not see them). `.brainclaw/` and `.git/` are always excluded. Set true to proceed anyway (the block is downgraded to a warning that lists the overlapping files). Boolean; the string "true"/"false" are also coerced.' },
|
|
953
|
+
ref: { type: 'string', description: 'Optional git ref (commit/branch/tag) for assign/review/reroute: the dispatched worker builds its worktree from this ref instead of HEAD. When set, uncommitted working-tree changes are intentionally out of scope and the dirty guard allows the dispatch. Ignored by consult/ideate/summarize (no worktree).' },
|
|
950
954
|
},
|
|
951
955
|
required: ['intent', 'task'],
|
|
952
956
|
},
|
|
@@ -1260,6 +1264,7 @@ export const FACADE_ORDER = [
|
|
|
1260
1264
|
'bclaw_context',
|
|
1261
1265
|
'bclaw_coordinate',
|
|
1262
1266
|
'bclaw_dispatch',
|
|
1267
|
+
'bclaw_dispatch_status',
|
|
1263
1268
|
'bclaw_loop',
|
|
1264
1269
|
'bclaw_setup',
|
|
1265
1270
|
];
|
|
@@ -2390,8 +2395,14 @@ async function _executeMcpToolCallInner(payload) {
|
|
|
2390
2395
|
const repos = scanGitRepos(roots);
|
|
2391
2396
|
const selectedRepos = parseRepoSelection(choice, repos, cwd);
|
|
2392
2397
|
const detected = detectAiAgent(env);
|
|
2393
|
-
const
|
|
2394
|
-
|
|
2398
|
+
const installedAgents = getInstalledAgentNames(buildAgentInventory(resolveHomeDir(env) ?? os.homedir(), env));
|
|
2399
|
+
const detectedSetupAgents = getDetectedSetupAgentNames(detected?.name, installedAgents);
|
|
2400
|
+
const agentList = ALL_KNOWN_AGENTS.map((a, i) => {
|
|
2401
|
+
const tag = a === detected?.name ? ' ← detected' : installedAgents.includes(a) ? ' ← installed' : '';
|
|
2402
|
+
return ` ${i + 1}) ${a}${tag}`;
|
|
2403
|
+
}).join('\n');
|
|
2404
|
+
const detectedLine = detectedSetupAgents.length > 0 ? `\nDetected install set: ${detectedSetupAgents.join(', ')}\n` : '\n';
|
|
2405
|
+
return { response: toolResponse({ content: [{ type: 'text', text: `Selected ${selectedRepos.length} repo(s). Detected AI agent: ${detected?.name ?? 'none'}.${detectedLine}\nAvailable agents:\n${agentList}\n\nAsk the user which agents to configure.` }], structuredContent: { pending_question: 'agent_selection', roots: rootsArg, repo_selection: choice, selected_repos: selectedRepos.map((r) => ({ path: r.path, name: r.name })), detected_agent: detected?.name ?? null, installed_agents: installedAgents, detected_setup_agents: detectedSetupAgents, all_agents: ALL_KNOWN_AGENTS, prompt: 'Please ask the user: "Which agents to configure? Reply: (d)etected installed, (a)ll, or agent names like claude-code,cursor"' } }) };
|
|
2395
2406
|
}
|
|
2396
2407
|
if (step === 'agent_selection') {
|
|
2397
2408
|
if (!rootsArg || !repoSelectionArg) {
|
|
@@ -2401,7 +2412,8 @@ async function _executeMcpToolCallInner(payload) {
|
|
|
2401
2412
|
const repos = scanGitRepos(roots);
|
|
2402
2413
|
const selectedRepos = parseRepoSelection(repoSelectionArg, repos, cwd);
|
|
2403
2414
|
const detected = detectAiAgent(env);
|
|
2404
|
-
const
|
|
2415
|
+
const installedAgents = getInstalledAgentNames(buildAgentInventory(resolveHomeDir(env) ?? os.homedir(), env));
|
|
2416
|
+
const selectedAgents = parseAgentSelection(choice, detected?.name, installedAgents);
|
|
2405
2417
|
const summary = [];
|
|
2406
2418
|
const written = runGlobalInstall(selectedAgents, env);
|
|
2407
2419
|
for (const f of written)
|
|
@@ -4426,31 +4438,11 @@ async function _executeMcpToolCallInner(payload) {
|
|
|
4426
4438
|
};
|
|
4427
4439
|
}
|
|
4428
4440
|
}
|
|
4429
|
-
// can_30c295b4 —
|
|
4430
|
-
//
|
|
4431
|
-
//
|
|
4432
|
-
// the
|
|
4433
|
-
//
|
|
4434
|
-
// when the source cwd is a git repo with a dirty working tree.
|
|
4435
|
-
// Override via allow_dirty=true when the caller knows the dispatched
|
|
4436
|
-
// work doesn't depend on the modified files (e.g. tests, docs-only
|
|
4437
|
-
// worker tasks). Has no effect when cwd is not a git repo.
|
|
4438
|
-
if (!req.allow_dirty && (req.intent === 'review' || req.intent === 'assign' || req.intent === 'consult' || req.intent === 'ideate')) {
|
|
4439
|
-
try {
|
|
4440
|
-
const { spawnSync } = await import('node:child_process');
|
|
4441
|
-
const probe = spawnSync('git', ['-C', cwd, 'status', '--porcelain'], { encoding: 'utf8', timeout: 5000 });
|
|
4442
|
-
if (probe.status === 0 && typeof probe.stdout === 'string' && probe.stdout.trim().length > 0) {
|
|
4443
|
-
const fileCount = probe.stdout.trim().split('\n').length;
|
|
4444
|
-
return {
|
|
4445
|
-
response: createToolErrorResponse('dirty_working_tree', `Refusing to dispatch: source working tree has ${fileCount} uncommitted file(s). The spawned worker will not see these changes (worktrees branch from HEAD). Commit or stash before dispatching, or pass allow_dirty=true to override. Source cwd: ${cwd}`),
|
|
4446
|
-
};
|
|
4447
|
-
}
|
|
4448
|
-
// probe.status !== 0 → not a git repo (or git unavailable) → skip the check silently
|
|
4449
|
-
}
|
|
4450
|
-
catch {
|
|
4451
|
-
// best-effort: never block dispatch on a pre-flight check failure unrelated to the actual dirty state
|
|
4452
|
-
}
|
|
4453
|
-
}
|
|
4441
|
+
// can_30c295b4 / trp#371 Tier 2 — the scope-aware dirty-working-tree
|
|
4442
|
+
// guard runs LOWER DOWN, after dispatchCwd / isCrossProject are
|
|
4443
|
+
// resolved (so it probes the dispatch TARGET, not the source, and only
|
|
4444
|
+
// for the intents that actually spawn a worktree worker). See the
|
|
4445
|
+
// assessDirtyDispatchGuard call after the cross-project block.
|
|
4454
4446
|
const warnings = [];
|
|
4455
4447
|
const artifacts = [];
|
|
4456
4448
|
const side_effects = [];
|
|
@@ -4504,6 +4496,43 @@ async function _executeMcpToolCallInner(payload) {
|
|
|
4504
4496
|
warnings.push(`cross-project dispatch (project='${req.project}') — auto-spawn disabled; the target agent picks up the brief async via its own bclaw_work.`);
|
|
4505
4497
|
}
|
|
4506
4498
|
const effectiveAutoExecute = isCrossProject ? false : req.autoExecute;
|
|
4499
|
+
// can_30c295b4 / trp#371 Tier 2 — scope-aware dirty-working-tree guard.
|
|
4500
|
+
// Only intents that spawn a worktree worker from HEAD can review/edit
|
|
4501
|
+
// stale code, so consult/ideate/summarize are NOT guarded (no worktree
|
|
4502
|
+
// → nothing to protect). Cross-project dispatch is inbox-only (no local
|
|
4503
|
+
// worktree spawned here) so it is skipped too — the target agent builds
|
|
4504
|
+
// its own worktree later via bclaw_work. The guard compares the dirty
|
|
4505
|
+
// files against the dispatch scope and only blocks when overlap can't be
|
|
4506
|
+
// ruled out; allow_dirty=true downgrades a block to a warning, and an
|
|
4507
|
+
// explicit ref makes working-tree dirt intentionally out of scope.
|
|
4508
|
+
const WORKTREE_SPAWNING_INTENTS = new Set(['assign', 'review', 'reroute']);
|
|
4509
|
+
if (!isCrossProject && WORKTREE_SPAWNING_INTENTS.has(req.intent)) {
|
|
4510
|
+
// Probe with the SAME scope the dispatch will actually claim, so the
|
|
4511
|
+
// resolution mirrors reality (codex r1): assign falls back to the task
|
|
4512
|
+
// text (mcp ~assignScope), reroute to the targeted active claim's scope.
|
|
4513
|
+
let guardScope = req.scope;
|
|
4514
|
+
if (req.intent === 'assign') {
|
|
4515
|
+
guardScope = req.scope ?? req.task;
|
|
4516
|
+
}
|
|
4517
|
+
else if (req.intent === 'reroute' && !req.scope) {
|
|
4518
|
+
guardScope = listClaims(dispatchCwd).find((c) => c.status === 'active')?.scope;
|
|
4519
|
+
}
|
|
4520
|
+
const { assessDirtyDispatchGuard } = await import('../core/dirty-scope.js');
|
|
4521
|
+
const assessment = assessDirtyDispatchGuard({
|
|
4522
|
+
cwd: dispatchCwd,
|
|
4523
|
+
scope: guardScope,
|
|
4524
|
+
allowDirty: req.allow_dirty,
|
|
4525
|
+
checkoutRef: req.ref,
|
|
4526
|
+
});
|
|
4527
|
+
if (assessment.decision === 'block') {
|
|
4528
|
+
return {
|
|
4529
|
+
response: createToolErrorResponse('dirty_working_tree', `${assessment.reason} (cwd: ${dispatchCwd})`),
|
|
4530
|
+
};
|
|
4531
|
+
}
|
|
4532
|
+
if (assessment.decision === 'warn') {
|
|
4533
|
+
warnings.push(`dirty_working_tree: ${assessment.reason}`);
|
|
4534
|
+
}
|
|
4535
|
+
}
|
|
4507
4536
|
/** Run E2E execution phase on prepared delivery entries. Returns overall execution status. */
|
|
4508
4537
|
const runCoordinateExecution = async (prepared, opts) => {
|
|
4509
4538
|
let overall = 'inbox_only';
|
|
@@ -4756,6 +4785,10 @@ async function _executeMcpToolCallInner(payload) {
|
|
|
4756
4785
|
dispatcherAgent: senderAgent,
|
|
4757
4786
|
sessionId: connectionSessionId,
|
|
4758
4787
|
cwd: dispatchCwd,
|
|
4788
|
+
// createCoordinatorClaim guarantees the worktree reflects this ref
|
|
4789
|
+
// (resets a stale branch / re-points a reused worktree) — see the
|
|
4790
|
+
// worktreeBaseRef invariant there (pln#520 Tier 2).
|
|
4791
|
+
worktreeBaseRef: req.ref,
|
|
4759
4792
|
});
|
|
4760
4793
|
const claimId = claimResult.claimId;
|
|
4761
4794
|
if (claimResult.worktreeWarning) {
|
|
@@ -4996,6 +5029,7 @@ async function _executeMcpToolCallInner(payload) {
|
|
|
4996
5029
|
dispatcherAgent: senderAgent,
|
|
4997
5030
|
sessionId: connectionSessionId,
|
|
4998
5031
|
cwd: dispatchCwd,
|
|
5032
|
+
worktreeBaseRef: req.ref,
|
|
4999
5033
|
});
|
|
5000
5034
|
if (claimResult.worktreeWarning)
|
|
5001
5035
|
out.warnings.push(claimResult.worktreeWarning);
|
|
@@ -5184,6 +5218,7 @@ async function _executeMcpToolCallInner(payload) {
|
|
|
5184
5218
|
dispatcherAgent: senderAgent,
|
|
5185
5219
|
sessionId: connectionSessionId,
|
|
5186
5220
|
cwd: dispatchCwd,
|
|
5221
|
+
worktreeBaseRef: req.ref,
|
|
5187
5222
|
});
|
|
5188
5223
|
newClaimId = rerouteClaimResult.claimId;
|
|
5189
5224
|
if (rerouteClaimResult.worktreeWarning) {
|
package/dist/commands/setup.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import readline from 'node:readline/promises';
|
|
4
5
|
import { spawnSync } from 'node:child_process';
|
|
@@ -7,7 +8,7 @@ import { detectAiAgent } from '../core/ai-agent-detection.js';
|
|
|
7
8
|
import { buildAiSurfaceInventory, renderAiSurfaceUsageHints } from '../core/ai-surface-inventory.js';
|
|
8
9
|
import { buildMachineProfile, saveMachineProfile, loadMachineProfile } from '../core/machine-profile.js';
|
|
9
10
|
import { buildAgentInventory, saveAgentInventory, loadAgentInventory } from '../core/agent-inventory.js';
|
|
10
|
-
import { ensureClaudeCodeUserSettings, ensureClaudeCodeUserCommand, ensureCursorMcpConfig, ensureWindsurfMcpConfig, ensureAntigravityMcpConfig, ensureContinueUserMcpConfig, ensureContinueUserPermissions, ensureCodexMcpConfig, writeDetectedAgentAutoConfig, describeAutoConfigWrite, ensureGitignoreEntries, collectWorkspaceGitignoreEntries, BRAINCLAW_EXCLUSIVE_DIRECTORIES, } from '../core/agent-files.js';
|
|
11
|
+
import { ensureClaudeCodeUserSettings, ensureClaudeCodeUserCommand, ensureCursorMcpConfig, ensureWindsurfMcpConfig, ensureAntigravityMcpConfig, ensureContinueUserMcpConfig, ensureContinueUserPermissions, ensureCodexMcpConfig, ensureHermesMcpConfig, writeDetectedAgentAutoConfig, describeAutoConfigWrite, ensureGitignoreEntries, collectWorkspaceGitignoreEntries, BRAINCLAW_EXCLUSIVE_DIRECTORIES, } from '../core/agent-files.js';
|
|
11
12
|
import { MEMORY_DIR, memoryExists, ensureMemoryDir } from '../core/io.js';
|
|
12
13
|
import { saveConfig, defaultConfig } from '../core/config.js';
|
|
13
14
|
import { readSetupState, resolveHomeDir, writeSetupState } from '../core/setup-state.js';
|
|
@@ -25,6 +26,9 @@ export const ALL_KNOWN_AGENTS = [
|
|
|
25
26
|
'antigravity',
|
|
26
27
|
'continue',
|
|
27
28
|
'roo',
|
|
29
|
+
'kilocode',
|
|
30
|
+
'mistral-vibe',
|
|
31
|
+
'hermes',
|
|
28
32
|
'openclaw',
|
|
29
33
|
'nanoclaw',
|
|
30
34
|
'nemoclaw',
|
|
@@ -135,13 +139,46 @@ export function parseRepoSelection(choice, repos, cwd = process.cwd()) {
|
|
|
135
139
|
return indices.map((i) => repos[i]);
|
|
136
140
|
}
|
|
137
141
|
// ─── Step 4: Agent selection ──────────────────────────────────────────────────
|
|
138
|
-
|
|
142
|
+
function uniqueKnownAgents(names) {
|
|
143
|
+
const seen = new Set();
|
|
144
|
+
const result = [];
|
|
145
|
+
for (const name of names) {
|
|
146
|
+
if (!name || !ALL_KNOWN_AGENTS.includes(name))
|
|
147
|
+
continue;
|
|
148
|
+
if (seen.has(name))
|
|
149
|
+
continue;
|
|
150
|
+
seen.add(name);
|
|
151
|
+
result.push(name);
|
|
152
|
+
}
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
export function getInstalledAgentNames(inventory) {
|
|
156
|
+
return uniqueKnownAgents(inventory?.agents.filter((agent) => agent.installed).map((agent) => agent.name) ?? []);
|
|
157
|
+
}
|
|
158
|
+
export function getDetectedSetupAgentNames(detected, installedAgents = []) {
|
|
159
|
+
return uniqueKnownAgents([
|
|
160
|
+
detected,
|
|
161
|
+
...ALL_KNOWN_AGENTS.filter((agent) => installedAgents.includes(agent)),
|
|
162
|
+
]);
|
|
163
|
+
}
|
|
164
|
+
export function parseAgentSelection(choice, detected, installedAgents = []) {
|
|
139
165
|
const c = choice.trim().toLowerCase();
|
|
140
166
|
if (c === 'a' || c === 'all')
|
|
141
167
|
return [...ALL_KNOWN_AGENTS];
|
|
142
|
-
if (c === 'd' || c === 'detected')
|
|
143
|
-
return detected
|
|
144
|
-
|
|
168
|
+
if (c === 'd' || c === 'detected' || c === 'installed') {
|
|
169
|
+
return getDetectedSetupAgentNames(detected, installedAgents);
|
|
170
|
+
}
|
|
171
|
+
const selected = [];
|
|
172
|
+
for (const token of c.split(',').map((a) => a.trim()).filter(Boolean)) {
|
|
173
|
+
const index = Number.parseInt(token, 10);
|
|
174
|
+
if (/^\d+$/.test(token) && index >= 1 && index <= ALL_KNOWN_AGENTS.length) {
|
|
175
|
+
selected.push(ALL_KNOWN_AGENTS[index - 1]);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (ALL_KNOWN_AGENTS.includes(token))
|
|
179
|
+
selected.push(token);
|
|
180
|
+
}
|
|
181
|
+
return uniqueKnownAgents(selected);
|
|
145
182
|
}
|
|
146
183
|
// ─── Step 5: Global install ───────────────────────────────────────────────────
|
|
147
184
|
export function initUserStore(home, env = process.env) {
|
|
@@ -237,6 +274,11 @@ export function runGlobalInstall(selectedAgents, env = process.env) {
|
|
|
237
274
|
if (r && (r.created || r.updated))
|
|
238
275
|
written.push(r.filePath);
|
|
239
276
|
}
|
|
277
|
+
if (selectedAgents.includes('hermes')) {
|
|
278
|
+
const r = ensureHermesMcpConfig(home);
|
|
279
|
+
if (r && (r.created || r.updated))
|
|
280
|
+
written.push(r.filePath);
|
|
281
|
+
}
|
|
240
282
|
return written;
|
|
241
283
|
}
|
|
242
284
|
// ─── Step 6: Init repos + configure agents ────────────────────────────────────
|
|
@@ -331,30 +373,35 @@ function logDetectedAgentSurfaces(detectedName, detectedSurfaces) {
|
|
|
331
373
|
console.log('These surfaces are tracked separately from coding agents and will use tailored onboarding flows.');
|
|
332
374
|
}
|
|
333
375
|
}
|
|
334
|
-
async function resolveSelectedAgentsForSetup(options, detectedName) {
|
|
376
|
+
async function resolveSelectedAgentsForSetup(options, detectedName, installedAgents = []) {
|
|
377
|
+
const detectedSetupAgents = getDetectedSetupAgentNames(detectedName, installedAgents);
|
|
335
378
|
console.log('Supported agents:');
|
|
336
379
|
ALL_KNOWN_AGENTS.forEach((a, i) => {
|
|
337
380
|
const tag = a === detectedName ? ' ← detected' : '';
|
|
338
|
-
|
|
381
|
+
const installedTag = installedAgents.includes(a) ? ' ← installed' : '';
|
|
382
|
+
console.log(` ${i + 1}) ${a}${tag}${tag ? '' : installedTag}`);
|
|
339
383
|
});
|
|
384
|
+
if (detectedSetupAgents.length > 0) {
|
|
385
|
+
console.log(`Detected install set: ${detectedSetupAgents.join(', ')}`);
|
|
386
|
+
}
|
|
340
387
|
let agentChoice;
|
|
341
388
|
if (options.agents) {
|
|
342
389
|
agentChoice = options.agents;
|
|
343
390
|
}
|
|
344
391
|
else if (options.yes || !process.stdin.isTTY) {
|
|
345
|
-
agentChoice =
|
|
392
|
+
agentChoice = detectedSetupAgents.length > 0 ? 'detected' : 'all';
|
|
346
393
|
}
|
|
347
394
|
else {
|
|
348
|
-
const defaultChoice =
|
|
395
|
+
const defaultChoice = detectedSetupAgents.length > 0 ? 'detected' : 'all';
|
|
349
396
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
350
397
|
try {
|
|
351
|
-
agentChoice = (await rl.question(`Configure agents: (d)etected, (a)ll, or numbers e.g. 1,3 [${defaultChoice}]: `)).trim() || defaultChoice;
|
|
398
|
+
agentChoice = (await rl.question(`Configure agents: (d)etected installed, (a)ll, names, or numbers e.g. 1,3 [${defaultChoice}]: `)).trim() || defaultChoice;
|
|
352
399
|
}
|
|
353
400
|
finally {
|
|
354
401
|
rl.close();
|
|
355
402
|
}
|
|
356
403
|
}
|
|
357
|
-
const selectedAgents = parseAgentSelection(agentChoice, detectedName);
|
|
404
|
+
const selectedAgents = parseAgentSelection(agentChoice, detectedName, installedAgents);
|
|
358
405
|
console.log(`Selected agents: ${selectedAgents.length === 0 ? '(none)' : selectedAgents.join(', ')}`);
|
|
359
406
|
return selectedAgents;
|
|
360
407
|
}
|
|
@@ -364,10 +411,12 @@ export async function runSetupMachine(options = {}) {
|
|
|
364
411
|
const detectedName = detectedAi?.name;
|
|
365
412
|
const testMode = process.env.BRAINCLAW_TEST_MODE === '1';
|
|
366
413
|
const detectedSurfaces = testMode ? [] : buildAiSurfaceInventory();
|
|
414
|
+
const agentInventory = testMode ? undefined : buildAgentInventory(resolveHomeDir(env) ?? os.homedir(), env);
|
|
415
|
+
const installedAgents = getInstalledAgentNames(agentInventory);
|
|
367
416
|
console.log(BRAINCLAW_ASCII);
|
|
368
417
|
console.log('Machine bootstrap only — no repositories will be scanned or initialized.');
|
|
369
418
|
logDetectedAgentSurfaces(detectedName, detectedSurfaces);
|
|
370
|
-
const selectedAgents = await resolveSelectedAgentsForSetup(options, detectedName);
|
|
419
|
+
const selectedAgents = await resolveSelectedAgentsForSetup(options, detectedName, installedAgents);
|
|
371
420
|
console.log('\n→ Installing machine-level brainclaw prerequisites...');
|
|
372
421
|
const written = runGlobalInstall(selectedAgents, env);
|
|
373
422
|
if (written.length > 0) {
|
|
@@ -484,8 +533,10 @@ export async function runSetup(options = {}) {
|
|
|
484
533
|
const detectedName = detectedAi?.name;
|
|
485
534
|
const testMode = process.env.BRAINCLAW_TEST_MODE === '1';
|
|
486
535
|
const detectedSurfaces = testMode ? [] : buildAiSurfaceInventory();
|
|
536
|
+
const agentInventory = testMode ? undefined : buildAgentInventory(resolveHomeDir(env) ?? os.homedir(), env);
|
|
537
|
+
const installedAgents = getInstalledAgentNames(agentInventory);
|
|
487
538
|
logDetectedAgentSurfaces(detectedName, detectedSurfaces);
|
|
488
|
-
const selectedAgents = await resolveSelectedAgentsForSetup(options, detectedName);
|
|
539
|
+
const selectedAgents = await resolveSelectedAgentsForSetup(options, detectedName, installedAgents);
|
|
489
540
|
// Step 5: Global install
|
|
490
541
|
console.log('\n→ Installing global brainclaw prerequisites...');
|
|
491
542
|
const written = runGlobalInstall(selectedAgents, env);
|
|
@@ -23,6 +23,7 @@ const AGENT_ALIASES = {
|
|
|
23
23
|
'gemini': 'antigravity',
|
|
24
24
|
'mistral': 'mistral-vibe',
|
|
25
25
|
'vibe': 'mistral-vibe',
|
|
26
|
+
'hermes-agent': 'hermes',
|
|
26
27
|
};
|
|
27
28
|
/** Resolve an alias to its canonical agent name, or return the input unchanged. */
|
|
28
29
|
export function resolveAgentAlias(name) {
|
|
@@ -227,6 +228,24 @@ const PROFILES = {
|
|
|
227
228
|
invoke_review_template: 'vibe --prompt "{prompt}" --auto-approve --max-turns 5',
|
|
228
229
|
invoke_consult_template: 'vibe --prompt "{prompt}" --auto-approve --max-turns 3',
|
|
229
230
|
},
|
|
231
|
+
// Hermes Agent (Nous Research) — autonomous, skills-first agent with native
|
|
232
|
+
// MCP client support via ~/.hermes/config.yaml. Brainclaw uses Hermes as a
|
|
233
|
+
// Tier B surface for now: MCP + universal .agents/skills/ skill, no native
|
|
234
|
+
// Brainclaw hooks until a dedicated Hermes plugin is shipped and validated.
|
|
235
|
+
hermes: {
|
|
236
|
+
name: 'hermes', category: 'autonomous-agent', workflowModel: 'task-based',
|
|
237
|
+
hasMcp: true, hasHooks: false, hasAutoApprove: false, hasSkills: true, hasRules: false,
|
|
238
|
+
instructionFile: 'AGENTS.md', sharedInstructionFile: true, mcpConfigScope: 'machine', templateTier: 'B',
|
|
239
|
+
role_capabilities: ['execute', 'review', 'consult'],
|
|
240
|
+
runtime: { mcp_direct: true, hooks: false, canBeSpawnedCli: true, canSpawnOtherCli: false, inbox: false },
|
|
241
|
+
max_concurrent_tasks: 1,
|
|
242
|
+
prompt_delivery: { methods: ['inline_arg', 'temp_file'], preferred: 'inline_arg', max_inline_length: 8000 },
|
|
243
|
+
execution_env: { surface: 'cli' },
|
|
244
|
+
invoke_template: 'hermes chat -q "{prompt}"',
|
|
245
|
+
invoke_binary: 'hermes',
|
|
246
|
+
invoke_review_template: 'hermes chat -q "{prompt}"',
|
|
247
|
+
invoke_consult_template: 'hermes chat -q "{prompt}"',
|
|
248
|
+
},
|
|
230
249
|
// --- Autonomous agents (headless, task-based or scheduled) ---
|
|
231
250
|
openclaw: {
|
|
232
251
|
name: 'openclaw', category: 'autonomous-agent', workflowModel: 'task-based',
|
package/dist/core/agent-files.js
CHANGED
|
@@ -338,6 +338,7 @@ export const AGENT_EXPORT_REGISTRY = [
|
|
|
338
338
|
{ agentName: 'roo', format: 'roo', relativePath: '.roo/rules/brainclaw.md' },
|
|
339
339
|
{ agentName: 'kilocode', format: 'kilocode', relativePath: '.kilo/rules/brainclaw.md' },
|
|
340
340
|
{ agentName: 'mistral-vibe', format: 'agents-md', relativePath: 'AGENTS.md' },
|
|
341
|
+
{ agentName: 'hermes', format: 'agents-md', relativePath: 'AGENTS.md' },
|
|
341
342
|
{ agentName: 'opencode', format: 'agents-md', relativePath: 'AGENTS.md' },
|
|
342
343
|
{ agentName: 'antigravity', format: 'gemini-md', relativePath: 'GEMINI.md' },
|
|
343
344
|
{ agentName: 'brainclaw', format: 'board-md', relativePath: 'BOARD.md' },
|
|
@@ -456,6 +457,8 @@ const ROO_MCP_RELATIVE_PATH = '.roo/mcp.json';
|
|
|
456
457
|
const KILOCODE_MCP_RELATIVE_PATH = '.kilo/mcp.json';
|
|
457
458
|
const KILOCODE_CONFIG_RELATIVE_PATH = 'kilo.jsonc';
|
|
458
459
|
const MISTRAL_VIBE_CONFIG_RELATIVE_PATH = '.vibe/config.toml';
|
|
460
|
+
const HERMES_CONFIG_RELATIVE_PATH = '.hermes/config.yaml';
|
|
461
|
+
const HERMES_EXTERNAL_SKILLS_RELATIVE_PATH = '.agents/skills';
|
|
459
462
|
const CONTINUE_CONFIG_RELATIVE_PATH = '.continue/config.json';
|
|
460
463
|
const CONTINUE_PERMISSIONS_RELATIVE_PATH = '.continue/permissions.yaml';
|
|
461
464
|
const OPENCODE_CONFIG_RELATIVE_PATH = 'opencode.json';
|
|
@@ -493,6 +496,7 @@ export const LOCAL_ONLY_AGENT_WORKSPACE_FILES = [
|
|
|
493
496
|
KILOCODE_MCP_RELATIVE_PATH,
|
|
494
497
|
KILOCODE_CONFIG_RELATIVE_PATH,
|
|
495
498
|
MISTRAL_VIBE_CONFIG_RELATIVE_PATH,
|
|
499
|
+
HERMES_CONFIG_RELATIVE_PATH,
|
|
496
500
|
CONTINUE_CONFIG_RELATIVE_PATH,
|
|
497
501
|
OPENCODE_CONFIG_RELATIVE_PATH,
|
|
498
502
|
WINDSURF_MCP_RELATIVE_PATH,
|
|
@@ -1278,6 +1282,78 @@ export function ensureMistralVibeMcpConfig(cwd) {
|
|
|
1278
1282
|
relativePath: MISTRAL_VIBE_CONFIG_RELATIVE_PATH,
|
|
1279
1283
|
};
|
|
1280
1284
|
}
|
|
1285
|
+
const HERMES_BRAINCLAW_MCP_TOOLS = [
|
|
1286
|
+
'bclaw_work',
|
|
1287
|
+
'bclaw_context',
|
|
1288
|
+
'bclaw_find',
|
|
1289
|
+
'bclaw_get',
|
|
1290
|
+
'bclaw_create',
|
|
1291
|
+
'bclaw_update',
|
|
1292
|
+
'bclaw_transition',
|
|
1293
|
+
];
|
|
1294
|
+
export function ensureHermesMcpConfig(homeDir, workspacePath) {
|
|
1295
|
+
if (!homeDir)
|
|
1296
|
+
return undefined;
|
|
1297
|
+
const filePath = path.join(homeDir, HERMES_CONFIG_RELATIVE_PATH);
|
|
1298
|
+
let existing = {};
|
|
1299
|
+
let existed = false;
|
|
1300
|
+
if (fs.existsSync(filePath)) {
|
|
1301
|
+
existed = true;
|
|
1302
|
+
try {
|
|
1303
|
+
const parsed = yaml.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
1304
|
+
existing = isJsonObject(parsed) ? { ...parsed } : {};
|
|
1305
|
+
}
|
|
1306
|
+
catch {
|
|
1307
|
+
existing = {};
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
const mcpServers = isJsonObject(existing.mcp_servers) ? { ...existing.mcp_servers } : {};
|
|
1311
|
+
const current = isJsonObject(mcpServers.brainclaw) ? { ...mcpServers.brainclaw } : {};
|
|
1312
|
+
const currentEnv = isJsonObject(current.env) ? { ...current.env } : {};
|
|
1313
|
+
const currentTools = isJsonObject(current.tools) ? { ...current.tools } : {};
|
|
1314
|
+
const skills = isJsonObject(existing.skills) ? { ...existing.skills } : {};
|
|
1315
|
+
const externalDirs = Array.isArray(skills.external_dirs)
|
|
1316
|
+
? skills.external_dirs.filter((value) => typeof value === 'string')
|
|
1317
|
+
: [];
|
|
1318
|
+
if (workspacePath) {
|
|
1319
|
+
const projectSkillsDir = path.resolve(workspacePath, HERMES_EXTERNAL_SKILLS_RELATIVE_PATH);
|
|
1320
|
+
const normalized = projectSkillsDir.replace(/\\/g, '/').toLowerCase();
|
|
1321
|
+
if (!externalDirs.some((dir) => dir.replace(/\\/g, '/').toLowerCase() === normalized)) {
|
|
1322
|
+
externalDirs.push(projectSkillsDir);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
const mcpCmd = getBrainclawMcpCommand();
|
|
1326
|
+
mcpServers.brainclaw = {
|
|
1327
|
+
...current,
|
|
1328
|
+
command: typeof current.command === 'string' ? current.command : mcpCmd.command,
|
|
1329
|
+
args: Array.isArray(current.args) ? current.args : mcpCmd.args,
|
|
1330
|
+
env: {
|
|
1331
|
+
...currentEnv,
|
|
1332
|
+
BRAINCLAW_AGENT: 'hermes',
|
|
1333
|
+
},
|
|
1334
|
+
tools: {
|
|
1335
|
+
...currentTools,
|
|
1336
|
+
include: Array.isArray(currentTools.include) ? currentTools.include : HERMES_BRAINCLAW_MCP_TOOLS,
|
|
1337
|
+
prompts: typeof currentTools.prompts === 'boolean' ? currentTools.prompts : false,
|
|
1338
|
+
resources: typeof currentTools.resources === 'boolean' ? currentTools.resources : false,
|
|
1339
|
+
},
|
|
1340
|
+
};
|
|
1341
|
+
const nextConfig = {
|
|
1342
|
+
...existing,
|
|
1343
|
+
mcp_servers: mcpServers,
|
|
1344
|
+
...(externalDirs.length > 0 ? { skills: { ...skills, external_dirs: externalDirs } } : {}),
|
|
1345
|
+
};
|
|
1346
|
+
const content = `# Managed by brainclaw — preserves existing Hermes settings\n${yaml.stringify(nextConfig)}`;
|
|
1347
|
+
const { created, updated } = writeTextFileIfChanged(filePath, content);
|
|
1348
|
+
return {
|
|
1349
|
+
kind: 'mcp',
|
|
1350
|
+
label: 'Hermes MCP settings',
|
|
1351
|
+
created: !existed && created,
|
|
1352
|
+
updated: existed && updated,
|
|
1353
|
+
filePath,
|
|
1354
|
+
relativePath: HERMES_CONFIG_RELATIVE_PATH,
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1281
1357
|
export function ensureCodexMcpConfig(homeDir, env = process.env) {
|
|
1282
1358
|
const codexHome = env.CODEX_HOME?.trim() || (homeDir ? path.join(homeDir, '.codex') : null);
|
|
1283
1359
|
if (!codexHome)
|
|
@@ -1814,6 +1890,13 @@ export function writeDetectedAgentAutoConfig(agentName, cwd, env = process.env)
|
|
|
1814
1890
|
return [ensureKilocodeMcpConfig(cwd), ensureKilocodeConfig(cwd), ensureUniversalBrainclawSkill(cwd)];
|
|
1815
1891
|
case 'mistral-vibe':
|
|
1816
1892
|
return [ensureMistralVibeMcpConfig(cwd), ensureUniversalBrainclawSkill(cwd)];
|
|
1893
|
+
case 'hermes': {
|
|
1894
|
+
const results = [ensureUniversalBrainclawSkill(cwd)];
|
|
1895
|
+
const mcp = ensureHermesMcpConfig(resolveHomeDir(env), cwd);
|
|
1896
|
+
if (mcp)
|
|
1897
|
+
results.push(mcp);
|
|
1898
|
+
return results;
|
|
1899
|
+
}
|
|
1817
1900
|
case 'codex': {
|
|
1818
1901
|
const results = [ensureUniversalBrainclawSkill(cwd)];
|
|
1819
1902
|
const result = ensureCodexMcpConfig(resolveHomeDir(env), env);
|
|
@@ -1916,6 +1999,8 @@ export function writeExportCompanionFiles(format, cwd, env = process.env) {
|
|
|
1916
1999
|
results.push(hooks);
|
|
1917
2000
|
return results;
|
|
1918
2001
|
}
|
|
2002
|
+
case 'agents-md':
|
|
2003
|
+
return [ensureUniversalBrainclawSkill(cwd)];
|
|
1919
2004
|
default:
|
|
1920
2005
|
return [];
|
|
1921
2006
|
}
|
|
@@ -1956,6 +2041,7 @@ export function patchAllMcpConfigs(cwd, env = process.env) {
|
|
|
1956
2041
|
ensureAntigravityMcpConfig(homeDir),
|
|
1957
2042
|
ensureOpenClawMcpConfig(homeDir),
|
|
1958
2043
|
ensureCodexMcpConfig(homeDir, env),
|
|
2044
|
+
ensureHermesMcpConfig(homeDir),
|
|
1959
2045
|
];
|
|
1960
2046
|
for (const r of userConfigs) {
|
|
1961
2047
|
if (r)
|