brainclaw 0.28.0 → 1.5.3
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 +193 -170
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/cli.js +683 -23
- package/dist/commands/accept.js +3 -0
- package/dist/commands/add-step.js +11 -26
- package/dist/commands/agent-board.js +70 -3
- package/dist/commands/audit.js +19 -0
- package/dist/commands/check-policy.js +54 -0
- package/dist/commands/check-security-mcp.js +145 -0
- package/dist/commands/check-security.js +106 -0
- package/dist/commands/claim-resource.js +1 -0
- package/dist/commands/codev.js +672 -0
- package/dist/commands/compact.js +74 -0
- package/dist/commands/complete-step.js +16 -26
- package/dist/commands/constraint.js +8 -20
- package/dist/commands/decision.js +9 -20
- package/dist/commands/delete-plan.js +10 -12
- package/dist/commands/delete-step.js +16 -0
- package/dist/commands/dispatch.js +163 -0
- package/dist/commands/doctor.js +1122 -49
- package/dist/commands/enable-agent.js +1 -0
- package/dist/commands/export.js +280 -22
- package/dist/commands/handoff.js +33 -0
- package/dist/commands/harvest.js +189 -0
- package/dist/commands/hooks.js +82 -25
- package/dist/commands/inbox.js +169 -0
- package/dist/commands/init.js +38 -31
- package/dist/commands/install-hooks.js +71 -44
- package/dist/commands/link.js +89 -0
- package/dist/commands/list-claims.js +48 -3
- package/dist/commands/list-plans.js +129 -25
- package/dist/commands/loops-handlers.js +409 -0
- package/dist/commands/mcp-read-handlers.js +1628 -0
- package/dist/commands/mcp-schemas.generated.js +74 -0
- package/dist/commands/mcp.js +4244 -1475
- package/dist/commands/plan-resource.js +64 -0
- package/dist/commands/plan.js +12 -26
- package/dist/commands/prune.js +37 -2
- package/dist/commands/reflect.js +20 -7
- package/dist/commands/release-claim.js +11 -6
- package/dist/commands/release-notes.js +170 -0
- package/dist/commands/repair.js +210 -0
- package/dist/commands/run-profile.js +57 -0
- package/dist/commands/sequence.js +113 -0
- package/dist/commands/session-end.js +423 -14
- package/dist/commands/session-start.js +214 -41
- package/dist/commands/setup-security.js +103 -0
- package/dist/commands/setup.js +42 -4
- package/dist/commands/stale.js +109 -0
- package/dist/commands/switch.js +131 -10
- package/dist/commands/trap.js +14 -31
- package/dist/commands/update-handoff.js +63 -4
- package/dist/commands/update-plan.js +21 -28
- package/dist/commands/update-step.js +37 -0
- package/dist/commands/upgrade.js +313 -6
- package/dist/commands/usage.js +102 -0
- package/dist/commands/version.js +20 -0
- package/dist/commands/who.js +124 -0
- package/dist/commands/worktree.js +105 -0
- package/dist/core/actions.js +315 -0
- package/dist/core/agent-capability.js +610 -17
- package/dist/core/agent-context.js +7 -1
- package/dist/core/agent-files.js +1169 -85
- package/dist/core/agent-integrations.js +160 -5
- package/dist/core/agent-inventory.js +2 -0
- package/dist/core/agent-profiles.js +93 -0
- package/dist/core/agent-registry.js +162 -30
- package/dist/core/agentrun-reconciler.js +345 -0
- package/dist/core/agentruns.js +424 -0
- package/dist/core/ai-agent-detection.js +31 -10
- package/dist/core/archival.js +77 -0
- package/dist/core/assignment-sweeper.js +82 -0
- package/dist/core/assignments.js +367 -0
- package/dist/core/audit.js +30 -0
- package/dist/core/bootstrap.js +61 -10
- package/dist/core/brainclaw-version.js +94 -2
- package/dist/core/candidates.js +93 -2
- package/dist/core/claims.js +419 -0
- package/dist/core/codev-metrics.js +77 -0
- package/dist/core/codev-personas.js +31 -0
- package/dist/core/codev-plan-gen.js +35 -0
- package/dist/core/codev-prompts.js +74 -0
- package/dist/core/codev-responses.js +62 -0
- package/dist/core/codev-rounds.js +218 -0
- package/dist/core/config.js +4 -0
- package/dist/core/context.js +454 -34
- package/dist/core/coordination.js +201 -6
- package/dist/core/cross-project.js +230 -16
- package/dist/core/default-profiles/doctor.yaml +11 -0
- package/dist/core/default-profiles/janitor.yaml +11 -0
- package/dist/core/default-profiles/onboarder.yaml +11 -0
- package/dist/core/default-profiles/reviewer.yaml +13 -0
- package/dist/core/dispatcher.js +1189 -0
- package/dist/core/duplicates.js +2 -2
- package/dist/core/entity-operations.js +450 -0
- package/dist/core/entity-registry.js +344 -0
- package/dist/core/event-log.js +1 -0
- package/dist/core/events.js +106 -2
- package/dist/core/execution-adapters.js +154 -0
- package/dist/core/execution-context.js +63 -0
- package/dist/core/execution-profile.js +270 -0
- package/dist/core/execution.js +255 -0
- package/dist/core/facade-schema.js +81 -0
- package/dist/core/federation-cloud.js +99 -0
- package/dist/core/federation-message.js +52 -0
- package/dist/core/federation-transport.js +65 -0
- package/dist/core/gc-semantic.js +482 -0
- package/dist/core/governance.js +247 -0
- package/dist/core/guards.js +19 -0
- package/dist/core/ideation.js +72 -0
- package/dist/core/identity.js +252 -28
- package/dist/core/ids.js +6 -0
- package/dist/core/input-validation.js +2 -2
- package/dist/core/instruction-templates.js +344 -136
- package/dist/core/io.js +90 -11
- package/dist/core/lock.js +6 -2
- package/dist/core/loops/brief-assembly.js +213 -0
- package/dist/core/loops/facade-schema.js +148 -0
- package/dist/core/loops/index.js +7 -0
- package/dist/core/loops/iteration-engine.js +139 -0
- package/dist/core/loops/lock.js +385 -0
- package/dist/core/loops/store.js +201 -0
- package/dist/core/loops/types.js +403 -0
- package/dist/core/loops/verbs.js +534 -0
- package/dist/core/markdown.js +15 -3
- package/dist/core/memory-compactor.js +432 -0
- package/dist/core/memory-git.js +152 -8
- package/dist/core/messaging.js +278 -0
- package/dist/core/migration.js +32 -1
- package/dist/core/mutation-pipeline.js +4 -2
- package/dist/core/operations/memory-mutation.js +129 -0
- package/dist/core/operations/memory-write.js +78 -0
- package/dist/core/operations/plan.js +190 -0
- package/dist/core/policy.js +169 -0
- package/dist/core/repo-analysis.js +67 -0
- package/dist/core/reputation.js +9 -3
- package/dist/core/schema.js +546 -21
- package/dist/core/search.js +21 -2
- package/dist/core/security-cache.js +71 -0
- package/dist/core/security-guard.js +152 -0
- package/dist/core/security-scoring.js +86 -0
- package/dist/core/sequence.js +130 -0
- package/dist/core/socket-client.js +113 -0
- package/dist/core/staleness.js +246 -0
- package/dist/core/state.js +98 -22
- package/dist/core/store-resolution.js +54 -12
- package/dist/core/toml-writer.js +76 -0
- package/dist/core/upgrades/backup.js +232 -0
- package/dist/core/upgrades/health-check.js +169 -0
- package/dist/core/upgrades/patches/candidate-archive.js +145 -0
- package/dist/core/upgrades/patches/handoff-review-strip.js +128 -0
- package/dist/core/upgrades/patches/provenance-rollout.js +136 -0
- package/dist/core/upgrades/schema-version.js +97 -0
- package/dist/core/worktree.js +606 -0
- package/dist/facts.js +114 -0
- package/dist/facts.json +111 -0
- package/docs/architecture/project-refs.md +5 -1
- package/docs/cli.md +690 -43
- package/docs/concepts/ideation-loop.md +317 -0
- package/docs/concepts/loop-engine.md +456 -0
- package/docs/concepts/mcp-governance.md +268 -0
- package/docs/concepts/memory-staleness.md +122 -0
- package/docs/concepts/multi-agent-workflows.md +166 -0
- package/docs/concepts/plans-and-claims.md +31 -6
- package/docs/concepts/project-md-convention.md +35 -0
- package/docs/concepts/troubleshooting.md +220 -0
- package/docs/concepts/upgrade-cli.md +202 -0
- package/docs/concepts/upgrade-dogfood-procedure.md +114 -0
- package/docs/context-format-changelog.md +2 -2
- package/docs/context-format.md +2 -2
- package/docs/index.md +68 -0
- package/docs/integrations/agents.md +15 -16
- package/docs/integrations/cline.md +88 -0
- package/docs/integrations/codex.md +75 -23
- package/docs/integrations/continue.md +60 -0
- package/docs/integrations/copilot.md +67 -9
- package/docs/integrations/kilocode.md +72 -0
- package/docs/integrations/mcp.md +304 -21
- package/docs/integrations/mistral-vibe.md +122 -0
- package/docs/integrations/opencode.md +84 -0
- package/docs/integrations/overview.md +23 -8
- package/docs/integrations/roo.md +74 -0
- package/docs/integrations/windsurf.md +83 -0
- package/docs/mcp-schema-changelog.md +191 -1
- package/docs/playbooks/integration/index.md +121 -0
- package/docs/playbooks/productivity/index.md +102 -0
- package/docs/playbooks/team/index.md +122 -0
- package/docs/product/agent-first-model.md +184 -0
- package/docs/product/entity-model-audit.md +462 -0
- package/docs/quickstart-existing-project.md +135 -0
- package/docs/quickstart.md +124 -37
- package/docs/release-maintenance.md +79 -0
- package/docs/review.md +2 -0
- package/docs/server-operations.md +118 -0
- package/package.json +20 -12
- package/dist/commands/claude-desktop-extension.js +0 -18
- package/dist/commands/diff.js +0 -99
- package/dist/core/claude-desktop-extension.js +0 -224
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import { getInstalledBrainclawVersion } from './brainclaw-version.js';
|
|
6
|
+
import { getAgentCapabilityProfile } from './agent-capability.js';
|
|
4
7
|
const SUPPORTED_AGENT_INTEGRATION_NAMES = new Set([
|
|
5
8
|
'github-copilot',
|
|
6
9
|
'claude-code',
|
|
@@ -12,6 +15,7 @@ const SUPPORTED_AGENT_INTEGRATION_NAMES = new Set([
|
|
|
12
15
|
'antigravity',
|
|
13
16
|
'continue',
|
|
14
17
|
'roo',
|
|
18
|
+
'kilocode',
|
|
15
19
|
'openclaw',
|
|
16
20
|
'nanoclaw',
|
|
17
21
|
'nemoclaw',
|
|
@@ -21,49 +25,69 @@ const SUPPORTED_AGENT_INTEGRATION_NAMES = new Set([
|
|
|
21
25
|
const DEFAULT_SURFACES = {
|
|
22
26
|
'github-copilot': [
|
|
23
27
|
{ kind: 'instructions', location: 'workspace', path: '.github/copilot-instructions.md' },
|
|
28
|
+
{ kind: 'hook', location: 'workspace', path: '.github/copilot/hooks.json' },
|
|
29
|
+
{ kind: 'mcp', location: 'workspace', path: '.vscode/settings.json' },
|
|
24
30
|
{ kind: 'skill', location: 'workspace', path: '.github/skills/brainclaw-context/SKILL.md' },
|
|
31
|
+
{ kind: 'skill', location: 'workspace', path: '.agents/skills/brainclaw/SKILL.md' },
|
|
25
32
|
],
|
|
26
33
|
'claude-code': [
|
|
27
34
|
{ kind: 'instructions', location: 'workspace', path: 'CLAUDE.md' },
|
|
35
|
+
{ kind: 'hook', location: 'machine', path: '.claude/settings.local.json' },
|
|
28
36
|
{ kind: 'mcp', location: 'workspace', path: '.mcp.json' },
|
|
29
37
|
{ kind: 'skill', location: 'workspace', path: '.claude/commands/brainclaw.md' },
|
|
30
38
|
],
|
|
31
39
|
'cursor': [
|
|
32
40
|
{ kind: 'instructions', location: 'workspace', path: '.cursor/rules/brainclaw.md' },
|
|
33
41
|
{ kind: 'rule', location: 'workspace', path: '.cursor/rules/brainclaw-mcp-shim.mdc' },
|
|
42
|
+
{ kind: 'hook', location: 'workspace', path: '.cursor/hooks.json' },
|
|
34
43
|
{ kind: 'mcp', location: 'machine', path: '.cursor/mcp.json' },
|
|
44
|
+
{ kind: 'skill', location: 'workspace', path: '.agents/skills/brainclaw/SKILL.md' },
|
|
35
45
|
],
|
|
36
46
|
'windsurf': [
|
|
37
47
|
{ kind: 'instructions', location: 'workspace', path: '.windsurfrules' },
|
|
38
48
|
{ kind: 'hook', location: 'workspace', path: '.windsurfrules' },
|
|
39
49
|
{ kind: 'mcp', location: 'machine', path: '.codeium/windsurf/mcp_config.json' },
|
|
50
|
+
{ kind: 'rule', location: 'workspace', path: '.windsurf/rules/brainclaw.md' },
|
|
40
51
|
],
|
|
41
52
|
'cline': [
|
|
42
53
|
{ kind: 'instructions', location: 'workspace', path: '.clinerules/brainclaw.md' },
|
|
54
|
+
{ kind: 'hook', location: 'workspace', path: '.clinerules/brainclaw.md' },
|
|
43
55
|
{ kind: 'mcp', location: 'workspace', path: '.vscode/cline_mcp_settings.json' },
|
|
44
56
|
],
|
|
45
57
|
'codex': [
|
|
46
58
|
{ kind: 'instructions', location: 'workspace', path: 'AGENTS.md' },
|
|
59
|
+
{ kind: 'hook', location: 'workspace', path: 'AGENTS.md' },
|
|
47
60
|
{ kind: 'mcp', location: 'machine', path: '.codex/config.toml' },
|
|
61
|
+
{ kind: 'skill', location: 'workspace', path: '.agents/skills/brainclaw/SKILL.md' },
|
|
48
62
|
],
|
|
49
63
|
'opencode': [
|
|
50
64
|
{ kind: 'instructions', location: 'workspace', path: 'AGENTS.md' },
|
|
51
65
|
{ kind: 'mcp', location: 'workspace', path: 'opencode.json' },
|
|
66
|
+
{ kind: 'skill', location: 'workspace', path: '.agents/skills/brainclaw/SKILL.md' },
|
|
52
67
|
],
|
|
53
68
|
'antigravity': [
|
|
54
69
|
{ kind: 'instructions', location: 'workspace', path: 'GEMINI.md' },
|
|
55
70
|
{ kind: 'mcp', location: 'machine', path: '.gemini/antigravity/mcp_config.json' },
|
|
71
|
+
{ kind: 'hook', location: 'machine', path: '.gemini/antigravity/hooks.json' },
|
|
56
72
|
],
|
|
57
73
|
'continue': [
|
|
58
74
|
{ kind: 'instructions', location: 'workspace', path: '.continue/rules/brainclaw.md' },
|
|
59
75
|
{ kind: 'mcp', location: 'workspace', path: '.continue/config.json' },
|
|
76
|
+
{ kind: 'permissions', location: 'machine', path: '.continue/permissions.yaml' },
|
|
60
77
|
],
|
|
61
78
|
'roo': [
|
|
62
79
|
{ kind: 'instructions', location: 'workspace', path: '.roo/rules/brainclaw.md' },
|
|
63
80
|
{ kind: 'mcp', location: 'workspace', path: '.roo/mcp.json' },
|
|
81
|
+
{ kind: 'skill', location: 'workspace', path: '.agents/skills/brainclaw/SKILL.md' },
|
|
82
|
+
],
|
|
83
|
+
'kilocode': [
|
|
84
|
+
{ kind: 'instructions', location: 'workspace', path: '.kilo/rules/brainclaw.md' },
|
|
85
|
+
{ kind: 'mcp', location: 'workspace', path: '.kilo/mcp.json' },
|
|
86
|
+
{ kind: 'skill', location: 'workspace', path: '.agents/skills/brainclaw/SKILL.md' },
|
|
64
87
|
],
|
|
65
88
|
'openclaw': [
|
|
66
89
|
{ kind: 'skill', location: 'machine', path: '.openclaw/workspace/skills/brainclaw/SKILL.md' },
|
|
90
|
+
{ kind: 'mcp', location: 'machine', path: '.openclaw/mcp.json' },
|
|
67
91
|
],
|
|
68
92
|
'nanoclaw': [
|
|
69
93
|
{ kind: 'skill', location: 'workspace', path: 'skills/nanoclaw/SKILL.md' },
|
|
@@ -100,6 +124,10 @@ export function buildAgentIntegrationDeclaration(agentName, declarationSource =
|
|
|
100
124
|
export function isAgentIntegrationName(value) {
|
|
101
125
|
return SUPPORTED_AGENT_INTEGRATION_NAMES.has(value);
|
|
102
126
|
}
|
|
127
|
+
let commandVersionProbeForTests;
|
|
128
|
+
export function setCommandVersionProbeForTests(probe) {
|
|
129
|
+
commandVersionProbeForTests = probe;
|
|
130
|
+
}
|
|
103
131
|
function resolveDeclaredSurfacePath(surface, cwd, env) {
|
|
104
132
|
if (!surface.path) {
|
|
105
133
|
return undefined;
|
|
@@ -110,24 +138,151 @@ function resolveDeclaredSurfacePath(surface, cwd, env) {
|
|
|
110
138
|
const homeDir = env.HOME?.trim() || env.USERPROFILE?.trim() || os.homedir();
|
|
111
139
|
return path.join(homeDir, surface.path);
|
|
112
140
|
}
|
|
113
|
-
function
|
|
141
|
+
export function extractMcpCommandVal(agentName, expectedPath) {
|
|
142
|
+
let content;
|
|
143
|
+
try {
|
|
144
|
+
content = fs.readFileSync(expectedPath, 'utf-8');
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return { is_valid: false };
|
|
148
|
+
}
|
|
149
|
+
if (expectedPath.endsWith('.toml')) {
|
|
150
|
+
const cmdMatch = content.match(/\[mcp_servers\.brainclaw\](?:[^\[]*)command\s*=\s*(["'])(.+?)\1/is);
|
|
151
|
+
const argsMatch = content.match(/\[mcp_servers\.brainclaw\](?:[^\[]*)args\s*=\s*\[(.+?)\]/is);
|
|
152
|
+
let args;
|
|
153
|
+
if (argsMatch) {
|
|
154
|
+
args = argsMatch[1]
|
|
155
|
+
.split(',')
|
|
156
|
+
.map(s => s.trim().replace(/^["']|["']$/g, '').replace(/\\\\/g, '\\'));
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
command: cmdMatch ? cmdMatch[2].replace(/\\\\/g, '\\') : undefined,
|
|
160
|
+
args,
|
|
161
|
+
is_valid: true,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
const j = JSON.parse(content);
|
|
166
|
+
let cmd;
|
|
167
|
+
let args;
|
|
168
|
+
if (agentName === 'github-copilot') {
|
|
169
|
+
const mcpServers = j['github.copilot.chat.mcpServers'];
|
|
170
|
+
cmd = mcpServers?.brainclaw?.command;
|
|
171
|
+
args = mcpServers?.brainclaw?.args;
|
|
172
|
+
}
|
|
173
|
+
else if (agentName === 'continue') {
|
|
174
|
+
const servers = Array.isArray(j.mcpServers) ? j.mcpServers : [];
|
|
175
|
+
const bc = servers.find((s) => s && s.name === 'brainclaw');
|
|
176
|
+
cmd = bc?.command;
|
|
177
|
+
args = bc?.args;
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
cmd = j.mcpServers?.brainclaw?.command;
|
|
181
|
+
args = j.mcpServers?.brainclaw?.args;
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
command: typeof cmd === 'string' ? cmd : undefined,
|
|
185
|
+
args: Array.isArray(args) ? args.filter(a => typeof a === 'string') : undefined,
|
|
186
|
+
is_valid: true
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return { is_valid: false };
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function getCommandVersion(cmdPath, args) {
|
|
194
|
+
if (commandVersionProbeForTests) {
|
|
195
|
+
return commandVersionProbeForTests(cmdPath, args);
|
|
196
|
+
}
|
|
197
|
+
if (cmdPath === 'npx')
|
|
198
|
+
return null; // dynamic
|
|
199
|
+
try {
|
|
200
|
+
const isNode = cmdPath.endsWith('node') || cmdPath.endsWith('node.exe');
|
|
201
|
+
const spawnArgs = isNode && args && args[0] ? [args[0], '--version'] : ['--version'];
|
|
202
|
+
const res = spawnSync(cmdPath, spawnArgs, { encoding: 'utf-8', timeout: 2000 });
|
|
203
|
+
if (res.status === 0 && res.stdout) {
|
|
204
|
+
return res.stdout.trim();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
catch { }
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
function surfaceExists(surface, cwd, env, agentName) {
|
|
114
211
|
const expectedPath = resolveDeclaredSurfacePath(surface, cwd, env);
|
|
115
|
-
|
|
212
|
+
const exists = expectedPath ? fs.existsSync(expectedPath) : false;
|
|
213
|
+
const result = {
|
|
116
214
|
...surface,
|
|
117
215
|
expected_path: expectedPath,
|
|
118
|
-
exists
|
|
216
|
+
exists,
|
|
119
217
|
};
|
|
218
|
+
if (surface.kind === 'mcp' && exists && agentName && expectedPath) {
|
|
219
|
+
const { command, args, is_valid } = extractMcpCommandVal(agentName, expectedPath);
|
|
220
|
+
if (!is_valid) {
|
|
221
|
+
result.drift_message = `MCP config file is invalid JSON/TOML`;
|
|
222
|
+
}
|
|
223
|
+
else if (!command) {
|
|
224
|
+
result.drift_message = `MCP config file is missing 'brainclaw' command`;
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
if (command !== 'npx' && !fs.existsSync(command) && !['brainclaw', 'node'].includes(path.basename(command).replace(/\.exe$/, ''))) {
|
|
228
|
+
result.drift_message = `MCP command points to a non-existent file: ${command}`;
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
const expectedVersion = getInstalledBrainclawVersion();
|
|
232
|
+
const cmdVersion = getCommandVersion(command, args);
|
|
233
|
+
if (cmdVersion && cmdVersion !== expectedVersion) {
|
|
234
|
+
result.drift_message = `MCP command version drift (found ${cmdVersion}, expected ${expectedVersion})`;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return result;
|
|
120
240
|
}
|
|
121
241
|
export function assessAgentIntegrationReadiness(config, cwd, env = process.env) {
|
|
122
242
|
return (config.agent_integrations?.declarations ?? []).map((declaration) => {
|
|
123
|
-
const surfaces = declaration.surfaces.map((surface) => surfaceExists(surface, cwd, env));
|
|
243
|
+
const surfaces = declaration.surfaces.map((surface) => surfaceExists(surface, cwd, env, declaration.agent_name));
|
|
124
244
|
const missingSurfaces = surfaces.filter((surface) => !surface.exists);
|
|
245
|
+
const driftingSurfaces = surfaces.filter((surface) => surface.drift_message != null);
|
|
246
|
+
let effectiveTier = 'tier-b';
|
|
247
|
+
const selfHealingGuidance = [];
|
|
248
|
+
const hasMissingMcpOrHook = missingSurfaces.some((s) => s.kind === 'mcp' || s.kind === 'hook');
|
|
249
|
+
const hasDriftingMcp = driftingSurfaces.some((s) => s.kind === 'mcp');
|
|
250
|
+
const profile = getAgentCapabilityProfile(declaration.agent_name);
|
|
251
|
+
if (!profile) {
|
|
252
|
+
effectiveTier = 'tier-c';
|
|
253
|
+
selfHealingGuidance.push(`Agent ${declaration.agent_name} lacks a known capability profile. Defaulting to Tier C.`);
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
const isPrimaryTierA = profile.templateTier === 'A';
|
|
257
|
+
if (isPrimaryTierA) {
|
|
258
|
+
if (hasMissingMcpOrHook || hasDriftingMcp) {
|
|
259
|
+
effectiveTier = 'tier-b';
|
|
260
|
+
selfHealingGuidance.push(`Agent ${declaration.agent_name} is degraded to Tier B because MCP or hooks are missing/drifting. Run 'brainclaw doctor --fix' or check integrations.`);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
effectiveTier = 'tier-a';
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
effectiveTier = 'tier-b'; // Inherently Tier B because context relies on native rules
|
|
268
|
+
if (hasMissingMcpOrHook || hasDriftingMcp) {
|
|
269
|
+
selfHealingGuidance.push(`Agent ${declaration.agent_name} is missing or drifting MCP or hook configurations. Run 'brainclaw doctor --fix'.`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (missingSurfaces.length === surfaces.length && surfaces.length > 0) {
|
|
274
|
+
effectiveTier = 'tier-c';
|
|
275
|
+
selfHealingGuidance.push(`Agent ${declaration.agent_name} has no configured surfaces and is falling back to compact Tier C behavior.`);
|
|
276
|
+
}
|
|
125
277
|
return {
|
|
126
278
|
agent_name: declaration.agent_name,
|
|
127
279
|
declaration_source: declaration.declaration_source,
|
|
128
|
-
ready: missingSurfaces.length === 0,
|
|
280
|
+
ready: missingSurfaces.length === 0 && driftingSurfaces.length === 0,
|
|
129
281
|
missing_surfaces: missingSurfaces,
|
|
282
|
+
drifting_surfaces: driftingSurfaces,
|
|
130
283
|
surfaces,
|
|
284
|
+
effective_tier: effectiveTier,
|
|
285
|
+
self_healing_guidance: selfHealingGuidance,
|
|
131
286
|
};
|
|
132
287
|
});
|
|
133
288
|
}
|
|
@@ -4,6 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { spawnSync } from 'node:child_process';
|
|
5
5
|
import yaml from 'yaml';
|
|
6
6
|
import { MEMORY_DIR } from './io.js';
|
|
7
|
+
import { detectHostExecutionProfile, } from './execution-profile.js';
|
|
7
8
|
function tryCommand(command, args, timeout = 5000) {
|
|
8
9
|
try {
|
|
9
10
|
const r = spawnSync(command, args, { encoding: 'utf-8', timeout, windowsHide: true });
|
|
@@ -327,6 +328,7 @@ export function buildAgentInventory(homeDir = os.homedir(), env = process.env) {
|
|
|
327
328
|
schema_version: 1,
|
|
328
329
|
generated_at: new Date().toISOString(),
|
|
329
330
|
agents,
|
|
331
|
+
host_execution_profile: detectHostExecutionProfile({ env, platform: process.platform }),
|
|
330
332
|
};
|
|
331
333
|
}
|
|
332
334
|
/**
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent profile system — load, list, and save reusable agent profiles.
|
|
3
|
+
*
|
|
4
|
+
* Profiles are YAML files in .brainclaw/agents/profiles/{name}.yaml
|
|
5
|
+
* that define a reusable agent invocation: prompt, invoke template,
|
|
6
|
+
* trust level, trigger mode, and optional scope.
|
|
7
|
+
*
|
|
8
|
+
* Built-in default profiles ship with the package in default-profiles/
|
|
9
|
+
* and are merged with user profiles (user overrides defaults).
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import yaml from 'yaml';
|
|
17
|
+
import { AgentProfileSchema } from './schema.js';
|
|
18
|
+
import { memoryDir } from './io.js';
|
|
19
|
+
const PROFILES_DIR = path.join('agents', 'profiles');
|
|
20
|
+
/** Directory containing built-in default profiles shipped with the package. */
|
|
21
|
+
const DEFAULT_PROFILES_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), 'default-profiles');
|
|
22
|
+
/** Resolve the user profiles directory for a given cwd. */
|
|
23
|
+
function profilesDir(cwd) {
|
|
24
|
+
return path.join(memoryDir(cwd), PROFILES_DIR);
|
|
25
|
+
}
|
|
26
|
+
/** Resolve the path to a specific user profile YAML file. */
|
|
27
|
+
function profilePath(name, cwd) {
|
|
28
|
+
return path.join(profilesDir(cwd), `${name}.yaml`);
|
|
29
|
+
}
|
|
30
|
+
/** Read and validate all .yaml profiles from a directory. */
|
|
31
|
+
function readProfilesFromDir(dir) {
|
|
32
|
+
if (!fs.existsSync(dir))
|
|
33
|
+
return [];
|
|
34
|
+
return fs.readdirSync(dir)
|
|
35
|
+
.filter(f => f.endsWith('.yaml'))
|
|
36
|
+
.sort()
|
|
37
|
+
.map(f => {
|
|
38
|
+
const raw = fs.readFileSync(path.join(dir, f), 'utf-8');
|
|
39
|
+
const parsed = yaml.parse(raw);
|
|
40
|
+
return AgentProfileSchema.safeParse(parsed);
|
|
41
|
+
})
|
|
42
|
+
.filter(r => r.success)
|
|
43
|
+
.map(r => r.data);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Load a single agent profile by name.
|
|
47
|
+
* Checks user profiles first, then built-in defaults.
|
|
48
|
+
* Throws if the profile does not exist or fails validation.
|
|
49
|
+
*/
|
|
50
|
+
export function loadProfile(name, cwd = process.cwd()) {
|
|
51
|
+
// User profile takes priority
|
|
52
|
+
const userPath = profilePath(name, cwd);
|
|
53
|
+
if (fs.existsSync(userPath)) {
|
|
54
|
+
const raw = fs.readFileSync(userPath, 'utf-8');
|
|
55
|
+
return AgentProfileSchema.parse(yaml.parse(raw));
|
|
56
|
+
}
|
|
57
|
+
// Fall back to built-in default
|
|
58
|
+
const defaultPath = path.join(DEFAULT_PROFILES_DIR, `${name}.yaml`);
|
|
59
|
+
if (fs.existsSync(defaultPath)) {
|
|
60
|
+
const raw = fs.readFileSync(defaultPath, 'utf-8');
|
|
61
|
+
return AgentProfileSchema.parse(yaml.parse(raw));
|
|
62
|
+
}
|
|
63
|
+
throw new Error(`Profile not found: ${name} (checked ${userPath} and built-in defaults)`);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* List all available agent profiles.
|
|
67
|
+
* Merges built-in defaults with user profiles. User profiles override defaults
|
|
68
|
+
* with the same name.
|
|
69
|
+
*/
|
|
70
|
+
export function listProfiles(cwd = process.cwd()) {
|
|
71
|
+
const defaults = readProfilesFromDir(DEFAULT_PROFILES_DIR);
|
|
72
|
+
const user = readProfilesFromDir(profilesDir(cwd));
|
|
73
|
+
// Index by name — user profiles override defaults
|
|
74
|
+
const merged = new Map();
|
|
75
|
+
for (const p of defaults)
|
|
76
|
+
merged.set(p.name, p);
|
|
77
|
+
for (const p of user)
|
|
78
|
+
merged.set(p.name, p);
|
|
79
|
+
return [...merged.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Save an agent profile to disk.
|
|
83
|
+
* Creates the profiles directory if it doesn't exist.
|
|
84
|
+
*/
|
|
85
|
+
export function saveProfile(profile, cwd = process.cwd()) {
|
|
86
|
+
const validated = AgentProfileSchema.parse(profile);
|
|
87
|
+
const dir = profilesDir(cwd);
|
|
88
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
89
|
+
const filepath = profilePath(validated.name, cwd);
|
|
90
|
+
fs.writeFileSync(filepath, yaml.stringify(validated), 'utf-8');
|
|
91
|
+
return filepath;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=agent-profiles.js.map
|
|
@@ -2,10 +2,11 @@ import crypto from 'node:crypto';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
+
import { isKnownAgent, getCapabilityProfile } from './agent-capability.js';
|
|
5
6
|
import { detectAiAgent } from './ai-agent-detection.js';
|
|
6
7
|
import { loadConfig, saveConfig } from './config.js';
|
|
7
8
|
import { nowISO } from './ids.js';
|
|
8
|
-
import { MEMORY_DIR, resolveEntityDir } from './io.js';
|
|
9
|
+
import { MEMORY_DIR, memoryExists, resolveEntityDir } from './io.js';
|
|
9
10
|
import { JsonStore } from './json-store.js';
|
|
10
11
|
import { AgentIdentityDocumentSchema, } from './schema.js';
|
|
11
12
|
import { logger } from './logger.js';
|
|
@@ -54,7 +55,7 @@ function agentStore(cwd, preferredDirName) {
|
|
|
54
55
|
sort: (a, b) => a.created_at.localeCompare(b.created_at) || a.agent_name.localeCompare(b.agent_name),
|
|
55
56
|
});
|
|
56
57
|
}
|
|
57
|
-
function normalizeAgentName(agentName) {
|
|
58
|
+
export function normalizeAgentName(agentName) {
|
|
58
59
|
return agentName.trim().toLowerCase();
|
|
59
60
|
}
|
|
60
61
|
function normalizeCapability(capability) {
|
|
@@ -168,6 +169,9 @@ export function registerAgentIdentity(input) {
|
|
|
168
169
|
else if (normalizedCapabilities.length > 0) {
|
|
169
170
|
updated = { ...updated, capabilities: mergeCapabilities(existing.capabilities ?? [], normalizedCapabilities) };
|
|
170
171
|
}
|
|
172
|
+
if (input.contextProfile && existing.context_profile !== input.contextProfile) {
|
|
173
|
+
updated = { ...updated, context_profile: input.contextProfile };
|
|
174
|
+
}
|
|
171
175
|
if (input.generateFingerprint) {
|
|
172
176
|
updated = withIdentityKey(updated, input.env, true);
|
|
173
177
|
}
|
|
@@ -180,11 +184,12 @@ export function registerAgentIdentity(input) {
|
|
|
180
184
|
schema_version: 2,
|
|
181
185
|
version: 1,
|
|
182
186
|
agent_id: generateAgentId(),
|
|
183
|
-
agent_name: input.agentName
|
|
187
|
+
agent_name: normalizeAgentName(input.agentName),
|
|
184
188
|
created_at: nowISO(),
|
|
185
189
|
kind: input.kind ?? 'unknown',
|
|
186
190
|
trust_level: input.trustLevel ?? 'contributor',
|
|
187
191
|
capabilities: normalizedCapabilities,
|
|
192
|
+
...(input.contextProfile ? { context_profile: input.contextProfile } : {}),
|
|
188
193
|
};
|
|
189
194
|
if (input.generateFingerprint) {
|
|
190
195
|
created = withIdentityKey(created, input.env, true);
|
|
@@ -192,7 +197,7 @@ export function registerAgentIdentity(input) {
|
|
|
192
197
|
saveAgentIdentity(created, input.cwd, input.preferredDirName);
|
|
193
198
|
return created;
|
|
194
199
|
}
|
|
195
|
-
export function resolveCurrentAgentIdentity(cwd, preferredDirName) {
|
|
200
|
+
export function resolveCurrentAgentIdentity(cwd, preferredDirName, homeDir) {
|
|
196
201
|
// env var takes priority over config — allows AI agent to self-identify
|
|
197
202
|
const envAgentId = (process.env.BRAINCLAW_AGENT_ID ?? '').trim();
|
|
198
203
|
const envAgentName = (process.env.BRAINCLAW_AGENT_NAME ?? process.env.BRAINCLAW_AGENT ?? '').trim();
|
|
@@ -206,25 +211,41 @@ export function resolveCurrentAgentIdentity(cwd, preferredDirName) {
|
|
|
206
211
|
if (byEnvName)
|
|
207
212
|
return byEnvName;
|
|
208
213
|
}
|
|
209
|
-
// Auto-detect from native agent env vars (e.g. CLAUDECODE, CURSOR_TRACE_ID).
|
|
210
|
-
//
|
|
211
|
-
|
|
214
|
+
// Auto-detect from native agent env vars (e.g. CLAUDECODE, CURSOR_TRACE_ID, CODEX_THREAD_ID).
|
|
215
|
+
// If detected agent is not registered, auto-register it as trusted agent.
|
|
216
|
+
// This is the primary identification path for MCP servers and CLI hooks.
|
|
217
|
+
const detected = detectAiAgent(process.env, homeDir);
|
|
212
218
|
if (detected) {
|
|
219
|
+
// If the detected name matches an explicit env var that was already tried
|
|
220
|
+
// and not found, don't auto-register — the caller expects a "not registered" error.
|
|
221
|
+
if (normalizeAgentName(detected.name) === normalizeAgentName(envAgentName)) {
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
213
224
|
const byDetected = findAgentIdentityByName(detected.name, cwd, preferredDirName);
|
|
214
225
|
if (byDetected)
|
|
215
226
|
return byDetected;
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
227
|
+
// Auto-register detected agent so it's immediately usable.
|
|
228
|
+
// This avoids the "not registered" error for agents detected for the first time.
|
|
229
|
+
try {
|
|
230
|
+
const autoRegistered = registerAgentIdentity({
|
|
231
|
+
agentName: normalizeAgentName(detected.name),
|
|
232
|
+
kind: detected.kind,
|
|
233
|
+
trustLevel: detected.trust_level,
|
|
234
|
+
cwd,
|
|
235
|
+
preferredDirName,
|
|
236
|
+
});
|
|
237
|
+
logger.debug(`Auto-registered detected agent: ${detected.name} (${autoRegistered.agent_id})`);
|
|
238
|
+
return autoRegistered;
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
// Non-fatal: registration may fail if store is read-only
|
|
223
242
|
}
|
|
224
243
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
244
|
+
// config.current_agent is NOT used for identity resolution — it's a singleton global
|
|
245
|
+
// that gets overwritten by whichever agent last ran register-agent --set-current.
|
|
246
|
+
// In multi-agent setups this always resolves to the wrong agent.
|
|
247
|
+
// The field remains in config for display (status, doctor) and for resolveExistingCurrentAgent
|
|
248
|
+
// which is used during setup/init only.
|
|
228
249
|
return undefined;
|
|
229
250
|
}
|
|
230
251
|
export function resolveRegisteredAgentIdentity(options = {}) {
|
|
@@ -285,13 +306,24 @@ export function requireRegisteredAgentIdentity(options = {}) {
|
|
|
285
306
|
}
|
|
286
307
|
if (agentName) {
|
|
287
308
|
const resolved = findAgentIdentityByName(agentName, cwd, preferredDirName);
|
|
288
|
-
if (
|
|
289
|
-
|
|
309
|
+
if (resolved)
|
|
310
|
+
return resolved;
|
|
311
|
+
// Auto-register if the agent is a known brainclaw-supported agent or declared in agent_integrations
|
|
312
|
+
const normalizedName = normalizeAgentName(agentName);
|
|
313
|
+
if (isKnownAgent(normalizedName) || isAgentDeclaredInIntegrations(normalizedName, cwd)) {
|
|
314
|
+
const autoRegistered = registerAgentIdentity({
|
|
315
|
+
agentName: normalizedName,
|
|
316
|
+
kind: 'agent',
|
|
317
|
+
trustLevel: 'contributor',
|
|
318
|
+
cwd,
|
|
319
|
+
preferredDirName,
|
|
320
|
+
});
|
|
321
|
+
return autoRegistered;
|
|
290
322
|
}
|
|
291
|
-
|
|
323
|
+
throw new AgentIdentityResolutionError(`Agent '${normalizedName}' is not registered. Run \`brainclaw register-agent ${normalizedName}\`.`, { agent_name: normalizedName });
|
|
292
324
|
}
|
|
293
325
|
const current = options.allowCurrent !== false
|
|
294
|
-
? resolveCurrentAgentIdentity(cwd, preferredDirName)
|
|
326
|
+
? resolveCurrentAgentIdentity(cwd, preferredDirName, options.homeDir)
|
|
295
327
|
: undefined;
|
|
296
328
|
if (current) {
|
|
297
329
|
return current;
|
|
@@ -300,14 +332,111 @@ export function requireRegisteredAgentIdentity(options = {}) {
|
|
|
300
332
|
const envAgent = resolveEnvAgentName(env);
|
|
301
333
|
if (envAgent) {
|
|
302
334
|
const resolved = findAgentIdentityByName(envAgent, cwd, preferredDirName);
|
|
303
|
-
if (
|
|
304
|
-
|
|
335
|
+
if (resolved)
|
|
336
|
+
return resolved;
|
|
337
|
+
// Auto-register env-declared agent if known or declared in agent_integrations
|
|
338
|
+
const normalizedEnv = normalizeAgentName(envAgent);
|
|
339
|
+
if (isKnownAgent(normalizedEnv) || isAgentDeclaredInIntegrations(normalizedEnv, cwd)) {
|
|
340
|
+
return registerAgentIdentity({
|
|
341
|
+
agentName: normalizedEnv,
|
|
342
|
+
kind: 'agent',
|
|
343
|
+
trustLevel: 'contributor',
|
|
344
|
+
cwd,
|
|
345
|
+
preferredDirName,
|
|
346
|
+
});
|
|
305
347
|
}
|
|
306
|
-
|
|
348
|
+
throw new AgentIdentityResolutionError(`Environment agent '${normalizedEnv}' is not registered.`, { agent_name: normalizedEnv });
|
|
307
349
|
}
|
|
308
350
|
}
|
|
309
351
|
throw new AgentIdentityResolutionError('No registered agent identity resolved. Use --agent/--agent-id or configure a current agent with `brainclaw register-agent <name> --set-current`.');
|
|
310
352
|
}
|
|
353
|
+
/**
|
|
354
|
+
* Resolve agent identity for session start, returning both the resolved identity and whether
|
|
355
|
+
* it was auto-registered (did not exist before this call).
|
|
356
|
+
*
|
|
357
|
+
* Unlike `requireRegisteredAgentIdentity`, this never throws for unknown agents — it will
|
|
358
|
+
* auto-register any resolvable name (from args or env) with contributor trust level.
|
|
359
|
+
* This implements the "separate known agent from current agent" principle: starting a session
|
|
360
|
+
* never requires prior registration.
|
|
361
|
+
*
|
|
362
|
+
* Throws only when no agent name can be derived at all.
|
|
363
|
+
*/
|
|
364
|
+
export function resolveOrAutoRegisterAgentIdentity(options = {}) {
|
|
365
|
+
const existingBefore = resolveRegisteredAgentIdentity(options);
|
|
366
|
+
try {
|
|
367
|
+
const identity = requireRegisteredAgentIdentity(options);
|
|
368
|
+
return { identity, auto_registered: !existingBefore };
|
|
369
|
+
}
|
|
370
|
+
catch (err) {
|
|
371
|
+
if (!(err instanceof AgentIdentityResolutionError))
|
|
372
|
+
throw err;
|
|
373
|
+
// Last-resort: derive a name from explicit arg or env and auto-register.
|
|
374
|
+
// This allows session_start to succeed even for agents not yet registered.
|
|
375
|
+
const candidateName = options.agentName?.trim()
|
|
376
|
+
|| (options.allowEnv !== false ? resolveEnvAgentName(options.env ?? process.env) : undefined);
|
|
377
|
+
if (!candidateName)
|
|
378
|
+
throw err;
|
|
379
|
+
const normalizedName = normalizeAgentName(candidateName);
|
|
380
|
+
const registered = registerAgentIdentity({
|
|
381
|
+
agentName: normalizedName,
|
|
382
|
+
kind: 'agent',
|
|
383
|
+
trustLevel: 'contributor',
|
|
384
|
+
cwd: options.cwd,
|
|
385
|
+
preferredDirName: options.preferredDirName,
|
|
386
|
+
});
|
|
387
|
+
return { identity: registered, auto_registered: true };
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Ensure that a target agent is registered in the current project before dispatch.
|
|
392
|
+
*
|
|
393
|
+
* If the agent is already registered, returns the existing identity.
|
|
394
|
+
* If not registered but has a known capability profile with canBeSpawnedCli=true,
|
|
395
|
+
* auto-registers it as a contributor agent with source='dispatch-auto-register'.
|
|
396
|
+
*
|
|
397
|
+
* Returns the identity, or undefined if the agent is unknown/not spawnable.
|
|
398
|
+
*/
|
|
399
|
+
export function ensureAgentRegisteredForDispatch(agentName, cwd) {
|
|
400
|
+
const normalized = normalizeAgentName(agentName);
|
|
401
|
+
// Already registered? Return as-is.
|
|
402
|
+
const existing = findAgentIdentityByName(normalized, cwd);
|
|
403
|
+
if (existing)
|
|
404
|
+
return existing;
|
|
405
|
+
// Check capability profile — only auto-register agents we know about
|
|
406
|
+
const profile = getCapabilityProfile(normalized);
|
|
407
|
+
if (!profile || !profile.runtime.canBeSpawnedCli)
|
|
408
|
+
return undefined;
|
|
409
|
+
// Auto-register with contributor trust
|
|
410
|
+
try {
|
|
411
|
+
const registered = registerAgentIdentity({
|
|
412
|
+
agentName: normalized,
|
|
413
|
+
kind: 'agent',
|
|
414
|
+
trustLevel: 'contributor',
|
|
415
|
+
capabilities: profile.role_capabilities ?? [],
|
|
416
|
+
cwd,
|
|
417
|
+
});
|
|
418
|
+
logger.debug(`Auto-registered agent for dispatch: ${normalized} (${registered.agent_id})`);
|
|
419
|
+
return registered;
|
|
420
|
+
}
|
|
421
|
+
catch {
|
|
422
|
+
// Non-fatal: store may be read-only
|
|
423
|
+
return undefined;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Check whether an agent name is declared in the project's agent_integrations config.
|
|
428
|
+
*/
|
|
429
|
+
function isAgentDeclaredInIntegrations(normalizedName, cwd) {
|
|
430
|
+
if (!memoryExists(cwd))
|
|
431
|
+
return false;
|
|
432
|
+
try {
|
|
433
|
+
const cfg = loadConfig(cwd);
|
|
434
|
+
return (cfg.agent_integrations?.declarations ?? []).some((d) => normalizeAgentName(d.agent_name) === normalizedName);
|
|
435
|
+
}
|
|
436
|
+
catch {
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
311
440
|
export function resolveAgentScope(agentName, cwd, preferredDirName) {
|
|
312
441
|
const explicit = agentName?.trim();
|
|
313
442
|
if (explicit) {
|
|
@@ -331,17 +460,20 @@ export function resolveCurrentModel(cwd) {
|
|
|
331
460
|
/**
|
|
332
461
|
* Returns the name of the current agent, with priority:
|
|
333
462
|
* 1. $BRAINCLAW_AGENT_NAME env var (AI agent self-declaration)
|
|
334
|
-
* 2. $BRAINCLAW_AGENT env var (legacy alias)
|
|
335
|
-
* 3.
|
|
463
|
+
* 2. $BRAINCLAW_AGENT env var (legacy alias / relay model)
|
|
464
|
+
* 3. detectAiAgent() (auto-detection from process env vars)
|
|
336
465
|
* 4. OS user (last-resort fallback)
|
|
466
|
+
*
|
|
467
|
+
* Note: config.current_agent is intentionally NOT used here — it's a singleton
|
|
468
|
+
* global that causes cross-agent confusion in multi-agent setups.
|
|
337
469
|
*/
|
|
338
|
-
export function resolveCurrentAgentName(cwd) {
|
|
470
|
+
export function resolveCurrentAgentName(cwd, homeDir) {
|
|
339
471
|
const fromEnv = (process.env.BRAINCLAW_AGENT_NAME ?? process.env.BRAINCLAW_AGENT)?.trim();
|
|
340
472
|
if (fromEnv)
|
|
341
473
|
return fromEnv;
|
|
342
|
-
const
|
|
343
|
-
if (
|
|
344
|
-
return
|
|
474
|
+
const detected = detectAiAgent(process.env, homeDir);
|
|
475
|
+
if (detected)
|
|
476
|
+
return detected.name;
|
|
345
477
|
return process.env.USER ?? process.env.USERNAME ?? 'unknown';
|
|
346
478
|
}
|
|
347
479
|
export function requireOperationalAgentIdentity(agentName, cwd, preferredDirName) {
|