brainclaw 0.19.2
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/LICENSE +74 -0
- package/README.md +226 -0
- package/dist/cli.js +1037 -0
- package/dist/commands/accept.js +149 -0
- package/dist/commands/adapter-openclaw-import.js +75 -0
- package/dist/commands/add-step.js +35 -0
- package/dist/commands/agent-board.js +106 -0
- package/dist/commands/audit.js +35 -0
- package/dist/commands/bootstrap.js +34 -0
- package/dist/commands/capability.js +104 -0
- package/dist/commands/changes.js +112 -0
- package/dist/commands/check-constraints.js +63 -0
- package/dist/commands/claim-resource.js +54 -0
- package/dist/commands/claim.js +92 -0
- package/dist/commands/complete-step.js +34 -0
- package/dist/commands/constraint.js +44 -0
- package/dist/commands/context-diff.js +32 -0
- package/dist/commands/context.js +63 -0
- package/dist/commands/decision.js +45 -0
- package/dist/commands/delete-plan.js +20 -0
- package/dist/commands/diff.js +99 -0
- package/dist/commands/doctor.js +1275 -0
- package/dist/commands/enable-agent.js +63 -0
- package/dist/commands/env.js +46 -0
- package/dist/commands/estimation-report.js +167 -0
- package/dist/commands/explore.js +47 -0
- package/dist/commands/export.js +381 -0
- package/dist/commands/handoff.js +63 -0
- package/dist/commands/history.js +22 -0
- package/dist/commands/hooks.js +123 -0
- package/dist/commands/init.js +356 -0
- package/dist/commands/install-hooks.js +115 -0
- package/dist/commands/instruction.js +56 -0
- package/dist/commands/list-agents.js +44 -0
- package/dist/commands/list-claims.js +45 -0
- package/dist/commands/list-instructions.js +50 -0
- package/dist/commands/list-plans.js +48 -0
- package/dist/commands/mcp-worker.js +12 -0
- package/dist/commands/mcp.js +2272 -0
- package/dist/commands/memory.js +283 -0
- package/dist/commands/metrics.js +175 -0
- package/dist/commands/plan-resource.js +62 -0
- package/dist/commands/plan.js +76 -0
- package/dist/commands/prune-candidates.js +36 -0
- package/dist/commands/prune.js +48 -0
- package/dist/commands/pull.js +25 -0
- package/dist/commands/push.js +28 -0
- package/dist/commands/rebuild.js +14 -0
- package/dist/commands/reflect-runtime-note.js +74 -0
- package/dist/commands/reflect.js +286 -0
- package/dist/commands/register-agent.js +29 -0
- package/dist/commands/reject.js +52 -0
- package/dist/commands/release-claim.js +41 -0
- package/dist/commands/release-claims.js +67 -0
- package/dist/commands/review.js +242 -0
- package/dist/commands/rollback.js +156 -0
- package/dist/commands/runtime-note.js +144 -0
- package/dist/commands/runtime-status.js +49 -0
- package/dist/commands/search.js +36 -0
- package/dist/commands/session-end.js +187 -0
- package/dist/commands/session-start.js +147 -0
- package/dist/commands/set-trust.js +92 -0
- package/dist/commands/setup.js +446 -0
- package/dist/commands/show-candidate.js +31 -0
- package/dist/commands/star-candidate.js +28 -0
- package/dist/commands/status.js +133 -0
- package/dist/commands/sync.js +159 -0
- package/dist/commands/tool.js +126 -0
- package/dist/commands/trap.js +74 -0
- package/dist/commands/update-handoff.js +23 -0
- package/dist/commands/update-plan.js +37 -0
- package/dist/commands/upgrade.js +382 -0
- package/dist/commands/use-candidate.js +35 -0
- package/dist/commands/version.js +96 -0
- package/dist/commands/watch.js +215 -0
- package/dist/commands/whoami.js +104 -0
- package/dist/core/agent-context.js +340 -0
- package/dist/core/agent-files.js +874 -0
- package/dist/core/agent-integrations.js +135 -0
- package/dist/core/agent-inventory.js +401 -0
- package/dist/core/agent-registry.js +420 -0
- package/dist/core/ai-agent-detection.js +140 -0
- package/dist/core/audit.js +85 -0
- package/dist/core/bootstrap.js +658 -0
- package/dist/core/brainclaw-version.js +433 -0
- package/dist/core/candidates.js +137 -0
- package/dist/core/circuit-breaker.js +118 -0
- package/dist/core/claims.js +72 -0
- package/dist/core/config.js +86 -0
- package/dist/core/context-diff.js +122 -0
- package/dist/core/context.js +1212 -0
- package/dist/core/contradictions.js +270 -0
- package/dist/core/coordination.js +86 -0
- package/dist/core/cross-project.js +99 -0
- package/dist/core/duplicates.js +72 -0
- package/dist/core/event-log.js +152 -0
- package/dist/core/events.js +56 -0
- package/dist/core/execution-context.js +204 -0
- package/dist/core/freshness.js +87 -0
- package/dist/core/global-registry.js +182 -0
- package/dist/core/host.js +10 -0
- package/dist/core/identity.js +151 -0
- package/dist/core/ids.js +56 -0
- package/dist/core/input-validation.js +81 -0
- package/dist/core/instructions.js +117 -0
- package/dist/core/io.js +191 -0
- package/dist/core/json-store.js +63 -0
- package/dist/core/lifecycle.js +45 -0
- package/dist/core/lock.js +129 -0
- package/dist/core/logger.js +49 -0
- package/dist/core/machine-profile.js +332 -0
- package/dist/core/markdown.js +120 -0
- package/dist/core/memory-git.js +133 -0
- package/dist/core/migration.js +247 -0
- package/dist/core/project-registry.js +64 -0
- package/dist/core/reflection-safety.js +21 -0
- package/dist/core/repo-analysis.js +133 -0
- package/dist/core/reputation.js +409 -0
- package/dist/core/runtime.js +134 -0
- package/dist/core/schema.js +580 -0
- package/dist/core/search.js +115 -0
- package/dist/core/security.js +66 -0
- package/dist/core/setup-state.js +50 -0
- package/dist/core/state.js +83 -0
- package/dist/core/store-resolution.js +119 -0
- package/dist/core/sync-remote.js +83 -0
- package/dist/core/traps.js +86 -0
- package/package.json +60 -0
|
@@ -0,0 +1,2272 @@
|
|
|
1
|
+
import readline from 'node:readline';
|
|
2
|
+
import { Worker } from 'node:worker_threads';
|
|
3
|
+
import { getTriggeredItems, renderTriggeredItems } from '../core/lifecycle.js';
|
|
4
|
+
import { resolveCrossProjectTarget, writeCrossProjectNote } from '../core/cross-project.js';
|
|
5
|
+
import { renderBootstrapSummary, runBootstrapProfile } from '../core/bootstrap.js';
|
|
6
|
+
import { buildAgentToolingContext, renderAgentToolingSummary } from '../core/agent-context.js';
|
|
7
|
+
import { buildCoordinationSnapshot } from '../core/coordination.js';
|
|
8
|
+
import { buildContext, renderContextMarkdown, renderContextPromptTemplate } from '../core/context.js';
|
|
9
|
+
import { buildExecutionContext, renderExecutionContextSummary } from '../core/execution-context.js';
|
|
10
|
+
import { loadConfig } from '../core/config.js';
|
|
11
|
+
import { loadState, saveState } from '../core/state.js';
|
|
12
|
+
import { memoryExists, memoryPath, writeFileAtomic } from '../core/io.js';
|
|
13
|
+
import { generateMarkdown } from '../core/markdown.js';
|
|
14
|
+
import { generateCandidateIdWithLabel, listArchivedCandidates, listCandidates, saveCandidate } from '../core/candidates.js';
|
|
15
|
+
import { generateClaimId, listClaims, loadClaim, saveClaim } from '../core/claims.js';
|
|
16
|
+
import { createRuntimeNote } from './runtime-note.js';
|
|
17
|
+
import { acceptCandidate } from './accept.js';
|
|
18
|
+
import { rejectCandidate } from './reject.js';
|
|
19
|
+
import { startSession } from './session-start.js';
|
|
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';
|
|
23
|
+
import { nowISO, generateIdWithLabel, generateId } from '../core/ids.js';
|
|
24
|
+
import { inferProjectFromTarget, loadInstructions, resolveInstructions } from '../core/instructions.js';
|
|
25
|
+
import { buildReputationSnapshot, toPublicReputationSummary } from '../core/reputation.js';
|
|
26
|
+
import { search } from '../core/search.js';
|
|
27
|
+
import { buildOperationalIdentity } from '../core/identity.js';
|
|
28
|
+
import { validateMcpInput, validateMcpField } from '../core/input-validation.js';
|
|
29
|
+
import { buildEstimationReport } from './estimation-report.js';
|
|
30
|
+
import { detectAiAgent } from '../core/ai-agent-detection.js';
|
|
31
|
+
import { checkGitPresence, scanGitRepos, parseRoots, parseRepoSelection, parseAgentSelection, runGlobalInstall, initReposAndConfigureAgents, readSetupState, ALL_KNOWN_AGENTS, } from './setup.js';
|
|
32
|
+
import { resolveTargetStore, resolveStoreChain } from '../core/store-resolution.js';
|
|
33
|
+
import { readUnseenEvents, buildNotificationSummary } from '../core/event-log.js';
|
|
34
|
+
export const SCHEMA_VERSION = '0.6.0';
|
|
35
|
+
export const MCP_PROTOCOL_VERSIONS = ['2025-11-25', '2024-11-05'];
|
|
36
|
+
export const MCP_SERVER_NOT_INITIALIZED = -32002;
|
|
37
|
+
export const MCP_READ_TOOLS = [
|
|
38
|
+
{
|
|
39
|
+
name: 'bclaw_get_context',
|
|
40
|
+
description: 'Get project memory context for a specific file or path.',
|
|
41
|
+
inputSchema: {
|
|
42
|
+
type: 'object',
|
|
43
|
+
properties: {
|
|
44
|
+
path: { type: 'string', description: 'The file path or glob pattern to filter memory by.' },
|
|
45
|
+
project: { type: 'string', description: 'Optional explicit project namespace for instruction resolution.' },
|
|
46
|
+
agent: { type: 'string', description: 'Optional agent name for agent-layer instruction resolution.' },
|
|
47
|
+
host: { type: 'string', description: 'Optional host identifier used to include machine-local runtime context.' },
|
|
48
|
+
allHosts: { type: 'boolean', description: 'Include machine-local runtime context from all hosts.' },
|
|
49
|
+
profile: { type: 'string', description: 'Optional profile override: dev, openclaw, ops, research.' },
|
|
50
|
+
includePending: { type: 'boolean', description: 'Include pending candidates in the context.' },
|
|
51
|
+
maxItems: { type: 'number', description: 'Maximum number of ranked items to return.' },
|
|
52
|
+
maxChars: { type: 'number', description: 'Approximate character budget applied after ranking.' },
|
|
53
|
+
digest: { type: 'boolean', description: 'Include a short deterministic digest for the selected context.' },
|
|
54
|
+
since_session: { type: 'string', description: 'Include a compact memory diff since the given session started.' },
|
|
55
|
+
bootstrap: { type: 'boolean', description: 'Enable brownfield bootstrap fallback when memory is sparse.' },
|
|
56
|
+
refreshBootstrap: { type: 'boolean', description: 'Refresh the brownfield bootstrap profile before building context.' },
|
|
57
|
+
format: { type: 'string', description: 'Output format: markdown, json, or template.' },
|
|
58
|
+
explain: { type: 'boolean', description: 'Include ranking reasons in markdown output.' },
|
|
59
|
+
compactTemplate: { type: 'boolean', description: 'Use compact template format when format=template.' },
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'bclaw_bootstrap',
|
|
65
|
+
description: 'Derive brownfield bootstrap signals from repository docs, manifests, and git history.',
|
|
66
|
+
inputSchema: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
properties: {
|
|
69
|
+
target: { type: 'string', description: 'Optional path or scope to tailor the bootstrap.' },
|
|
70
|
+
refresh: { type: 'boolean', description: 'Force a fresh bootstrap scan.' },
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: 'bclaw_get_execution_context',
|
|
76
|
+
description: 'Inspect the local execution environment and optionally agent tooling signals.',
|
|
77
|
+
inputSchema: {
|
|
78
|
+
type: 'object',
|
|
79
|
+
properties: {
|
|
80
|
+
includeAgentTooling: { type: 'boolean', description: 'Include AGENTS.md, skills, and local MCP inventory.' },
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: 'bclaw_read_handoff',
|
|
86
|
+
description: 'Read an open handoff ticket with its captured git diff and state snapshot.',
|
|
87
|
+
inputSchema: {
|
|
88
|
+
type: 'object',
|
|
89
|
+
properties: {
|
|
90
|
+
id: { type: 'string', description: 'The handoff ID.' },
|
|
91
|
+
},
|
|
92
|
+
required: ['id'],
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'bclaw_get_agent_board',
|
|
97
|
+
description: 'Get an agent collaboration board with active plans, claims, handoffs, and resolved instructions.',
|
|
98
|
+
inputSchema: {
|
|
99
|
+
type: 'object',
|
|
100
|
+
properties: {
|
|
101
|
+
agent: { type: 'string', description: 'Optional agent name to filter claims and handoffs.' },
|
|
102
|
+
project: { type: 'string', description: 'Optional project namespace.' },
|
|
103
|
+
path: { type: 'string', description: 'Optional target path used to infer project scope.' },
|
|
104
|
+
host: { type: 'string', description: 'Optional host identifier used to include machine-local runtime notes.' },
|
|
105
|
+
allHosts: { type: 'boolean', description: 'Include machine-local runtime notes from all hosts.' },
|
|
106
|
+
includeReputation: { type: 'boolean', description: 'Include bounded reputation summaries for board consumers.' },
|
|
107
|
+
includeSessionMeta: { type: 'boolean', description: 'Include session_start/session_end runtime notes (excluded by default to reduce noise).' },
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'bclaw_search',
|
|
113
|
+
description: 'Full-text search across all memory items (decisions, constraints, traps, candidates, handoffs) using BM25 scoring.',
|
|
114
|
+
inputSchema: {
|
|
115
|
+
type: 'object',
|
|
116
|
+
properties: {
|
|
117
|
+
query: { type: 'string', description: 'Search query string.' },
|
|
118
|
+
type: { type: 'string', description: 'Filter by item type (decision, constraint, trap, handoff, candidate, plan).' },
|
|
119
|
+
section: { type: 'string', description: 'Filter by section (state, candidates, runtime).' },
|
|
120
|
+
since: { type: 'string', description: 'Filter items created after this ISO date.' },
|
|
121
|
+
limit: { type: 'number', description: 'Maximum number of results to return (default 10).' },
|
|
122
|
+
},
|
|
123
|
+
required: ['query'],
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: 'bclaw_estimation_report',
|
|
128
|
+
description: 'Show estimation accuracy report for completed plans. Returns ratio of estimated vs actual effort per agent.',
|
|
129
|
+
inputSchema: {
|
|
130
|
+
type: 'object',
|
|
131
|
+
properties: {
|
|
132
|
+
agent: { type: 'string', description: 'Filter by agent/author name.' },
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: 'bclaw_list_plans',
|
|
138
|
+
description: 'List plan items with optional filters on status, type, assignee, and project.',
|
|
139
|
+
inputSchema: {
|
|
140
|
+
type: 'object',
|
|
141
|
+
properties: {
|
|
142
|
+
all: { type: 'boolean', description: 'Include done and dropped plans.' },
|
|
143
|
+
status: { type: 'string', description: 'Filter by status: todo, in_progress, blocked, done, dropped.' },
|
|
144
|
+
type: { type: 'string', description: 'Filter by plan type.' },
|
|
145
|
+
assignee: { type: 'string', description: 'Filter by assignee name.' },
|
|
146
|
+
project: { type: 'string', description: 'Filter by project namespace.' },
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: 'bclaw_list_claims',
|
|
152
|
+
description: 'List work claims with optional filters on project, plan, and agent.',
|
|
153
|
+
inputSchema: {
|
|
154
|
+
type: 'object',
|
|
155
|
+
properties: {
|
|
156
|
+
all: { type: 'boolean', description: 'Include released claims.' },
|
|
157
|
+
project: { type: 'string', description: 'Filter by project namespace.' },
|
|
158
|
+
plan: { type: 'string', description: 'Filter by linked plan id.' },
|
|
159
|
+
agent: { type: 'string', description: 'Filter by agent name.' },
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: 'bclaw_list_agents',
|
|
165
|
+
description: 'List registered agent identities and optionally include bounded reputation summaries.',
|
|
166
|
+
inputSchema: {
|
|
167
|
+
type: 'object',
|
|
168
|
+
properties: {
|
|
169
|
+
includeReputation: { type: 'boolean', description: 'Include bounded reputation summaries for each agent.' },
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: 'bclaw_list_instructions',
|
|
175
|
+
description: 'List raw or resolved shared instructions with the same filters exposed by the CLI.',
|
|
176
|
+
inputSchema: {
|
|
177
|
+
type: 'object',
|
|
178
|
+
properties: {
|
|
179
|
+
layer: { type: 'string', description: 'Filter by layer: global, project, agent.' },
|
|
180
|
+
project: { type: 'string', description: 'Project namespace filter.' },
|
|
181
|
+
agent: { type: 'string', description: 'Agent name filter.' },
|
|
182
|
+
active: { type: 'boolean', description: 'Only include active instructions.' },
|
|
183
|
+
resolved: { type: 'boolean', description: 'Resolve effective instructions for the given scope.' },
|
|
184
|
+
path: { type: 'string', description: 'Infer project namespace from a target path when strategy=folder.' },
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: 'bclaw_list_candidates',
|
|
190
|
+
description: 'List review candidates across pending, accepted, rejected, or all queues.',
|
|
191
|
+
inputSchema: {
|
|
192
|
+
type: 'object',
|
|
193
|
+
properties: {
|
|
194
|
+
status: { type: 'string', description: 'Candidate bucket: pending, accepted, rejected, or all.' },
|
|
195
|
+
type: { type: 'string', description: 'Filter by candidate type.' },
|
|
196
|
+
assignee: { type: 'string', description: 'Filter pending candidates by assignee tag (assignee:<name>).' },
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
name: 'bclaw_get_capabilities',
|
|
202
|
+
description: 'List all registered project capabilities with full metadata.',
|
|
203
|
+
inputSchema: {
|
|
204
|
+
type: 'object',
|
|
205
|
+
properties: {
|
|
206
|
+
category: { type: 'string', description: 'Filter by capability category.' },
|
|
207
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Filter by tags (any).' },
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
name: 'bclaw_list_tools',
|
|
213
|
+
description: 'List all registered project tools with metadata.',
|
|
214
|
+
inputSchema: {
|
|
215
|
+
type: 'object',
|
|
216
|
+
properties: {
|
|
217
|
+
type: { type: 'string', description: 'Filter by tool type (workflow, validator, generator, utility, explorer).' },
|
|
218
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Filter by tags (any).' },
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: 'bclaw_search_tools',
|
|
224
|
+
description: 'Search tools by query and tags.',
|
|
225
|
+
inputSchema: {
|
|
226
|
+
type: 'object',
|
|
227
|
+
properties: {
|
|
228
|
+
query: { type: 'string', description: 'Search query (matches tool name, description, tags).' },
|
|
229
|
+
type: { type: 'string', description: 'Filter by tool type.' },
|
|
230
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Filter by tags (all must match).' },
|
|
231
|
+
},
|
|
232
|
+
required: ['query'],
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
];
|
|
236
|
+
const MCP_WRITE_TOOLS = [
|
|
237
|
+
{
|
|
238
|
+
name: 'bclaw_setup',
|
|
239
|
+
description: 'Interactive onboarding wizard — global agent install + multi-repo brainclaw init. Use the resume pattern: call without step to start, then pass step+choice to advance through each stage.',
|
|
240
|
+
inputSchema: {
|
|
241
|
+
type: 'object',
|
|
242
|
+
properties: {
|
|
243
|
+
step: { type: 'string', description: 'Current step to resume: "project_roots", "repo_selection", or "agent_selection". Omit to start from the beginning.' },
|
|
244
|
+
choice: { type: 'string', description: 'User choice for the current step (e.g. path list, "all", "detected", or comma-separated numbers).' },
|
|
245
|
+
roots: { type: 'string', description: 'Comma-separated root paths (required from step "repo_selection" onward to re-scan).' },
|
|
246
|
+
repo_selection: { type: 'string', description: 'Repo selection choice from previous step (required for "agent_selection" step).' },
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
name: 'bclaw_write_note',
|
|
252
|
+
description: 'Add a runtime note. Requires contributor trust level or above. Use crossProject to push a notification note to a linked project (requires role: publisher in cross_project_links config).',
|
|
253
|
+
inputSchema: {
|
|
254
|
+
type: 'object',
|
|
255
|
+
properties: {
|
|
256
|
+
text: { type: 'string', description: 'Note content.' },
|
|
257
|
+
agent: { type: 'string', description: 'Agent name.' },
|
|
258
|
+
agentId: { type: 'string', description: 'Registered agent id.' },
|
|
259
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Optional tags.' },
|
|
260
|
+
visibility: { type: 'string', description: 'Visibility: shared, machine, private.' },
|
|
261
|
+
ttl: { type: 'string', description: 'Optional TTL: 30m, 2h, 7d.' },
|
|
262
|
+
autoReflect: { type: 'boolean', description: 'Attempt to reflect the runtime note into durable memory immediately.' },
|
|
263
|
+
crossProject: { type: 'string', description: 'Push note to a linked project (name or path). Requires role: publisher in cross_project_links config.' },
|
|
264
|
+
},
|
|
265
|
+
required: ['text'],
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
name: 'bclaw_create_candidate',
|
|
270
|
+
description: 'Create a memory candidate for review. Trusted/curator agents write through directly.',
|
|
271
|
+
inputSchema: {
|
|
272
|
+
type: 'object',
|
|
273
|
+
properties: {
|
|
274
|
+
text: { type: 'string', description: 'Candidate content.' },
|
|
275
|
+
type: { type: 'string', description: 'Type: constraint, decision, trap, handoff.' },
|
|
276
|
+
agent: { type: 'string', description: 'Author agent name.' },
|
|
277
|
+
agentId: { type: 'string', description: 'Registered author agent id.' },
|
|
278
|
+
tags: { type: 'array', items: { type: 'string' } },
|
|
279
|
+
category: { type: 'string', description: 'Category for constraints: architecture, performance, security, reliability, compatibility, process, other.' },
|
|
280
|
+
outcome: { type: 'string', description: 'Outcome for decisions: approved, rejected, deferred, pending.' },
|
|
281
|
+
severity: { type: 'string', description: 'Severity for traps: low, medium, high.' },
|
|
282
|
+
planId: { type: 'string', description: 'Optional plan item ID this decision or trap relates to.' },
|
|
283
|
+
},
|
|
284
|
+
required: ['text', 'type'],
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
name: 'bclaw_accept',
|
|
289
|
+
description: 'Accept a pending candidate into canonical memory. Requires trusted or curator trust level.',
|
|
290
|
+
inputSchema: {
|
|
291
|
+
type: 'object',
|
|
292
|
+
properties: {
|
|
293
|
+
id: { type: 'string', description: 'Candidate ID to accept.' },
|
|
294
|
+
by: { type: 'string', description: 'Reviewer identity.' },
|
|
295
|
+
byId: { type: 'string', description: 'Reviewer agent id.' },
|
|
296
|
+
},
|
|
297
|
+
required: ['id'],
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
name: 'bclaw_reject',
|
|
302
|
+
description: 'Reject a pending candidate. Requires trusted or curator trust level.',
|
|
303
|
+
inputSchema: {
|
|
304
|
+
type: 'object',
|
|
305
|
+
properties: {
|
|
306
|
+
id: { type: 'string', description: 'Candidate ID to reject.' },
|
|
307
|
+
reason: { type: 'string', description: 'Reason for rejection.' },
|
|
308
|
+
by: { type: 'string', description: 'Reviewer identity.' },
|
|
309
|
+
byId: { type: 'string', description: 'Reviewer agent id.' },
|
|
310
|
+
},
|
|
311
|
+
required: ['id'],
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
name: 'bclaw_claim',
|
|
316
|
+
description: 'Claim a work scope (advisory lock). Requires contributor trust level or above.',
|
|
317
|
+
inputSchema: {
|
|
318
|
+
type: 'object',
|
|
319
|
+
properties: {
|
|
320
|
+
scope: { type: 'string', description: 'Scope being claimed.' },
|
|
321
|
+
description: { type: 'string', description: 'Description of the work.' },
|
|
322
|
+
agent: { type: 'string', description: 'Agent or person name.' },
|
|
323
|
+
agentId: { type: 'string', description: 'Registered agent id.' },
|
|
324
|
+
planId: { type: 'string', description: 'Optional linked plan item ID.' },
|
|
325
|
+
store: { type: 'string', description: 'Target store level: local (default), repo, workspace.' },
|
|
326
|
+
},
|
|
327
|
+
required: ['scope', 'description'],
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
name: 'bclaw_release_claim',
|
|
332
|
+
description: 'Release a work claim.',
|
|
333
|
+
inputSchema: {
|
|
334
|
+
type: 'object',
|
|
335
|
+
properties: {
|
|
336
|
+
id: { type: 'string', description: 'Claim ID to release.' },
|
|
337
|
+
planStatus: { type: 'string', description: 'Optional: update linked plan status.' },
|
|
338
|
+
},
|
|
339
|
+
required: ['id'],
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
name: 'bclaw_session_start',
|
|
344
|
+
description: 'Start a session and capture initial context. Pass includeContext and/or includeBoard to get full context + agent board in a single call, eliminating the need for separate bclaw_get_context and bclaw_get_agent_board calls.',
|
|
345
|
+
inputSchema: {
|
|
346
|
+
type: 'object',
|
|
347
|
+
properties: {
|
|
348
|
+
agent: { type: 'string', description: 'Agent name.' },
|
|
349
|
+
agentId: { type: 'string', description: 'Registered agent id.' },
|
|
350
|
+
context: { type: 'string', description: 'Context target path.' },
|
|
351
|
+
includeContext: { type: 'boolean', description: 'Include project memory context in the response (equivalent to bclaw_get_context).' },
|
|
352
|
+
includeBoard: { type: 'boolean', description: 'Include agent board (plans, claims, handoffs) in the response (equivalent to bclaw_get_agent_board).' },
|
|
353
|
+
contextProfile: { type: 'string', description: 'Context profile when includeContext is true: dev, openclaw, ops, research.' },
|
|
354
|
+
contextFormat: { type: 'string', description: 'Context format when includeContext is true: markdown, json, or template.' },
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
name: 'bclaw_session_end',
|
|
360
|
+
description: 'End a session and optionally auto-reflect observations as candidates.',
|
|
361
|
+
inputSchema: {
|
|
362
|
+
type: 'object',
|
|
363
|
+
properties: {
|
|
364
|
+
session: { type: 'string', description: 'Session ID.' },
|
|
365
|
+
agent: { type: 'string', description: 'Agent name.' },
|
|
366
|
+
agentId: { type: 'string', description: 'Registered agent id.' },
|
|
367
|
+
summary: { type: 'string', description: 'Session summary text.' },
|
|
368
|
+
autoReflect: { type: 'boolean', description: 'Auto-reflect session notes as candidates.' },
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
name: 'bclaw_create_plan',
|
|
374
|
+
description: 'Create a new plan item. Requires contributor trust level or above.',
|
|
375
|
+
inputSchema: {
|
|
376
|
+
type: 'object',
|
|
377
|
+
properties: {
|
|
378
|
+
text: { type: 'string', description: 'Plan item description.' },
|
|
379
|
+
type: { type: 'string', description: 'Plan type: feat, fix, chore, spike, doc.' },
|
|
380
|
+
agent: { type: 'string', description: 'Agent name.' },
|
|
381
|
+
agentId: { type: 'string', description: 'Registered agent id.' },
|
|
382
|
+
priority: { type: 'string', description: 'Priority: low, medium, high, critical.' },
|
|
383
|
+
estimate: { type: 'number', description: 'Estimated effort in minutes (positive integer).' },
|
|
384
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Tags for the plan item.' },
|
|
385
|
+
assignee: { type: 'string', description: 'Assignee agent or person name.' },
|
|
386
|
+
},
|
|
387
|
+
required: ['text'],
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
name: 'bclaw_update_plan',
|
|
392
|
+
description: 'Update the status, effort, or other fields of a plan item. Requires contributor trust level or above.',
|
|
393
|
+
inputSchema: {
|
|
394
|
+
type: 'object',
|
|
395
|
+
properties: {
|
|
396
|
+
id: { type: 'string', description: 'Plan item ID.' },
|
|
397
|
+
agent: { type: 'string', description: 'Agent name.' },
|
|
398
|
+
agentId: { type: 'string', description: 'Registered agent id.' },
|
|
399
|
+
status: { type: 'string', description: 'New status: todo, in_progress, done, blocked, cancelled.' },
|
|
400
|
+
actualEffort: { type: 'string', description: 'Actual effort (e.g. "45min", "2h").' },
|
|
401
|
+
priority: { type: 'string', description: 'New priority: low, medium, high, critical.' },
|
|
402
|
+
assignee: { type: 'string', description: 'New assignee.' },
|
|
403
|
+
},
|
|
404
|
+
required: ['id'],
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
name: 'bclaw_add_step',
|
|
409
|
+
description: 'Add a sub-step to a plan item. Requires contributor trust level or above.',
|
|
410
|
+
inputSchema: {
|
|
411
|
+
type: 'object',
|
|
412
|
+
properties: {
|
|
413
|
+
planId: { type: 'string', description: 'Plan item ID.' },
|
|
414
|
+
text: { type: 'string', description: 'Step description.' },
|
|
415
|
+
agent: { type: 'string', description: 'Agent name.' },
|
|
416
|
+
agentId: { type: 'string', description: 'Registered agent id.' },
|
|
417
|
+
assignee: { type: 'string', description: 'Optional assignee.' },
|
|
418
|
+
},
|
|
419
|
+
required: ['planId', 'text'],
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
name: 'bclaw_complete_step',
|
|
424
|
+
description: 'Mark a plan sub-step as done. Requires contributor trust level or above.',
|
|
425
|
+
inputSchema: {
|
|
426
|
+
type: 'object',
|
|
427
|
+
properties: {
|
|
428
|
+
planId: { type: 'string', description: 'Plan item ID.' },
|
|
429
|
+
stepId: { type: 'string', description: 'Step ID to complete.' },
|
|
430
|
+
agent: { type: 'string', description: 'Agent name.' },
|
|
431
|
+
agentId: { type: 'string', description: 'Registered agent id.' },
|
|
432
|
+
},
|
|
433
|
+
required: ['planId', 'stepId'],
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
name: 'bclaw_delete_memory',
|
|
438
|
+
description: 'Delete a memory item (constraint, decision, or trap) by ID. Requires trusted or curator trust level.',
|
|
439
|
+
inputSchema: {
|
|
440
|
+
type: 'object',
|
|
441
|
+
properties: {
|
|
442
|
+
id: { type: 'string', description: 'ID of the item to delete.' },
|
|
443
|
+
type: { type: 'string', description: 'Item type: constraint, decision, trap.' },
|
|
444
|
+
agent: { type: 'string', description: 'Agent name.' },
|
|
445
|
+
agentId: { type: 'string', description: 'Registered agent id.' },
|
|
446
|
+
},
|
|
447
|
+
required: ['id', 'type'],
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
name: 'bclaw_update_memory',
|
|
452
|
+
description: 'Update text, tags, or trap status of a constraint, decision, or trap by ID. Optionally move it to a different store level. Requires trusted or curator trust level.',
|
|
453
|
+
inputSchema: {
|
|
454
|
+
type: 'object',
|
|
455
|
+
properties: {
|
|
456
|
+
id: { type: 'string', description: 'ID of the item to update.' },
|
|
457
|
+
type: { type: 'string', description: 'Item type: constraint, decision, trap.' },
|
|
458
|
+
text: { type: 'string', description: 'New text (optional).' },
|
|
459
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'New tags (replaces existing).' },
|
|
460
|
+
status: { type: 'string', description: 'New status for traps: active, resolved, expired.' },
|
|
461
|
+
moveToStore: { type: 'string', description: 'Move item to a different store level: local, repo, workspace, user.' },
|
|
462
|
+
agent: { type: 'string', description: 'Agent name.' },
|
|
463
|
+
agentId: { type: 'string', description: 'Registered agent id.' },
|
|
464
|
+
},
|
|
465
|
+
required: ['id', 'type'],
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
];
|
|
469
|
+
const ALL_TOOLS = [...MCP_READ_TOOLS, ...MCP_WRITE_TOOLS];
|
|
470
|
+
class McpProtocolError extends Error {
|
|
471
|
+
code;
|
|
472
|
+
id;
|
|
473
|
+
data;
|
|
474
|
+
constructor(code, message, id = null, data) {
|
|
475
|
+
super(message);
|
|
476
|
+
this.code = code;
|
|
477
|
+
this.id = id;
|
|
478
|
+
this.data = data;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
function isRecord(value) {
|
|
482
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
483
|
+
}
|
|
484
|
+
function isSupportedProtocolVersion(value) {
|
|
485
|
+
return typeof value === 'string' && MCP_PROTOCOL_VERSIONS.includes(value);
|
|
486
|
+
}
|
|
487
|
+
function toolResponse(response, isError = false) {
|
|
488
|
+
const structuredContent = response.structuredContent
|
|
489
|
+
? { ...response.structuredContent, schema_version: SCHEMA_VERSION }
|
|
490
|
+
: undefined;
|
|
491
|
+
return {
|
|
492
|
+
...response,
|
|
493
|
+
structuredContent,
|
|
494
|
+
isError,
|
|
495
|
+
schema_version: SCHEMA_VERSION,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
export function createToolErrorResponse(kind, message, details) {
|
|
499
|
+
return toolResponse({
|
|
500
|
+
content: [{ type: 'text', text: `Error: ${message}` }],
|
|
501
|
+
structuredContent: {
|
|
502
|
+
error: {
|
|
503
|
+
kind,
|
|
504
|
+
message,
|
|
505
|
+
...(details !== undefined ? { details } : {}),
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
}, true);
|
|
509
|
+
}
|
|
510
|
+
function requireObjectParams(params, id) {
|
|
511
|
+
if (params === undefined) {
|
|
512
|
+
return {};
|
|
513
|
+
}
|
|
514
|
+
if (!isRecord(params)) {
|
|
515
|
+
throw new McpProtocolError(-32602, 'Invalid params', id);
|
|
516
|
+
}
|
|
517
|
+
return params;
|
|
518
|
+
}
|
|
519
|
+
function resolveRequestedProtocolVersion(params, id) {
|
|
520
|
+
const requested = params.protocolVersion;
|
|
521
|
+
if (!isSupportedProtocolVersion(requested)) {
|
|
522
|
+
throw new McpProtocolError(-32602, 'Unsupported protocol version', id, { supportedVersions: MCP_PROTOCOL_VERSIONS });
|
|
523
|
+
}
|
|
524
|
+
return requested;
|
|
525
|
+
}
|
|
526
|
+
function getCancelledRequestId(params) {
|
|
527
|
+
const candidate = params.requestId ?? params.id;
|
|
528
|
+
if (typeof candidate === 'string' || typeof candidate === 'number' || candidate === null) {
|
|
529
|
+
return candidate;
|
|
530
|
+
}
|
|
531
|
+
return undefined;
|
|
532
|
+
}
|
|
533
|
+
function resolveMutationIdentity(args, fields, cwd) {
|
|
534
|
+
try {
|
|
535
|
+
return {
|
|
536
|
+
identity: requireRegisteredAgentIdentity({
|
|
537
|
+
agentName: typeof args[fields.nameField] === 'string' ? String(args[fields.nameField]) : undefined,
|
|
538
|
+
agentId: typeof args[fields.idField] === 'string' ? String(args[fields.idField]) : undefined,
|
|
539
|
+
cwd,
|
|
540
|
+
allowCurrent: true,
|
|
541
|
+
allowEnv: true,
|
|
542
|
+
}),
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
catch (error) {
|
|
546
|
+
if (error instanceof AgentIdentityResolutionError) {
|
|
547
|
+
return {
|
|
548
|
+
error: {
|
|
549
|
+
kind: error.kind,
|
|
550
|
+
message: error.message,
|
|
551
|
+
details: error.details,
|
|
552
|
+
},
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
return {
|
|
556
|
+
error: {
|
|
557
|
+
kind: 'identity_error',
|
|
558
|
+
message: error instanceof Error ? error.message : String(error),
|
|
559
|
+
},
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
function ensureTrust(args, fields, level, cwd) {
|
|
564
|
+
const resolved = resolveMutationIdentity(args, fields, cwd);
|
|
565
|
+
if ('error' in resolved) {
|
|
566
|
+
return resolved;
|
|
567
|
+
}
|
|
568
|
+
try {
|
|
569
|
+
requireMinimumTrustLevel(resolved.identity, level);
|
|
570
|
+
return resolved;
|
|
571
|
+
}
|
|
572
|
+
catch (error) {
|
|
573
|
+
if (error instanceof AgentTrustError) {
|
|
574
|
+
return {
|
|
575
|
+
error: {
|
|
576
|
+
kind: error.kind,
|
|
577
|
+
message: error.message,
|
|
578
|
+
details: error.details,
|
|
579
|
+
},
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
return {
|
|
583
|
+
error: {
|
|
584
|
+
kind: 'trust_error',
|
|
585
|
+
message: error instanceof Error ? error.message : String(error),
|
|
586
|
+
},
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
function explicitSessionIdFromEnv() {
|
|
591
|
+
return process.env.BRAINCLAW_SESSION_ID?.trim()
|
|
592
|
+
|| process.env.OPENCLAW_SESSION_ID?.trim()
|
|
593
|
+
|| process.env.CLAUDE_SESSION_ID?.trim()
|
|
594
|
+
|| process.env.COPILOT_SESSION_ID?.trim();
|
|
595
|
+
}
|
|
596
|
+
export function parseMcpLine(line) {
|
|
597
|
+
let parsed;
|
|
598
|
+
try {
|
|
599
|
+
parsed = JSON.parse(line);
|
|
600
|
+
}
|
|
601
|
+
catch {
|
|
602
|
+
throw new McpProtocolError(-32700, 'Parse error');
|
|
603
|
+
}
|
|
604
|
+
if (Array.isArray(parsed)) {
|
|
605
|
+
throw new McpProtocolError(-32600, 'Batch requests are not supported');
|
|
606
|
+
}
|
|
607
|
+
if (!isRecord(parsed)) {
|
|
608
|
+
throw new McpProtocolError(-32600, 'Invalid Request');
|
|
609
|
+
}
|
|
610
|
+
if (parsed.jsonrpc !== '2.0') {
|
|
611
|
+
throw new McpProtocolError(-32600, 'Invalid Request');
|
|
612
|
+
}
|
|
613
|
+
if (typeof parsed.method !== 'string' || parsed.method.trim() === '') {
|
|
614
|
+
throw new McpProtocolError(-32600, 'Invalid Request');
|
|
615
|
+
}
|
|
616
|
+
if ('id' in parsed && typeof parsed.id !== 'string' && typeof parsed.id !== 'number' && parsed.id !== null) {
|
|
617
|
+
throw new McpProtocolError(-32600, 'Invalid Request');
|
|
618
|
+
}
|
|
619
|
+
if ('params' in parsed && parsed.params !== undefined && !isRecord(parsed.params)) {
|
|
620
|
+
throw new McpProtocolError(-32602, 'Invalid params', ('id' in parsed ? parsed.id : null) ?? null);
|
|
621
|
+
}
|
|
622
|
+
return {
|
|
623
|
+
jsonrpc: '2.0',
|
|
624
|
+
id: ('id' in parsed ? parsed.id : undefined),
|
|
625
|
+
method: parsed.method,
|
|
626
|
+
params: parsed.params,
|
|
627
|
+
isNotification: !('id' in parsed),
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
export function createInitializeResult(protocolVersion) {
|
|
631
|
+
return {
|
|
632
|
+
protocolVersion,
|
|
633
|
+
serverInfo: { name: 'brainclaw', version: SCHEMA_VERSION },
|
|
634
|
+
capabilities: { tools: { listChanged: false } },
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
export class McpTaskRunner {
|
|
638
|
+
executeTool;
|
|
639
|
+
onResult;
|
|
640
|
+
onInternalError;
|
|
641
|
+
active;
|
|
642
|
+
queue = [];
|
|
643
|
+
constructor(options) {
|
|
644
|
+
this.executeTool = options.executeTool;
|
|
645
|
+
this.onResult = options.onResult;
|
|
646
|
+
this.onInternalError = options.onInternalError;
|
|
647
|
+
}
|
|
648
|
+
get activeRequestId() {
|
|
649
|
+
return this.active?.requestId;
|
|
650
|
+
}
|
|
651
|
+
get queuedRequestIds() {
|
|
652
|
+
return this.queue.map((task) => task.requestId);
|
|
653
|
+
}
|
|
654
|
+
enqueue(requestId, payload) {
|
|
655
|
+
this.queue.push({
|
|
656
|
+
requestId,
|
|
657
|
+
payload,
|
|
658
|
+
controller: new AbortController(),
|
|
659
|
+
cancelled: false,
|
|
660
|
+
});
|
|
661
|
+
this.drain();
|
|
662
|
+
}
|
|
663
|
+
cancel(requestId) {
|
|
664
|
+
if (this.active && this.active.requestId === requestId) {
|
|
665
|
+
this.active.cancelled = true;
|
|
666
|
+
this.active.controller.abort();
|
|
667
|
+
return 'active';
|
|
668
|
+
}
|
|
669
|
+
const index = this.queue.findIndex((task) => task.requestId === requestId);
|
|
670
|
+
if (index >= 0) {
|
|
671
|
+
const [task] = this.queue.splice(index, 1);
|
|
672
|
+
task.cancelled = true;
|
|
673
|
+
task.controller.abort();
|
|
674
|
+
return 'queued';
|
|
675
|
+
}
|
|
676
|
+
return 'missing';
|
|
677
|
+
}
|
|
678
|
+
close() {
|
|
679
|
+
if (this.active) {
|
|
680
|
+
this.active.cancelled = true;
|
|
681
|
+
this.active.controller.abort();
|
|
682
|
+
}
|
|
683
|
+
for (const task of this.queue) {
|
|
684
|
+
task.cancelled = true;
|
|
685
|
+
task.controller.abort();
|
|
686
|
+
}
|
|
687
|
+
this.queue = [];
|
|
688
|
+
}
|
|
689
|
+
drain() {
|
|
690
|
+
if (this.active) {
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
const next = this.queue.shift();
|
|
694
|
+
if (!next) {
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
if (next.cancelled) {
|
|
698
|
+
this.drain();
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
this.active = next;
|
|
702
|
+
void this.runTask(next);
|
|
703
|
+
}
|
|
704
|
+
async runTask(task) {
|
|
705
|
+
try {
|
|
706
|
+
const outcome = await this.executeTool(task.payload, task.controller.signal);
|
|
707
|
+
if (!task.cancelled) {
|
|
708
|
+
this.onResult(task.requestId, outcome);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
catch (error) {
|
|
712
|
+
if (!task.cancelled) {
|
|
713
|
+
this.onInternalError(task.requestId, error);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
finally {
|
|
717
|
+
if (this.active === task) {
|
|
718
|
+
this.active = undefined;
|
|
719
|
+
}
|
|
720
|
+
this.drain();
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
export class McpServerConnection {
|
|
725
|
+
cwd;
|
|
726
|
+
state = 'pre_init';
|
|
727
|
+
protocolVersion;
|
|
728
|
+
connectionSessionId;
|
|
729
|
+
send;
|
|
730
|
+
taskRunner;
|
|
731
|
+
constructor(options) {
|
|
732
|
+
this.cwd = options.cwd;
|
|
733
|
+
this.send = options.send;
|
|
734
|
+
this.taskRunner = new McpTaskRunner({
|
|
735
|
+
executeTool: options.executeTool ?? createWorkerToolExecutor(),
|
|
736
|
+
onResult: (requestId, outcome) => {
|
|
737
|
+
this.connectionSessionId = outcome.nextConnectionSessionId;
|
|
738
|
+
this.sendResult(requestId, outcome.response);
|
|
739
|
+
},
|
|
740
|
+
onInternalError: (requestId, error) => {
|
|
741
|
+
this.sendError(requestId, -32603, error instanceof Error ? error.message : 'Internal error');
|
|
742
|
+
},
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
handleLine(line) {
|
|
746
|
+
if (this.state === 'closed' || !line.trim()) {
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
let message;
|
|
750
|
+
try {
|
|
751
|
+
message = parseMcpLine(line);
|
|
752
|
+
}
|
|
753
|
+
catch (error) {
|
|
754
|
+
if (error instanceof McpProtocolError) {
|
|
755
|
+
this.sendError(error.id, error.code, error.message, error.data);
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
this.sendError(null, -32603, error instanceof Error ? error.message : 'Internal error');
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
const { id, method, isNotification } = message;
|
|
762
|
+
try {
|
|
763
|
+
if (method === 'notifications/cancelled') {
|
|
764
|
+
this.handleCancellation(requireObjectParams(message.params, null));
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
if (method === 'initialize') {
|
|
768
|
+
if (isNotification) {
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
if (this.state !== 'pre_init') {
|
|
772
|
+
this.sendError(id ?? null, -32600, 'Server already initialized');
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
const params = requireObjectParams(message.params, id ?? null);
|
|
776
|
+
const protocolVersion = resolveRequestedProtocolVersion(params, id ?? null);
|
|
777
|
+
this.protocolVersion = protocolVersion;
|
|
778
|
+
this.state = 'awaiting_initialized';
|
|
779
|
+
this.sendResult(id ?? null, createInitializeResult(protocolVersion));
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
if (method === 'notifications/initialized' || method === 'initialized') {
|
|
783
|
+
if (this.state === 'awaiting_initialized') {
|
|
784
|
+
this.state = 'ready';
|
|
785
|
+
}
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
if (method === 'ping') {
|
|
789
|
+
if (!isNotification) {
|
|
790
|
+
this.sendResult(id ?? null, {});
|
|
791
|
+
}
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
if (this.state !== 'ready') {
|
|
795
|
+
if (!isNotification) {
|
|
796
|
+
this.sendError(id ?? null, MCP_SERVER_NOT_INITIALIZED, 'Server not initialized');
|
|
797
|
+
}
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
if (method === 'tools/list') {
|
|
801
|
+
if (!isNotification) {
|
|
802
|
+
this.sendResult(id ?? null, { tools: ALL_TOOLS });
|
|
803
|
+
}
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
if (method === 'tools/call') {
|
|
807
|
+
if (isNotification) {
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
const params = requireObjectParams(message.params, id ?? null);
|
|
811
|
+
const name = typeof params.name === 'string' ? params.name : undefined;
|
|
812
|
+
if (!name) {
|
|
813
|
+
this.sendError(id ?? null, -32602, 'Invalid params');
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
const args = params.arguments === undefined ? {} : requireObjectParams(params.arguments, id ?? null);
|
|
817
|
+
this.taskRunner.enqueue(id ?? null, {
|
|
818
|
+
name,
|
|
819
|
+
args,
|
|
820
|
+
cwd: this.cwd,
|
|
821
|
+
connectionSessionId: this.connectionSessionId,
|
|
822
|
+
});
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
if (!isNotification) {
|
|
826
|
+
this.sendError(id ?? null, -32601, `Method not found: ${method}`);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
catch (error) {
|
|
830
|
+
if (error instanceof McpProtocolError) {
|
|
831
|
+
this.sendError(error.id, error.code, error.message, error.data);
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
this.sendError(id ?? null, -32603, error instanceof Error ? error.message : 'Internal error');
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
close() {
|
|
838
|
+
this.state = 'closed';
|
|
839
|
+
this.taskRunner.close();
|
|
840
|
+
}
|
|
841
|
+
handleCancellation(params) {
|
|
842
|
+
const requestId = getCancelledRequestId(params);
|
|
843
|
+
if (requestId === undefined) {
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
this.taskRunner.cancel(requestId);
|
|
847
|
+
}
|
|
848
|
+
sendResult(id, result) {
|
|
849
|
+
this.send({ jsonrpc: '2.0', id, result });
|
|
850
|
+
}
|
|
851
|
+
sendError(id, code, message, data) {
|
|
852
|
+
this.send({
|
|
853
|
+
jsonrpc: '2.0',
|
|
854
|
+
id,
|
|
855
|
+
error: {
|
|
856
|
+
code,
|
|
857
|
+
message,
|
|
858
|
+
...(data !== undefined ? { data } : {}),
|
|
859
|
+
},
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
export function runMcp() {
|
|
864
|
+
const cwd = process.cwd();
|
|
865
|
+
if (!memoryExists(cwd)) {
|
|
866
|
+
console.error('Project memory not initialized. Run `brainclaw init` first.');
|
|
867
|
+
process.exit(1);
|
|
868
|
+
}
|
|
869
|
+
const rl = readline.createInterface({
|
|
870
|
+
input: process.stdin,
|
|
871
|
+
output: process.stdout,
|
|
872
|
+
terminal: false,
|
|
873
|
+
});
|
|
874
|
+
const connection = new McpServerConnection({
|
|
875
|
+
cwd,
|
|
876
|
+
send: (message) => {
|
|
877
|
+
process.stdout.write(JSON.stringify(message) + '\n');
|
|
878
|
+
},
|
|
879
|
+
});
|
|
880
|
+
rl.on('line', (line) => {
|
|
881
|
+
connection.handleLine(line);
|
|
882
|
+
});
|
|
883
|
+
rl.on('close', () => {
|
|
884
|
+
connection.close();
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
function createWorkerToolExecutor() {
|
|
888
|
+
return (payload, signal) => new Promise((resolve, reject) => {
|
|
889
|
+
const worker = new Worker(new URL('./mcp-worker.js', import.meta.url), {
|
|
890
|
+
workerData: payload,
|
|
891
|
+
});
|
|
892
|
+
let settled = false;
|
|
893
|
+
const cleanup = () => {
|
|
894
|
+
worker.removeAllListeners('message');
|
|
895
|
+
worker.removeAllListeners('error');
|
|
896
|
+
worker.removeAllListeners('exit');
|
|
897
|
+
signal.removeEventListener('abort', onAbort);
|
|
898
|
+
};
|
|
899
|
+
const settle = (fn) => {
|
|
900
|
+
if (settled) {
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
settled = true;
|
|
904
|
+
cleanup();
|
|
905
|
+
fn();
|
|
906
|
+
};
|
|
907
|
+
const onAbort = () => {
|
|
908
|
+
settle(() => {
|
|
909
|
+
void worker.terminate().finally(() => reject(new Error('Task cancelled')));
|
|
910
|
+
});
|
|
911
|
+
};
|
|
912
|
+
worker.on('message', (message) => {
|
|
913
|
+
settle(() => {
|
|
914
|
+
resolve(message);
|
|
915
|
+
});
|
|
916
|
+
});
|
|
917
|
+
worker.on('error', (error) => {
|
|
918
|
+
settle(() => {
|
|
919
|
+
reject(error);
|
|
920
|
+
});
|
|
921
|
+
});
|
|
922
|
+
worker.on('exit', (code) => {
|
|
923
|
+
if (settled) {
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
settle(() => {
|
|
927
|
+
reject(new Error(`Worker exited unexpectedly with code ${code}`));
|
|
928
|
+
});
|
|
929
|
+
});
|
|
930
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
export function normaliseFormat(value) {
|
|
934
|
+
if (value === 'json' || value === 'template') {
|
|
935
|
+
return value;
|
|
936
|
+
}
|
|
937
|
+
return 'markdown';
|
|
938
|
+
}
|
|
939
|
+
export function renderContextForMcp(result, format, options) {
|
|
940
|
+
if (format === 'json') {
|
|
941
|
+
return JSON.stringify(result, null, 2);
|
|
942
|
+
}
|
|
943
|
+
if (format === 'template') {
|
|
944
|
+
const compact = options.compactTemplate || result.profile === 'openclaw';
|
|
945
|
+
return renderContextPromptTemplate(result, compact);
|
|
946
|
+
}
|
|
947
|
+
return renderContextMarkdown(result, options.explain);
|
|
948
|
+
}
|
|
949
|
+
export function parseTtl(ttl) {
|
|
950
|
+
const match = /^(\d+)([mhd])$/.exec(ttl.trim().toLowerCase());
|
|
951
|
+
if (!match)
|
|
952
|
+
return undefined;
|
|
953
|
+
const value = parseInt(match[1], 10);
|
|
954
|
+
const unit = match[2];
|
|
955
|
+
const ms = unit === 'm' ? value * 60_000 : unit === 'h' ? value * 3_600_000 : value * 86_400_000;
|
|
956
|
+
return new Date(Date.now() + ms).toISOString();
|
|
957
|
+
}
|
|
958
|
+
function getReviewAssignee(tags) {
|
|
959
|
+
for (const tag of tags) {
|
|
960
|
+
if (tag.startsWith('assignee:')) {
|
|
961
|
+
return tag.slice('assignee:'.length).trim() || undefined;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
return undefined;
|
|
965
|
+
}
|
|
966
|
+
export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
967
|
+
const cwd = context.cwd ?? process.cwd();
|
|
968
|
+
if (name === 'bclaw_get_context') {
|
|
969
|
+
const result = buildContext({
|
|
970
|
+
target: args.path,
|
|
971
|
+
project: args.project,
|
|
972
|
+
agent: args.agent,
|
|
973
|
+
host: args.host,
|
|
974
|
+
allHosts: args.allHosts,
|
|
975
|
+
profile: args.profile,
|
|
976
|
+
includePending: args.includePending,
|
|
977
|
+
maxItems: args.maxItems,
|
|
978
|
+
maxChars: args.maxChars,
|
|
979
|
+
digest: args.digest,
|
|
980
|
+
sinceSession: args.since_session,
|
|
981
|
+
bootstrap: args.bootstrap,
|
|
982
|
+
refreshBootstrap: args.refreshBootstrap,
|
|
983
|
+
cwd,
|
|
984
|
+
});
|
|
985
|
+
// Load available capabilities and tools
|
|
986
|
+
const state = loadState(cwd);
|
|
987
|
+
const capabilities = state.recent_decisions.filter((d) => d.tags.includes('capability'));
|
|
988
|
+
const tools = state.recent_decisions.filter((d) => d.tags.includes('tool'));
|
|
989
|
+
const format = normaliseFormat(args.format);
|
|
990
|
+
const content = renderContextForMcp(result, format, {
|
|
991
|
+
explain: args.explain,
|
|
992
|
+
compactTemplate: args.compactTemplate,
|
|
993
|
+
});
|
|
994
|
+
// Add metadata discovery section to content
|
|
995
|
+
let enrichedContent = content;
|
|
996
|
+
if (capabilities.length > 0 || tools.length > 0) {
|
|
997
|
+
const suggestions = [];
|
|
998
|
+
if (capabilities.length > 0) {
|
|
999
|
+
suggestions.push(`\n## Available Capabilities (${capabilities.length})`);
|
|
1000
|
+
capabilities.slice(0, 5).forEach((cap) => {
|
|
1001
|
+
const category = cap.tags.find((t) => t !== 'capability') || 'general';
|
|
1002
|
+
suggestions.push(`- [${cap.id}] ${cap.text.split('\n')[0]} (${category})`);
|
|
1003
|
+
});
|
|
1004
|
+
if (capabilities.length > 5) {
|
|
1005
|
+
suggestions.push(`- ... and ${capabilities.length - 5} more`);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
if (tools.length > 0) {
|
|
1009
|
+
suggestions.push(`\n## Available Tools (${tools.length})`);
|
|
1010
|
+
tools.slice(0, 5).forEach((tool) => {
|
|
1011
|
+
const type = tool.tags.find((t) => t !== 'tool') || 'utility';
|
|
1012
|
+
suggestions.push(`- [${tool.id}] ${tool.text.split('\n')[0]} (${type})`);
|
|
1013
|
+
});
|
|
1014
|
+
if (tools.length > 5) {
|
|
1015
|
+
suggestions.push(`- ... and ${tools.length - 5} more`);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
suggestions.push('\n💡 Tip: Use bclaw_get_capabilities, bclaw_list_tools, or bclaw_search_tools for detailed discovery');
|
|
1019
|
+
enrichedContent = content + suggestions.join('\n');
|
|
1020
|
+
}
|
|
1021
|
+
// Check for unseen events from other agents
|
|
1022
|
+
const agentName = args.agent ?? resolveCurrentAgentName(cwd);
|
|
1023
|
+
const unseenEvents = readUnseenEvents(agentName, cwd);
|
|
1024
|
+
const notifications = buildNotificationSummary(unseenEvents);
|
|
1025
|
+
return {
|
|
1026
|
+
content: [{ type: 'text', text: enrichedContent || 'No relevant memory found.' }],
|
|
1027
|
+
structuredContent: {
|
|
1028
|
+
...result,
|
|
1029
|
+
available_capabilities: capabilities.map((cap) => ({
|
|
1030
|
+
id: cap.id,
|
|
1031
|
+
name: cap.text.split('\n')[0],
|
|
1032
|
+
category: cap.tags.find((t) => t !== 'capability') || 'general',
|
|
1033
|
+
})),
|
|
1034
|
+
available_tools: tools.map((tool) => ({
|
|
1035
|
+
id: tool.id,
|
|
1036
|
+
name: tool.text.split('\n')[0],
|
|
1037
|
+
type: tool.tags.find((t) => t !== 'tool') || 'utility',
|
|
1038
|
+
})),
|
|
1039
|
+
...(notifications ? { pending_notifications: notifications, unseen_event_count: unseenEvents.length } : {}),
|
|
1040
|
+
},
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
if (name === 'bclaw_bootstrap') {
|
|
1044
|
+
const result = runBootstrapProfile({
|
|
1045
|
+
target: args.target,
|
|
1046
|
+
refresh: args.refresh,
|
|
1047
|
+
cwd,
|
|
1048
|
+
});
|
|
1049
|
+
return {
|
|
1050
|
+
content: [{ type: 'text', text: renderBootstrapSummary(result) }],
|
|
1051
|
+
structuredContent: {
|
|
1052
|
+
summary: result.profile.summary,
|
|
1053
|
+
target: result.profile.target,
|
|
1054
|
+
repo_fingerprint: result.profile.repo_fingerprint,
|
|
1055
|
+
sources_scanned: result.profile.sources_scanned,
|
|
1056
|
+
seed_count: result.profile.seed_count,
|
|
1057
|
+
seeds: result.seeds,
|
|
1058
|
+
reused_profile: result.reusedProfile,
|
|
1059
|
+
},
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
if (name === 'bclaw_get_execution_context') {
|
|
1063
|
+
const executionContext = buildExecutionContext({ cwd });
|
|
1064
|
+
const agentTooling = args.includeAgentTooling ? buildAgentToolingContext({ cwd }) : undefined;
|
|
1065
|
+
const text = [
|
|
1066
|
+
renderExecutionContextSummary(executionContext, true),
|
|
1067
|
+
...(agentTooling ? ['', renderAgentToolingSummary(agentTooling)] : []),
|
|
1068
|
+
].join('\n');
|
|
1069
|
+
return {
|
|
1070
|
+
content: [{ type: 'text', text }],
|
|
1071
|
+
structuredContent: {
|
|
1072
|
+
execution_context: executionContext,
|
|
1073
|
+
...(agentTooling ? { agent_tooling: agentTooling } : {}),
|
|
1074
|
+
},
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
if (name === 'bclaw_read_handoff') {
|
|
1078
|
+
const state = loadState(cwd);
|
|
1079
|
+
const handoff = state.open_handoffs.find((entry) => entry.id === args.id);
|
|
1080
|
+
let text = `Handoff not found: ${String(args.id)}`;
|
|
1081
|
+
if (handoff) {
|
|
1082
|
+
text = `From: ${handoff.from}\nTo: ${handoff.to}\nTask: ${handoff.text}\n\n`;
|
|
1083
|
+
if (handoff.snapshot?.diff) {
|
|
1084
|
+
text += `--- Uncommitted Git Diff ---\n\`\`\`diff\n${handoff.snapshot.diff}\n\`\`\`\n`;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
return { content: [{ type: 'text', text }] };
|
|
1088
|
+
}
|
|
1089
|
+
if (name === 'bclaw_get_agent_board') {
|
|
1090
|
+
const board = buildCoordinationSnapshot({
|
|
1091
|
+
agent: args.agent,
|
|
1092
|
+
project: args.project,
|
|
1093
|
+
target: args.path,
|
|
1094
|
+
host: args.host,
|
|
1095
|
+
allHosts: args.allHosts,
|
|
1096
|
+
includeReputation: args.includeReputation,
|
|
1097
|
+
includeSessionMeta: args.includeSessionMeta,
|
|
1098
|
+
autoAcknowledge: true,
|
|
1099
|
+
cwd,
|
|
1100
|
+
});
|
|
1101
|
+
const lines = [];
|
|
1102
|
+
lines.push(`Agent board${board.agent ? ` for ${board.agent}` : ''}${board.project ? ` (${board.project})` : ''}`);
|
|
1103
|
+
lines.push('');
|
|
1104
|
+
if (board.project_id)
|
|
1105
|
+
lines.push(`Project ID: ${board.project_id}`);
|
|
1106
|
+
if (board.agent && board.agent_id)
|
|
1107
|
+
lines.push(`Agent ID: ${board.agent_id}`);
|
|
1108
|
+
lines.push(`Current host: ${board.current_host}`);
|
|
1109
|
+
if (board.all_hosts)
|
|
1110
|
+
lines.push('Host filter: all-hosts');
|
|
1111
|
+
else if (board.host_filter)
|
|
1112
|
+
lines.push(`Host filter: ${board.host_filter}`);
|
|
1113
|
+
if (args.includeReputation && board.reputation_summary) {
|
|
1114
|
+
lines.push(`Reputation: tracked=${board.reputation_summary.tracked_agents}, avg_trust=${board.reputation_summary.avg_internal_trust}`);
|
|
1115
|
+
if (board.agent_reputation) {
|
|
1116
|
+
lines.push(`Agent trust: ${board.agent_reputation.internal_trust} (cq=${board.agent_reputation.contribution_quality}, rv=${board.agent_reputation.review_reliability}, ct=${board.agent_reputation.continuity_hygiene})`);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
lines.push(`Active plans: ${board.active_plans.length}`);
|
|
1120
|
+
for (const plan of board.active_plans.slice(0, 10)) {
|
|
1121
|
+
const claims = plan.claims.length ? ` claims=${plan.claims.map((claim) => claim.agent).join(',')}` : '';
|
|
1122
|
+
lines.push(`- [${plan.id}] ${plan.text} (${plan.status}, ${plan.priority})${claims}`);
|
|
1123
|
+
}
|
|
1124
|
+
lines.push(`Active claims: ${board.active_claims.length}`);
|
|
1125
|
+
for (const claim of board.active_claims.slice(0, 10)) {
|
|
1126
|
+
const identity = claim.agent_id ? ` [${claim.agent_id}]` : '';
|
|
1127
|
+
const session = claim.session_id ? ` session=${claim.session_id}` : '';
|
|
1128
|
+
lines.push(`- [${claim.id}] ${claim.agent}${identity} -> ${claim.scope}${claim.plan_id ? ` (plan ${claim.plan_id})` : ''}${session}`);
|
|
1129
|
+
}
|
|
1130
|
+
const sessionMetaHint = board.session_meta_hidden > 0 ? ` (+${board.session_meta_hidden} session lifecycle notes hidden — pass includeSessionMeta to show)` : '';
|
|
1131
|
+
lines.push(`Runtime notes: ${board.runtime_notes.length}${sessionMetaHint}`);
|
|
1132
|
+
for (const note of board.runtime_notes.slice(-10)) {
|
|
1133
|
+
const scope = note.visibility === 'shared' ? 'shared' : `${note.visibility}:${note.host_id ?? 'unknown-host'}`;
|
|
1134
|
+
const identity = note.agent_id ? ` [${note.agent_id}]` : '';
|
|
1135
|
+
lines.push(`- [${note.id}] ${note.agent}${identity}: ${note.text}${note.plan_id ? ` (plan ${note.plan_id})` : ''} [${scope}]`);
|
|
1136
|
+
}
|
|
1137
|
+
lines.push(`Open handoffs: ${board.open_handoffs.length}`);
|
|
1138
|
+
for (const handoff of board.open_handoffs.slice(0, 10)) {
|
|
1139
|
+
lines.push(`- [${handoff.id}] ${handoff.from} -> ${handoff.to}: ${handoff.text}`);
|
|
1140
|
+
}
|
|
1141
|
+
lines.push(`Resolved instructions: ${board.resolved_instructions.length}`);
|
|
1142
|
+
for (const instruction of board.resolved_instructions.slice(0, 10)) {
|
|
1143
|
+
lines.push(`- [${instruction.id}] <${instruction.layer}${instruction.scope ? `:${instruction.scope}` : ''}> ${instruction.text}`);
|
|
1144
|
+
}
|
|
1145
|
+
return {
|
|
1146
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
1147
|
+
structuredContent: { ...board },
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
if (name === 'bclaw_search') {
|
|
1151
|
+
const query = String(args.query ?? '');
|
|
1152
|
+
if (!query) {
|
|
1153
|
+
throw new Error('Missing required argument: query');
|
|
1154
|
+
}
|
|
1155
|
+
const results = search({
|
|
1156
|
+
query,
|
|
1157
|
+
section: (args.section ?? args.type),
|
|
1158
|
+
since: args.since,
|
|
1159
|
+
maxResults: typeof args.limit === 'number' ? args.limit : 10,
|
|
1160
|
+
cwd,
|
|
1161
|
+
});
|
|
1162
|
+
const lines = results.map((result) => `[${result.id}] (${result.section}) score=${result.score.toFixed(2)}: ${result.text.slice(0, 120)}`);
|
|
1163
|
+
return {
|
|
1164
|
+
content: [{ type: 'text', text: results.length > 0 ? lines.join('\n') : 'No results found.' }],
|
|
1165
|
+
structuredContent: { results, total: results.length },
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
if (name === 'bclaw_estimation_report') {
|
|
1169
|
+
const report = buildEstimationReport({ agent: args.agent, cwd });
|
|
1170
|
+
const lines = [`Estimation Report — ${report.summary.total} completed plan(s)`];
|
|
1171
|
+
if (report.summary.calibration_hint) {
|
|
1172
|
+
lines.push(`Calibration: ${report.summary.calibration_hint}`);
|
|
1173
|
+
lines.push(`Median ratio: ${report.summary.median_ratio}x · Mean: ${report.summary.mean_ratio}x`);
|
|
1174
|
+
}
|
|
1175
|
+
for (const e of report.entries) {
|
|
1176
|
+
const est = e.estimated_minutes !== undefined ? `est:${e.estimated_minutes}min` : 'no estimate';
|
|
1177
|
+
const act = e.elapsed_minutes !== undefined ? `actual:${e.elapsed_minutes}min` : 'no actual';
|
|
1178
|
+
const ratio = e.ratio !== undefined ? ` ratio:${e.ratio}x` : '';
|
|
1179
|
+
lines.push(`[${e.id.slice(0, 8)}] ${e.text.slice(0, 60)} — ${est} · ${act}${ratio}`);
|
|
1180
|
+
}
|
|
1181
|
+
return {
|
|
1182
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
1183
|
+
structuredContent: report,
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
if (name === 'bclaw_list_plans') {
|
|
1187
|
+
let plans = loadState(cwd).plan_items;
|
|
1188
|
+
if (!args.all) {
|
|
1189
|
+
plans = plans.filter((plan) => plan.status !== 'done' && plan.status !== 'dropped');
|
|
1190
|
+
}
|
|
1191
|
+
if (args.status) {
|
|
1192
|
+
plans = plans.filter((plan) => plan.status === args.status);
|
|
1193
|
+
}
|
|
1194
|
+
if (args.type) {
|
|
1195
|
+
plans = plans.filter((plan) => plan.type === args.type);
|
|
1196
|
+
}
|
|
1197
|
+
if (args.assignee) {
|
|
1198
|
+
const assignee = String(args.assignee).toLowerCase();
|
|
1199
|
+
plans = plans.filter((plan) => plan.assignee?.toLowerCase() === assignee);
|
|
1200
|
+
}
|
|
1201
|
+
if (args.project) {
|
|
1202
|
+
const project = String(args.project).toLowerCase();
|
|
1203
|
+
plans = plans.filter((plan) => plan.project?.toLowerCase() === project);
|
|
1204
|
+
}
|
|
1205
|
+
const lines = plans.length === 0
|
|
1206
|
+
? ['No plan items found.']
|
|
1207
|
+
: [
|
|
1208
|
+
`${plans.length} plan item(s):`,
|
|
1209
|
+
...plans.map((plan) => {
|
|
1210
|
+
const meta = [plan.type ?? 'feat', plan.status, plan.priority];
|
|
1211
|
+
if (plan.assignee)
|
|
1212
|
+
meta.push(`assignee ${plan.assignee}`);
|
|
1213
|
+
if (plan.project)
|
|
1214
|
+
meta.push(`project ${plan.project}`);
|
|
1215
|
+
if (plan.depends_on.length > 0)
|
|
1216
|
+
meta.push(`depends_on ${plan.depends_on.join(',')}`);
|
|
1217
|
+
const tags = plan.tags.length ? ` [${plan.tags.join(', ')}]` : '';
|
|
1218
|
+
return `[${plan.id}] ${plan.text} (${meta.join(' · ')})${tags}`;
|
|
1219
|
+
}),
|
|
1220
|
+
];
|
|
1221
|
+
return {
|
|
1222
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
1223
|
+
structuredContent: { total: plans.length, plans },
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
if (name === 'bclaw_list_claims') {
|
|
1227
|
+
let claims = listClaims(cwd);
|
|
1228
|
+
if (!args.all) {
|
|
1229
|
+
claims = claims.filter((claim) => claim.status === 'active');
|
|
1230
|
+
}
|
|
1231
|
+
if (args.project) {
|
|
1232
|
+
claims = claims.filter((claim) => claim.project === args.project);
|
|
1233
|
+
}
|
|
1234
|
+
if (args.plan) {
|
|
1235
|
+
claims = claims.filter((claim) => claim.plan_id === args.plan);
|
|
1236
|
+
}
|
|
1237
|
+
if (args.agent) {
|
|
1238
|
+
claims = claims.filter((claim) => claim.agent === args.agent);
|
|
1239
|
+
}
|
|
1240
|
+
const label = args.all ? 'claim(s)' : 'active claim(s)';
|
|
1241
|
+
const lines = claims.length === 0
|
|
1242
|
+
? ['No active claims.']
|
|
1243
|
+
: [
|
|
1244
|
+
`${claims.length} ${label}:`,
|
|
1245
|
+
...claims.map((claim) => {
|
|
1246
|
+
const status = claim.status !== 'active' ? ` (${claim.status})` : '';
|
|
1247
|
+
const extras = [];
|
|
1248
|
+
if (claim.session_id)
|
|
1249
|
+
extras.push(`session ${claim.session_id.slice(-8)}`);
|
|
1250
|
+
if (claim.plan_id)
|
|
1251
|
+
extras.push(`plan ${claim.plan_id}`);
|
|
1252
|
+
if (claim.project)
|
|
1253
|
+
extras.push(`project ${claim.project}`);
|
|
1254
|
+
const suffix = extras.length ? ` [${extras.join(', ')}]` : '';
|
|
1255
|
+
return `[${claim.id}] ${claim.agent} -> ${claim.scope}: ${claim.description}${suffix}${status}`;
|
|
1256
|
+
}),
|
|
1257
|
+
];
|
|
1258
|
+
return {
|
|
1259
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
1260
|
+
structuredContent: { total: claims.length, claims },
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
if (name === 'bclaw_list_agents') {
|
|
1264
|
+
const agents = listAgentIdentities(cwd);
|
|
1265
|
+
const current = resolveCurrentAgentIdentity(cwd);
|
|
1266
|
+
const reputation = args.includeReputation ? buildReputationSnapshot(cwd) : undefined;
|
|
1267
|
+
const reputationById = new Map((reputation?.agents ?? []).map((agent) => [agent.agent_id ?? agent.key, toPublicReputationSummary(agent)]));
|
|
1268
|
+
const structuredAgents = args.includeReputation
|
|
1269
|
+
? agents.map((agent) => ({
|
|
1270
|
+
...agent,
|
|
1271
|
+
reputation: reputationById.get(agent.agent_id),
|
|
1272
|
+
}))
|
|
1273
|
+
: agents;
|
|
1274
|
+
const lines = structuredAgents.length === 0
|
|
1275
|
+
? ['No registered agents.']
|
|
1276
|
+
: [
|
|
1277
|
+
`${structuredAgents.length} registered agent(s):`,
|
|
1278
|
+
...structuredAgents.map((agent) => {
|
|
1279
|
+
const reputation = agent.reputation;
|
|
1280
|
+
const currentLabel = current?.agent_id === agent.agent_id ? ' [current]' : '';
|
|
1281
|
+
const capabilitiesLabel = agent.capabilities.length > 0 ? ` caps=${agent.capabilities.join(',')}` : '';
|
|
1282
|
+
const fingerprintLabel = agent.identity_key ? ` fp=${agent.identity_key.fingerprint.slice(0, 12)}` : '';
|
|
1283
|
+
const reputationLabel = reputation
|
|
1284
|
+
? ` trust=${reputation.internal_trust} cq=${reputation.contribution_quality} rv=${reputation.review_reliability} ct=${reputation.continuity_hygiene}`
|
|
1285
|
+
: '';
|
|
1286
|
+
return `- ${agent.agent_name} (${agent.agent_id}, kind=${agent.kind})${currentLabel}${reputationLabel}${capabilitiesLabel}${fingerprintLabel}`;
|
|
1287
|
+
}),
|
|
1288
|
+
];
|
|
1289
|
+
return {
|
|
1290
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
1291
|
+
structuredContent: {
|
|
1292
|
+
current_agent_id: current?.agent_id,
|
|
1293
|
+
current_agent: current?.agent_name,
|
|
1294
|
+
agents: structuredAgents,
|
|
1295
|
+
},
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
if (name === 'bclaw_list_instructions') {
|
|
1299
|
+
const config = loadConfig(cwd);
|
|
1300
|
+
const project = args.project;
|
|
1301
|
+
const inferredProject = project ?? inferProjectFromTarget(args.path, config);
|
|
1302
|
+
const resolvedAgent = args.resolved ? resolveAgentScope(args.agent) : args.agent;
|
|
1303
|
+
const source = args.resolved
|
|
1304
|
+
? resolveInstructions(loadInstructions(cwd), { project: inferredProject, agent: resolvedAgent })
|
|
1305
|
+
: loadInstructions(cwd);
|
|
1306
|
+
let entries = source;
|
|
1307
|
+
if (args.active) {
|
|
1308
|
+
entries = entries.filter((entry) => entry.active);
|
|
1309
|
+
}
|
|
1310
|
+
if (args.layer) {
|
|
1311
|
+
entries = entries.filter((entry) => entry.layer === args.layer);
|
|
1312
|
+
}
|
|
1313
|
+
if (inferredProject) {
|
|
1314
|
+
entries = entries.filter((entry) => entry.layer !== 'project' || entry.scope === inferredProject);
|
|
1315
|
+
}
|
|
1316
|
+
if (args.agent) {
|
|
1317
|
+
entries = entries.filter((entry) => entry.layer !== 'agent' || entry.scope === args.agent);
|
|
1318
|
+
}
|
|
1319
|
+
const lines = entries.length === 0
|
|
1320
|
+
? ['No instructions found.']
|
|
1321
|
+
: [
|
|
1322
|
+
`${entries.length} instruction(s):`,
|
|
1323
|
+
...entries.map((entry) => {
|
|
1324
|
+
const scope = entry.scope ? `:${entry.scope}` : '';
|
|
1325
|
+
const flags = [entry.layer];
|
|
1326
|
+
if (!entry.active)
|
|
1327
|
+
flags.push('inactive');
|
|
1328
|
+
if (entry.supersedes)
|
|
1329
|
+
flags.push(`supersedes ${entry.supersedes}`);
|
|
1330
|
+
const tags = entry.tags.length ? ` [${entry.tags.join(', ')}]` : '';
|
|
1331
|
+
return `[${entry.id}] <${entry.layer}${scope}> ${entry.text} (${flags.join(' · ')})${tags}`;
|
|
1332
|
+
}),
|
|
1333
|
+
];
|
|
1334
|
+
return {
|
|
1335
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
1336
|
+
structuredContent: { total: entries.length, instructions: entries },
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
if (name === 'bclaw_list_candidates') {
|
|
1340
|
+
const status = String(args.status ?? 'pending').toLowerCase();
|
|
1341
|
+
let candidates = status === 'accepted'
|
|
1342
|
+
? listArchivedCandidates('accepted', cwd)
|
|
1343
|
+
: status === 'rejected'
|
|
1344
|
+
? listArchivedCandidates('rejected', cwd)
|
|
1345
|
+
: status === 'all'
|
|
1346
|
+
? [
|
|
1347
|
+
...listCandidates('pending', cwd),
|
|
1348
|
+
...listArchivedCandidates('accepted', cwd),
|
|
1349
|
+
...listArchivedCandidates('rejected', cwd),
|
|
1350
|
+
]
|
|
1351
|
+
: listCandidates('pending', cwd);
|
|
1352
|
+
if (args.type) {
|
|
1353
|
+
candidates = candidates.filter((candidate) => candidate.type === args.type);
|
|
1354
|
+
}
|
|
1355
|
+
if (args.assignee) {
|
|
1356
|
+
const assignee = String(args.assignee).toLowerCase();
|
|
1357
|
+
candidates = candidates.filter((candidate) => getReviewAssignee(candidate.tags)?.toLowerCase() === assignee);
|
|
1358
|
+
}
|
|
1359
|
+
const lines = candidates.length === 0
|
|
1360
|
+
? ['No candidates found.']
|
|
1361
|
+
: [
|
|
1362
|
+
`${candidates.length} candidate(s):`,
|
|
1363
|
+
...candidates.map((candidate) => {
|
|
1364
|
+
const assignee = getReviewAssignee(candidate.tags);
|
|
1365
|
+
const tags = candidate.tags.length ? ` [${candidate.tags.join(', ')}]` : '';
|
|
1366
|
+
const assigneeLabel = assignee ? ` assignee=${assignee}` : '';
|
|
1367
|
+
return `[${candidate.id}] ${candidate.type}/${candidate.status}${assigneeLabel}: ${candidate.text}${tags}`;
|
|
1368
|
+
}),
|
|
1369
|
+
];
|
|
1370
|
+
return {
|
|
1371
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
1372
|
+
structuredContent: { total: candidates.length, candidates },
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
if (name === 'bclaw_get_capabilities') {
|
|
1376
|
+
const state = loadState(cwd);
|
|
1377
|
+
const capabilities = state.recent_decisions.filter((d) => d.tags.includes('capability'));
|
|
1378
|
+
const filtered = capabilities.filter((cap) => {
|
|
1379
|
+
const categoryFilter = args.category;
|
|
1380
|
+
const tagsFilter = args.tags;
|
|
1381
|
+
if (categoryFilter) {
|
|
1382
|
+
const capCategory = cap.tags.find((t) => t !== 'capability');
|
|
1383
|
+
if (capCategory !== categoryFilter)
|
|
1384
|
+
return false;
|
|
1385
|
+
}
|
|
1386
|
+
if (tagsFilter && tagsFilter.length > 0) {
|
|
1387
|
+
const hasAllTags = tagsFilter.every((tag) => cap.tags.includes(tag));
|
|
1388
|
+
if (!hasAllTags)
|
|
1389
|
+
return false;
|
|
1390
|
+
}
|
|
1391
|
+
return true;
|
|
1392
|
+
});
|
|
1393
|
+
const lines = [`Capabilities (${filtered.length}):`];
|
|
1394
|
+
filtered.forEach((cap) => {
|
|
1395
|
+
const category = cap.tags.find((t) => t !== 'capability') || 'general';
|
|
1396
|
+
const otherTags = cap.tags.filter((t) => t !== 'capability' && t !== category);
|
|
1397
|
+
lines.push(`\n[${cap.id}] ${cap.text.split('\n')[0]}`);
|
|
1398
|
+
lines.push(` Category: ${category}`);
|
|
1399
|
+
lines.push(` Author: ${cap.author}`);
|
|
1400
|
+
if (otherTags.length > 0) {
|
|
1401
|
+
lines.push(` Tags: ${otherTags.join(', ')}`);
|
|
1402
|
+
}
|
|
1403
|
+
});
|
|
1404
|
+
return {
|
|
1405
|
+
content: [{ type: 'text', text: lines.join('\n') || 'No capabilities found.' }],
|
|
1406
|
+
structuredContent: { total: filtered.length, capabilities: filtered },
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
if (name === 'bclaw_list_tools') {
|
|
1410
|
+
const state = loadState(cwd);
|
|
1411
|
+
const tools = state.recent_decisions.filter((d) => d.tags.includes('tool'));
|
|
1412
|
+
const filtered = tools.filter((tool) => {
|
|
1413
|
+
const typeFilter = args.type;
|
|
1414
|
+
const tagsFilter = args.tags;
|
|
1415
|
+
if (typeFilter) {
|
|
1416
|
+
const toolType = tool.tags.find((t) => t !== 'tool');
|
|
1417
|
+
if (toolType !== typeFilter)
|
|
1418
|
+
return false;
|
|
1419
|
+
}
|
|
1420
|
+
if (tagsFilter && tagsFilter.length > 0) {
|
|
1421
|
+
const hasAllTags = tagsFilter.every((tag) => tool.tags.includes(tag));
|
|
1422
|
+
if (!hasAllTags)
|
|
1423
|
+
return false;
|
|
1424
|
+
}
|
|
1425
|
+
return true;
|
|
1426
|
+
});
|
|
1427
|
+
const lines = [`Tools (${filtered.length}):`];
|
|
1428
|
+
filtered.forEach((tool) => {
|
|
1429
|
+
const type = tool.tags.find((t) => t !== 'tool') || 'utility';
|
|
1430
|
+
const otherTags = tool.tags.filter((t) => t !== 'tool' && t !== type);
|
|
1431
|
+
lines.push(`\n[${tool.id}] ${tool.text.split('\n')[0]}`);
|
|
1432
|
+
lines.push(` Type: ${type}`);
|
|
1433
|
+
lines.push(` Author: ${tool.author}`);
|
|
1434
|
+
if (otherTags.length > 0) {
|
|
1435
|
+
lines.push(` Tags: ${otherTags.join(', ')}`);
|
|
1436
|
+
}
|
|
1437
|
+
});
|
|
1438
|
+
return {
|
|
1439
|
+
content: [{ type: 'text', text: lines.join('\n') || 'No tools found.' }],
|
|
1440
|
+
structuredContent: { total: filtered.length, tools: filtered },
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
if (name === 'bclaw_search_tools') {
|
|
1444
|
+
const query = String(args.query ?? '');
|
|
1445
|
+
if (!query) {
|
|
1446
|
+
throw new Error('Missing required argument: query');
|
|
1447
|
+
}
|
|
1448
|
+
const state = loadState(cwd);
|
|
1449
|
+
const tools = state.recent_decisions.filter((d) => d.tags.includes('tool'));
|
|
1450
|
+
const filtered = tools.filter((tool) => {
|
|
1451
|
+
const typeFilter = args.type;
|
|
1452
|
+
const tagsFilter = args.tags;
|
|
1453
|
+
// Type filter
|
|
1454
|
+
if (typeFilter) {
|
|
1455
|
+
const toolType = tool.tags.find((t) => t !== 'tool');
|
|
1456
|
+
if (toolType !== typeFilter)
|
|
1457
|
+
return false;
|
|
1458
|
+
}
|
|
1459
|
+
// Tags filter (all must match)
|
|
1460
|
+
if (tagsFilter && tagsFilter.length > 0) {
|
|
1461
|
+
const hasAllTags = tagsFilter.every((tag) => tool.tags.includes(tag));
|
|
1462
|
+
if (!hasAllTags)
|
|
1463
|
+
return false;
|
|
1464
|
+
}
|
|
1465
|
+
// Query search (name, description, tags)
|
|
1466
|
+
const queryLower = query.toLowerCase();
|
|
1467
|
+
return (tool.text.toLowerCase().includes(queryLower) ||
|
|
1468
|
+
tool.tags.some((tag) => tag.toLowerCase().includes(queryLower)));
|
|
1469
|
+
});
|
|
1470
|
+
const lines = [`Search results for '${query}' (${filtered.length} tool(s)):`];
|
|
1471
|
+
filtered.forEach((tool) => {
|
|
1472
|
+
const type = tool.tags.find((t) => t !== 'tool') || 'utility';
|
|
1473
|
+
lines.push(`\n[${tool.id}] ${tool.text.split('\n')[0]}`);
|
|
1474
|
+
lines.push(` Type: ${type}`);
|
|
1475
|
+
});
|
|
1476
|
+
return {
|
|
1477
|
+
content: [{ type: 'text', text: lines.join('\n') || 'No tools found.' }],
|
|
1478
|
+
structuredContent: { query, total: filtered.length, tools: filtered },
|
|
1479
|
+
};
|
|
1480
|
+
}
|
|
1481
|
+
throw new Error(`Unknown read tool: ${name}`);
|
|
1482
|
+
}
|
|
1483
|
+
export async function executeMcpToolCall(payload) {
|
|
1484
|
+
const { name, args, cwd, connectionSessionId } = payload;
|
|
1485
|
+
try {
|
|
1486
|
+
if (MCP_READ_TOOLS.some((tool) => tool.name === name)) {
|
|
1487
|
+
return {
|
|
1488
|
+
response: toolResponse(handleMcpReadToolCall(name, args, { cwd })),
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
if (name === 'bclaw_setup') {
|
|
1492
|
+
const step = args.step;
|
|
1493
|
+
const choice = args.choice ?? '';
|
|
1494
|
+
const rootsArg = args.roots;
|
|
1495
|
+
const repoSelectionArg = args.repo_selection;
|
|
1496
|
+
const env = process.env;
|
|
1497
|
+
if (!checkGitPresence()) {
|
|
1498
|
+
return { response: toolResponse({ content: [{ type: 'text', text: 'Git is not installed or not found in PATH. Install git from https://git-scm.com before running brainclaw setup.' }], structuredContent: { error: 'git_not_found' } }, true) };
|
|
1499
|
+
}
|
|
1500
|
+
if (!step) {
|
|
1501
|
+
const existingState = readSetupState(env);
|
|
1502
|
+
const alreadyRun = existingState ? `Setup was previously run on ${new Date(existingState.completed_at).toLocaleDateString()}. You can re-run it.` : undefined;
|
|
1503
|
+
return { response: toolResponse({ content: [{ type: 'text', text: [alreadyRun, "Where are the user's project directories? Please ask the user to provide one or more root paths where their git repositories are located (e.g. ~/Projects, C:\\Users\\user\\code)."].filter(Boolean).join('\n\n') }], structuredContent: { pending_question: 'project_roots', prompt: 'Please ask the user: "Where are your projects? Enter one or more root directories (comma-separated):"', ...(alreadyRun ? { already_run: alreadyRun } : {}) } }) };
|
|
1504
|
+
}
|
|
1505
|
+
if (step === 'project_roots') {
|
|
1506
|
+
const roots = parseRoots(choice, env);
|
|
1507
|
+
if (roots.length === 0) {
|
|
1508
|
+
return { response: toolResponse({ content: [{ type: 'text', text: 'No valid directories found from the provided paths. Please ask the user for valid root directories.' }], structuredContent: { error: 'no_valid_roots', provided: choice } }, true) };
|
|
1509
|
+
}
|
|
1510
|
+
const repos = scanGitRepos(roots);
|
|
1511
|
+
const repoList = repos.map((r, i) => ` ${i + 1}) ${r.alreadyInitialised ? '[✔ init]' : '[ ]'} ${r.name} (${r.path})`).join('\n');
|
|
1512
|
+
return { response: toolResponse({ content: [{ type: 'text', text: `Found ${repos.length} repository candidate(s):\n${repoList}\n\nAsk the user which repositories to initialise.` }], structuredContent: { pending_question: 'repo_selection', roots: roots.join(','), repos: repos.map((r) => ({ path: r.path, name: r.name, alreadyInitialised: r.alreadyInitialised })), prompt: 'Please ask the user: "Which repositories to initialise? Reply: (a)ll, (c)urrent, or numbers like 1,3"' } }) };
|
|
1513
|
+
}
|
|
1514
|
+
if (step === 'repo_selection') {
|
|
1515
|
+
if (!rootsArg) {
|
|
1516
|
+
return { response: toolResponse({ content: [{ type: 'text', text: 'Missing roots parameter. Pass the roots value from the previous step.' }], structuredContent: { error: 'missing_roots' } }, true) };
|
|
1517
|
+
}
|
|
1518
|
+
const roots = parseRoots(rootsArg, env);
|
|
1519
|
+
const repos = scanGitRepos(roots);
|
|
1520
|
+
const selectedRepos = parseRepoSelection(choice, repos, cwd);
|
|
1521
|
+
const detected = detectAiAgent(env);
|
|
1522
|
+
const agentList = ALL_KNOWN_AGENTS.map((a, i) => ` ${i + 1}) ${a}${a === detected?.name ? ' ← detected' : ''}`).join('\n');
|
|
1523
|
+
return { response: toolResponse({ content: [{ type: 'text', text: `Selected ${selectedRepos.length} repo(s). Detected AI agent: ${detected?.name ?? 'none'}.\n\nAvailable agents:\n${agentList}\n\nAsk the user which agents to configure.` }], structuredContent: { pending_question: 'agent_selection', roots: rootsArg, repo_selection: choice, selected_repos: selectedRepos.map((r) => ({ path: r.path, name: r.name })), detected_agent: detected?.name ?? null, all_agents: ALL_KNOWN_AGENTS, prompt: 'Please ask the user: "Which agents to configure? Reply: (d)etected, (a)ll, or agent names like claude-code,cursor"' } }) };
|
|
1524
|
+
}
|
|
1525
|
+
if (step === 'agent_selection') {
|
|
1526
|
+
if (!rootsArg || !repoSelectionArg) {
|
|
1527
|
+
return { response: toolResponse({ content: [{ type: 'text', text: 'Missing roots or repo_selection parameter from previous steps.' }], structuredContent: { error: 'missing_params' } }, true) };
|
|
1528
|
+
}
|
|
1529
|
+
const roots = parseRoots(rootsArg, env);
|
|
1530
|
+
const repos = scanGitRepos(roots);
|
|
1531
|
+
const selectedRepos = parseRepoSelection(repoSelectionArg, repos, cwd);
|
|
1532
|
+
const detected = detectAiAgent(env);
|
|
1533
|
+
const selectedAgents = parseAgentSelection(choice, detected?.name);
|
|
1534
|
+
const summary = [];
|
|
1535
|
+
const written = runGlobalInstall(selectedAgents, env);
|
|
1536
|
+
for (const f of written)
|
|
1537
|
+
summary.push(`✔ Global config: ${f}`);
|
|
1538
|
+
const { initialisedRepos, configActions } = await initReposAndConfigureAgents(selectedRepos, selectedAgents, env);
|
|
1539
|
+
for (const p of initialisedRepos)
|
|
1540
|
+
summary.push(`✔ Initialised repo: ${p}`);
|
|
1541
|
+
for (const a of configActions)
|
|
1542
|
+
summary.push(a);
|
|
1543
|
+
let reloadMsg = '✔ Setup complete! Reload your AI agent session to activate brainclaw MCP tools.';
|
|
1544
|
+
if (detected?.name === 'claude-code')
|
|
1545
|
+
reloadMsg += '\n → In VS Code: Cmd/Ctrl+Shift+P → "Claude: Reload MCP Servers"';
|
|
1546
|
+
else if (detected?.name === 'cursor')
|
|
1547
|
+
reloadMsg += '\n → In Cursor: restart the editor';
|
|
1548
|
+
else if (detected?.name === 'windsurf')
|
|
1549
|
+
reloadMsg += '\n → In Windsurf: restart the editor';
|
|
1550
|
+
return { response: toolResponse({ content: [{ type: 'text', text: [reloadMsg, '', ...summary].join('\n') }], structuredContent: { setup_complete: true, initialised_repos: initialisedRepos, global_configs_written: written, agent_configs_written: configActions, detected_agent: detected?.name ?? null, summary } }) };
|
|
1551
|
+
}
|
|
1552
|
+
return { response: toolResponse({ content: [{ type: 'text', text: `Unknown step: "${step}". Valid steps: project_roots, repo_selection, agent_selection.` }], structuredContent: { error: 'unknown_step', step } }, true) };
|
|
1553
|
+
}
|
|
1554
|
+
if (name === 'bclaw_write_note') {
|
|
1555
|
+
const resolved = ensureTrust(args, { nameField: 'agent', idField: 'agentId' }, 'contributor', cwd);
|
|
1556
|
+
if (resolved.error) {
|
|
1557
|
+
return { response: createToolErrorResponse(resolved.error.kind, resolved.error.message, resolved.error.details) };
|
|
1558
|
+
}
|
|
1559
|
+
const text = String(args.text ?? '');
|
|
1560
|
+
const tags = args.tags ?? [];
|
|
1561
|
+
const inputValidation = validateMcpInput(text, tags);
|
|
1562
|
+
if (!inputValidation.ok) {
|
|
1563
|
+
return { response: createToolErrorResponse('validation_error', inputValidation.errors[0]?.message ?? 'Invalid input', inputValidation.errors) };
|
|
1564
|
+
}
|
|
1565
|
+
const identity = resolved.identity;
|
|
1566
|
+
// Cross-project push
|
|
1567
|
+
if (args.crossProject) {
|
|
1568
|
+
try {
|
|
1569
|
+
const link = resolveCrossProjectTarget(String(args.crossProject), cwd);
|
|
1570
|
+
const opIdentity = buildOperationalIdentity(identity.agent_name, cwd, {
|
|
1571
|
+
agentId: identity.agent_id,
|
|
1572
|
+
sessionId: connectionSessionId,
|
|
1573
|
+
});
|
|
1574
|
+
const noteId = generateId('rtn');
|
|
1575
|
+
writeCrossProjectNote(link.absolutePath, {
|
|
1576
|
+
schema_version: 2,
|
|
1577
|
+
id: noteId,
|
|
1578
|
+
agent: opIdentity.agent,
|
|
1579
|
+
agent_id: opIdentity.agent_id,
|
|
1580
|
+
project_id: opIdentity.project_id ?? '',
|
|
1581
|
+
session_id: opIdentity.session_id,
|
|
1582
|
+
text,
|
|
1583
|
+
created_at: nowISO(),
|
|
1584
|
+
tags,
|
|
1585
|
+
visibility: 'shared',
|
|
1586
|
+
host_id: opIdentity.host_id ?? '',
|
|
1587
|
+
note_type: 'observation',
|
|
1588
|
+
}, cwd);
|
|
1589
|
+
return {
|
|
1590
|
+
response: toolResponse({
|
|
1591
|
+
content: [{ type: 'text', text: `✔ Cross-project note pushed to '${link.projectName}' [${noteId}]` }],
|
|
1592
|
+
note_id: noteId,
|
|
1593
|
+
target_project: link.projectName,
|
|
1594
|
+
target_path: link.absolutePath,
|
|
1595
|
+
}),
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
catch (e) {
|
|
1599
|
+
return { response: createToolErrorResponse('validation_error', e instanceof Error ? e.message : String(e)) };
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
const result = createRuntimeNote(text, {
|
|
1603
|
+
agent: identity.agent_name,
|
|
1604
|
+
agentId: identity.agent_id,
|
|
1605
|
+
tag: tags,
|
|
1606
|
+
visibility: args.visibility ?? 'shared',
|
|
1607
|
+
ttl: args.ttl,
|
|
1608
|
+
autoReflect: args.autoReflect,
|
|
1609
|
+
cwd,
|
|
1610
|
+
sessionId: connectionSessionId,
|
|
1611
|
+
}, false);
|
|
1612
|
+
return {
|
|
1613
|
+
response: toolResponse({
|
|
1614
|
+
content: [{ type: 'text', text: `✔ Note created [${result.noteId}]` }],
|
|
1615
|
+
note_id: result.noteId,
|
|
1616
|
+
session_id: result.sessionId,
|
|
1617
|
+
auto_reflect_attempted: result.autoReflectAttempted,
|
|
1618
|
+
detected_type: result.detectedType,
|
|
1619
|
+
candidate_id: result.candidateId,
|
|
1620
|
+
promoted_item_id: result.promotedItemId,
|
|
1621
|
+
skip_reason: result.skipReason,
|
|
1622
|
+
}),
|
|
1623
|
+
nextConnectionSessionId: explicitSessionIdFromEnv() ? undefined : result.sessionId,
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1626
|
+
if (name === 'bclaw_create_candidate') {
|
|
1627
|
+
const resolved = ensureTrust(args, { nameField: 'agent', idField: 'agentId' }, 'contributor', cwd);
|
|
1628
|
+
if (resolved.error) {
|
|
1629
|
+
return { response: createToolErrorResponse(resolved.error.kind, resolved.error.message, resolved.error.details) };
|
|
1630
|
+
}
|
|
1631
|
+
const candidateText = String(args.text ?? '');
|
|
1632
|
+
const candidateTags = args.tags ?? [];
|
|
1633
|
+
const candidateValidation = validateMcpInput(candidateText, candidateTags);
|
|
1634
|
+
if (!candidateValidation.ok) {
|
|
1635
|
+
return { response: createToolErrorResponse('validation_error', candidateValidation.errors[0]?.message ?? 'Invalid input', candidateValidation.errors) };
|
|
1636
|
+
}
|
|
1637
|
+
const resolvedIdentity = resolved.identity;
|
|
1638
|
+
const identity = buildOperationalIdentity(resolvedIdentity.agent_name, cwd, {
|
|
1639
|
+
agentId: resolvedIdentity.agent_id,
|
|
1640
|
+
sessionId: connectionSessionId,
|
|
1641
|
+
});
|
|
1642
|
+
const candId = generateCandidateIdWithLabel(cwd);
|
|
1643
|
+
const type = String(args.type ?? 'decision');
|
|
1644
|
+
const writeThrough = agentCanWriteDirect(identity.agent_id ?? resolvedIdentity.agent_id, cwd);
|
|
1645
|
+
const candidatePlanId = args.planId;
|
|
1646
|
+
const candidate = {
|
|
1647
|
+
id: candId.id,
|
|
1648
|
+
short_label: candId.short_label,
|
|
1649
|
+
type,
|
|
1650
|
+
text: candidateText,
|
|
1651
|
+
created_at: nowISO(),
|
|
1652
|
+
author: identity.agent,
|
|
1653
|
+
author_id: identity.agent_id,
|
|
1654
|
+
project_id: identity.project_id,
|
|
1655
|
+
host_id: identity.host_id,
|
|
1656
|
+
session_id: identity.session_id,
|
|
1657
|
+
tags: candidateTags,
|
|
1658
|
+
status: 'pending',
|
|
1659
|
+
severity: type === 'trap' ? (args.severity ?? 'medium') : undefined,
|
|
1660
|
+
category: type === 'constraint' ? args.category : undefined,
|
|
1661
|
+
outcome: type === 'decision' ? args.outcome : undefined,
|
|
1662
|
+
plan_id: candidatePlanId,
|
|
1663
|
+
star_count: 0,
|
|
1664
|
+
starred_by: [],
|
|
1665
|
+
usage_count: 0,
|
|
1666
|
+
usage_events: [],
|
|
1667
|
+
};
|
|
1668
|
+
const planPrompt = (type === 'decision' || type === 'trap') && !candidatePlanId
|
|
1669
|
+
? `\n💡 Does this ${type} relate to an active plan item? If so, re-run with planId: 'pln_xxx' to link it.`
|
|
1670
|
+
: '';
|
|
1671
|
+
if (writeThrough) {
|
|
1672
|
+
saveCandidate(candidate, cwd);
|
|
1673
|
+
const accepted = acceptCandidate(candId.id, resolvedIdentity.agent_name, cwd, resolvedIdentity.agent_id);
|
|
1674
|
+
appendAuditEntry({ actor: resolvedIdentity.agent_name, actor_id: resolvedIdentity.agent_id, action: 'promote_direct', item_id: candId.id, item_type: type }, cwd);
|
|
1675
|
+
return {
|
|
1676
|
+
response: toolResponse({
|
|
1677
|
+
content: [{ type: 'text', text: `✔ Direct write [${candId.short_label}] (trusted agent)${planPrompt}` }],
|
|
1678
|
+
candidate_id: candId.id,
|
|
1679
|
+
promoted_item_id: accepted.promoted_item_id,
|
|
1680
|
+
write_through: true,
|
|
1681
|
+
}),
|
|
1682
|
+
nextConnectionSessionId: explicitSessionIdFromEnv() ? undefined : identity.session_id,
|
|
1683
|
+
};
|
|
1684
|
+
}
|
|
1685
|
+
saveCandidate(candidate, cwd);
|
|
1686
|
+
appendAuditEntry({ actor: resolvedIdentity.agent_name, actor_id: resolvedIdentity.agent_id, action: 'create', item_id: candId.id, item_type: type }, cwd);
|
|
1687
|
+
return {
|
|
1688
|
+
response: toolResponse({
|
|
1689
|
+
content: [{ type: 'text', text: `✔ Candidate created [${candId.short_label}] (pending review)${planPrompt}` }],
|
|
1690
|
+
candidate_id: candId.id,
|
|
1691
|
+
write_through: false,
|
|
1692
|
+
}),
|
|
1693
|
+
nextConnectionSessionId: explicitSessionIdFromEnv() ? undefined : identity.session_id,
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
1696
|
+
if (name === 'bclaw_accept') {
|
|
1697
|
+
const resolved = ensureTrust({ ...args, by: args.by ?? args.agent, byId: args.byId ?? args.agentId }, { nameField: 'by', idField: 'byId' }, 'trusted', cwd);
|
|
1698
|
+
if (resolved.error) {
|
|
1699
|
+
return { response: createToolErrorResponse(resolved.error.kind, resolved.error.message, resolved.error.details) };
|
|
1700
|
+
}
|
|
1701
|
+
const candId = String(args.id ?? '').trim();
|
|
1702
|
+
if (!candId) {
|
|
1703
|
+
return { response: createToolErrorResponse('validation_error', 'Missing required argument: id') };
|
|
1704
|
+
}
|
|
1705
|
+
const accepted = acceptCandidate(candId, resolved.identity.agent_name, cwd, resolved.identity.agent_id);
|
|
1706
|
+
return {
|
|
1707
|
+
response: toolResponse({
|
|
1708
|
+
content: [{ type: 'text', text: `✔ Accepted [${candId}]` }],
|
|
1709
|
+
candidate_id: candId,
|
|
1710
|
+
promoted_item_id: accepted.promoted_item_id,
|
|
1711
|
+
}),
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
if (name === 'bclaw_reject') {
|
|
1715
|
+
const resolved = ensureTrust({ ...args, by: args.by ?? args.agent, byId: args.byId ?? args.agentId }, { nameField: 'by', idField: 'byId' }, 'trusted', cwd);
|
|
1716
|
+
if (resolved.error) {
|
|
1717
|
+
return { response: createToolErrorResponse(resolved.error.kind, resolved.error.message, resolved.error.details) };
|
|
1718
|
+
}
|
|
1719
|
+
const candId = String(args.id ?? '').trim();
|
|
1720
|
+
if (!candId) {
|
|
1721
|
+
return { response: createToolErrorResponse('validation_error', 'Missing required argument: id') };
|
|
1722
|
+
}
|
|
1723
|
+
rejectCandidate(candId, args.reason, resolved.identity.agent_name, cwd, resolved.identity.agent_id);
|
|
1724
|
+
return {
|
|
1725
|
+
response: toolResponse({
|
|
1726
|
+
content: [{ type: 'text', text: `✔ Rejected [${candId}]` }],
|
|
1727
|
+
candidate_id: candId,
|
|
1728
|
+
}),
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1731
|
+
if (name === 'bclaw_claim') {
|
|
1732
|
+
const storeTarget = args.store ?? 'local';
|
|
1733
|
+
const claimCwd = resolveTargetStore(cwd, storeTarget);
|
|
1734
|
+
const resolved = ensureTrust(args, { nameField: 'agent', idField: 'agentId' }, 'contributor', claimCwd);
|
|
1735
|
+
if (resolved.error) {
|
|
1736
|
+
return { response: createToolErrorResponse(resolved.error.kind, resolved.error.message, resolved.error.details) };
|
|
1737
|
+
}
|
|
1738
|
+
const claimScope = String(args.scope ?? '').trim();
|
|
1739
|
+
const claimDescription = String(args.description ?? '').trim();
|
|
1740
|
+
const scopeCheck = validateMcpField(claimScope, 'scope');
|
|
1741
|
+
if (!scopeCheck.ok) {
|
|
1742
|
+
return { response: createToolErrorResponse('validation_error', scopeCheck.message) };
|
|
1743
|
+
}
|
|
1744
|
+
const descCheck = validateMcpField(claimDescription, 'description');
|
|
1745
|
+
if (!descCheck.ok) {
|
|
1746
|
+
return { response: createToolErrorResponse('validation_error', descCheck.message) };
|
|
1747
|
+
}
|
|
1748
|
+
const resolvedIdentity = resolved.identity;
|
|
1749
|
+
const identity = buildOperationalIdentity(resolvedIdentity.agent_name, claimCwd, {
|
|
1750
|
+
agentId: resolvedIdentity.agent_id,
|
|
1751
|
+
sessionId: connectionSessionId,
|
|
1752
|
+
});
|
|
1753
|
+
const claimId = generateClaimId();
|
|
1754
|
+
saveClaim({
|
|
1755
|
+
id: claimId,
|
|
1756
|
+
agent: identity.agent,
|
|
1757
|
+
agent_id: identity.agent_id,
|
|
1758
|
+
project_id: identity.project_id,
|
|
1759
|
+
host_id: identity.host_id,
|
|
1760
|
+
session_id: identity.session_id,
|
|
1761
|
+
scope: claimScope,
|
|
1762
|
+
description: claimDescription,
|
|
1763
|
+
created_at: nowISO(),
|
|
1764
|
+
status: 'active',
|
|
1765
|
+
plan_id: args.planId,
|
|
1766
|
+
}, claimCwd);
|
|
1767
|
+
appendAuditEntry({ actor: resolvedIdentity.agent_name, actor_id: resolvedIdentity.agent_id, action: 'claim', item_id: claimId, item_type: 'claim' }, claimCwd);
|
|
1768
|
+
const postClaimItems = getTriggeredItems('trigger:post-claim', claimCwd);
|
|
1769
|
+
const postClaimText = renderTriggeredItems(postClaimItems);
|
|
1770
|
+
const noPlanWarn = !args.planId
|
|
1771
|
+
? '\n⚠ No plan item linked to this claim. Run bclaw_create_plan first and pass planId to track this work formally.'
|
|
1772
|
+
: '';
|
|
1773
|
+
const claimText = `✔ Claimed scope [${claimId}]${noPlanWarn}${postClaimText ? `\n${postClaimText}` : ''}`;
|
|
1774
|
+
return {
|
|
1775
|
+
response: toolResponse({
|
|
1776
|
+
content: [{ type: 'text', text: claimText }],
|
|
1777
|
+
claim_id: claimId,
|
|
1778
|
+
session_id: identity.session_id,
|
|
1779
|
+
triggered_items: postClaimItems,
|
|
1780
|
+
}),
|
|
1781
|
+
nextConnectionSessionId: explicitSessionIdFromEnv() ? undefined : identity.session_id,
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
if (name === 'bclaw_release_claim') {
|
|
1785
|
+
const claimId = String(args.id ?? '').trim();
|
|
1786
|
+
if (!claimId) {
|
|
1787
|
+
return { response: createToolErrorResponse('validation_error', 'Missing required argument: id') };
|
|
1788
|
+
}
|
|
1789
|
+
let claimObj;
|
|
1790
|
+
try {
|
|
1791
|
+
claimObj = loadClaim(claimId, cwd);
|
|
1792
|
+
}
|
|
1793
|
+
catch {
|
|
1794
|
+
return { response: createToolErrorResponse('not_found', `Claim not found: ${claimId}`) };
|
|
1795
|
+
}
|
|
1796
|
+
saveClaim({ ...claimObj, status: 'released', released_at: nowISO() }, cwd);
|
|
1797
|
+
appendAuditEntry({ actor: claimObj.agent, action: 'release_claim', item_id: claimId, item_type: 'claim' }, cwd);
|
|
1798
|
+
const releasePlanStatus = args.planStatus;
|
|
1799
|
+
let releasePlanUpdated = false;
|
|
1800
|
+
if (releasePlanStatus && claimObj.plan_id) {
|
|
1801
|
+
const releaseState = loadState(cwd);
|
|
1802
|
+
const releasePlan = releaseState.plan_items.find((item) => item.id === claimObj.plan_id);
|
|
1803
|
+
if (releasePlan) {
|
|
1804
|
+
const ts = nowISO();
|
|
1805
|
+
releasePlan.status = releasePlanStatus;
|
|
1806
|
+
if (releasePlanStatus === 'in_progress' && !releasePlan.started_at)
|
|
1807
|
+
releasePlan.started_at = ts;
|
|
1808
|
+
if (releasePlanStatus === 'done' && !releasePlan.completed_at)
|
|
1809
|
+
releasePlan.completed_at = ts;
|
|
1810
|
+
releasePlan.updated_at = ts;
|
|
1811
|
+
saveState(releaseState, cwd);
|
|
1812
|
+
writeFileAtomic(memoryPath('project.md', cwd), generateMarkdown(releaseState, cwd));
|
|
1813
|
+
releasePlanUpdated = true;
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
return {
|
|
1817
|
+
response: toolResponse({
|
|
1818
|
+
content: [{ type: 'text', text: `✔ Released claim [${claimId}]${releasePlanUpdated ? ` — plan ${claimObj.plan_id} → ${releasePlanStatus}` : ''}` }],
|
|
1819
|
+
claim_id: claimId,
|
|
1820
|
+
...(releasePlanUpdated ? { plan_id: claimObj.plan_id, plan_status: releasePlanStatus } : {}),
|
|
1821
|
+
}),
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1824
|
+
if (name === 'bclaw_session_start') {
|
|
1825
|
+
const resolved = ensureTrust(args, { nameField: 'agent', idField: 'agentId' }, 'contributor', cwd);
|
|
1826
|
+
if (resolved.error) {
|
|
1827
|
+
return { response: createToolErrorResponse(resolved.error.kind, resolved.error.message, resolved.error.details) };
|
|
1828
|
+
}
|
|
1829
|
+
const result = startSession({
|
|
1830
|
+
agent: resolved.identity?.agent_name,
|
|
1831
|
+
agentId: resolved.identity?.agent_id,
|
|
1832
|
+
context: args.context,
|
|
1833
|
+
cwd,
|
|
1834
|
+
});
|
|
1835
|
+
const postSessionStartItems = getTriggeredItems('trigger:post-session-start', cwd);
|
|
1836
|
+
const postSessionStartText = renderTriggeredItems(postSessionStartItems);
|
|
1837
|
+
const sessionStartMsg = postSessionStartText
|
|
1838
|
+
? `✔ Session started\n${postSessionStartText}`
|
|
1839
|
+
: '✔ Session started';
|
|
1840
|
+
const contentParts = [{ type: 'text', text: sessionStartMsg }];
|
|
1841
|
+
const structured = {
|
|
1842
|
+
session_id: result.session_id,
|
|
1843
|
+
agent: result.agent,
|
|
1844
|
+
context_target: result.context_target,
|
|
1845
|
+
};
|
|
1846
|
+
if (args.includeContext) {
|
|
1847
|
+
const ctxResult = buildContext({
|
|
1848
|
+
target: args.context,
|
|
1849
|
+
agent: resolved.identity?.agent_name,
|
|
1850
|
+
profile: args.contextProfile,
|
|
1851
|
+
cwd,
|
|
1852
|
+
});
|
|
1853
|
+
const format = normaliseFormat(args.contextFormat);
|
|
1854
|
+
const ctxText = renderContextForMcp(ctxResult, format, {});
|
|
1855
|
+
contentParts.push({ type: 'text', text: ctxText || 'No relevant memory found.' });
|
|
1856
|
+
structured.context = ctxResult;
|
|
1857
|
+
}
|
|
1858
|
+
if (args.includeBoard) {
|
|
1859
|
+
const board = buildCoordinationSnapshot({
|
|
1860
|
+
agent: resolved.identity?.agent_name,
|
|
1861
|
+
autoAcknowledge: true,
|
|
1862
|
+
cwd,
|
|
1863
|
+
});
|
|
1864
|
+
const boardLines = [];
|
|
1865
|
+
boardLines.push(`Active plans: ${board.active_plans.length}`);
|
|
1866
|
+
for (const plan of board.active_plans.slice(0, 10)) {
|
|
1867
|
+
const claims = plan.claims.length ? ` claims=${plan.claims.map((c) => c.agent).join(',')}` : '';
|
|
1868
|
+
boardLines.push(`- [${plan.id}] ${plan.text} (${plan.status}, ${plan.priority})${claims}`);
|
|
1869
|
+
}
|
|
1870
|
+
boardLines.push(`Active claims: ${board.active_claims.length}`);
|
|
1871
|
+
for (const claim of board.active_claims.slice(0, 10)) {
|
|
1872
|
+
boardLines.push(`- [${claim.id}] ${claim.agent} -> ${claim.scope}`);
|
|
1873
|
+
}
|
|
1874
|
+
boardLines.push(`Open handoffs: ${board.open_handoffs.length}`);
|
|
1875
|
+
for (const handoff of board.open_handoffs.slice(0, 5)) {
|
|
1876
|
+
boardLines.push(`- [${handoff.id}] ${handoff.from} -> ${handoff.to}: ${handoff.text}`);
|
|
1877
|
+
}
|
|
1878
|
+
contentParts.push({ type: 'text', text: boardLines.join('\n') });
|
|
1879
|
+
structured.board = board;
|
|
1880
|
+
}
|
|
1881
|
+
return {
|
|
1882
|
+
response: toolResponse({
|
|
1883
|
+
content: contentParts,
|
|
1884
|
+
...structured,
|
|
1885
|
+
}),
|
|
1886
|
+
nextConnectionSessionId: explicitSessionIdFromEnv() ? undefined : result.session_id,
|
|
1887
|
+
};
|
|
1888
|
+
}
|
|
1889
|
+
if (name === 'bclaw_session_end') {
|
|
1890
|
+
const resolved = ensureTrust(args, { nameField: 'agent', idField: 'agentId' }, 'contributor', cwd);
|
|
1891
|
+
if (resolved.error) {
|
|
1892
|
+
return { response: createToolErrorResponse(resolved.error.kind, resolved.error.message, resolved.error.details) };
|
|
1893
|
+
}
|
|
1894
|
+
const result = endSession({
|
|
1895
|
+
session: args.session,
|
|
1896
|
+
agent: resolved.identity?.agent_name,
|
|
1897
|
+
agentId: resolved.identity?.agent_id,
|
|
1898
|
+
summary: args.summary,
|
|
1899
|
+
autoReflect: args.autoReflect,
|
|
1900
|
+
cwd,
|
|
1901
|
+
});
|
|
1902
|
+
const preSessionEndItems = getTriggeredItems('trigger:pre-session-end', cwd);
|
|
1903
|
+
const preSessionEndText = renderTriggeredItems(preSessionEndItems);
|
|
1904
|
+
const sessionEndMsg = preSessionEndText
|
|
1905
|
+
? `✔ Session ended\n${preSessionEndText}`
|
|
1906
|
+
: '✔ Session ended';
|
|
1907
|
+
return {
|
|
1908
|
+
response: toolResponse({
|
|
1909
|
+
content: [{ type: 'text', text: sessionEndMsg }],
|
|
1910
|
+
session_id: result.session_id,
|
|
1911
|
+
notes_in_session: result.notes_in_session,
|
|
1912
|
+
candidates_created: result.candidates_created,
|
|
1913
|
+
context_diff: result.context_diff,
|
|
1914
|
+
triggered_items: preSessionEndItems,
|
|
1915
|
+
}),
|
|
1916
|
+
nextConnectionSessionId: undefined,
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1919
|
+
if (name === 'bclaw_create_plan') {
|
|
1920
|
+
const resolved = ensureTrust(args, { nameField: 'agent', idField: 'agentId' }, 'contributor', cwd);
|
|
1921
|
+
if (resolved.error) {
|
|
1922
|
+
return { response: createToolErrorResponse(resolved.error.kind, resolved.error.message, resolved.error.details) };
|
|
1923
|
+
}
|
|
1924
|
+
const planText = String(args.text ?? '').trim();
|
|
1925
|
+
if (!planText) {
|
|
1926
|
+
return { response: createToolErrorResponse('validation_error', 'Missing required argument: text') };
|
|
1927
|
+
}
|
|
1928
|
+
const textCheck = validateMcpField(planText, 'text');
|
|
1929
|
+
if (!textCheck.ok) {
|
|
1930
|
+
return { response: createToolErrorResponse('validation_error', textCheck.message) };
|
|
1931
|
+
}
|
|
1932
|
+
let estimatedEffort;
|
|
1933
|
+
if (args.estimate !== undefined) {
|
|
1934
|
+
const n = Number(args.estimate);
|
|
1935
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
1936
|
+
return { response: createToolErrorResponse('validation_error', 'estimate must be a positive integer (minutes)') };
|
|
1937
|
+
}
|
|
1938
|
+
estimatedEffort = n;
|
|
1939
|
+
}
|
|
1940
|
+
const state = loadState(cwd);
|
|
1941
|
+
const { id, short_label } = generateIdWithLabel('plan_items');
|
|
1942
|
+
const timestamp = nowISO();
|
|
1943
|
+
const planType = args.type;
|
|
1944
|
+
const entry = {
|
|
1945
|
+
id,
|
|
1946
|
+
short_label,
|
|
1947
|
+
text: planText,
|
|
1948
|
+
type: planType,
|
|
1949
|
+
created_at: timestamp,
|
|
1950
|
+
updated_at: timestamp,
|
|
1951
|
+
author: resolved.identity.agent_name,
|
|
1952
|
+
status: 'todo',
|
|
1953
|
+
priority: args.priority ?? 'medium',
|
|
1954
|
+
assignee: args.assignee,
|
|
1955
|
+
tags: args.tags ?? [],
|
|
1956
|
+
depends_on: [],
|
|
1957
|
+
estimated_effort: estimatedEffort,
|
|
1958
|
+
};
|
|
1959
|
+
state.plan_items.push(entry);
|
|
1960
|
+
saveState(state, cwd);
|
|
1961
|
+
writeFileAtomic(memoryPath('project.md', cwd), generateMarkdown(state, cwd));
|
|
1962
|
+
appendAuditEntry({ actor: resolved.identity.agent_name, actor_id: resolved.identity.agent_id, action: 'create', item_id: id, item_type: 'plan' }, cwd);
|
|
1963
|
+
return {
|
|
1964
|
+
response: toolResponse({
|
|
1965
|
+
content: [{ type: 'text', text: `✔ Plan item added: [${id}] ${planText}` }],
|
|
1966
|
+
plan_id: id,
|
|
1967
|
+
}),
|
|
1968
|
+
};
|
|
1969
|
+
}
|
|
1970
|
+
if (name === 'bclaw_update_plan') {
|
|
1971
|
+
const resolved = ensureTrust(args, { nameField: 'agent', idField: 'agentId' }, 'contributor', cwd);
|
|
1972
|
+
if (resolved.error) {
|
|
1973
|
+
return { response: createToolErrorResponse(resolved.error.kind, resolved.error.message, resolved.error.details) };
|
|
1974
|
+
}
|
|
1975
|
+
const planId = String(args.id ?? '').trim();
|
|
1976
|
+
if (!planId) {
|
|
1977
|
+
return { response: createToolErrorResponse('validation_error', 'Missing required argument: id') };
|
|
1978
|
+
}
|
|
1979
|
+
const state = loadState(cwd);
|
|
1980
|
+
const plan = state.plan_items.find((item) => item.id === planId || item.short_label === planId);
|
|
1981
|
+
if (!plan) {
|
|
1982
|
+
return { response: createToolErrorResponse('not_found', `Plan item '${planId}' not found`) };
|
|
1983
|
+
}
|
|
1984
|
+
const timestamp = nowISO();
|
|
1985
|
+
if (args.status) {
|
|
1986
|
+
plan.status = args.status;
|
|
1987
|
+
if (args.status === 'in_progress' && !plan.started_at)
|
|
1988
|
+
plan.started_at = timestamp;
|
|
1989
|
+
if (args.status === 'done' && !plan.completed_at)
|
|
1990
|
+
plan.completed_at = timestamp;
|
|
1991
|
+
}
|
|
1992
|
+
if (args.assignee !== undefined)
|
|
1993
|
+
plan.assignee = args.assignee;
|
|
1994
|
+
if (args.priority)
|
|
1995
|
+
plan.priority = args.priority;
|
|
1996
|
+
if (args.actualEffort)
|
|
1997
|
+
plan.actual_effort = args.actualEffort;
|
|
1998
|
+
plan.updated_at = timestamp;
|
|
1999
|
+
saveState(state, cwd);
|
|
2000
|
+
writeFileAtomic(memoryPath('project.md', cwd), generateMarkdown(state, cwd));
|
|
2001
|
+
appendAuditEntry({ actor: resolved.identity.agent_name, actor_id: resolved.identity.agent_id, action: 'update', item_id: plan.id, item_type: 'plan' }, cwd);
|
|
2002
|
+
return {
|
|
2003
|
+
response: toolResponse({
|
|
2004
|
+
content: [{ type: 'text', text: `✔ Plan item updated: [${plan.id}] ${plan.text}` }],
|
|
2005
|
+
plan_id: plan.id,
|
|
2006
|
+
status: plan.status,
|
|
2007
|
+
}),
|
|
2008
|
+
};
|
|
2009
|
+
}
|
|
2010
|
+
if (name === 'bclaw_add_step') {
|
|
2011
|
+
const resolved = ensureTrust(args, { nameField: 'agent', idField: 'agentId' }, 'contributor', cwd);
|
|
2012
|
+
if (resolved.error) {
|
|
2013
|
+
return { response: createToolErrorResponse(resolved.error.kind, resolved.error.message, resolved.error.details) };
|
|
2014
|
+
}
|
|
2015
|
+
const stepPlanId = String(args.planId ?? '').trim();
|
|
2016
|
+
const stepText = String(args.text ?? '').trim();
|
|
2017
|
+
if (!stepPlanId)
|
|
2018
|
+
return { response: createToolErrorResponse('validation_error', 'Missing required argument: planId') };
|
|
2019
|
+
if (!stepText)
|
|
2020
|
+
return { response: createToolErrorResponse('validation_error', 'Missing required argument: text') };
|
|
2021
|
+
const state = loadState(cwd);
|
|
2022
|
+
const plan = state.plan_items.find((p) => p.id === stepPlanId || p.short_label === stepPlanId);
|
|
2023
|
+
if (!plan)
|
|
2024
|
+
return { response: createToolErrorResponse('not_found', `Plan '${stepPlanId}' not found`) };
|
|
2025
|
+
const step = {
|
|
2026
|
+
id: generateId('plan_steps'),
|
|
2027
|
+
text: stepText,
|
|
2028
|
+
status: 'todo',
|
|
2029
|
+
assignee: args.assignee,
|
|
2030
|
+
created_at: nowISO(),
|
|
2031
|
+
updated_at: nowISO(),
|
|
2032
|
+
};
|
|
2033
|
+
plan.steps = [...(plan.steps ?? []), step];
|
|
2034
|
+
plan.updated_at = nowISO();
|
|
2035
|
+
saveState(state, cwd);
|
|
2036
|
+
writeFileAtomic(memoryPath('project.md', cwd), generateMarkdown(state, cwd));
|
|
2037
|
+
const total = plan.steps.length;
|
|
2038
|
+
const done = plan.steps.filter((s) => s.status === 'done').length;
|
|
2039
|
+
return {
|
|
2040
|
+
response: toolResponse({
|
|
2041
|
+
content: [{ type: 'text', text: `✔ Step added: [${step.id}] ${stepText} (${done}/${total} done)` }],
|
|
2042
|
+
step_id: step.id,
|
|
2043
|
+
plan_id: plan.id,
|
|
2044
|
+
progress: { done, total },
|
|
2045
|
+
}),
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
2048
|
+
if (name === 'bclaw_complete_step') {
|
|
2049
|
+
const resolved = ensureTrust(args, { nameField: 'agent', idField: 'agentId' }, 'contributor', cwd);
|
|
2050
|
+
if (resolved.error) {
|
|
2051
|
+
return { response: createToolErrorResponse(resolved.error.kind, resolved.error.message, resolved.error.details) };
|
|
2052
|
+
}
|
|
2053
|
+
const csPlanId = String(args.planId ?? '').trim();
|
|
2054
|
+
const csStepId = String(args.stepId ?? '').trim();
|
|
2055
|
+
if (!csPlanId)
|
|
2056
|
+
return { response: createToolErrorResponse('validation_error', 'Missing required argument: planId') };
|
|
2057
|
+
if (!csStepId)
|
|
2058
|
+
return { response: createToolErrorResponse('validation_error', 'Missing required argument: stepId') };
|
|
2059
|
+
const state = loadState(cwd);
|
|
2060
|
+
const plan = state.plan_items.find((p) => p.id === csPlanId || p.short_label === csPlanId);
|
|
2061
|
+
if (!plan)
|
|
2062
|
+
return { response: createToolErrorResponse('not_found', `Plan '${csPlanId}' not found`) };
|
|
2063
|
+
const step = (plan.steps ?? []).find((s) => s.id === csStepId);
|
|
2064
|
+
if (!step)
|
|
2065
|
+
return { response: createToolErrorResponse('not_found', `Step '${csStepId}' not found in plan '${csPlanId}'`) };
|
|
2066
|
+
step.status = 'done';
|
|
2067
|
+
step.updated_at = nowISO();
|
|
2068
|
+
plan.updated_at = nowISO();
|
|
2069
|
+
saveState(state, cwd);
|
|
2070
|
+
writeFileAtomic(memoryPath('project.md', cwd), generateMarkdown(state, cwd));
|
|
2071
|
+
const total = plan.steps.length;
|
|
2072
|
+
const done = plan.steps.filter((s) => s.status === 'done').length;
|
|
2073
|
+
return {
|
|
2074
|
+
response: toolResponse({
|
|
2075
|
+
content: [{ type: 'text', text: `✔ Step completed: [${step.id}] ${step.text} (${done}/${total} done)` }],
|
|
2076
|
+
step_id: step.id,
|
|
2077
|
+
plan_id: plan.id,
|
|
2078
|
+
progress: { done, total },
|
|
2079
|
+
all_done: done === total,
|
|
2080
|
+
}),
|
|
2081
|
+
};
|
|
2082
|
+
}
|
|
2083
|
+
if (name === 'bclaw_delete_memory') {
|
|
2084
|
+
const resolved = ensureTrust(args, { nameField: 'agent', idField: 'agentId' }, 'trusted', cwd);
|
|
2085
|
+
if (resolved.error) {
|
|
2086
|
+
return { response: createToolErrorResponse(resolved.error.kind, resolved.error.message, resolved.error.details) };
|
|
2087
|
+
}
|
|
2088
|
+
const itemId = String(args.id ?? '').trim();
|
|
2089
|
+
const itemType = String(args.type ?? '').trim();
|
|
2090
|
+
if (!itemId)
|
|
2091
|
+
return { response: createToolErrorResponse('validation_error', 'Missing required argument: id') };
|
|
2092
|
+
if (!itemType)
|
|
2093
|
+
return { response: createToolErrorResponse('validation_error', 'Missing required argument: type') };
|
|
2094
|
+
if (!['constraint', 'decision', 'trap'].includes(itemType)) {
|
|
2095
|
+
return { response: createToolErrorResponse('validation_error', `Invalid type: ${itemType}`) };
|
|
2096
|
+
}
|
|
2097
|
+
// Walk store chain to find the item
|
|
2098
|
+
const chain = resolveStoreChain(cwd);
|
|
2099
|
+
let foundStore;
|
|
2100
|
+
for (const store of chain) {
|
|
2101
|
+
const state = loadState(store.cwd);
|
|
2102
|
+
const found = (itemType === 'constraint' && state.active_constraints.some((c) => c.id === itemId || c.short_label === itemId)) ||
|
|
2103
|
+
(itemType === 'decision' && state.recent_decisions.some((d) => d.id === itemId || d.short_label === itemId)) ||
|
|
2104
|
+
(itemType === 'trap' && state.known_traps.some((t) => t.id === itemId || t.short_label === itemId));
|
|
2105
|
+
if (found) {
|
|
2106
|
+
foundStore = store;
|
|
2107
|
+
break;
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
if (!foundStore) {
|
|
2111
|
+
return { response: createToolErrorResponse('not_found', `${itemType} with id '${itemId}' not found in any store`) };
|
|
2112
|
+
}
|
|
2113
|
+
// Delete from the found store
|
|
2114
|
+
const state = loadState(foundStore.cwd);
|
|
2115
|
+
const beforeCount = itemType === 'constraint' ? state.active_constraints.length :
|
|
2116
|
+
itemType === 'decision' ? state.recent_decisions.length :
|
|
2117
|
+
state.known_traps.length;
|
|
2118
|
+
if (itemType === 'constraint') {
|
|
2119
|
+
state.active_constraints = state.active_constraints.filter((c) => c.id !== itemId && c.short_label !== itemId);
|
|
2120
|
+
}
|
|
2121
|
+
else if (itemType === 'decision') {
|
|
2122
|
+
state.recent_decisions = state.recent_decisions.filter((d) => d.id !== itemId && d.short_label !== itemId);
|
|
2123
|
+
}
|
|
2124
|
+
else if (itemType === 'trap') {
|
|
2125
|
+
state.known_traps = state.known_traps.filter((t) => t.id !== itemId && t.short_label !== itemId);
|
|
2126
|
+
}
|
|
2127
|
+
saveState(state, foundStore.cwd);
|
|
2128
|
+
writeFileAtomic(memoryPath('project.md', foundStore.cwd), generateMarkdown(state, foundStore.cwd));
|
|
2129
|
+
appendAuditEntry({ actor: resolved.identity.agent_name, actor_id: resolved.identity.agent_id, action: 'delete', item_id: itemId, item_type: itemType }, foundStore.cwd);
|
|
2130
|
+
return {
|
|
2131
|
+
response: toolResponse({
|
|
2132
|
+
content: [{ type: 'text', text: `✔ Deleted [${itemId}] (${itemType})` }],
|
|
2133
|
+
deleted_id: itemId,
|
|
2134
|
+
item_type: itemType,
|
|
2135
|
+
store_level: foundStore.role,
|
|
2136
|
+
}),
|
|
2137
|
+
};
|
|
2138
|
+
}
|
|
2139
|
+
if (name === 'bclaw_update_memory') {
|
|
2140
|
+
const resolved = ensureTrust(args, { nameField: 'agent', idField: 'agentId' }, 'trusted', cwd);
|
|
2141
|
+
if (resolved.error) {
|
|
2142
|
+
return { response: createToolErrorResponse(resolved.error.kind, resolved.error.message, resolved.error.details) };
|
|
2143
|
+
}
|
|
2144
|
+
const itemId = String(args.id ?? '').trim();
|
|
2145
|
+
const itemType = String(args.type ?? '').trim();
|
|
2146
|
+
const newText = args.text ? String(args.text).trim() : undefined;
|
|
2147
|
+
const newTags = Array.isArray(args.tags) ? args.tags.map((t) => String(t).trim()) : undefined;
|
|
2148
|
+
const newStatus = args.status ? String(args.status).trim() : undefined;
|
|
2149
|
+
const moveToStore = args.moveToStore ? String(args.moveToStore).trim() : undefined;
|
|
2150
|
+
if (!itemId)
|
|
2151
|
+
return { response: createToolErrorResponse('validation_error', 'Missing required argument: id') };
|
|
2152
|
+
if (!itemType)
|
|
2153
|
+
return { response: createToolErrorResponse('validation_error', 'Missing required argument: type') };
|
|
2154
|
+
if (!['constraint', 'decision', 'trap'].includes(itemType)) {
|
|
2155
|
+
return { response: createToolErrorResponse('validation_error', `Invalid type for update: ${itemType}`) };
|
|
2156
|
+
}
|
|
2157
|
+
if (!newText && !newTags && !newStatus && !moveToStore) {
|
|
2158
|
+
return { response: createToolErrorResponse('validation_error', 'At least one of text, tags, status, or moveToStore must be provided') };
|
|
2159
|
+
}
|
|
2160
|
+
if (moveToStore && !['local', 'repo', 'workspace', 'user'].includes(moveToStore)) {
|
|
2161
|
+
return { response: createToolErrorResponse('validation_error', `Invalid moveToStore target: ${moveToStore}`) };
|
|
2162
|
+
}
|
|
2163
|
+
if (newStatus && itemType !== 'trap') {
|
|
2164
|
+
return { response: createToolErrorResponse('validation_error', 'status updates are only supported for traps') };
|
|
2165
|
+
}
|
|
2166
|
+
if (newStatus && !['active', 'resolved', 'expired'].includes(newStatus)) {
|
|
2167
|
+
return { response: createToolErrorResponse('validation_error', `Invalid trap status: ${newStatus}`) };
|
|
2168
|
+
}
|
|
2169
|
+
// Walk store chain to find the item
|
|
2170
|
+
const chain = resolveStoreChain(cwd);
|
|
2171
|
+
let sourceStore;
|
|
2172
|
+
let item;
|
|
2173
|
+
for (const store of chain) {
|
|
2174
|
+
const state = loadState(store.cwd);
|
|
2175
|
+
if (itemType === 'constraint') {
|
|
2176
|
+
item = state.active_constraints.find((c) => c.id === itemId || c.short_label === itemId);
|
|
2177
|
+
}
|
|
2178
|
+
else if (itemType === 'decision') {
|
|
2179
|
+
item = state.recent_decisions.find((d) => d.id === itemId || d.short_label === itemId);
|
|
2180
|
+
}
|
|
2181
|
+
else if (itemType === 'trap') {
|
|
2182
|
+
item = state.known_traps.find((t) => t.id === itemId || t.short_label === itemId);
|
|
2183
|
+
}
|
|
2184
|
+
if (item) {
|
|
2185
|
+
sourceStore = store;
|
|
2186
|
+
break;
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
if (!sourceStore || !item) {
|
|
2190
|
+
return { response: createToolErrorResponse('not_found', `${itemType} with id '${itemId}' not found in any store`) };
|
|
2191
|
+
}
|
|
2192
|
+
const previousStore = sourceStore.role;
|
|
2193
|
+
// Update text and tags
|
|
2194
|
+
if (newText)
|
|
2195
|
+
item.text = newText;
|
|
2196
|
+
if (newTags)
|
|
2197
|
+
item.tags = newTags;
|
|
2198
|
+
if (newStatus && itemType === 'trap')
|
|
2199
|
+
item.status = newStatus;
|
|
2200
|
+
// Handle moveToStore
|
|
2201
|
+
if (moveToStore) {
|
|
2202
|
+
const targetCwd = resolveTargetStore(cwd, moveToStore);
|
|
2203
|
+
// Delete from source store
|
|
2204
|
+
const sourceState = loadState(sourceStore.cwd);
|
|
2205
|
+
if (itemType === 'constraint') {
|
|
2206
|
+
sourceState.active_constraints = sourceState.active_constraints.filter((c) => c.id !== itemId);
|
|
2207
|
+
}
|
|
2208
|
+
else if (itemType === 'decision') {
|
|
2209
|
+
sourceState.recent_decisions = sourceState.recent_decisions.filter((d) => d.id !== itemId);
|
|
2210
|
+
}
|
|
2211
|
+
else if (itemType === 'trap') {
|
|
2212
|
+
sourceState.known_traps = sourceState.known_traps.filter((t) => t.id !== itemId);
|
|
2213
|
+
}
|
|
2214
|
+
saveState(sourceState, sourceStore.cwd);
|
|
2215
|
+
writeFileAtomic(memoryPath('project.md', sourceStore.cwd), generateMarkdown(sourceState, sourceStore.cwd));
|
|
2216
|
+
// Add to target store
|
|
2217
|
+
const targetState = loadState(targetCwd);
|
|
2218
|
+
if (itemType === 'constraint') {
|
|
2219
|
+
targetState.active_constraints.push(item);
|
|
2220
|
+
}
|
|
2221
|
+
else if (itemType === 'decision') {
|
|
2222
|
+
targetState.recent_decisions.push(item);
|
|
2223
|
+
}
|
|
2224
|
+
else if (itemType === 'trap') {
|
|
2225
|
+
targetState.known_traps.push(item);
|
|
2226
|
+
}
|
|
2227
|
+
saveState(targetState, targetCwd);
|
|
2228
|
+
writeFileAtomic(memoryPath('project.md', targetCwd), generateMarkdown(targetState, targetCwd));
|
|
2229
|
+
}
|
|
2230
|
+
else {
|
|
2231
|
+
// Just update in place
|
|
2232
|
+
const state = loadState(sourceStore.cwd);
|
|
2233
|
+
if (itemType === 'constraint') {
|
|
2234
|
+
const idx = state.active_constraints.findIndex((c) => c.id === itemId);
|
|
2235
|
+
if (idx >= 0)
|
|
2236
|
+
state.active_constraints[idx] = item;
|
|
2237
|
+
}
|
|
2238
|
+
else if (itemType === 'decision') {
|
|
2239
|
+
const idx = state.recent_decisions.findIndex((d) => d.id === itemId);
|
|
2240
|
+
if (idx >= 0)
|
|
2241
|
+
state.recent_decisions[idx] = item;
|
|
2242
|
+
}
|
|
2243
|
+
else if (itemType === 'trap') {
|
|
2244
|
+
const idx = state.known_traps.findIndex((t) => t.id === itemId);
|
|
2245
|
+
if (idx >= 0)
|
|
2246
|
+
state.known_traps[idx] = item;
|
|
2247
|
+
}
|
|
2248
|
+
saveState(state, sourceStore.cwd);
|
|
2249
|
+
writeFileAtomic(memoryPath('project.md', sourceStore.cwd), generateMarkdown(state, sourceStore.cwd));
|
|
2250
|
+
}
|
|
2251
|
+
appendAuditEntry({ actor: resolved.identity.agent_name, actor_id: resolved.identity.agent_id, action: 'update', item_id: itemId, item_type: itemType }, sourceStore.cwd);
|
|
2252
|
+
return {
|
|
2253
|
+
response: toolResponse({
|
|
2254
|
+
content: [{ type: 'text', text: `✔ Updated [${itemId}] (${itemType})` }],
|
|
2255
|
+
updated_id: itemId,
|
|
2256
|
+
item_type: itemType,
|
|
2257
|
+
previous_store: previousStore,
|
|
2258
|
+
new_store: moveToStore,
|
|
2259
|
+
}),
|
|
2260
|
+
};
|
|
2261
|
+
}
|
|
2262
|
+
return {
|
|
2263
|
+
response: createToolErrorResponse('unknown_tool', `Unknown tool: ${name}`),
|
|
2264
|
+
};
|
|
2265
|
+
}
|
|
2266
|
+
catch (error) {
|
|
2267
|
+
return {
|
|
2268
|
+
response: createToolErrorResponse('command_error', error instanceof Error ? error.message : String(error)),
|
|
2269
|
+
};
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
//# sourceMappingURL=mcp.js.map
|