brainclaw 0.24.0 ā 0.27.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/dist/cli.js +33 -2
- package/dist/commands/capability.js +21 -32
- package/dist/commands/check-events.js +36 -0
- package/dist/commands/claim.js +2 -1
- package/dist/commands/context-diff.js +62 -4
- package/dist/commands/discover.js +21 -0
- package/dist/commands/doctor.js +67 -11
- package/dist/commands/explore.js +7 -10
- package/dist/commands/export.js +40 -2
- package/dist/commands/hooks.js +33 -17
- package/dist/commands/init.js +41 -0
- package/dist/commands/mcp.js +417 -75
- package/dist/commands/migrate.js +75 -0
- package/dist/commands/runtime-note.js +1 -0
- package/dist/commands/tool.js +29 -39
- package/dist/core/agent-files.js +13 -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 +14 -1
- package/docs/cli.md +7 -5
- package/docs/concepts/plans-and-claims.md +10 -0
- package/docs/integrations/agents.md +2 -1
- package/docs/integrations/mcp.md +1 -1
- 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';
|
|
@@ -18,8 +18,8 @@ import { acceptCandidate } from './accept.js';
|
|
|
18
18
|
import { rejectCandidate } from './reject.js';
|
|
19
19
|
import { startSession } from './session-start.js';
|
|
20
20
|
import { endSession } from './session-end.js';
|
|
21
|
-
import { agentCanWriteDirect, AgentIdentityResolutionError, AgentTrustError, listAgentIdentities, requireMinimumTrustLevel, requireRegisteredAgentIdentity, resolveAgentScope, resolveCurrentAgentIdentity, resolveCurrentAgentName, } from '../core/agent-registry.js';
|
|
22
|
-
import { appendAuditEntry } from '../core/audit.js';
|
|
21
|
+
import { agentCanWriteDirect, AgentIdentityResolutionError, AgentTrustError, listAgentIdentities, requireMinimumTrustLevel, requireRegisteredAgentIdentity, resolveAgentScope, resolveCurrentAgentIdentity, resolveCurrentAgentName, resolveCurrentModel, } from '../core/agent-registry.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';
|
|
@@ -156,6 +159,10 @@ export const MCP_READ_TOOLS = [
|
|
|
156
159
|
type: { type: 'string', description: 'Filter by plan type.' },
|
|
157
160
|
assignee: { type: 'string', description: 'Filter by assignee name.' },
|
|
158
161
|
project: { type: 'string', description: 'Filter by project namespace.' },
|
|
162
|
+
id: { type: 'string', description: 'Get a single plan by ID (exact match).' },
|
|
163
|
+
limit: { type: 'number', description: 'Maximum number of plans to return (default: 20).' },
|
|
164
|
+
offset: { type: 'number', description: 'Number of plans to skip (for pagination).' },
|
|
165
|
+
compact: { type: 'boolean', description: 'Return only key fields (id, short_label, text, status, priority) to reduce output size.' },
|
|
159
166
|
},
|
|
160
167
|
},
|
|
161
168
|
},
|
|
@@ -244,6 +251,62 @@ export const MCP_READ_TOOLS = [
|
|
|
244
251
|
required: ['query'],
|
|
245
252
|
},
|
|
246
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
|
+
},
|
|
247
310
|
];
|
|
248
311
|
const MCP_WRITE_TOOLS = [
|
|
249
312
|
{
|
|
@@ -295,6 +358,8 @@ const MCP_WRITE_TOOLS = [
|
|
|
295
358
|
outcome: { type: 'string', description: 'Outcome for decisions: approved, rejected, deferred, pending.' },
|
|
296
359
|
severity: { type: 'string', description: 'Severity for traps: low, medium, high.' },
|
|
297
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).' },
|
|
298
363
|
},
|
|
299
364
|
required: ['text', 'type'],
|
|
300
365
|
},
|
|
@@ -480,6 +545,52 @@ const MCP_WRITE_TOOLS = [
|
|
|
480
545
|
required: ['id', 'type'],
|
|
481
546
|
},
|
|
482
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
|
+
},
|
|
483
594
|
];
|
|
484
595
|
const ALL_TOOLS = [...MCP_READ_TOOLS, ...MCP_WRITE_TOOLS];
|
|
485
596
|
class McpProtocolError extends Error {
|
|
@@ -1036,10 +1147,9 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1036
1147
|
refreshBootstrap: args.refreshBootstrap,
|
|
1037
1148
|
cwd,
|
|
1038
1149
|
});
|
|
1039
|
-
// Load available capabilities and tools
|
|
1040
|
-
const
|
|
1041
|
-
const
|
|
1042
|
-
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);
|
|
1043
1153
|
const format = normaliseFormat(args.format);
|
|
1044
1154
|
const content = renderContextForMcp(result, format, {
|
|
1045
1155
|
explain: args.explain,
|
|
@@ -1052,8 +1162,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1052
1162
|
if (capabilities.length > 0) {
|
|
1053
1163
|
suggestions.push(`\n## Available Capabilities (${capabilities.length})`);
|
|
1054
1164
|
capabilities.slice(0, 5).forEach((cap) => {
|
|
1055
|
-
|
|
1056
|
-
suggestions.push(`- [${cap.id}] ${cap.text.split('\n')[0]} (${category})`);
|
|
1165
|
+
suggestions.push(`- [${cap.id}] ${cap.name} (${cap.category})`);
|
|
1057
1166
|
});
|
|
1058
1167
|
if (capabilities.length > 5) {
|
|
1059
1168
|
suggestions.push(`- ... and ${capabilities.length - 5} more`);
|
|
@@ -1062,8 +1171,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1062
1171
|
if (tools.length > 0) {
|
|
1063
1172
|
suggestions.push(`\n## Available Tools (${tools.length})`);
|
|
1064
1173
|
tools.slice(0, 5).forEach((tool) => {
|
|
1065
|
-
|
|
1066
|
-
suggestions.push(`- [${tool.id}] ${tool.text.split('\n')[0]} (${type})`);
|
|
1174
|
+
suggestions.push(`- [${tool.id}] ${tool.name} (${tool.type})`);
|
|
1067
1175
|
});
|
|
1068
1176
|
if (tools.length > 5) {
|
|
1069
1177
|
suggestions.push(`- ... and ${tools.length - 5} more`);
|
|
@@ -1082,13 +1190,13 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1082
1190
|
...result,
|
|
1083
1191
|
available_capabilities: capabilities.map((cap) => ({
|
|
1084
1192
|
id: cap.id,
|
|
1085
|
-
name: cap.
|
|
1086
|
-
category: cap.
|
|
1193
|
+
name: cap.name,
|
|
1194
|
+
category: cap.category,
|
|
1087
1195
|
})),
|
|
1088
1196
|
available_tools: tools.map((tool) => ({
|
|
1089
1197
|
id: tool.id,
|
|
1090
|
-
name: tool.
|
|
1091
|
-
type: tool.
|
|
1198
|
+
name: tool.name,
|
|
1199
|
+
type: tool.type,
|
|
1092
1200
|
})),
|
|
1093
1201
|
...(notifications ? { pending_notifications: notifications, unseen_event_count: unseenEvents.length } : {}),
|
|
1094
1202
|
},
|
|
@@ -1265,6 +1373,12 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1265
1373
|
for (const instruction of board.resolved_instructions.slice(0, 10)) {
|
|
1266
1374
|
lines.push(`- [${instruction.id}] <${instruction.layer}${instruction.scope ? `:${instruction.scope}` : ''}> ${instruction.text}`);
|
|
1267
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
|
+
}
|
|
1268
1382
|
return {
|
|
1269
1383
|
content: [{ type: 'text', text: lines.join('\n') }],
|
|
1270
1384
|
structuredContent: { ...board },
|
|
@@ -1308,6 +1422,18 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1308
1422
|
}
|
|
1309
1423
|
if (name === 'bclaw_list_plans') {
|
|
1310
1424
|
let plans = loadState(cwd).plan_items;
|
|
1425
|
+
// Direct lookup by ID
|
|
1426
|
+
if (args.id) {
|
|
1427
|
+
const plan = plans.find((p) => p.id === String(args.id) || p.short_label === String(args.id));
|
|
1428
|
+
if (!plan) {
|
|
1429
|
+
return { content: [{ type: 'text', text: `Plan '${args.id}' not found.` }], structuredContent: { total: 0, plans: [] } };
|
|
1430
|
+
}
|
|
1431
|
+
return {
|
|
1432
|
+
content: [{ type: 'text', text: `[${plan.id}] ${plan.text} (${plan.status}, ${plan.priority})` }],
|
|
1433
|
+
structuredContent: { total: 1, plans: [plan] },
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
// Filters
|
|
1311
1437
|
if (!args.all) {
|
|
1312
1438
|
plans = plans.filter((plan) => plan.status !== 'done' && plan.status !== 'dropped');
|
|
1313
1439
|
}
|
|
@@ -1325,11 +1451,16 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1325
1451
|
const project = String(args.project).toLowerCase();
|
|
1326
1452
|
plans = plans.filter((plan) => plan.project?.toLowerCase() === project);
|
|
1327
1453
|
}
|
|
1328
|
-
const
|
|
1454
|
+
const totalFiltered = plans.length;
|
|
1455
|
+
// Pagination
|
|
1456
|
+
const offset = Math.max(0, Number(args.offset) || 0);
|
|
1457
|
+
const limit = Math.max(1, Number(args.limit) || 20);
|
|
1458
|
+
const paginated = plans.slice(offset, offset + limit);
|
|
1459
|
+
const lines = paginated.length === 0
|
|
1329
1460
|
? ['No plan items found.']
|
|
1330
1461
|
: [
|
|
1331
|
-
`${
|
|
1332
|
-
...
|
|
1462
|
+
`${totalFiltered} plan(s)${totalFiltered > paginated.length ? ` (showing ${offset + 1}-${offset + paginated.length})` : ''}:`,
|
|
1463
|
+
...paginated.map((plan) => {
|
|
1333
1464
|
const meta = [plan.type ?? 'feat', plan.status, plan.priority];
|
|
1334
1465
|
if (plan.assignee)
|
|
1335
1466
|
meta.push(`assignee ${plan.assignee}`);
|
|
@@ -1341,9 +1472,15 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1341
1472
|
return `[${plan.id}] ${plan.text} (${meta.join(' Ā· ')})${tags}`;
|
|
1342
1473
|
}),
|
|
1343
1474
|
];
|
|
1475
|
+
// Compact mode: strip heavy fields
|
|
1476
|
+
const outputPlans = args.compact
|
|
1477
|
+
? paginated.map(({ id, short_label, text, status, priority, tags, assignee, type }) => ({
|
|
1478
|
+
id, short_label, text, status, priority, tags, assignee, type,
|
|
1479
|
+
}))
|
|
1480
|
+
: paginated;
|
|
1344
1481
|
return {
|
|
1345
1482
|
content: [{ type: 'text', text: lines.join('\n') }],
|
|
1346
|
-
structuredContent: { total:
|
|
1483
|
+
structuredContent: { total: totalFiltered, offset, limit, plans: outputPlans },
|
|
1347
1484
|
};
|
|
1348
1485
|
}
|
|
1349
1486
|
if (name === 'bclaw_list_claims') {
|
|
@@ -1496,32 +1633,25 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1496
1633
|
};
|
|
1497
1634
|
}
|
|
1498
1635
|
if (name === 'bclaw_get_capabilities') {
|
|
1499
|
-
const
|
|
1500
|
-
const
|
|
1501
|
-
const filtered = capabilities.filter((cap) => {
|
|
1636
|
+
const allCapabilities = listCapabilities(cwd);
|
|
1637
|
+
const filtered = allCapabilities.filter((cap) => {
|
|
1502
1638
|
const categoryFilter = args.category;
|
|
1503
1639
|
const tagsFilter = args.tags;
|
|
1504
|
-
if (categoryFilter)
|
|
1505
|
-
|
|
1506
|
-
if (capCategory !== categoryFilter)
|
|
1507
|
-
return false;
|
|
1508
|
-
}
|
|
1640
|
+
if (categoryFilter && cap.category !== categoryFilter)
|
|
1641
|
+
return false;
|
|
1509
1642
|
if (tagsFilter && tagsFilter.length > 0) {
|
|
1510
|
-
|
|
1511
|
-
if (!hasAllTags)
|
|
1643
|
+
if (!tagsFilter.every((tag) => cap.tags.includes(tag)))
|
|
1512
1644
|
return false;
|
|
1513
1645
|
}
|
|
1514
1646
|
return true;
|
|
1515
1647
|
});
|
|
1516
1648
|
const lines = [`Capabilities (${filtered.length}):`];
|
|
1517
1649
|
filtered.forEach((cap) => {
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
lines.push(`\n[${cap.id}] ${cap.text.split('\n')[0]}`);
|
|
1521
|
-
lines.push(` Category: ${category}`);
|
|
1650
|
+
lines.push(`\n[${cap.id}] ${cap.name}`);
|
|
1651
|
+
lines.push(` Category: ${cap.category}`);
|
|
1522
1652
|
lines.push(` Author: ${cap.author}`);
|
|
1523
|
-
if (
|
|
1524
|
-
lines.push(` Tags: ${
|
|
1653
|
+
if (cap.tags.length > 0) {
|
|
1654
|
+
lines.push(` Tags: ${cap.tags.join(', ')}`);
|
|
1525
1655
|
}
|
|
1526
1656
|
});
|
|
1527
1657
|
return {
|
|
@@ -1530,32 +1660,25 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1530
1660
|
};
|
|
1531
1661
|
}
|
|
1532
1662
|
if (name === 'bclaw_list_tools') {
|
|
1533
|
-
const
|
|
1534
|
-
const
|
|
1535
|
-
const filtered = tools.filter((tool) => {
|
|
1663
|
+
const allTools = listRegistryTools(cwd);
|
|
1664
|
+
const filtered = allTools.filter((tool) => {
|
|
1536
1665
|
const typeFilter = args.type;
|
|
1537
1666
|
const tagsFilter = args.tags;
|
|
1538
|
-
if (typeFilter)
|
|
1539
|
-
|
|
1540
|
-
if (toolType !== typeFilter)
|
|
1541
|
-
return false;
|
|
1542
|
-
}
|
|
1667
|
+
if (typeFilter && tool.type !== typeFilter)
|
|
1668
|
+
return false;
|
|
1543
1669
|
if (tagsFilter && tagsFilter.length > 0) {
|
|
1544
|
-
|
|
1545
|
-
if (!hasAllTags)
|
|
1670
|
+
if (!tagsFilter.every((tag) => tool.tags.includes(tag)))
|
|
1546
1671
|
return false;
|
|
1547
1672
|
}
|
|
1548
1673
|
return true;
|
|
1549
1674
|
});
|
|
1550
1675
|
const lines = [`Tools (${filtered.length}):`];
|
|
1551
1676
|
filtered.forEach((tool) => {
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
lines.push(`\n[${tool.id}] ${tool.text.split('\n')[0]}`);
|
|
1555
|
-
lines.push(` Type: ${type}`);
|
|
1677
|
+
lines.push(`\n[${tool.id}] ${tool.name}`);
|
|
1678
|
+
lines.push(` Type: ${tool.type}`);
|
|
1556
1679
|
lines.push(` Author: ${tool.author}`);
|
|
1557
|
-
if (
|
|
1558
|
-
lines.push(` Tags: ${
|
|
1680
|
+
if (tool.tags.length > 0) {
|
|
1681
|
+
lines.push(` Tags: ${tool.tags.join(', ')}`);
|
|
1559
1682
|
}
|
|
1560
1683
|
});
|
|
1561
1684
|
return {
|
|
@@ -1568,39 +1691,152 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1568
1691
|
if (!query) {
|
|
1569
1692
|
throw new Error('Missing required argument: query');
|
|
1570
1693
|
}
|
|
1571
|
-
const
|
|
1572
|
-
const
|
|
1573
|
-
const filtered =
|
|
1694
|
+
const allTools = listRegistryTools(cwd);
|
|
1695
|
+
const queryLower = query.toLowerCase();
|
|
1696
|
+
const filtered = allTools.filter((tool) => {
|
|
1574
1697
|
const typeFilter = args.type;
|
|
1575
1698
|
const tagsFilter = args.tags;
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
const toolType = tool.tags.find((t) => t !== 'tool');
|
|
1579
|
-
if (toolType !== typeFilter)
|
|
1580
|
-
return false;
|
|
1581
|
-
}
|
|
1582
|
-
// Tags filter (all must match)
|
|
1699
|
+
if (typeFilter && tool.type !== typeFilter)
|
|
1700
|
+
return false;
|
|
1583
1701
|
if (tagsFilter && tagsFilter.length > 0) {
|
|
1584
|
-
|
|
1585
|
-
if (!hasAllTags)
|
|
1702
|
+
if (!tagsFilter.every((tag) => tool.tags.includes(tag)))
|
|
1586
1703
|
return false;
|
|
1587
1704
|
}
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
return (tool.text.toLowerCase().includes(queryLower) ||
|
|
1705
|
+
return (tool.name.toLowerCase().includes(queryLower) ||
|
|
1706
|
+
tool.description.toLowerCase().includes(queryLower) ||
|
|
1591
1707
|
tool.tags.some((tag) => tag.toLowerCase().includes(queryLower)));
|
|
1592
1708
|
});
|
|
1593
1709
|
const lines = [`Search results for '${query}' (${filtered.length} tool(s)):`];
|
|
1594
1710
|
filtered.forEach((tool) => {
|
|
1595
|
-
|
|
1596
|
-
lines.push(
|
|
1597
|
-
lines.push(` Type: ${type}`);
|
|
1711
|
+
lines.push(`\n[${tool.id}] ${tool.name}`);
|
|
1712
|
+
lines.push(` Type: ${tool.type}`);
|
|
1598
1713
|
});
|
|
1599
1714
|
return {
|
|
1600
1715
|
content: [{ type: 'text', text: lines.join('\n') || 'No tools found.' }],
|
|
1601
1716
|
structuredContent: { query, total: filtered.length, tools: filtered },
|
|
1602
1717
|
};
|
|
1603
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
|
+
}
|
|
1604
1840
|
throw new Error(`Unknown read tool: ${name}`);
|
|
1605
1841
|
}
|
|
1606
1842
|
export async function executeMcpToolCall(payload) {
|
|
@@ -1611,6 +1847,8 @@ export async function executeMcpToolCall(payload) {
|
|
|
1611
1847
|
response: toolResponse(handleMcpReadToolCall(name, args, { cwd })),
|
|
1612
1848
|
};
|
|
1613
1849
|
}
|
|
1850
|
+
// Resolve model once for all write operations
|
|
1851
|
+
const currentModel = resolveCurrentModel(cwd);
|
|
1614
1852
|
if (name === 'bclaw_setup') {
|
|
1615
1853
|
const step = args.step;
|
|
1616
1854
|
const choice = args.choice ?? '';
|
|
@@ -1794,6 +2032,7 @@ export async function executeMcpToolCall(payload) {
|
|
|
1794
2032
|
autoReflect: args.autoReflect,
|
|
1795
2033
|
cwd,
|
|
1796
2034
|
sessionId: connectionSessionId,
|
|
2035
|
+
model: currentModel,
|
|
1797
2036
|
}, false);
|
|
1798
2037
|
return {
|
|
1799
2038
|
response: toolResponse({
|
|
@@ -1829,6 +2068,9 @@ export async function executeMcpToolCall(payload) {
|
|
|
1829
2068
|
const type = String(args.type ?? 'decision');
|
|
1830
2069
|
const writeThrough = agentCanWriteDirect(identity.agent_id ?? resolvedIdentity.agent_id, cwd);
|
|
1831
2070
|
const candidatePlanId = args.planId;
|
|
2071
|
+
const candidateScope = args.scope;
|
|
2072
|
+
const targetStore = args.store;
|
|
2073
|
+
const effectiveCwd = targetStore ? resolveTargetStore(cwd, targetStore) : cwd;
|
|
1832
2074
|
const candidate = {
|
|
1833
2075
|
id: candId.id,
|
|
1834
2076
|
short_label: candId.short_label,
|
|
@@ -1845,7 +2087,9 @@ export async function executeMcpToolCall(payload) {
|
|
|
1845
2087
|
severity: type === 'trap' ? (args.severity ?? 'medium') : undefined,
|
|
1846
2088
|
category: type === 'constraint' ? args.category : undefined,
|
|
1847
2089
|
outcome: type === 'decision' ? args.outcome : undefined,
|
|
2090
|
+
scope: candidateScope,
|
|
1848
2091
|
plan_id: candidatePlanId,
|
|
2092
|
+
model: currentModel,
|
|
1849
2093
|
star_count: 0,
|
|
1850
2094
|
starred_by: [],
|
|
1851
2095
|
usage_count: 0,
|
|
@@ -1854,22 +2098,25 @@ export async function executeMcpToolCall(payload) {
|
|
|
1854
2098
|
const planPrompt = (type === 'decision' || type === 'trap') && !candidatePlanId
|
|
1855
2099
|
? `\nš” Does this ${type} relate to an active plan item? If so, re-run with planId: 'pln_xxx' to link it.`
|
|
1856
2100
|
: '';
|
|
2101
|
+
const storeLabel = targetStore && targetStore !== 'local' ? ` [store: ${targetStore}]` : '';
|
|
1857
2102
|
if (writeThrough) {
|
|
1858
|
-
saveCandidate(candidate,
|
|
1859
|
-
const accepted = acceptCandidate(candId.id, resolvedIdentity.agent_name,
|
|
1860
|
-
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);
|
|
1861
2106
|
return {
|
|
1862
2107
|
response: toolResponse({
|
|
1863
|
-
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}` }],
|
|
1864
2109
|
candidate_id: candId.id,
|
|
1865
2110
|
promoted_item_id: accepted.promoted_item_id,
|
|
1866
2111
|
write_through: true,
|
|
2112
|
+
store: targetStore ?? 'local',
|
|
2113
|
+
scope: candidateScope,
|
|
1867
2114
|
}),
|
|
1868
2115
|
nextConnectionSessionId: explicitSessionIdFromEnv() ? undefined : identity.session_id,
|
|
1869
2116
|
};
|
|
1870
2117
|
}
|
|
1871
|
-
saveCandidate(candidate,
|
|
1872
|
-
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);
|
|
1873
2120
|
return {
|
|
1874
2121
|
response: toolResponse({
|
|
1875
2122
|
content: [{ type: 'text', text: `ā Candidate created [${candId.short_label}] (pending review)${planPrompt}` }],
|
|
@@ -1949,6 +2196,7 @@ export async function executeMcpToolCall(payload) {
|
|
|
1949
2196
|
created_at: nowISO(),
|
|
1950
2197
|
status: 'active',
|
|
1951
2198
|
plan_id: args.planId,
|
|
2199
|
+
model: currentModel,
|
|
1952
2200
|
}, claimCwd);
|
|
1953
2201
|
appendAuditEntry({ actor: resolvedIdentity.agent_name, actor_id: resolvedIdentity.agent_id, action: 'claim', item_id: claimId, item_type: 'claim' }, claimCwd);
|
|
1954
2202
|
const postClaimItems = getTriggeredItems('trigger:post-claim', claimCwd);
|
|
@@ -2436,6 +2684,100 @@ export async function executeMcpToolCall(payload) {
|
|
|
2436
2684
|
}),
|
|
2437
2685
|
};
|
|
2438
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
|
+
}
|
|
2439
2781
|
return {
|
|
2440
2782
|
response: createToolErrorResponse('unknown_tool', `Unknown tool: ${name}`),
|
|
2441
2783
|
};
|