brainclaw 1.6.0 → 1.7.1
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 +33 -9
- 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/commands/switch.js +40 -8
- 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/identity.js +40 -13
- 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
|
|
|
@@ -342,11 +343,34 @@ npm run test:coverage # with coverage report
|
|
|
342
343
|
|
|
343
344
|
## Changelog
|
|
344
345
|
|
|
345
|
-
For older releases (v0.x and the early v1.0 launch series), `git log` on `master` is the source of truth — every release commit follows the `chore(release): bump version to <semver>` convention, and the matching feature/fix commits reference their plan id (e.g. `feat(mcp): self-heal ... (pln#478)`).
|
|
346
|
-
|
|
347
|
-
### v1.
|
|
348
|
-
|
|
349
|
-
- **
|
|
346
|
+
For older releases (v0.x and the early v1.0 launch series), `git log` on `master` is the source of truth — every release commit follows the `chore(release): bump version to <semver>` convention, and the matching feature/fix commits reference their plan id (e.g. `feat(mcp): self-heal ... (pln#478)`).
|
|
347
|
+
|
|
348
|
+
### v1.7.1
|
|
349
|
+
|
|
350
|
+
- **MCP project context isolation fix** — `bclaw_switch` now keeps MCP switches
|
|
351
|
+
session-scoped even when the agent session has to be resolved or created on
|
|
352
|
+
the fly. Session lookup honors explicit session IDs, avoids adopting another
|
|
353
|
+
live process's session, detects Codex via native `CODEX_*` runtime variables,
|
|
354
|
+
and `bclaw_switch(list=true)` reports the session active project with
|
|
355
|
+
`active_source`.
|
|
356
|
+
|
|
357
|
+
### v1.7.0
|
|
358
|
+
|
|
359
|
+
- **Dispatch reliability + scope-aware dirty guard** — evidence-first
|
|
360
|
+
`agent_run` reconciliation avoids false terminal states, `bclaw_coordinate`
|
|
361
|
+
accepts pinned refs and a scope-aware `allow_dirty` guard, and the Hermes
|
|
362
|
+
agent integration joins the supported surfaces.
|
|
363
|
+
|
|
364
|
+
### v1.6.0
|
|
365
|
+
|
|
366
|
+
- **Bootstrap loop + cross-project agent workflow** — the bootstrap ideation
|
|
367
|
+
preset can materialize `PROJECT.md`, `bclaw_init_project` initializes and links
|
|
368
|
+
arbitrary project paths, and `project=` routing reaches `bclaw_work` /
|
|
369
|
+
`bclaw_loop` for linked-project operations.
|
|
370
|
+
|
|
371
|
+
### v1.5.3
|
|
372
|
+
|
|
373
|
+
- **Cross-project canonical grammar + CLI parity** (pln#359, all phases) — the canonical grammar (`bclaw_find / get / create / update / remove / transition`), `bclaw_context`, and `bclaw_coordinate` now accept an optional `project: <name>` argument that routes the operation to a linked project. Two link kinds are recognised: `cross_project_links` (sibling/peer projects in `config.yaml`, `brainclaw link list`) and workspace store-chain children. Arbitrary directory paths are rejected — adoption requires an explicit link, which gives the user a single point of control over what an agent can reach. Identity is sourced from the caller's home registry; entity writes + audit log entries land in the target. Unknown project names throw `validation_error` with a hint listing the configured links — no silent fallback. Cross-project `bclaw_coordinate` is **inbox-only**: claim/assignment/message all land in the target, the target agent picks the brief up async via its own `bclaw_work`, and auto-spawn from the source process is force-disabled because the spawn cwd / worktree are tied to the target's git repo (a warning surfaces in `FacadeResponse.warnings`). The CLI exposes the same as a global `--project <name>` flag, mutually exclusive with `--cwd`. Refs: helper `resolveProjectCwd` in `src/core/cross-project.ts`, MCP write/read handler dispatch in `src/commands/mcp.ts` and `src/commands/mcp-read-handlers.ts`, `--project` plumbing in `src/cli.ts` preAction, surface advertisement in `src/core/instruction-templates.ts`, plus tests in `tests/unit/cross-project.test.ts` (10 unit cases on the helper), `tests/unit/bclaw-coordinate.test.ts` (4 cross-project routing cases), and `tests/cli-cross-project.test.ts` (5 e2e cases). Closes the `--cwd` workaround pattern that had been the day-to-day shape of multi-project sessions.
|
|
350
374
|
- **Site facts contract** (umbrella `pln_7fdfd70d` sprint 0) — new `scripts/emit-site-facts.mjs` emits `dist/facts.{js,json}` from `MCP_TOOL_NAMES` + `ENTITY_NAMES` so the brainclaw-site (and any consumer) can pull live tool/entity counts at build time without forking the values into a hand-maintained config. The package `files` list ships `dist/facts.json`; build:cli runs the emitter as part of the chain.
|
|
351
375
|
|
|
352
376
|
### v1.5.2
|
|
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);
|
package/dist/commands/switch.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { loadActiveProject, saveActiveProject, clearActiveProject } from '../core/active-project.js';
|
|
3
|
-
import { loadCurrentSession, saveCurrentSession } from '../core/identity.js';
|
|
3
|
+
import { buildOperationalIdentity, loadCurrentSession, saveCurrentSession } from '../core/identity.js';
|
|
4
4
|
import { memoryExists } from '../core/io.js';
|
|
5
5
|
import { resolveProjectRef } from '../core/store-resolution.js';
|
|
6
|
-
import { resolveProjectCwd } from '../core/cross-project.js';
|
|
6
|
+
import { resolveCrossProjectLinks, resolveProjectCwd } from '../core/cross-project.js';
|
|
7
7
|
import { scanNestedBrainclawProjects } from '../core/workspace-projects.js';
|
|
8
8
|
import { loadConfig } from '../core/config.js';
|
|
9
9
|
/**
|
|
@@ -43,8 +43,12 @@ export function switchProject(projectRef, options = {}) {
|
|
|
43
43
|
}
|
|
44
44
|
catch { /* name is optional */ }
|
|
45
45
|
const now = new Date().toISOString();
|
|
46
|
-
const session = loadCurrentSession(cwd);
|
|
47
46
|
const sessionOnly = options.sessionOnly ?? true;
|
|
47
|
+
let session = loadCurrentSession(cwd);
|
|
48
|
+
if (!session && sessionOnly) {
|
|
49
|
+
buildOperationalIdentity(undefined, cwd, { persistImplicitSession: true });
|
|
50
|
+
session = loadCurrentSession(cwd);
|
|
51
|
+
}
|
|
48
52
|
if (session && sessionOnly) {
|
|
49
53
|
saveCurrentSession({
|
|
50
54
|
...session,
|
|
@@ -52,6 +56,9 @@ export function switchProject(projectRef, options = {}) {
|
|
|
52
56
|
}, cwd);
|
|
53
57
|
return { switched: true, path: resolved, name: projectName, scope: 'session', workspace_root: wsRoot };
|
|
54
58
|
}
|
|
59
|
+
if (sessionOnly) {
|
|
60
|
+
throw new Error('Cannot switch project without an active agent session. Start with bclaw_work or bclaw_session_start first.');
|
|
61
|
+
}
|
|
55
62
|
if (session) {
|
|
56
63
|
// Also write to session even when not sessionOnly
|
|
57
64
|
saveCurrentSession({
|
|
@@ -75,15 +82,30 @@ export function listAvailableProjects(cwd) {
|
|
|
75
82
|
if (!wsRoot) {
|
|
76
83
|
throw new Error('No brainclaw workspace found.');
|
|
77
84
|
}
|
|
78
|
-
const
|
|
85
|
+
const sessionActive = loadCurrentSession(cwd)?.active_project;
|
|
86
|
+
const globalActive = loadActiveProject(wsRoot);
|
|
87
|
+
const active = sessionActive ?? globalActive;
|
|
88
|
+
const activeSource = sessionActive ? 'session' : globalActive ? 'global' : 'none';
|
|
79
89
|
const projects = [];
|
|
90
|
+
const seen = new Set();
|
|
91
|
+
const addProject = (project) => {
|
|
92
|
+
const projectPath = path.resolve(project.path);
|
|
93
|
+
if (seen.has(projectPath))
|
|
94
|
+
return;
|
|
95
|
+
seen.add(projectPath);
|
|
96
|
+
projects.push({
|
|
97
|
+
...project,
|
|
98
|
+
path: projectPath,
|
|
99
|
+
active: active?.path ? path.resolve(active.path) === projectPath : false,
|
|
100
|
+
});
|
|
101
|
+
};
|
|
80
102
|
if (memoryExists(wsRoot)) {
|
|
81
103
|
try {
|
|
82
104
|
const config = loadConfig(wsRoot);
|
|
83
|
-
|
|
105
|
+
addProject({ name: config.project_name, path: wsRoot, relative_path: '.' });
|
|
84
106
|
}
|
|
85
107
|
catch {
|
|
86
|
-
|
|
108
|
+
addProject({ path: wsRoot, relative_path: '.' });
|
|
87
109
|
}
|
|
88
110
|
}
|
|
89
111
|
const children = scanNestedBrainclawProjects(wsRoot, 7);
|
|
@@ -92,9 +114,19 @@ export function listAvailableProjects(cwd) {
|
|
|
92
114
|
if (childPath === wsRoot)
|
|
93
115
|
continue;
|
|
94
116
|
const rel = path.relative(wsRoot, childPath) || '.';
|
|
95
|
-
|
|
117
|
+
addProject({ name: child.project_name, path: childPath, relative_path: rel });
|
|
118
|
+
}
|
|
119
|
+
for (const link of resolveCrossProjectLinks(wsRoot)) {
|
|
120
|
+
if (!link.available)
|
|
121
|
+
continue;
|
|
122
|
+
const linkPath = path.resolve(link.absolutePath);
|
|
123
|
+
addProject({
|
|
124
|
+
name: link.projectName,
|
|
125
|
+
path: linkPath,
|
|
126
|
+
relative_path: path.relative(wsRoot, linkPath) || '.',
|
|
127
|
+
});
|
|
96
128
|
}
|
|
97
|
-
return { workspace_root: wsRoot, projects };
|
|
129
|
+
return { workspace_root: wsRoot, active_source: activeSource, projects };
|
|
98
130
|
}
|
|
99
131
|
export function runSwitch(projectRef, options = {}) {
|
|
100
132
|
// Use real cwd, not effective cwd — switch must see the full workspace
|
|
@@ -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',
|