brainclaw 0.25.3 ā 0.28.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 +14 -1
- package/dist/cli.js +32 -2
- package/dist/commands/capability.js +21 -32
- package/dist/commands/check-events.js +36 -0
- package/dist/commands/discover.js +21 -0
- package/dist/commands/doctor.js +67 -11
- package/dist/commands/explore.js +7 -10
- package/dist/commands/hooks.js +33 -17
- package/dist/commands/mcp.js +380 -70
- package/dist/commands/migrate.js +75 -0
- package/dist/commands/tool.js +29 -39
- package/dist/core/agent-capability.js +55 -110
- package/dist/core/agent-files.js +13 -0
- package/dist/core/agent-integrations.js +17 -0
- package/dist/core/context.js +102 -8
- package/dist/core/coordination.js +25 -0
- package/dist/core/io.js +2 -0
- package/dist/core/migration.js +3 -1
- package/dist/core/project-discovery.js +236 -0
- package/dist/core/registries.js +120 -0
- package/dist/core/schema.js +11 -2
- package/docs/integrations/agents.md +10 -3
- package/package.json +7 -1
package/dist/commands/mcp.js
CHANGED
|
@@ -9,7 +9,7 @@ import { buildContext, renderContextMarkdown, renderContextPromptTemplate } from
|
|
|
9
9
|
import { buildExecutionContext, renderExecutionContextSummary } from '../core/execution-context.js';
|
|
10
10
|
import { checkBrainclawInstallableUpdate, renderBrainclawInstallableUpdateNotice } from '../core/brainclaw-version.js';
|
|
11
11
|
import { loadConfig } from '../core/config.js';
|
|
12
|
-
import { loadState, persistState } from '../core/state.js';
|
|
12
|
+
import { loadState, persistState, saveState } from '../core/state.js';
|
|
13
13
|
import { memoryExists } from '../core/io.js';
|
|
14
14
|
import { generateCandidateIdWithLabel, listArchivedCandidates, listCandidates, saveCandidate } from '../core/candidates.js';
|
|
15
15
|
import { generateClaimId, listClaims, loadClaim, saveClaim } from '../core/claims.js';
|
|
@@ -19,7 +19,7 @@ import { rejectCandidate } from './reject.js';
|
|
|
19
19
|
import { startSession } from './session-start.js';
|
|
20
20
|
import { endSession } from './session-end.js';
|
|
21
21
|
import { agentCanWriteDirect, AgentIdentityResolutionError, AgentTrustError, listAgentIdentities, requireMinimumTrustLevel, requireRegisteredAgentIdentity, resolveAgentScope, resolveCurrentAgentIdentity, resolveCurrentAgentName, resolveCurrentModel, } from '../core/agent-registry.js';
|
|
22
|
-
import { appendAuditEntry } from '../core/audit.js';
|
|
22
|
+
import { appendAuditEntry, readAuditLog } from '../core/audit.js';
|
|
23
23
|
import { nowISO, generateIdWithLabel, generateId } from '../core/ids.js';
|
|
24
24
|
import { inferProjectFromTarget, loadInstructions, resolveInstructions } from '../core/instructions.js';
|
|
25
25
|
import { buildReputationSnapshot, toPublicReputationSummary } from '../core/reputation.js';
|
|
@@ -27,6 +27,9 @@ import { search } from '../core/search.js';
|
|
|
27
27
|
import { buildOperationalIdentity } from '../core/identity.js';
|
|
28
28
|
import { validateMcpInput, validateMcpField } from '../core/input-validation.js';
|
|
29
29
|
import { buildEstimationReport } from './estimation-report.js';
|
|
30
|
+
import { runDoctor } from './doctor.js';
|
|
31
|
+
import { buildProjectDiscovery, saveDiscoveryProfile, loadDiscoveryProfile, renderDiscoverySummary } from '../core/project-discovery.js';
|
|
32
|
+
import { listCapabilities, listTools as listRegistryTools, createCapability, createTool as createRegistryTool } from '../core/registries.js';
|
|
30
33
|
import { detectAiAgent } from '../core/ai-agent-detection.js';
|
|
31
34
|
import { checkGitPresence, scanGitRepos, parseRoots, parseRepoSelection, parseAgentSelection, runGlobalInstall, initReposAndConfigureAgents, readSetupState, ALL_KNOWN_AGENTS, } from './setup.js';
|
|
32
35
|
import { resolveEffectiveCwd, resolveTargetStore, resolveStoreChain } from '../core/store-resolution.js';
|
|
@@ -248,6 +251,62 @@ export const MCP_READ_TOOLS = [
|
|
|
248
251
|
required: ['query'],
|
|
249
252
|
},
|
|
250
253
|
},
|
|
254
|
+
{
|
|
255
|
+
name: 'bclaw_doctor',
|
|
256
|
+
description: 'Run health checks on the brainclaw memory store. Returns structured check results with ok/warn/error status and metrics.',
|
|
257
|
+
inputSchema: {
|
|
258
|
+
type: 'object',
|
|
259
|
+
properties: {
|
|
260
|
+
migrationCheck: { type: 'boolean', description: 'Include detailed schema migration status.' },
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
name: 'bclaw_history',
|
|
266
|
+
description: 'Show full mutation history of a memory item from the audit log.',
|
|
267
|
+
inputSchema: {
|
|
268
|
+
type: 'object',
|
|
269
|
+
properties: {
|
|
270
|
+
id: { type: 'string', description: 'Item ID to retrieve history for.' },
|
|
271
|
+
},
|
|
272
|
+
required: ['id'],
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: 'bclaw_audit',
|
|
277
|
+
description: 'View the append-only audit log of all memory mutations.',
|
|
278
|
+
inputSchema: {
|
|
279
|
+
type: 'object',
|
|
280
|
+
properties: {
|
|
281
|
+
since: { type: 'string', description: 'Show entries since this ISO date.' },
|
|
282
|
+
actor: { type: 'string', description: 'Filter by actor name or agent ID.' },
|
|
283
|
+
action: { type: 'string', description: 'Filter by action type (create, accept, reject, etc.).' },
|
|
284
|
+
limit: { type: 'number', description: 'Show last N entries (default 20).' },
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
name: 'bclaw_get_discovery',
|
|
290
|
+
description: 'Scan workspace for MCP configs, instruction files, skills, hooks, and agent integrations. Returns a structured discovery profile. Saves result to .brainclaw/discovery/ by default.',
|
|
291
|
+
inputSchema: {
|
|
292
|
+
type: 'object',
|
|
293
|
+
properties: {
|
|
294
|
+
refresh: { type: 'boolean', description: 'Force a fresh scan even if a cached profile exists (default: true).' },
|
|
295
|
+
noSave: { type: 'boolean', description: 'Do not persist the discovery profile.' },
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
name: 'bclaw_conflict_check',
|
|
301
|
+
description: 'Check for claim conflicts between the current agent and other agents. Returns overlapping scopes.',
|
|
302
|
+
inputSchema: {
|
|
303
|
+
type: 'object',
|
|
304
|
+
properties: {
|
|
305
|
+
agent: { type: 'string', description: 'Agent name to check conflicts for (default: current agent).' },
|
|
306
|
+
agentId: { type: 'string', description: 'Registered agent id.' },
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
},
|
|
251
310
|
];
|
|
252
311
|
const MCP_WRITE_TOOLS = [
|
|
253
312
|
{
|
|
@@ -299,6 +358,8 @@ const MCP_WRITE_TOOLS = [
|
|
|
299
358
|
outcome: { type: 'string', description: 'Outcome for decisions: approved, rejected, deferred, pending.' },
|
|
300
359
|
severity: { type: 'string', description: 'Severity for traps: low, medium, high.' },
|
|
301
360
|
planId: { type: 'string', description: 'Optional plan item ID this decision or trap relates to.' },
|
|
361
|
+
scope: { type: 'string', description: 'Memory scope: project (default), machine, or user. Machine-scoped items apply to all projects on this machine.' },
|
|
362
|
+
store: { type: 'string', description: 'Target store level: local (default), repo, workspace, user. Use "user" to write to ~/.brainclaw/ (visible across all projects).' },
|
|
302
363
|
},
|
|
303
364
|
required: ['text', 'type'],
|
|
304
365
|
},
|
|
@@ -484,6 +545,52 @@ const MCP_WRITE_TOOLS = [
|
|
|
484
545
|
required: ['id', 'type'],
|
|
485
546
|
},
|
|
486
547
|
},
|
|
548
|
+
{
|
|
549
|
+
name: 'bclaw_add_capability',
|
|
550
|
+
description: 'Register a new project capability. Requires contributor trust level or above.',
|
|
551
|
+
inputSchema: {
|
|
552
|
+
type: 'object',
|
|
553
|
+
properties: {
|
|
554
|
+
name: { type: 'string', description: 'Capability name.' },
|
|
555
|
+
description: { type: 'string', description: 'Capability description.' },
|
|
556
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Additional tags.' },
|
|
557
|
+
agent: { type: 'string', description: 'Agent name.' },
|
|
558
|
+
agentId: { type: 'string', description: 'Registered agent id.' },
|
|
559
|
+
},
|
|
560
|
+
required: ['name', 'description'],
|
|
561
|
+
},
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
name: 'bclaw_add_tool',
|
|
565
|
+
description: 'Register a new project tool. Requires contributor trust level or above.',
|
|
566
|
+
inputSchema: {
|
|
567
|
+
type: 'object',
|
|
568
|
+
properties: {
|
|
569
|
+
name: { type: 'string', description: 'Tool name.' },
|
|
570
|
+
description: { type: 'string', description: 'Tool description.' },
|
|
571
|
+
type: { type: 'string', description: 'Tool type: workflow, validator, generator, utility, explorer (default: utility).' },
|
|
572
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Additional tags.' },
|
|
573
|
+
agent: { type: 'string', description: 'Agent name.' },
|
|
574
|
+
agentId: { type: 'string', description: 'Registered agent id.' },
|
|
575
|
+
},
|
|
576
|
+
required: ['name', 'description'],
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
name: 'bclaw_update_handoff',
|
|
581
|
+
description: 'Update the status or recipient of an open handoff. Requires contributor trust level or above.',
|
|
582
|
+
inputSchema: {
|
|
583
|
+
type: 'object',
|
|
584
|
+
properties: {
|
|
585
|
+
id: { type: 'string', description: 'Handoff ID to update.' },
|
|
586
|
+
status: { type: 'string', description: 'New status: open, closed.' },
|
|
587
|
+
to: { type: 'string', description: 'New recipient agent name.' },
|
|
588
|
+
agent: { type: 'string', description: 'Agent name.' },
|
|
589
|
+
agentId: { type: 'string', description: 'Registered agent id.' },
|
|
590
|
+
},
|
|
591
|
+
required: ['id'],
|
|
592
|
+
},
|
|
593
|
+
},
|
|
487
594
|
];
|
|
488
595
|
const ALL_TOOLS = [...MCP_READ_TOOLS, ...MCP_WRITE_TOOLS];
|
|
489
596
|
class McpProtocolError extends Error {
|
|
@@ -1040,10 +1147,9 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1040
1147
|
refreshBootstrap: args.refreshBootstrap,
|
|
1041
1148
|
cwd,
|
|
1042
1149
|
});
|
|
1043
|
-
// Load available capabilities and tools
|
|
1044
|
-
const
|
|
1045
|
-
const
|
|
1046
|
-
const tools = state.recent_decisions.filter((d) => d.tags.includes('tool'));
|
|
1150
|
+
// Load available capabilities and tools from dedicated registries
|
|
1151
|
+
const capabilities = listCapabilities(cwd);
|
|
1152
|
+
const tools = listRegistryTools(cwd);
|
|
1047
1153
|
const format = normaliseFormat(args.format);
|
|
1048
1154
|
const content = renderContextForMcp(result, format, {
|
|
1049
1155
|
explain: args.explain,
|
|
@@ -1056,8 +1162,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1056
1162
|
if (capabilities.length > 0) {
|
|
1057
1163
|
suggestions.push(`\n## Available Capabilities (${capabilities.length})`);
|
|
1058
1164
|
capabilities.slice(0, 5).forEach((cap) => {
|
|
1059
|
-
|
|
1060
|
-
suggestions.push(`- [${cap.id}] ${cap.text.split('\n')[0]} (${category})`);
|
|
1165
|
+
suggestions.push(`- [${cap.id}] ${cap.name} (${cap.category})`);
|
|
1061
1166
|
});
|
|
1062
1167
|
if (capabilities.length > 5) {
|
|
1063
1168
|
suggestions.push(`- ... and ${capabilities.length - 5} more`);
|
|
@@ -1066,8 +1171,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1066
1171
|
if (tools.length > 0) {
|
|
1067
1172
|
suggestions.push(`\n## Available Tools (${tools.length})`);
|
|
1068
1173
|
tools.slice(0, 5).forEach((tool) => {
|
|
1069
|
-
|
|
1070
|
-
suggestions.push(`- [${tool.id}] ${tool.text.split('\n')[0]} (${type})`);
|
|
1174
|
+
suggestions.push(`- [${tool.id}] ${tool.name} (${tool.type})`);
|
|
1071
1175
|
});
|
|
1072
1176
|
if (tools.length > 5) {
|
|
1073
1177
|
suggestions.push(`- ... and ${tools.length - 5} more`);
|
|
@@ -1086,13 +1190,13 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1086
1190
|
...result,
|
|
1087
1191
|
available_capabilities: capabilities.map((cap) => ({
|
|
1088
1192
|
id: cap.id,
|
|
1089
|
-
name: cap.
|
|
1090
|
-
category: cap.
|
|
1193
|
+
name: cap.name,
|
|
1194
|
+
category: cap.category,
|
|
1091
1195
|
})),
|
|
1092
1196
|
available_tools: tools.map((tool) => ({
|
|
1093
1197
|
id: tool.id,
|
|
1094
|
-
name: tool.
|
|
1095
|
-
type: tool.
|
|
1198
|
+
name: tool.name,
|
|
1199
|
+
type: tool.type,
|
|
1096
1200
|
})),
|
|
1097
1201
|
...(notifications ? { pending_notifications: notifications, unseen_event_count: unseenEvents.length } : {}),
|
|
1098
1202
|
},
|
|
@@ -1269,6 +1373,12 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1269
1373
|
for (const instruction of board.resolved_instructions.slice(0, 10)) {
|
|
1270
1374
|
lines.push(`- [${instruction.id}] <${instruction.layer}${instruction.scope ? `:${instruction.scope}` : ''}> ${instruction.text}`);
|
|
1271
1375
|
}
|
|
1376
|
+
if (board.other_agents && board.other_agents.length > 0) {
|
|
1377
|
+
lines.push(`Other agents: ${board.other_agents.length}`);
|
|
1378
|
+
for (const other of board.other_agents) {
|
|
1379
|
+
lines.push(`- ${other.name}: ${other.claim_count} claim(s) on ${other.scopes.join(', ')}`);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1272
1382
|
return {
|
|
1273
1383
|
content: [{ type: 'text', text: lines.join('\n') }],
|
|
1274
1384
|
structuredContent: { ...board },
|
|
@@ -1523,32 +1633,25 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1523
1633
|
};
|
|
1524
1634
|
}
|
|
1525
1635
|
if (name === 'bclaw_get_capabilities') {
|
|
1526
|
-
const
|
|
1527
|
-
const
|
|
1528
|
-
const filtered = capabilities.filter((cap) => {
|
|
1636
|
+
const allCapabilities = listCapabilities(cwd);
|
|
1637
|
+
const filtered = allCapabilities.filter((cap) => {
|
|
1529
1638
|
const categoryFilter = args.category;
|
|
1530
1639
|
const tagsFilter = args.tags;
|
|
1531
|
-
if (categoryFilter)
|
|
1532
|
-
|
|
1533
|
-
if (capCategory !== categoryFilter)
|
|
1534
|
-
return false;
|
|
1535
|
-
}
|
|
1640
|
+
if (categoryFilter && cap.category !== categoryFilter)
|
|
1641
|
+
return false;
|
|
1536
1642
|
if (tagsFilter && tagsFilter.length > 0) {
|
|
1537
|
-
|
|
1538
|
-
if (!hasAllTags)
|
|
1643
|
+
if (!tagsFilter.every((tag) => cap.tags.includes(tag)))
|
|
1539
1644
|
return false;
|
|
1540
1645
|
}
|
|
1541
1646
|
return true;
|
|
1542
1647
|
});
|
|
1543
1648
|
const lines = [`Capabilities (${filtered.length}):`];
|
|
1544
1649
|
filtered.forEach((cap) => {
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
lines.push(`\n[${cap.id}] ${cap.text.split('\n')[0]}`);
|
|
1548
|
-
lines.push(` Category: ${category}`);
|
|
1650
|
+
lines.push(`\n[${cap.id}] ${cap.name}`);
|
|
1651
|
+
lines.push(` Category: ${cap.category}`);
|
|
1549
1652
|
lines.push(` Author: ${cap.author}`);
|
|
1550
|
-
if (
|
|
1551
|
-
lines.push(` Tags: ${
|
|
1653
|
+
if (cap.tags.length > 0) {
|
|
1654
|
+
lines.push(` Tags: ${cap.tags.join(', ')}`);
|
|
1552
1655
|
}
|
|
1553
1656
|
});
|
|
1554
1657
|
return {
|
|
@@ -1557,32 +1660,25 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1557
1660
|
};
|
|
1558
1661
|
}
|
|
1559
1662
|
if (name === 'bclaw_list_tools') {
|
|
1560
|
-
const
|
|
1561
|
-
const
|
|
1562
|
-
const filtered = tools.filter((tool) => {
|
|
1663
|
+
const allTools = listRegistryTools(cwd);
|
|
1664
|
+
const filtered = allTools.filter((tool) => {
|
|
1563
1665
|
const typeFilter = args.type;
|
|
1564
1666
|
const tagsFilter = args.tags;
|
|
1565
|
-
if (typeFilter)
|
|
1566
|
-
|
|
1567
|
-
if (toolType !== typeFilter)
|
|
1568
|
-
return false;
|
|
1569
|
-
}
|
|
1667
|
+
if (typeFilter && tool.type !== typeFilter)
|
|
1668
|
+
return false;
|
|
1570
1669
|
if (tagsFilter && tagsFilter.length > 0) {
|
|
1571
|
-
|
|
1572
|
-
if (!hasAllTags)
|
|
1670
|
+
if (!tagsFilter.every((tag) => tool.tags.includes(tag)))
|
|
1573
1671
|
return false;
|
|
1574
1672
|
}
|
|
1575
1673
|
return true;
|
|
1576
1674
|
});
|
|
1577
1675
|
const lines = [`Tools (${filtered.length}):`];
|
|
1578
1676
|
filtered.forEach((tool) => {
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
lines.push(`\n[${tool.id}] ${tool.text.split('\n')[0]}`);
|
|
1582
|
-
lines.push(` Type: ${type}`);
|
|
1677
|
+
lines.push(`\n[${tool.id}] ${tool.name}`);
|
|
1678
|
+
lines.push(` Type: ${tool.type}`);
|
|
1583
1679
|
lines.push(` Author: ${tool.author}`);
|
|
1584
|
-
if (
|
|
1585
|
-
lines.push(` Tags: ${
|
|
1680
|
+
if (tool.tags.length > 0) {
|
|
1681
|
+
lines.push(` Tags: ${tool.tags.join(', ')}`);
|
|
1586
1682
|
}
|
|
1587
1683
|
});
|
|
1588
1684
|
return {
|
|
@@ -1595,39 +1691,152 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1595
1691
|
if (!query) {
|
|
1596
1692
|
throw new Error('Missing required argument: query');
|
|
1597
1693
|
}
|
|
1598
|
-
const
|
|
1599
|
-
const
|
|
1600
|
-
const filtered =
|
|
1694
|
+
const allTools = listRegistryTools(cwd);
|
|
1695
|
+
const queryLower = query.toLowerCase();
|
|
1696
|
+
const filtered = allTools.filter((tool) => {
|
|
1601
1697
|
const typeFilter = args.type;
|
|
1602
1698
|
const tagsFilter = args.tags;
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
const toolType = tool.tags.find((t) => t !== 'tool');
|
|
1606
|
-
if (toolType !== typeFilter)
|
|
1607
|
-
return false;
|
|
1608
|
-
}
|
|
1609
|
-
// Tags filter (all must match)
|
|
1699
|
+
if (typeFilter && tool.type !== typeFilter)
|
|
1700
|
+
return false;
|
|
1610
1701
|
if (tagsFilter && tagsFilter.length > 0) {
|
|
1611
|
-
|
|
1612
|
-
if (!hasAllTags)
|
|
1702
|
+
if (!tagsFilter.every((tag) => tool.tags.includes(tag)))
|
|
1613
1703
|
return false;
|
|
1614
1704
|
}
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
return (tool.text.toLowerCase().includes(queryLower) ||
|
|
1705
|
+
return (tool.name.toLowerCase().includes(queryLower) ||
|
|
1706
|
+
tool.description.toLowerCase().includes(queryLower) ||
|
|
1618
1707
|
tool.tags.some((tag) => tag.toLowerCase().includes(queryLower)));
|
|
1619
1708
|
});
|
|
1620
1709
|
const lines = [`Search results for '${query}' (${filtered.length} tool(s)):`];
|
|
1621
1710
|
filtered.forEach((tool) => {
|
|
1622
|
-
|
|
1623
|
-
lines.push(
|
|
1624
|
-
lines.push(` Type: ${type}`);
|
|
1711
|
+
lines.push(`\n[${tool.id}] ${tool.name}`);
|
|
1712
|
+
lines.push(` Type: ${tool.type}`);
|
|
1625
1713
|
});
|
|
1626
1714
|
return {
|
|
1627
1715
|
content: [{ type: 'text', text: lines.join('\n') || 'No tools found.' }],
|
|
1628
1716
|
structuredContent: { query, total: filtered.length, tools: filtered },
|
|
1629
1717
|
};
|
|
1630
1718
|
}
|
|
1719
|
+
if (name === 'bclaw_get_discovery') {
|
|
1720
|
+
const refresh = args.refresh !== false; // default: true
|
|
1721
|
+
const noSave = args.noSave;
|
|
1722
|
+
let profile;
|
|
1723
|
+
if (!refresh) {
|
|
1724
|
+
profile = loadDiscoveryProfile(cwd);
|
|
1725
|
+
}
|
|
1726
|
+
if (!profile) {
|
|
1727
|
+
profile = buildProjectDiscovery({ cwd });
|
|
1728
|
+
if (!noSave) {
|
|
1729
|
+
saveDiscoveryProfile(profile, cwd);
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
return {
|
|
1733
|
+
content: [{ type: 'text', text: renderDiscoverySummary(profile) }],
|
|
1734
|
+
structuredContent: { ...profile, schema_version: SCHEMA_VERSION },
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
if (name === 'bclaw_conflict_check') {
|
|
1738
|
+
const agentNameArg = args.agent;
|
|
1739
|
+
const agentIdArg = args.agentId;
|
|
1740
|
+
const currentAgentName = agentNameArg ?? resolveCurrentAgentName(cwd);
|
|
1741
|
+
const allClaimsForCheck = listClaims(cwd).filter((c) => c.status === 'active');
|
|
1742
|
+
const myClaimsForCheck = allClaimsForCheck.filter((c) => agentIdArg ? c.agent_id === agentIdArg : c.agent === currentAgentName);
|
|
1743
|
+
const otherClaimsForCheck = allClaimsForCheck.filter((c) => agentIdArg ? c.agent_id !== agentIdArg : c.agent !== currentAgentName);
|
|
1744
|
+
const conflicts = [];
|
|
1745
|
+
for (const mine of myClaimsForCheck) {
|
|
1746
|
+
const myScopes = mine.scope.replace(/\\/g, '/').split(/\s+/);
|
|
1747
|
+
for (const other of otherClaimsForCheck) {
|
|
1748
|
+
const otherScopes = other.scope.replace(/\\/g, '/').split(/\s+/);
|
|
1749
|
+
for (const ms of myScopes) {
|
|
1750
|
+
for (const os of otherScopes) {
|
|
1751
|
+
if (ms === os || ms.startsWith(os + '/') || os.startsWith(ms + '/')) {
|
|
1752
|
+
conflicts.push({
|
|
1753
|
+
my_claim: mine.id, my_scope: mine.scope,
|
|
1754
|
+
other_claim: other.id, other_agent: other.agent, other_scope: other.scope,
|
|
1755
|
+
reason: ms === os ? `exact: ${ms}` : `overlap: ${ms} ā ${os}`,
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
const text = conflicts.length === 0
|
|
1763
|
+
? `No claim conflicts for ${currentAgentName}.`
|
|
1764
|
+
: `${conflicts.length} conflict(s) found:\n${conflicts.map((c) => ` ${c.my_scope} ā ${c.other_agent}:${c.other_scope} (${c.reason})`).join('\n')}`;
|
|
1765
|
+
return {
|
|
1766
|
+
content: [{ type: 'text', text }],
|
|
1767
|
+
structuredContent: { agent: currentAgentName, conflicts, total: conflicts.length, schema_version: SCHEMA_VERSION },
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
if (name === 'bclaw_doctor') {
|
|
1771
|
+
// Capture doctor JSON output by redirecting console.log
|
|
1772
|
+
const captured = [];
|
|
1773
|
+
const origLog = console.log;
|
|
1774
|
+
const origWarn = console.warn;
|
|
1775
|
+
const origError = console.error;
|
|
1776
|
+
console.log = (...a) => captured.push(a.join(' '));
|
|
1777
|
+
console.warn = (...a) => captured.push(a.join(' '));
|
|
1778
|
+
console.error = (...a) => captured.push(a.join(' '));
|
|
1779
|
+
try {
|
|
1780
|
+
runDoctor({ json: true, cwd, migrationCheck: args.migrationCheck });
|
|
1781
|
+
}
|
|
1782
|
+
finally {
|
|
1783
|
+
console.log = origLog;
|
|
1784
|
+
console.warn = origWarn;
|
|
1785
|
+
console.error = origError;
|
|
1786
|
+
}
|
|
1787
|
+
const jsonStr = captured.join('\n');
|
|
1788
|
+
let structured = {};
|
|
1789
|
+
try {
|
|
1790
|
+
structured = JSON.parse(jsonStr);
|
|
1791
|
+
}
|
|
1792
|
+
catch { /* non-JSON fallback */ }
|
|
1793
|
+
const ok = structured.ok;
|
|
1794
|
+
const checks = structured.checks ?? [];
|
|
1795
|
+
const errors = checks.filter(c => c.status === 'error');
|
|
1796
|
+
const warns = checks.filter(c => c.status === 'warn');
|
|
1797
|
+
const summary = ok
|
|
1798
|
+
? `ā All ${checks.length} checks passed.`
|
|
1799
|
+
: `${errors.length} error(s), ${warns.length} warning(s) out of ${checks.length} checks.`;
|
|
1800
|
+
return {
|
|
1801
|
+
content: [{ type: 'text', text: summary }],
|
|
1802
|
+
structuredContent: { ...structured, schema_version: SCHEMA_VERSION },
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
if (name === 'bclaw_history') {
|
|
1806
|
+
const id = String(args.id ?? '').trim();
|
|
1807
|
+
if (!id)
|
|
1808
|
+
throw new Error('Missing required argument: id');
|
|
1809
|
+
const entries = readAuditLog({ itemId: id }, cwd);
|
|
1810
|
+
const lines = [`History for ${id} ā ${entries.length} event(s):`];
|
|
1811
|
+
for (const e of entries) {
|
|
1812
|
+
const reason = e.reason ? ` | ${e.reason}` : '';
|
|
1813
|
+
lines.push(` ${e.timestamp} [${e.actor}] ${e.action}${reason}`);
|
|
1814
|
+
}
|
|
1815
|
+
return {
|
|
1816
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
1817
|
+
structuredContent: { id, total: entries.length, entries, schema_version: SCHEMA_VERSION },
|
|
1818
|
+
};
|
|
1819
|
+
}
|
|
1820
|
+
if (name === 'bclaw_audit') {
|
|
1821
|
+
const limit = args.limit ?? 20;
|
|
1822
|
+
const entries = readAuditLog({
|
|
1823
|
+
since: args.since,
|
|
1824
|
+
actor: args.actor,
|
|
1825
|
+
action: args.action,
|
|
1826
|
+
}, cwd);
|
|
1827
|
+
const sliced = entries.slice(-limit);
|
|
1828
|
+
const lines = [`Audit log ā showing ${sliced.length} of ${entries.length} entries:`];
|
|
1829
|
+
for (const e of sliced) {
|
|
1830
|
+
const itemInfo = e.item_id ? ` ā ${e.item_id}` : '';
|
|
1831
|
+
const typeInfo = e.item_type ? ` (${e.item_type})` : '';
|
|
1832
|
+
const reason = e.reason ? ` | ${e.reason}` : '';
|
|
1833
|
+
lines.push(` ${e.timestamp} [${e.actor}] ${e.action}${itemInfo}${typeInfo}${reason}`);
|
|
1834
|
+
}
|
|
1835
|
+
return {
|
|
1836
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
1837
|
+
structuredContent: { total: entries.length, returned: sliced.length, entries: sliced, schema_version: SCHEMA_VERSION },
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1631
1840
|
throw new Error(`Unknown read tool: ${name}`);
|
|
1632
1841
|
}
|
|
1633
1842
|
export async function executeMcpToolCall(payload) {
|
|
@@ -1859,6 +2068,9 @@ export async function executeMcpToolCall(payload) {
|
|
|
1859
2068
|
const type = String(args.type ?? 'decision');
|
|
1860
2069
|
const writeThrough = agentCanWriteDirect(identity.agent_id ?? resolvedIdentity.agent_id, cwd);
|
|
1861
2070
|
const candidatePlanId = args.planId;
|
|
2071
|
+
const candidateScope = args.scope;
|
|
2072
|
+
const targetStore = args.store;
|
|
2073
|
+
const effectiveCwd = targetStore ? resolveTargetStore(cwd, targetStore) : cwd;
|
|
1862
2074
|
const candidate = {
|
|
1863
2075
|
id: candId.id,
|
|
1864
2076
|
short_label: candId.short_label,
|
|
@@ -1875,6 +2087,7 @@ export async function executeMcpToolCall(payload) {
|
|
|
1875
2087
|
severity: type === 'trap' ? (args.severity ?? 'medium') : undefined,
|
|
1876
2088
|
category: type === 'constraint' ? args.category : undefined,
|
|
1877
2089
|
outcome: type === 'decision' ? args.outcome : undefined,
|
|
2090
|
+
scope: candidateScope,
|
|
1878
2091
|
plan_id: candidatePlanId,
|
|
1879
2092
|
model: currentModel,
|
|
1880
2093
|
star_count: 0,
|
|
@@ -1885,22 +2098,25 @@ export async function executeMcpToolCall(payload) {
|
|
|
1885
2098
|
const planPrompt = (type === 'decision' || type === 'trap') && !candidatePlanId
|
|
1886
2099
|
? `\nš” Does this ${type} relate to an active plan item? If so, re-run with planId: 'pln_xxx' to link it.`
|
|
1887
2100
|
: '';
|
|
2101
|
+
const storeLabel = targetStore && targetStore !== 'local' ? ` [store: ${targetStore}]` : '';
|
|
1888
2102
|
if (writeThrough) {
|
|
1889
|
-
saveCandidate(candidate,
|
|
1890
|
-
const accepted = acceptCandidate(candId.id, resolvedIdentity.agent_name,
|
|
1891
|
-
appendAuditEntry({ actor: resolvedIdentity.agent_name, actor_id: resolvedIdentity.agent_id, action: 'promote_direct', item_id: candId.id, item_type: type },
|
|
2103
|
+
saveCandidate(candidate, effectiveCwd);
|
|
2104
|
+
const accepted = acceptCandidate(candId.id, resolvedIdentity.agent_name, effectiveCwd, resolvedIdentity.agent_id);
|
|
2105
|
+
appendAuditEntry({ actor: resolvedIdentity.agent_name, actor_id: resolvedIdentity.agent_id, action: 'promote_direct', item_id: candId.id, item_type: type }, effectiveCwd);
|
|
1892
2106
|
return {
|
|
1893
2107
|
response: toolResponse({
|
|
1894
|
-
content: [{ type: 'text', text: `ā Direct write [${candId.short_label}] (trusted agent)${planPrompt}` }],
|
|
2108
|
+
content: [{ type: 'text', text: `ā Direct write [${candId.short_label}] (trusted agent)${storeLabel}${planPrompt}` }],
|
|
1895
2109
|
candidate_id: candId.id,
|
|
1896
2110
|
promoted_item_id: accepted.promoted_item_id,
|
|
1897
2111
|
write_through: true,
|
|
2112
|
+
store: targetStore ?? 'local',
|
|
2113
|
+
scope: candidateScope,
|
|
1898
2114
|
}),
|
|
1899
2115
|
nextConnectionSessionId: explicitSessionIdFromEnv() ? undefined : identity.session_id,
|
|
1900
2116
|
};
|
|
1901
2117
|
}
|
|
1902
|
-
saveCandidate(candidate,
|
|
1903
|
-
appendAuditEntry({ actor: resolvedIdentity.agent_name, actor_id: resolvedIdentity.agent_id, action: 'create', item_id: candId.id, item_type: type },
|
|
2118
|
+
saveCandidate(candidate, effectiveCwd);
|
|
2119
|
+
appendAuditEntry({ actor: resolvedIdentity.agent_name, actor_id: resolvedIdentity.agent_id, action: 'create', item_id: candId.id, item_type: type }, effectiveCwd);
|
|
1904
2120
|
return {
|
|
1905
2121
|
response: toolResponse({
|
|
1906
2122
|
content: [{ type: 'text', text: `ā Candidate created [${candId.short_label}] (pending review)${planPrompt}` }],
|
|
@@ -2468,6 +2684,100 @@ export async function executeMcpToolCall(payload) {
|
|
|
2468
2684
|
}),
|
|
2469
2685
|
};
|
|
2470
2686
|
}
|
|
2687
|
+
if (name === 'bclaw_add_capability') {
|
|
2688
|
+
const capName = String(args.name ?? '').trim();
|
|
2689
|
+
const capDesc = String(args.description ?? '').trim();
|
|
2690
|
+
if (!capName || !capDesc) {
|
|
2691
|
+
return { response: createToolErrorResponse('validation_error', 'Missing required arguments: name and description') };
|
|
2692
|
+
}
|
|
2693
|
+
const resolved = ensureTrust(args, { nameField: 'agent', idField: 'agentId' }, 'contributor', cwd);
|
|
2694
|
+
if (resolved.error) {
|
|
2695
|
+
return { response: createToolErrorResponse(resolved.error.kind, resolved.error.message, resolved.error.details) };
|
|
2696
|
+
}
|
|
2697
|
+
const resolvedIdentity = resolved.identity;
|
|
2698
|
+
const extraTags = Array.isArray(args.tags) ? args.tags : [];
|
|
2699
|
+
const cap = createCapability({
|
|
2700
|
+
name: capName,
|
|
2701
|
+
description: capDesc,
|
|
2702
|
+
tags: extraTags,
|
|
2703
|
+
author: resolvedIdentity.agent_name,
|
|
2704
|
+
authorId: resolvedIdentity.agent_id,
|
|
2705
|
+
model: currentModel,
|
|
2706
|
+
}, cwd);
|
|
2707
|
+
appendAuditEntry({ actor: resolvedIdentity.agent_name, actor_id: resolvedIdentity.agent_id, action: 'create', item_id: cap.id, item_type: 'capability', reason: `capability: ${capName}` }, cwd);
|
|
2708
|
+
return {
|
|
2709
|
+
response: toolResponse({
|
|
2710
|
+
content: [{ type: 'text', text: `ā Capability registered: [${cap.id}] ${capName}` }],
|
|
2711
|
+
id: cap.id,
|
|
2712
|
+
name: capName,
|
|
2713
|
+
schema_version: SCHEMA_VERSION,
|
|
2714
|
+
}),
|
|
2715
|
+
};
|
|
2716
|
+
}
|
|
2717
|
+
if (name === 'bclaw_add_tool') {
|
|
2718
|
+
const toolName = String(args.name ?? '').trim();
|
|
2719
|
+
const toolDesc = String(args.description ?? '').trim();
|
|
2720
|
+
if (!toolName || !toolDesc) {
|
|
2721
|
+
return { response: createToolErrorResponse('validation_error', 'Missing required arguments: name and description') };
|
|
2722
|
+
}
|
|
2723
|
+
const resolved = ensureTrust(args, { nameField: 'agent', idField: 'agentId' }, 'contributor', cwd);
|
|
2724
|
+
if (resolved.error) {
|
|
2725
|
+
return { response: createToolErrorResponse(resolved.error.kind, resolved.error.message, resolved.error.details) };
|
|
2726
|
+
}
|
|
2727
|
+
const resolvedIdentity = resolved.identity;
|
|
2728
|
+
const toolType = String(args.type ?? 'utility');
|
|
2729
|
+
const extraTags = Array.isArray(args.tags) ? args.tags : [];
|
|
2730
|
+
const tool = createRegistryTool({
|
|
2731
|
+
name: toolName,
|
|
2732
|
+
description: toolDesc,
|
|
2733
|
+
type: toolType,
|
|
2734
|
+
tags: extraTags,
|
|
2735
|
+
author: resolvedIdentity.agent_name,
|
|
2736
|
+
authorId: resolvedIdentity.agent_id,
|
|
2737
|
+
model: currentModel,
|
|
2738
|
+
}, cwd);
|
|
2739
|
+
appendAuditEntry({ actor: resolvedIdentity.agent_name, actor_id: resolvedIdentity.agent_id, action: 'create', item_id: tool.id, item_type: 'tool', reason: `tool: ${toolName}` }, cwd);
|
|
2740
|
+
return {
|
|
2741
|
+
response: toolResponse({
|
|
2742
|
+
content: [{ type: 'text', text: `ā Tool registered: [${tool.id}] ${toolName} (${toolType})` }],
|
|
2743
|
+
id: tool.id,
|
|
2744
|
+
name: toolName,
|
|
2745
|
+
type: toolType,
|
|
2746
|
+
schema_version: SCHEMA_VERSION,
|
|
2747
|
+
}),
|
|
2748
|
+
};
|
|
2749
|
+
}
|
|
2750
|
+
if (name === 'bclaw_update_handoff') {
|
|
2751
|
+
const handoffId = String(args.id ?? '').trim();
|
|
2752
|
+
if (!handoffId) {
|
|
2753
|
+
return { response: createToolErrorResponse('validation_error', 'Missing required argument: id') };
|
|
2754
|
+
}
|
|
2755
|
+
const resolved = ensureTrust(args, { nameField: 'agent', idField: 'agentId' }, 'contributor', cwd);
|
|
2756
|
+
if (resolved.error) {
|
|
2757
|
+
return { response: createToolErrorResponse(resolved.error.kind, resolved.error.message, resolved.error.details) };
|
|
2758
|
+
}
|
|
2759
|
+
const resolvedIdentity = resolved.identity;
|
|
2760
|
+
const state = loadState(cwd);
|
|
2761
|
+
const handoff = state.open_handoffs.find((h) => h.id === handoffId);
|
|
2762
|
+
if (!handoff) {
|
|
2763
|
+
return { response: createToolErrorResponse('not_found', `Handoff not found: ${handoffId}`) };
|
|
2764
|
+
}
|
|
2765
|
+
if (args.status)
|
|
2766
|
+
handoff.status = args.status;
|
|
2767
|
+
if (args.to)
|
|
2768
|
+
handoff.to = String(args.to);
|
|
2769
|
+
saveState(state, cwd);
|
|
2770
|
+
appendAuditEntry({ actor: resolvedIdentity.agent_name, actor_id: resolvedIdentity.agent_id, action: 'update', item_id: handoffId, item_type: 'handoff' }, cwd);
|
|
2771
|
+
return {
|
|
2772
|
+
response: toolResponse({
|
|
2773
|
+
content: [{ type: 'text', text: `ā Handoff updated: [${handoffId}] ${handoff.from} ā ${handoff.to} (${handoff.status})` }],
|
|
2774
|
+
handoff_id: handoffId,
|
|
2775
|
+
status: handoff.status,
|
|
2776
|
+
to: handoff.to,
|
|
2777
|
+
schema_version: SCHEMA_VERSION,
|
|
2778
|
+
}),
|
|
2779
|
+
};
|
|
2780
|
+
}
|
|
2471
2781
|
return {
|
|
2472
2782
|
response: createToolErrorResponse('unknown_tool', `Unknown tool: ${name}`),
|
|
2473
2783
|
};
|