brainclaw 0.29.2 → 1.5.4

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.
Files changed (197) hide show
  1. package/LICENSE +21 -74
  2. package/README.md +199 -176
  3. package/dist/brainclaw-vscode.vsix +0 -0
  4. package/dist/cli.js +710 -25
  5. package/dist/commands/accept.js +3 -0
  6. package/dist/commands/add-step.js +11 -26
  7. package/dist/commands/agent-board.js +70 -3
  8. package/dist/commands/audit.js +19 -0
  9. package/dist/commands/check-policy.js +54 -0
  10. package/dist/commands/check-security-mcp.js +145 -0
  11. package/dist/commands/check-security.js +106 -0
  12. package/dist/commands/claim-resource.js +1 -0
  13. package/dist/commands/codev.js +672 -0
  14. package/dist/commands/compact.js +74 -0
  15. package/dist/commands/complete-step.js +16 -26
  16. package/dist/commands/constraint.js +8 -20
  17. package/dist/commands/decision.js +9 -20
  18. package/dist/commands/delete-plan.js +10 -12
  19. package/dist/commands/delete-step.js +16 -0
  20. package/dist/commands/dispatch.js +163 -0
  21. package/dist/commands/doctor.js +1122 -49
  22. package/dist/commands/enable-agent.js +1 -0
  23. package/dist/commands/export.js +280 -22
  24. package/dist/commands/handoff.js +33 -0
  25. package/dist/commands/harvest.js +189 -0
  26. package/dist/commands/hooks.js +82 -25
  27. package/dist/commands/inbox.js +169 -0
  28. package/dist/commands/init.js +38 -31
  29. package/dist/commands/install-hooks.js +71 -44
  30. package/dist/commands/link.js +89 -0
  31. package/dist/commands/list-claims.js +48 -3
  32. package/dist/commands/list-plans.js +129 -25
  33. package/dist/commands/loops-handlers.js +409 -0
  34. package/dist/commands/mcp-read-handlers.js +1628 -0
  35. package/dist/commands/mcp-schemas.generated.js +269 -0
  36. package/dist/commands/mcp.js +4224 -1501
  37. package/dist/commands/plan-resource.js +64 -0
  38. package/dist/commands/plan.js +12 -26
  39. package/dist/commands/prune.js +37 -2
  40. package/dist/commands/reflect.js +20 -7
  41. package/dist/commands/release-claim.js +11 -6
  42. package/dist/commands/release-notes.js +170 -0
  43. package/dist/commands/repair.js +210 -0
  44. package/dist/commands/run-profile.js +57 -0
  45. package/dist/commands/sequence.js +113 -0
  46. package/dist/commands/session-end.js +423 -14
  47. package/dist/commands/session-start.js +214 -41
  48. package/dist/commands/setup-security.js +103 -0
  49. package/dist/commands/setup.js +42 -4
  50. package/dist/commands/stale.js +109 -0
  51. package/dist/commands/switch.js +100 -2
  52. package/dist/commands/trap.js +14 -31
  53. package/dist/commands/update-handoff.js +63 -4
  54. package/dist/commands/update-plan.js +21 -28
  55. package/dist/commands/update-step.js +37 -0
  56. package/dist/commands/upgrade.js +313 -6
  57. package/dist/commands/usage.js +102 -0
  58. package/dist/commands/version.js +20 -0
  59. package/dist/commands/who.js +33 -5
  60. package/dist/commands/worktree.js +105 -0
  61. package/dist/core/actions.js +315 -0
  62. package/dist/core/agent-capability.js +610 -17
  63. package/dist/core/agent-context.js +7 -1
  64. package/dist/core/agent-files.js +1169 -85
  65. package/dist/core/agent-integrations.js +160 -5
  66. package/dist/core/agent-inventory.js +2 -0
  67. package/dist/core/agent-profiles.js +93 -0
  68. package/dist/core/agent-registry.js +162 -30
  69. package/dist/core/agentrun-reconciler.js +345 -0
  70. package/dist/core/agentruns.js +424 -0
  71. package/dist/core/ai-agent-detection.js +31 -10
  72. package/dist/core/archival.js +77 -0
  73. package/dist/core/assignment-sweeper.js +82 -0
  74. package/dist/core/assignments.js +367 -0
  75. package/dist/core/audit.js +30 -0
  76. package/dist/core/brainclaw-version.js +94 -2
  77. package/dist/core/candidates.js +93 -2
  78. package/dist/core/claims.js +419 -0
  79. package/dist/core/codev-metrics.js +77 -0
  80. package/dist/core/codev-personas.js +31 -0
  81. package/dist/core/codev-plan-gen.js +35 -0
  82. package/dist/core/codev-prompts.js +74 -0
  83. package/dist/core/codev-responses.js +62 -0
  84. package/dist/core/codev-rounds.js +218 -0
  85. package/dist/core/config.js +4 -0
  86. package/dist/core/context.js +381 -34
  87. package/dist/core/coordination.js +201 -6
  88. package/dist/core/cross-project.js +230 -16
  89. package/dist/core/default-profiles/doctor.yaml +11 -0
  90. package/dist/core/default-profiles/janitor.yaml +11 -0
  91. package/dist/core/default-profiles/onboarder.yaml +11 -0
  92. package/dist/core/default-profiles/reviewer.yaml +13 -0
  93. package/dist/core/dispatcher.js +1189 -0
  94. package/dist/core/duplicates.js +2 -2
  95. package/dist/core/entity-operations.js +450 -0
  96. package/dist/core/entity-registry.js +344 -0
  97. package/dist/core/events.js +106 -2
  98. package/dist/core/execution-adapters.js +154 -0
  99. package/dist/core/execution-context.js +63 -0
  100. package/dist/core/execution-profile.js +270 -0
  101. package/dist/core/execution.js +255 -0
  102. package/dist/core/facade-schema.js +81 -0
  103. package/dist/core/federation-cloud.js +99 -0
  104. package/dist/core/federation-message.js +52 -0
  105. package/dist/core/federation-transport.js +65 -0
  106. package/dist/core/gc-semantic.js +482 -0
  107. package/dist/core/governance.js +247 -0
  108. package/dist/core/guards.js +19 -0
  109. package/dist/core/ideation.js +72 -0
  110. package/dist/core/identity.js +110 -25
  111. package/dist/core/ids.js +6 -0
  112. package/dist/core/input-validation.js +2 -2
  113. package/dist/core/instruction-templates.js +344 -136
  114. package/dist/core/io.js +90 -11
  115. package/dist/core/lock.js +6 -2
  116. package/dist/core/loops/brief-assembly.js +213 -0
  117. package/dist/core/loops/facade-schema.js +148 -0
  118. package/dist/core/loops/index.js +7 -0
  119. package/dist/core/loops/iteration-engine.js +139 -0
  120. package/dist/core/loops/lock.js +385 -0
  121. package/dist/core/loops/store.js +201 -0
  122. package/dist/core/loops/types.js +403 -0
  123. package/dist/core/loops/verbs.js +534 -0
  124. package/dist/core/markdown.js +15 -3
  125. package/dist/core/memory-compactor.js +432 -0
  126. package/dist/core/memory-git.js +152 -8
  127. package/dist/core/messaging.js +278 -0
  128. package/dist/core/migration.js +32 -1
  129. package/dist/core/mutation-pipeline.js +4 -2
  130. package/dist/core/operations/memory-mutation.js +129 -0
  131. package/dist/core/operations/memory-write.js +78 -0
  132. package/dist/core/operations/plan.js +190 -0
  133. package/dist/core/policy.js +169 -0
  134. package/dist/core/reputation.js +9 -3
  135. package/dist/core/schema.js +491 -6
  136. package/dist/core/search.js +21 -2
  137. package/dist/core/security-cache.js +71 -0
  138. package/dist/core/security-guard.js +152 -0
  139. package/dist/core/security-scoring.js +86 -0
  140. package/dist/core/sequence.js +130 -0
  141. package/dist/core/socket-client.js +113 -0
  142. package/dist/core/staleness.js +246 -0
  143. package/dist/core/state.js +98 -22
  144. package/dist/core/store-resolution.js +43 -11
  145. package/dist/core/toml-writer.js +76 -0
  146. package/dist/core/upgrades/backup.js +232 -0
  147. package/dist/core/upgrades/health-check.js +169 -0
  148. package/dist/core/upgrades/patches/candidate-archive.js +145 -0
  149. package/dist/core/upgrades/patches/handoff-review-strip.js +128 -0
  150. package/dist/core/upgrades/patches/provenance-rollout.js +136 -0
  151. package/dist/core/upgrades/schema-version.js +97 -0
  152. package/dist/core/worktree.js +606 -0
  153. package/dist/facts.js +114 -0
  154. package/dist/facts.json +111 -0
  155. package/docs/architecture/project-refs.md +5 -1
  156. package/docs/cli.md +690 -43
  157. package/docs/concepts/ideation-loop.md +317 -0
  158. package/docs/concepts/loop-engine.md +456 -0
  159. package/docs/concepts/mcp-governance.md +268 -0
  160. package/docs/concepts/memory-staleness.md +122 -0
  161. package/docs/concepts/multi-agent-workflows.md +166 -0
  162. package/docs/concepts/plans-and-claims.md +31 -6
  163. package/docs/concepts/project-md-convention.md +35 -0
  164. package/docs/concepts/troubleshooting.md +220 -0
  165. package/docs/concepts/upgrade-cli.md +202 -0
  166. package/docs/concepts/upgrade-dogfood-procedure.md +114 -0
  167. package/docs/context-format-changelog.md +2 -2
  168. package/docs/context-format.md +2 -2
  169. package/docs/index.md +68 -0
  170. package/docs/integrations/agents.md +15 -16
  171. package/docs/integrations/cline.md +88 -0
  172. package/docs/integrations/codex.md +75 -23
  173. package/docs/integrations/continue.md +60 -0
  174. package/docs/integrations/copilot.md +67 -9
  175. package/docs/integrations/kilocode.md +72 -0
  176. package/docs/integrations/mcp.md +304 -21
  177. package/docs/integrations/mistral-vibe.md +122 -0
  178. package/docs/integrations/opencode.md +84 -0
  179. package/docs/integrations/overview.md +23 -8
  180. package/docs/integrations/roo.md +74 -0
  181. package/docs/integrations/windsurf.md +83 -0
  182. package/docs/mcp-schema-changelog.md +191 -1
  183. package/docs/playbooks/integration/index.md +121 -0
  184. package/docs/playbooks/productivity/index.md +102 -0
  185. package/docs/playbooks/team/index.md +122 -0
  186. package/docs/product/agent-first-model.md +184 -0
  187. package/docs/product/entity-model-audit.md +462 -0
  188. package/docs/product/positioning.md +10 -10
  189. package/docs/quickstart-existing-project.md +135 -0
  190. package/docs/quickstart.md +124 -37
  191. package/docs/release-maintenance.md +79 -0
  192. package/docs/review.md +2 -0
  193. package/docs/server-operations.md +118 -0
  194. package/package.json +21 -13
  195. package/dist/commands/claude-desktop-extension.js +0 -18
  196. package/dist/commands/diff.js +0 -99
  197. package/dist/core/claude-desktop-extension.js +0 -224
@@ -0,0 +1,1628 @@
1
+ import { applyBootstrapImport, renderBootstrapInterview, renderBootstrapSummary, runBootstrapProfile, uninstallBootstrapImport } from '../core/bootstrap.js';
2
+ import { buildAgentToolingContext, renderAgentToolingSummary } from '../core/agent-context.js';
3
+ import { buildCoordinationSnapshot } from '../core/coordination.js';
4
+ import { scanDescendantPlans } from './list-plans.js';
5
+ import { buildContext } from '../core/context.js';
6
+ import { buildExecutionContext, renderExecutionContextSummary } from '../core/execution-context.js';
7
+ import { checkBrainclawInstallableUpdate, renderBrainclawInstallableUpdateNotice } from '../core/brainclaw-version.js';
8
+ import { loadConfig } from '../core/config.js';
9
+ import { loadAllSessions, loadCurrentSession, saveCurrentSession, gcStaleSessions } from '../core/identity.js';
10
+ import { loadState } from '../core/state.js';
11
+ import { listArchivedCandidates, listCandidates, resolvedSource } from '../core/candidates.js';
12
+ import { listClaims, assessClaimLiveness } from '../core/claims.js';
13
+ import { listAssignments } from '../core/assignments.js';
14
+ import { listAgentRuns } from '../core/agentruns.js';
15
+ import { reconcileAgentRun } from '../core/agentrun-reconciler.js';
16
+ import { listActionRequired } from '../core/actions.js';
17
+ import { queryRuntimeEvents } from '../core/events.js';
18
+ import { listSequences, getActiveSequence } from '../core/sequence.js';
19
+ import { resolveCurrentHostId } from '../core/host.js';
20
+ import { listAgentIdentities, resolveAgentScope, resolveCurrentAgentIdentity, resolveCurrentAgentName, } from '../core/agent-registry.js';
21
+ import { readAuditLog } from '../core/audit.js';
22
+ import { readInbox, getThread } from '../core/messaging.js';
23
+ import { analyzeSequence } from '../core/dispatcher.js';
24
+ import { checkPolicy } from '../core/policy.js';
25
+ import { buildGovernanceReport, renderGovernanceMarkdown } from '../core/governance.js';
26
+ import { inferProjectFromTarget, loadInstructions, resolveInstructions } from '../core/instructions.js';
27
+ import { buildReputationSnapshot, toPublicReputationSummary } from '../core/reputation.js';
28
+ import { search } from '../core/search.js';
29
+ import { buildEstimationReport } from './estimation-report.js';
30
+ import { runDoctor } from './doctor.js';
31
+ import { buildProjectDiscovery, saveDiscoveryProfile, loadDiscoveryProfile, renderDiscoverySummary } from '../core/project-discovery.js';
32
+ import { listCapabilities, listTools as listRegistryTools } from '../core/registries.js';
33
+ import { listAvailableProjects, switchProject } from './switch.js';
34
+ import { resolveEffectiveCwd } from '../core/store-resolution.js';
35
+ import { resolveProjectCwd } from '../core/cross-project.js';
36
+ import { readUnseenEvents, buildNotificationSummary } from '../core/event-log.js';
37
+ import { BootstrapInterviewAnswerSchema, AssignmentStatusSchema, AgentRunStatusSchema, AgentRunTransportSchema, ActionRequiredStatusSchema, ActionRequiredKindSchema } from '../core/schema.js';
38
+ import { SCHEMA_VERSION, createToolErrorResponse, normaliseFormat, renderContextForMcp, } from './mcp.js';
39
+ function normalizeBootstrapInterviewAnswersArg(value) {
40
+ if (!Array.isArray(value)) {
41
+ return [];
42
+ }
43
+ return value.map((entry) => BootstrapInterviewAnswerSchema.parse(entry));
44
+ }
45
+ /** Validate a string enum filter or return undefined. Throws on invalid. */
46
+ function validateEnumFilter(value, schema, label) {
47
+ if (value === undefined || value === null || value === '')
48
+ return undefined;
49
+ const str = String(value);
50
+ const result = schema.safeParse(str);
51
+ if (!result.success) {
52
+ throw new Error(`Invalid ${label}: '${str}'`);
53
+ }
54
+ return str;
55
+ }
56
+ function normalizeBootstrapInterviewAudienceArg(value) {
57
+ if (value === 'cli' || value === 'ide_chat' || value === 'any') {
58
+ return value;
59
+ }
60
+ return 'any';
61
+ }
62
+ function getReviewAssignee(tags) {
63
+ for (const tag of tags) {
64
+ if (tag.startsWith('assignee:')) {
65
+ return tag.slice('assignee:'.length).trim() || undefined;
66
+ }
67
+ }
68
+ return undefined;
69
+ }
70
+ export function handleMcpReadToolCall(name, args = {}, context = {}) {
71
+ let cwd = context.cwd ?? resolveEffectiveCwd();
72
+ // If a project param is provided, resolve it to an actual cwd override.
73
+ // resolveProjectCwd unifies cross_project_links (siblings/peers) AND
74
+ // workspace store-chain children. Throws on unknown project — surfaces
75
+ // visibly as a tool error rather than silently falling back to the
76
+ // current project, which would mislead the caller.
77
+ const projectArg = args.project;
78
+ if (projectArg) {
79
+ cwd = resolveProjectCwd(projectArg, cwd);
80
+ }
81
+ if (name === 'bclaw_get_context') {
82
+ const result = buildContext({
83
+ target: args.path,
84
+ project: projectArg,
85
+ agent: args.agent,
86
+ host: args.host,
87
+ allHosts: args.allHosts,
88
+ profile: args.profile,
89
+ includePending: args.includePending,
90
+ maxItems: args.maxItems,
91
+ maxChars: args.maxChars,
92
+ digest: args.digest,
93
+ sinceSession: args.since_session,
94
+ bootstrap: args.bootstrap,
95
+ refreshBootstrap: args.refreshBootstrap,
96
+ cwd,
97
+ });
98
+ // Load available capabilities and tools from dedicated registries
99
+ const capabilities = listCapabilities(cwd);
100
+ const tools = listRegistryTools(cwd);
101
+ const format = normaliseFormat(args.format);
102
+ const content = renderContextForMcp(result, format, {
103
+ explain: args.explain,
104
+ compactTemplate: args.compactTemplate,
105
+ });
106
+ // Add metadata discovery section to content
107
+ let enrichedContent = content;
108
+ if (capabilities.length > 0 || tools.length > 0) {
109
+ const suggestions = [];
110
+ if (capabilities.length > 0) {
111
+ suggestions.push(`\n## Available Capabilities (${capabilities.length})`);
112
+ capabilities.slice(0, 5).forEach((cap) => {
113
+ suggestions.push(`- [${cap.id}] ${cap.name} (${cap.category})`);
114
+ });
115
+ if (capabilities.length > 5) {
116
+ suggestions.push(`- ... and ${capabilities.length - 5} more`);
117
+ }
118
+ }
119
+ if (tools.length > 0) {
120
+ suggestions.push(`\n## Available Tools (${tools.length})`);
121
+ tools.slice(0, 5).forEach((tool) => {
122
+ suggestions.push(`- [${tool.id}] ${tool.name} (${tool.type})`);
123
+ });
124
+ if (tools.length > 5) {
125
+ suggestions.push(`- ... and ${tools.length - 5} more`);
126
+ }
127
+ }
128
+ suggestions.push('\n💡 Tip: Use bclaw_get_capabilities, bclaw_list_tools, or bclaw_search_tools for detailed discovery');
129
+ enrichedContent = content + suggestions.join('\n');
130
+ }
131
+ // Check for unseen events from other agents
132
+ const agentName = args.agent ?? resolveCurrentAgentName(cwd);
133
+ const unseenEvents = readUnseenEvents(agentName, cwd);
134
+ const notifications = buildNotificationSummary(unseenEvents);
135
+ return {
136
+ content: [{ type: 'text', text: enrichedContent || 'No relevant memory found.' }],
137
+ structuredContent: {
138
+ ...result,
139
+ available_capabilities: capabilities.map((cap) => ({
140
+ id: cap.id,
141
+ name: cap.name,
142
+ category: cap.category,
143
+ })),
144
+ available_tools: tools.map((tool) => ({
145
+ id: tool.id,
146
+ name: tool.name,
147
+ type: tool.type,
148
+ })),
149
+ ...(notifications ? { pending_notifications: notifications, unseen_event_count: unseenEvents.length } : {}),
150
+ },
151
+ };
152
+ }
153
+ if (name === 'bclaw_read_handoff') {
154
+ const handoffId = String(args.id ?? '').trim();
155
+ if (!handoffId) {
156
+ throw new Error('Missing required argument: id');
157
+ }
158
+ const state = loadState(cwd);
159
+ const handoff = state.open_handoffs.find((item) => item.id === handoffId || item.short_label === handoffId);
160
+ if (!handoff) {
161
+ return {
162
+ content: [{ type: 'text', text: `Handoff not found: ${handoffId}` }],
163
+ structuredContent: { handoff_id: handoffId, found: false, schema_version: SCHEMA_VERSION },
164
+ };
165
+ }
166
+ const lines = [
167
+ `Handoff ${handoff.short_label ?? handoff.id}`,
168
+ `From: ${handoff.from}`,
169
+ `To: ${handoff.to}`,
170
+ `Status: ${handoff.status}`,
171
+ `Created: ${handoff.created_at}`,
172
+ '',
173
+ handoff.text,
174
+ ];
175
+ if (handoff.narrative) {
176
+ lines.push('', 'Narrative:', handoff.narrative);
177
+ }
178
+ if (handoff.review) {
179
+ lines.push('', 'Review:');
180
+ if (handoff.review.reviewer)
181
+ lines.push(`Reviewer: ${handoff.review.reviewer}`);
182
+ if (handoff.review.verdict)
183
+ lines.push(`Verdict: ${handoff.review.verdict}`);
184
+ if (handoff.review.reviewed_by)
185
+ lines.push(`Reviewed by: ${handoff.review.reviewed_by}`);
186
+ if (handoff.review.summary)
187
+ lines.push(`Summary: ${handoff.review.summary}`);
188
+ for (const issue of handoff.review.blocking_issues ?? []) {
189
+ lines.push(`Blocking issue: ${issue}`);
190
+ }
191
+ for (const suggestion of handoff.review.suggestions ?? []) {
192
+ lines.push(`Suggestion: ${suggestion}`);
193
+ }
194
+ }
195
+ if (handoff.snapshot?.diff) {
196
+ lines.push('', 'Uncommitted Git Diff:', handoff.snapshot.diff);
197
+ }
198
+ return {
199
+ content: [{ type: 'text', text: lines.join('\n') }],
200
+ structuredContent: { handoff, schema_version: SCHEMA_VERSION },
201
+ };
202
+ }
203
+ if (name === 'bclaw_bootstrap') {
204
+ const interviewAnswers = normalizeBootstrapInterviewAnswersArg(args.interviewAnswers);
205
+ if (args.apply && args.uninstall) {
206
+ throw new Error('bclaw_bootstrap does not allow apply and uninstall at the same time.');
207
+ }
208
+ if (args.uninstall) {
209
+ const result = uninstallBootstrapImport(cwd);
210
+ const text = !result.receipt
211
+ ? 'No bootstrap import receipt found.'
212
+ : `Bootstrap uninstall completed: ${result.deactivatedCount} instruction(s) deactivated, ${result.deletedCount} artifact(s) deleted, ${result.skippedCount} artifact(s) skipped.`;
213
+ return {
214
+ content: [{ type: 'text', text }],
215
+ structuredContent: {
216
+ receipt: result.receipt,
217
+ deactivated_count: result.deactivatedCount,
218
+ deleted_count: result.deletedCount,
219
+ skipped_count: result.skippedCount,
220
+ },
221
+ };
222
+ }
223
+ if (args.apply) {
224
+ const applied = applyBootstrapImport({
225
+ target: args.target,
226
+ refresh: args.refresh,
227
+ interviewAnswers,
228
+ cwd,
229
+ });
230
+ return {
231
+ content: [{
232
+ type: 'text',
233
+ text: `Bootstrap import applied: ${applied.createdCount} item(s) created, ${applied.skippedCount} suggestion(s) skipped.`,
234
+ }],
235
+ structuredContent: {
236
+ created_count: applied.createdCount,
237
+ skipped_count: applied.skippedCount,
238
+ receipt: applied.receipt,
239
+ import_plan: applied.proposal,
240
+ },
241
+ };
242
+ }
243
+ const result = runBootstrapProfile({
244
+ target: args.target,
245
+ refresh: args.refresh,
246
+ interviewAnswers,
247
+ cwd,
248
+ });
249
+ const audience = normalizeBootstrapInterviewAudienceArg(args.audience);
250
+ const text = args.interview
251
+ ? renderBootstrapInterview(result, audience)
252
+ : renderBootstrapSummary(result);
253
+ // Extract top-level suggested questions for conversational bootstrap (step 11)
254
+ const suggestedQuestions = result.importPlan.interview?.questions?.map((q) => ({
255
+ id: q.id,
256
+ prompt: q.prompt,
257
+ rationale: q.rationale,
258
+ priority: q.priority,
259
+ })) ?? [];
260
+ // Separate auto-imports (high confidence) from proposals (need discussion)
261
+ const autoImports = result.importPlan.suggestions.filter((s) => s.confidence === 'high');
262
+ const proposals = result.importPlan.suggestions.filter((s) => s.confidence !== 'high');
263
+ return {
264
+ content: [{ type: 'text', text }],
265
+ structuredContent: {
266
+ summary: result.profile.summary,
267
+ target: result.profile.target,
268
+ repo_fingerprint: result.profile.repo_fingerprint,
269
+ sources_scanned: result.profile.sources_scanned,
270
+ workspace_kind: result.profile.workspace_kind,
271
+ onboarding_mode: result.profile.onboarding_mode,
272
+ confidence: result.profile.confidence,
273
+ native_instruction_files: result.profile.native_instruction_files,
274
+ gaps: result.profile.gaps,
275
+ seed_count: result.profile.seed_count,
276
+ seeds: result.seeds,
277
+ import_plan: result.importPlan,
278
+ auto_imports: autoImports,
279
+ proposals,
280
+ suggested_questions: suggestedQuestions,
281
+ last_application: result.lastApplication,
282
+ reused_profile: result.reusedProfile,
283
+ },
284
+ };
285
+ }
286
+ if (name === 'bclaw_get_execution_context') {
287
+ const executionContext = buildExecutionContext({ cwd });
288
+ const config = loadConfig(cwd);
289
+ const installableUpdate = checkBrainclawInstallableUpdate(config, cwd, { useDefaultNpmSource: true });
290
+ const installableUpdateNotice = renderBrainclawInstallableUpdateNotice(installableUpdate);
291
+ const agentTooling = args.includeAgentTooling ? buildAgentToolingContext({ cwd }) : undefined;
292
+ const text = [
293
+ renderExecutionContextSummary(executionContext, true),
294
+ ...(installableUpdateNotice ? ['', installableUpdateNotice] : []),
295
+ ...(agentTooling ? ['', renderAgentToolingSummary(agentTooling)] : []),
296
+ ].join('\n');
297
+ return {
298
+ content: [{ type: 'text', text }],
299
+ structuredContent: {
300
+ execution_context: executionContext,
301
+ installable_update: {
302
+ ...installableUpdate,
303
+ ...(installableUpdate.agent_release_notes
304
+ ? { agent_release_notes: installableUpdate.agent_release_notes }
305
+ : {}),
306
+ },
307
+ ...(agentTooling ? { agent_tooling: agentTooling } : {}),
308
+ },
309
+ };
310
+ }
311
+ if (name === 'bclaw_release_notes') {
312
+ const config = loadConfig(cwd);
313
+ const updateCheck = checkBrainclawInstallableUpdate(config, cwd, { useDefaultNpmSource: true });
314
+ const arn = updateCheck.agent_release_notes;
315
+ const lines = [];
316
+ if (arn) {
317
+ lines.push(`Version: ${updateCheck.latest_installable_version ?? 'unknown'}`);
318
+ lines.push(`Summary: ${arn.summary}`);
319
+ if (arn.agent_relevance)
320
+ lines.push(`Agent relevance: ${arn.agent_relevance}`);
321
+ lines.push(`Breaking risk: ${arn.breaking_risk ?? 'none'}`);
322
+ if (arn.recommended_for && arn.recommended_for.length > 0) {
323
+ lines.push(`Recommended for: ${arn.recommended_for.join(', ')}`);
324
+ }
325
+ if (arn.highlights && arn.highlights.length > 0) {
326
+ lines.push('Highlights:');
327
+ for (const h of arn.highlights)
328
+ lines.push(` • ${h}`);
329
+ }
330
+ if (arn.action_recommendation)
331
+ lines.push(`Action: ${arn.action_recommendation}`);
332
+ }
333
+ else if (updateCheck.release_notes) {
334
+ lines.push(`Version: ${updateCheck.latest_installable_version ?? 'unknown'}`);
335
+ lines.push(updateCheck.release_notes);
336
+ }
337
+ else {
338
+ lines.push('No agent release notes available for the configured update source.');
339
+ if (updateCheck.status === 'not_configured') {
340
+ lines.push('Configure brainclaw_update_source in your project config to enable update checks.');
341
+ }
342
+ }
343
+ return {
344
+ content: [{ type: 'text', text: lines.join('\n') }],
345
+ structuredContent: {
346
+ status: updateCheck.status,
347
+ latest_installable_version: updateCheck.latest_installable_version,
348
+ agent_release_notes: arn ?? null,
349
+ release_notes: updateCheck.release_notes ?? null,
350
+ },
351
+ };
352
+ }
353
+ if (name === 'bclaw_get_agent_board_summary') {
354
+ const config = loadConfig(cwd);
355
+ const state = loadState(cwd);
356
+ const agent = args.agent ?? resolveCurrentAgentName(cwd);
357
+ const currentHost = resolveCurrentHostId();
358
+ const activeClaims = listClaims(cwd).filter((c) => c.status === 'active');
359
+ const pendingActions = listActionRequired(cwd).filter((a) => a.status === 'pending');
360
+ const agents = listAgentIdentities(cwd);
361
+ const sessions = loadAllSessions(cwd);
362
+ const activeSequence = getActiveSequence(cwd);
363
+ const sequenceTodoCount = activeSequence
364
+ ? activeSequence.items.filter((item) => {
365
+ const plan = state.plan_items.find((p) => p.id === item.planId || p.short_label === item.planId);
366
+ return !plan || plan.status === 'todo';
367
+ }).length
368
+ : 0;
369
+ const summary = {
370
+ project_id: config.project_id,
371
+ agent,
372
+ current_host: currentHost,
373
+ attention_required: pendingActions.length,
374
+ in_progress: activeClaims.length,
375
+ plans: {
376
+ in_progress: state.plan_items.filter((p) => p.status === 'in_progress').length,
377
+ todo: state.plan_items.filter((p) => p.status === 'todo').length,
378
+ },
379
+ traps: {
380
+ high: state.known_traps.filter((t) => t.severity === 'high').length,
381
+ total: state.known_traps.length,
382
+ },
383
+ agents: agents.length,
384
+ sessions: sessions.length,
385
+ sequences: {
386
+ active_name: activeSequence?.name ?? null,
387
+ todo_count: sequenceTodoCount,
388
+ },
389
+ };
390
+ return {
391
+ content: [{ type: 'text', text: JSON.stringify(summary, null, 2) }],
392
+ structuredContent: summary,
393
+ };
394
+ }
395
+ if (name === 'bclaw_get_agent_board') {
396
+ const board = buildCoordinationSnapshot({
397
+ agent: args.agent,
398
+ project: args.project,
399
+ target: args.path,
400
+ host: args.host,
401
+ allHosts: args.allHosts,
402
+ includeReputation: args.includeReputation,
403
+ includeSessionMeta: args.includeSessionMeta,
404
+ autoAcknowledge: true,
405
+ cwd,
406
+ });
407
+ const lines = [];
408
+ lines.push(`Agent board${board.agent ? ` for ${board.agent}` : ''}${board.project ? ` (${board.project})` : ''}`);
409
+ lines.push('');
410
+ if (board.project_id)
411
+ lines.push(`Project ID: ${board.project_id}`);
412
+ if (board.agent && board.agent_id)
413
+ lines.push(`Agent ID: ${board.agent_id}`);
414
+ lines.push(`Current host: ${board.current_host}`);
415
+ if (board.all_hosts)
416
+ lines.push('Host filter: all-hosts');
417
+ else if (board.host_filter)
418
+ lines.push(`Host filter: ${board.host_filter}`);
419
+ if (args.includeReputation && board.reputation_summary) {
420
+ lines.push(`Reputation: tracked=${board.reputation_summary.tracked_agents}, avg_trust=${board.reputation_summary.avg_internal_trust}`);
421
+ if (board.agent_reputation) {
422
+ 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})`);
423
+ }
424
+ }
425
+ lines.push(`Active plans: ${board.active_plans.length}`);
426
+ for (const plan of board.active_plans.slice(0, 10)) {
427
+ const claims = plan.claims.length ? ` claims=${plan.claims.map((claim) => claim.agent).join(',')}` : '';
428
+ lines.push(`- [${plan.id}] ${plan.text} (${plan.status}, ${plan.priority})${claims}`);
429
+ }
430
+ lines.push(`Active claims: ${board.active_claims.length}`);
431
+ for (const claim of board.active_claims.slice(0, 10)) {
432
+ const identity = claim.agent_id ? ` [${claim.agent_id}]` : '';
433
+ const session = claim.session_id ? ` session=${claim.session_id}` : '';
434
+ const liveness = assessClaimLiveness(claim).status;
435
+ const liveTag = liveness === 'live' || liveness === 'young' ? '' : ` [${liveness.toUpperCase()}]`;
436
+ lines.push(`- [${claim.id}] ${claim.agent}${identity} -> ${claim.scope}${claim.plan_id ? ` (plan ${claim.plan_id})` : ''}${session}${liveTag}`);
437
+ }
438
+ lines.push(`Active assignments: ${board.active_assignments.length}`);
439
+ for (const assignment of board.active_assignments.slice(0, 10)) {
440
+ const plan = assignment.plan_id ? ` plan=${assignment.plan_id}` : '';
441
+ const session = assignment.session_id ? ` session=${assignment.session_id}` : '';
442
+ lines.push(`- [${assignment.id}] ${assignment.agent} (${assignment.status}) -> ${assignment.scope}${plan}${session}`);
443
+ }
444
+ lines.push(`Active runs: ${board.active_runs.length}`);
445
+ for (const run of board.active_runs.slice(0, 10)) {
446
+ const assignment = run.assignment_id ? ` assignment=${run.assignment_id}` : '';
447
+ const attempt = ` attempt=${run.attempt_index}`;
448
+ const session = run.session_id ? ` session=${run.session_id}` : '';
449
+ lines.push(`- [${run.id}] ${run.agent} (${run.status}/${run.transport}) -> ${run.scope}${assignment}${attempt}${session}`);
450
+ }
451
+ lines.push(`Pending actions: ${board.active_actions.length}`);
452
+ for (const action of board.active_actions.slice(0, 10)) {
453
+ const run = action.run_id ? ` run=${action.run_id}` : '';
454
+ const session = action.session_id ? ` session=${action.session_id}` : '';
455
+ lines.push(`- [${action.id}] ${action.agent} (${action.kind}) -> ${action.title}${run}${session}`);
456
+ }
457
+ lines.push(`Active sequence: ${board.active_sequence ? `1 (${board.active_sequence.name})` : '0'}`);
458
+ if (board.active_sequence) {
459
+ lines.push(`- [${board.active_sequence.id}] ${board.active_sequence.name} (${board.active_sequence.status})`);
460
+ for (const item of board.active_sequence.items.slice(0, 10)) {
461
+ const lane = item.lane ? ` lane=${item.lane}` : '';
462
+ const hardAfter = item.hard_after.length ? ` hard_after=${item.hard_after.join(',')}` : '';
463
+ const softAfter = item.soft_after.length ? ` soft_after=${item.soft_after.join(',')}` : '';
464
+ lines.push(` #${item.rank} ${item.planId}${lane}${hardAfter}${softAfter}`);
465
+ }
466
+ }
467
+ const sessionMetaHint = board.session_meta_hidden > 0 ? ` (+${board.session_meta_hidden} session lifecycle notes hidden — pass includeSessionMeta to show)` : '';
468
+ lines.push(`Runtime notes: ${board.runtime_notes.length}${sessionMetaHint}`);
469
+ for (const note of board.runtime_notes.slice(-10)) {
470
+ const scope = note.visibility === 'shared' ? 'shared' : `${note.visibility}:${note.host_id ?? 'unknown-host'}`;
471
+ const identity = note.agent_id ? ` [${note.agent_id}]` : '';
472
+ lines.push(`- [${note.id}] ${note.agent}${identity}: ${note.text}${note.plan_id ? ` (plan ${note.plan_id})` : ''} [${scope}]`);
473
+ }
474
+ lines.push(`Open handoffs: ${board.open_handoffs.length}`);
475
+ for (const handoff of board.open_handoffs.slice(0, 10)) {
476
+ const contractHint = handoff.contract ? ' [contract]' : '';
477
+ lines.push(`- [${handoff.id}] ${handoff.from} -> ${handoff.to}: ${handoff.text}${contractHint}`);
478
+ }
479
+ lines.push(`Resolved instructions: ${board.resolved_instructions.length}`);
480
+ for (const instruction of board.resolved_instructions.slice(0, 10)) {
481
+ lines.push(`- [${instruction.id}] <${instruction.layer}${instruction.scope ? `:${instruction.scope}` : ''}> ${instruction.text}`);
482
+ }
483
+ if (board.other_agents && board.other_agents.length > 0) {
484
+ lines.push(`Other agents: ${board.other_agents.length}`);
485
+ for (const other of board.other_agents) {
486
+ lines.push(`- ${other.name}: ${other.claim_count} claim(s) on ${other.scopes.join(', ')}`);
487
+ }
488
+ }
489
+ return {
490
+ content: [{ type: 'text', text: lines.join('\n') }],
491
+ structuredContent: { ...board },
492
+ };
493
+ }
494
+ if (name === 'bclaw_search') {
495
+ const query = String(args.query ?? '');
496
+ if (!query) {
497
+ throw new Error('Missing required argument: query');
498
+ }
499
+ const offset = Math.max(0, Number(args.offset) || 0);
500
+ const limit = typeof args.limit === 'number' ? args.limit : 10;
501
+ const allResults = search({
502
+ query,
503
+ section: (args.section ?? args.type),
504
+ since: args.since,
505
+ maxResults: offset + limit,
506
+ cwd,
507
+ });
508
+ const total = allResults.length;
509
+ const page = allResults.slice(offset, offset + limit);
510
+ const lines = page.map((result) => `[${result.id}] (${result.section}) score=${result.score.toFixed(2)}: ${result.text.slice(0, 120)}`);
511
+ return {
512
+ content: [{ type: 'text', text: page.length > 0 ? lines.join('\n') : 'No results found.' }],
513
+ structuredContent: { total, offset, limit, results: page },
514
+ };
515
+ }
516
+ if (name === 'bclaw_estimation_report') {
517
+ const report = buildEstimationReport({ agent: args.agent, cwd });
518
+ const lines = [`Estimation Report — ${report.summary.total} completed plan(s)`];
519
+ if (report.summary.calibration_hint) {
520
+ lines.push(`Calibration: ${report.summary.calibration_hint}`);
521
+ lines.push(`Median ratio: ${report.summary.median_ratio}x · Mean: ${report.summary.mean_ratio}x`);
522
+ }
523
+ for (const e of report.entries) {
524
+ const est = e.estimated_minutes !== undefined ? `est:${e.estimated_minutes}min` : 'no estimate';
525
+ const act = e.elapsed_minutes !== undefined ? `actual:${e.elapsed_minutes}min` : 'no actual';
526
+ const ratio = e.ratio !== undefined ? ` ratio:${e.ratio}x` : '';
527
+ lines.push(`[${e.id.slice(0, 8)}] ${e.text.slice(0, 60)} — ${est} · ${act}${ratio}`);
528
+ }
529
+ return {
530
+ content: [{ type: 'text', text: lines.join('\n') }],
531
+ structuredContent: report,
532
+ };
533
+ }
534
+ if (name === 'bclaw_list_plans') {
535
+ let plans = loadState(cwd).plan_items;
536
+ // Direct lookup by ID
537
+ if (args.id) {
538
+ const plan = plans.find((p) => p.id === String(args.id) || p.short_label === String(args.id));
539
+ if (!plan) {
540
+ return { content: [{ type: 'text', text: `Plan '${args.id}' not found.` }], structuredContent: { total: 0, plans: [] } };
541
+ }
542
+ return {
543
+ content: [{ type: 'text', text: `[${plan.id}] ${plan.text} (${plan.status}, ${plan.priority})` }],
544
+ structuredContent: { total: 1, plans: [plan] },
545
+ };
546
+ }
547
+ // Filters
548
+ if (!args.all) {
549
+ plans = plans.filter((plan) => plan.status !== 'done' && plan.status !== 'dropped');
550
+ }
551
+ if (args.status) {
552
+ plans = plans.filter((plan) => plan.status === args.status);
553
+ }
554
+ if (args.type) {
555
+ plans = plans.filter((plan) => plan.type === args.type);
556
+ }
557
+ if (args.assignee) {
558
+ const assignee = String(args.assignee).toLowerCase();
559
+ plans = plans.filter((plan) => plan.assignee?.toLowerCase() === assignee);
560
+ }
561
+ if (args.project) {
562
+ const project = String(args.project).toLowerCase();
563
+ plans = plans.filter((plan) => plan.project?.toLowerCase() === project);
564
+ }
565
+ const totalFiltered = plans.length;
566
+ // Descendant discovery
567
+ const descendantGroups = args.recursive
568
+ ? scanDescendantPlans(cwd, {
569
+ all: args.all,
570
+ status: args.status,
571
+ type: args.type,
572
+ assignee: args.assignee,
573
+ project: args.project,
574
+ })
575
+ : [];
576
+ const totalDescendantPlans = descendantGroups.reduce((sum, g) => sum + g.plans.length, 0);
577
+ // Pagination (local plans only)
578
+ const offset = Math.max(0, Number(args.offset) || 0);
579
+ const limit = Math.max(1, Number(args.limit) || 20);
580
+ const paginated = plans.slice(offset, offset + limit);
581
+ const lines = [];
582
+ if (args.recursive) {
583
+ lines.push(`── local (${totalFiltered} plans) ──`);
584
+ }
585
+ if (paginated.length === 0 && !args.recursive) {
586
+ lines.push('No plan items found.');
587
+ // Signal descendant plans when 0 local results
588
+ if (!args.recursive) {
589
+ const signalGroups = scanDescendantPlans(cwd, {
590
+ all: args.all,
591
+ status: args.status,
592
+ });
593
+ const signalTotal = signalGroups.reduce((sum, g) => sum + g.plans.length, 0);
594
+ if (signalTotal > 0) {
595
+ lines.push(`ℹ ${signalTotal} plan(s) found in ${signalGroups.length} descendant project(s) (use recursive: true to see all)`);
596
+ }
597
+ }
598
+ }
599
+ else if (paginated.length > 0) {
600
+ if (!args.recursive) {
601
+ lines.push(`${totalFiltered} plan(s)${totalFiltered > paginated.length ? ` (showing ${offset + 1}-${offset + paginated.length})` : ''}:`);
602
+ }
603
+ for (const plan of paginated) {
604
+ const meta = [plan.type ?? 'feat', plan.status, plan.priority];
605
+ if (plan.assignee)
606
+ meta.push(`assignee ${plan.assignee}`);
607
+ if (plan.project)
608
+ meta.push(`project ${plan.project}`);
609
+ if (plan.depends_on.length > 0)
610
+ meta.push(`depends_on ${plan.depends_on.join(',')}`);
611
+ const tags = plan.tags.length ? ` [${plan.tags.join(', ')}]` : '';
612
+ lines.push(`[${plan.id}] ${plan.text} (${meta.join(' · ')})${tags}`);
613
+ }
614
+ }
615
+ else {
616
+ lines.push(' (none)');
617
+ }
618
+ // Append descendant groups
619
+ for (const group of descendantGroups) {
620
+ const label = group.project_name ?? group.relative_path;
621
+ lines.push(`\n── ${label} (${group.plans.length} plans) ──`);
622
+ for (const plan of group.plans) {
623
+ const meta = [plan.type ?? 'feat', plan.status, plan.priority];
624
+ const tags = plan.tags.length ? ` [${plan.tags.join(', ')}]` : '';
625
+ lines.push(`[${plan.id}] ${plan.text} (${meta.join(' · ')})${tags}`);
626
+ }
627
+ }
628
+ // Compact mode: strip heavy fields
629
+ const outputPlans = args.compact
630
+ ? paginated.map(({ id, short_label, text, status, priority, tags, assignee, type }) => ({
631
+ id, short_label, text, status, priority, tags, assignee, type,
632
+ }))
633
+ : paginated;
634
+ return {
635
+ content: [{ type: 'text', text: lines.join('\n') }],
636
+ structuredContent: {
637
+ total: totalFiltered,
638
+ offset,
639
+ limit,
640
+ plans: outputPlans,
641
+ ...(descendantGroups.length > 0 ? { descendants: descendantGroups, total_with_descendants: totalFiltered + totalDescendantPlans } : {}),
642
+ },
643
+ };
644
+ }
645
+ if (name === 'bclaw_list_sequences') {
646
+ const status = args.status;
647
+ const id = args.id;
648
+ const offset = Math.max(0, Number(args.offset) || 0);
649
+ const limit = Math.max(1, Number(args.limit) || 20);
650
+ let sequences = listSequences(cwd);
651
+ if (status) {
652
+ sequences = sequences.filter((sequence) => sequence.status === status);
653
+ }
654
+ if (id) {
655
+ sequences = sequences.filter((sequence) => sequence.id === id || sequence.short_label === id);
656
+ }
657
+ const total = sequences.length;
658
+ const page = sequences.slice(offset, offset + limit);
659
+ const compact = args.compact === true;
660
+ const lines = page.length === 0
661
+ ? ['No sequences found.']
662
+ : [
663
+ `${total} sequence(s)${total > limit ? ` (showing ${offset + 1}-${offset + page.length})` : ''}:`,
664
+ ...page.map((sequence) => compact
665
+ ? `[${sequence.id}] ${sequence.name} (${sequence.status})`
666
+ : `[${sequence.id}] ${sequence.name} (${sequence.status}, items=${sequence.items.length})`),
667
+ ];
668
+ return {
669
+ content: [{ type: 'text', text: lines.join('\n') }],
670
+ structuredContent: { total, offset, limit, sequences: page },
671
+ };
672
+ }
673
+ if (name === 'bclaw_list_claims') {
674
+ let claims = listClaims(cwd);
675
+ if (!args.all) {
676
+ claims = claims.filter((claim) => claim.status === 'active');
677
+ }
678
+ if (args.project) {
679
+ claims = claims.filter((claim) => claim.project === args.project);
680
+ }
681
+ if (args.plan) {
682
+ claims = claims.filter((claim) => claim.plan_id === args.plan);
683
+ }
684
+ if (args.agent) {
685
+ claims = claims.filter((claim) => claim.agent === args.agent);
686
+ }
687
+ const total = claims.length;
688
+ const offset = Math.max(0, Number(args.offset) || 0);
689
+ const limit = Math.max(1, Number(args.limit) || 20);
690
+ const page = claims.slice(offset, offset + limit);
691
+ const label = args.all ? 'claim(s)' : 'active claim(s)';
692
+ const lines = page.length === 0
693
+ ? ['No active claims.']
694
+ : [
695
+ `${total} ${label}${total > limit ? ` (showing ${offset + 1}-${offset + page.length})` : ''}:`,
696
+ ...page.map((claim) => {
697
+ const status = claim.status !== 'active' ? ` (${claim.status})` : '';
698
+ const extras = [];
699
+ if (claim.session_id)
700
+ extras.push(`session ${claim.session_id.slice(-8)}`);
701
+ if (claim.plan_id)
702
+ extras.push(`plan ${claim.plan_id}`);
703
+ if (claim.project)
704
+ extras.push(`project ${claim.project}`);
705
+ const suffix = extras.length ? ` [${extras.join(', ')}]` : '';
706
+ return `[${claim.id}] ${claim.agent} -> ${claim.scope}: ${claim.description}${suffix}${status}`;
707
+ }),
708
+ ];
709
+ return {
710
+ content: [{ type: 'text', text: lines.join('\n') }],
711
+ structuredContent: { total, offset, limit, claims: page },
712
+ };
713
+ }
714
+ if (name === 'bclaw_list_assignments') {
715
+ const status = validateEnumFilter(args.status, AssignmentStatusSchema, 'assignment status');
716
+ const id = args.id;
717
+ const claimId = args.claimId;
718
+ const planId = args.planId;
719
+ const sequenceId = args.sequenceId;
720
+ const agent = args.agent;
721
+ const offset = Math.max(0, Number(args.offset) || 0);
722
+ const limit = Math.max(1, Number(args.limit) || 20);
723
+ let assignments = listAssignments(cwd, {
724
+ ...(status ? { status } : {}),
725
+ ...(agent ? { agent } : {}),
726
+ ...(claimId ? { claim_id: claimId } : {}),
727
+ ...(planId ? { plan_id: planId } : {}),
728
+ ...(sequenceId ? { sequence_id: sequenceId } : {}),
729
+ });
730
+ if (id) {
731
+ assignments = assignments.filter((assignment) => assignment.id === id || assignment.short_label === id);
732
+ }
733
+ const total = assignments.length;
734
+ const page = assignments.slice(offset, offset + limit);
735
+ const compact = args.compact === true;
736
+ const lines = page.length === 0
737
+ ? ['No assignments found.']
738
+ : [
739
+ `${total} assignment(s)${total > limit ? ` (showing ${offset + 1}-${offset + page.length})` : ''}:`,
740
+ ...page.map((assignment) => {
741
+ if (compact) {
742
+ return `[${assignment.id}] ${assignment.agent} (${assignment.status}) -> ${assignment.scope}`;
743
+ }
744
+ const refs = [];
745
+ if (assignment.claim_id)
746
+ refs.push(`claim ${assignment.claim_id}`);
747
+ if (assignment.plan_id)
748
+ refs.push(`plan ${assignment.plan_id}`);
749
+ if (assignment.sequence_id)
750
+ refs.push(`sequence ${assignment.sequence_id}`);
751
+ if (assignment.session_id)
752
+ refs.push(`session ${assignment.session_id.slice(-8)}`);
753
+ const suffix = refs.length ? ` [${refs.join(', ')}]` : '';
754
+ return `[${assignment.id}] ${assignment.agent} (${assignment.status}) -> ${assignment.scope}: ${assignment.description}${suffix}`;
755
+ }),
756
+ ];
757
+ const outputAssignments = compact
758
+ ? page.map((assignment) => ({
759
+ id: assignment.id,
760
+ short_label: assignment.short_label,
761
+ agent: assignment.agent,
762
+ status: assignment.status,
763
+ scope: assignment.scope,
764
+ claim_id: assignment.claim_id,
765
+ plan_id: assignment.plan_id,
766
+ sequence_id: assignment.sequence_id,
767
+ updated_at: assignment.updated_at,
768
+ last_heartbeat_at: assignment.last_heartbeat_at,
769
+ }))
770
+ : page;
771
+ return {
772
+ content: [{ type: 'text', text: lines.join('\n') }],
773
+ structuredContent: { total, offset, limit, assignments: outputAssignments },
774
+ };
775
+ }
776
+ if (name === 'bclaw_list_runs') {
777
+ const status = validateEnumFilter(args.status, AgentRunStatusSchema, 'run status');
778
+ const transport = validateEnumFilter(args.transport, AgentRunTransportSchema, 'run transport');
779
+ const id = args.id;
780
+ const assignmentId = args.assignmentId;
781
+ const claimId = args.claimId;
782
+ const planId = args.planId;
783
+ const sequenceId = args.sequenceId;
784
+ const agent = args.agent;
785
+ const offset = Math.max(0, Number(args.offset) || 0);
786
+ const limit = Math.max(1, Number(args.limit) || 20);
787
+ let runs = listAgentRuns(cwd, {
788
+ ...(status ? { status } : {}),
789
+ ...(transport ? { transport } : {}),
790
+ ...(agent ? { agent } : {}),
791
+ ...(assignmentId ? { assignment_id: assignmentId } : {}),
792
+ ...(claimId ? { claim_id: claimId } : {}),
793
+ ...(planId ? { plan_id: planId } : {}),
794
+ ...(sequenceId ? { sequence_id: sequenceId } : {}),
795
+ });
796
+ if (id) {
797
+ runs = runs.filter((run) => run.id === id || run.short_label === id);
798
+ }
799
+ const total = runs.length;
800
+ const page = runs.slice(offset, offset + limit);
801
+ const compact = args.compact === true;
802
+ const lines = page.length === 0
803
+ ? ['No runs found.']
804
+ : [
805
+ `${total} run(s)${total > limit ? ` (showing ${offset + 1}-${offset + page.length})` : ''}:`,
806
+ ...page.map((run) => {
807
+ if (compact) {
808
+ return `[${run.id}] ${run.agent} (${run.status}/${run.transport}) -> ${run.assignment_id}`;
809
+ }
810
+ const refs = [`assignment ${run.assignment_id}`, `attempt ${run.attempt_index}`];
811
+ if (run.claim_id)
812
+ refs.push(`claim ${run.claim_id}`);
813
+ if (run.plan_id)
814
+ refs.push(`plan ${run.plan_id}`);
815
+ if (run.session_id)
816
+ refs.push(`session ${run.session_id.slice(-8)}`);
817
+ const suffix = refs.length ? ` [${refs.join(', ')}]` : '';
818
+ return `[${run.id}] ${run.agent} (${run.status}/${run.transport}) -> ${run.scope}: ${run.description}${suffix}`;
819
+ }),
820
+ ];
821
+ const outputRuns = compact
822
+ ? page.map((run) => ({
823
+ id: run.id,
824
+ short_label: run.short_label,
825
+ agent: run.agent,
826
+ status: run.status,
827
+ transport: run.transport,
828
+ assignment_id: run.assignment_id,
829
+ claim_id: run.claim_id,
830
+ plan_id: run.plan_id,
831
+ sequence_id: run.sequence_id,
832
+ attempt_index: run.attempt_index,
833
+ updated_at: run.updated_at,
834
+ last_event_at: run.last_event_at,
835
+ }))
836
+ : page;
837
+ return {
838
+ content: [{ type: 'text', text: lines.join('\n') }],
839
+ structuredContent: { total, offset, limit, runs: outputRuns },
840
+ };
841
+ }
842
+ if (name === 'bclaw_assignment_events') {
843
+ const id = args.id;
844
+ const assignmentId = args.assignmentId;
845
+ const runId = args.runId;
846
+ const claimId = args.claimId;
847
+ const sessionId = args.sessionId;
848
+ const agent = args.agent;
849
+ const eventType = args.eventType;
850
+ const offset = Math.max(0, Number(args.offset) || 0);
851
+ const limit = Math.max(1, Number(args.limit) || 20);
852
+ const compact = args.compact === true;
853
+ // pln#496 Phase 2 (steps stp_344f99b3 + stp_e2b4429c): reconcile any
854
+ // non-terminal runs in the query scope before reading events. Targeted —
855
+ // we only reconcile when the caller is asking about a specific run /
856
+ // assignment / claim, never in the broad-list-all case (too aggressive
857
+ // and rarely actionable). This converges silent-completion cases (codex
858
+ // committed but never called bclaw_assignment_update) and surfaces
859
+ // delivered_but_unverified for spawns past the 60s grace with no
860
+ // life-sign — see runtime_note run_77e65e77 for the empirical case.
861
+ try {
862
+ if (runId) {
863
+ reconcileAgentRun(runId, cwd);
864
+ }
865
+ else if (assignmentId) {
866
+ for (const run of listAgentRuns(cwd, { assignment_id: assignmentId })) {
867
+ reconcileAgentRun(run.id, cwd);
868
+ }
869
+ }
870
+ else if (claimId) {
871
+ for (const run of listAgentRuns(cwd, { claim_id: claimId })) {
872
+ reconcileAgentRun(run.id, cwd);
873
+ }
874
+ }
875
+ }
876
+ catch { /* defensive: never block events query on reconcile failure */ }
877
+ let events = queryRuntimeEvents({
878
+ ...(id ? { id } : {}),
879
+ ...(assignmentId ? { assignment_id: assignmentId } : {}),
880
+ ...(runId ? { run_id: runId } : {}),
881
+ ...(claimId ? { claim_id: claimId } : {}),
882
+ ...(sessionId ? { session_id: sessionId } : {}),
883
+ ...(agent ? { agent } : {}),
884
+ ...(eventType ? { event_type: eventType } : {}),
885
+ }, cwd);
886
+ const total = events.length;
887
+ const page = events.slice(offset, offset + limit);
888
+ const lines = page.length === 0
889
+ ? ['No runtime events found.']
890
+ : [
891
+ `${total} runtime event(s)${total > limit ? ` (showing ${offset + 1}-${offset + page.length})` : ''}:`,
892
+ ...page.map((event) => {
893
+ if (compact) {
894
+ return `[${event.id}] ${event.event_type} ${event.agent}${event.assignment_id ? ` assignment=${event.assignment_id}` : ''}${event.run_id ? ` run=${event.run_id}` : ''}`;
895
+ }
896
+ const refs = [];
897
+ if (event.assignment_id)
898
+ refs.push(`assignment ${event.assignment_id}`);
899
+ if (event.run_id)
900
+ refs.push(`run ${event.run_id}`);
901
+ if (event.claim_id)
902
+ refs.push(`claim ${event.claim_id}`);
903
+ if (event.session_id)
904
+ refs.push(`session ${event.session_id.slice(-8)}`);
905
+ const suffix = refs.length ? ` [${refs.join(', ')}]` : '';
906
+ return `[${event.id}] ${event.event_type} ${event.agent}: ${event.text}${suffix}`;
907
+ }),
908
+ ];
909
+ const outputEvents = compact
910
+ ? page.map((event) => ({
911
+ id: event.id,
912
+ created_at: event.created_at,
913
+ event_type: event.event_type,
914
+ agent: event.agent,
915
+ assignment_id: event.assignment_id,
916
+ run_id: event.run_id,
917
+ claim_id: event.claim_id,
918
+ session_id: event.session_id,
919
+ status: event.status,
920
+ status_reason: event.status_reason,
921
+ }))
922
+ : page;
923
+ return {
924
+ content: [{ type: 'text', text: lines.join('\n') }],
925
+ structuredContent: { total, offset, limit, events: outputEvents },
926
+ };
927
+ }
928
+ if (name === 'bclaw_list_actions') {
929
+ const status = validateEnumFilter(args.status, ActionRequiredStatusSchema, 'action status');
930
+ const kind = validateEnumFilter(args.kind, ActionRequiredKindSchema, 'action kind');
931
+ const id = args.id;
932
+ const assignmentId = args.assignmentId;
933
+ const runId = args.runId;
934
+ const claimId = args.claimId;
935
+ const agent = args.agent;
936
+ const offset = Math.max(0, Number(args.offset) || 0);
937
+ const limit = Math.max(1, Number(args.limit) || 20);
938
+ const compact = args.compact === true;
939
+ let actions = listActionRequired(cwd, {
940
+ ...(status ? { status } : {}),
941
+ ...(kind ? { kind } : {}),
942
+ ...(agent ? { agent } : {}),
943
+ ...(assignmentId ? { assignment_id: assignmentId } : {}),
944
+ ...(runId ? { run_id: runId } : {}),
945
+ ...(claimId ? { claim_id: claimId } : {}),
946
+ });
947
+ if (id) {
948
+ actions = actions.filter((action) => action.id === id || action.short_label === id);
949
+ }
950
+ const total = actions.length;
951
+ const page = actions.slice(offset, offset + limit);
952
+ const lines = page.length === 0
953
+ ? ['No actions found.']
954
+ : [
955
+ `${total} action(s)${total > limit ? ` (showing ${offset + 1}-${offset + page.length})` : ''}:`,
956
+ ...page.map((action) => {
957
+ if (compact) {
958
+ return `[${action.id}] ${action.kind} (${action.status}) -> ${action.assignment_id}`;
959
+ }
960
+ const refs = [`assignment ${action.assignment_id}`];
961
+ if (action.run_id)
962
+ refs.push(`run ${action.run_id}`);
963
+ if (action.claim_id)
964
+ refs.push(`claim ${action.claim_id}`);
965
+ if (action.session_id)
966
+ refs.push(`session ${action.session_id.slice(-8)}`);
967
+ const suffix = refs.length ? ` [${refs.join(', ')}]` : '';
968
+ return `[${action.id}] ${action.kind} (${action.status}) ${action.title}: ${action.prompt}${suffix}`;
969
+ }),
970
+ ];
971
+ const outputActions = compact
972
+ ? page.map((action) => ({
973
+ id: action.id,
974
+ kind: action.kind,
975
+ status: action.status,
976
+ assignment_id: action.assignment_id,
977
+ run_id: action.run_id,
978
+ claim_id: action.claim_id,
979
+ title: action.title,
980
+ updated_at: action.updated_at,
981
+ resolved_at: action.resolved_at,
982
+ }))
983
+ : page;
984
+ return {
985
+ content: [{ type: 'text', text: lines.join('\n') }],
986
+ structuredContent: { total, offset, limit, actions: outputActions },
987
+ };
988
+ }
989
+ if (name === 'bclaw_list_agents') {
990
+ const agents = listAgentIdentities(cwd);
991
+ const current = resolveCurrentAgentIdentity(cwd);
992
+ const reputation = args.includeReputation ? buildReputationSnapshot(cwd) : undefined;
993
+ const reputationById = new Map((reputation?.agents ?? []).map((agent) => [agent.agent_id ?? agent.key, toPublicReputationSummary(agent)]));
994
+ const structuredAgents = args.includeReputation
995
+ ? agents.map((agent) => ({
996
+ ...agent,
997
+ reputation: reputationById.get(agent.agent_id),
998
+ }))
999
+ : agents;
1000
+ const lines = structuredAgents.length === 0
1001
+ ? ['No registered agents.']
1002
+ : [
1003
+ `${structuredAgents.length} registered agent(s):`,
1004
+ ...structuredAgents.map((agent) => {
1005
+ const reputation = agent.reputation;
1006
+ const currentLabel = current?.agent_id === agent.agent_id ? ' [current]' : '';
1007
+ const capabilitiesLabel = agent.capabilities.length > 0 ? ` caps=${agent.capabilities.join(',')}` : '';
1008
+ const fingerprintLabel = agent.identity_key ? ` fp=${agent.identity_key.fingerprint.slice(0, 12)}` : '';
1009
+ const reputationLabel = reputation
1010
+ ? ` trust=${reputation.internal_trust} cq=${reputation.contribution_quality} rv=${reputation.review_reliability} ct=${reputation.continuity_hygiene}`
1011
+ : '';
1012
+ return `- ${agent.agent_name} (${agent.agent_id}, kind=${agent.kind})${currentLabel}${reputationLabel}${capabilitiesLabel}${fingerprintLabel}`;
1013
+ }),
1014
+ ];
1015
+ return {
1016
+ content: [{ type: 'text', text: lines.join('\n') }],
1017
+ structuredContent: {
1018
+ current_agent_id: current?.agent_id,
1019
+ current_agent: current?.agent_name,
1020
+ agents: structuredAgents,
1021
+ },
1022
+ };
1023
+ }
1024
+ if (name === 'bclaw_list_instructions') {
1025
+ const config = loadConfig(cwd);
1026
+ const project = args.project;
1027
+ const inferredProject = project ?? inferProjectFromTarget(args.path, config);
1028
+ const resolvedAgent = args.resolved ? resolveAgentScope(args.agent) : args.agent;
1029
+ const source = args.resolved
1030
+ ? resolveInstructions(loadInstructions(cwd), { project: inferredProject, agent: resolvedAgent })
1031
+ : loadInstructions(cwd);
1032
+ let entries = source;
1033
+ if (args.active) {
1034
+ entries = entries.filter((entry) => entry.active);
1035
+ }
1036
+ if (args.layer) {
1037
+ entries = entries.filter((entry) => entry.layer === args.layer);
1038
+ }
1039
+ if (inferredProject) {
1040
+ entries = entries.filter((entry) => entry.layer !== 'project' || entry.scope === inferredProject);
1041
+ }
1042
+ if (args.agent) {
1043
+ entries = entries.filter((entry) => entry.layer !== 'agent' || entry.scope === args.agent);
1044
+ }
1045
+ const total = entries.length;
1046
+ const offset = Math.max(0, Number(args.offset) || 0);
1047
+ const limit = Math.max(1, Number(args.limit) || 20);
1048
+ const page = entries.slice(offset, offset + limit);
1049
+ const lines = page.length === 0
1050
+ ? ['No instructions found.']
1051
+ : [
1052
+ `${total} instruction(s)${total > limit ? ` (showing ${offset + 1}-${offset + page.length})` : ''}:`,
1053
+ ...page.map((entry) => {
1054
+ const scope = entry.scope ? `:${entry.scope}` : '';
1055
+ const flags = [entry.layer];
1056
+ if (!entry.active)
1057
+ flags.push('inactive');
1058
+ if (entry.supersedes)
1059
+ flags.push(`supersedes ${entry.supersedes}`);
1060
+ const tags = entry.tags.length ? ` [${entry.tags.join(', ')}]` : '';
1061
+ return `[${entry.id}] <${entry.layer}${scope}> ${entry.text} (${flags.join(' · ')})${tags}`;
1062
+ }),
1063
+ ];
1064
+ return {
1065
+ content: [{ type: 'text', text: lines.join('\n') }],
1066
+ structuredContent: { total, offset, limit, instructions: page },
1067
+ };
1068
+ }
1069
+ if (name === 'bclaw_list_candidates') {
1070
+ const status = String(args.status ?? 'pending').toLowerCase();
1071
+ let candidates = status === 'accepted'
1072
+ ? listArchivedCandidates('accepted', cwd)
1073
+ : status === 'rejected'
1074
+ ? listArchivedCandidates('rejected', cwd)
1075
+ : status === 'all'
1076
+ ? [
1077
+ ...listCandidates('pending', cwd),
1078
+ ...listArchivedCandidates('accepted', cwd),
1079
+ ...listArchivedCandidates('rejected', cwd),
1080
+ ]
1081
+ : listCandidates('pending', cwd);
1082
+ // source / auto_generated filters — resolved source defaults missing field to 'human' (backward compat)
1083
+ if (args.source !== undefined) {
1084
+ const validSources = ['auto', 'agent', 'human'];
1085
+ const sourceArg = String(args.source);
1086
+ if (validSources.includes(sourceArg)) {
1087
+ candidates = candidates.filter((c) => resolvedSource(c) === sourceArg);
1088
+ }
1089
+ }
1090
+ if (args.auto_generated === false) {
1091
+ candidates = candidates.filter((c) => resolvedSource(c) !== 'auto');
1092
+ }
1093
+ else if (args.auto_generated === true) {
1094
+ candidates = candidates.filter((c) => resolvedSource(c) === 'auto');
1095
+ }
1096
+ if (args.type) {
1097
+ candidates = candidates.filter((candidate) => candidate.type === args.type);
1098
+ }
1099
+ if (args.assignee) {
1100
+ const assignee = String(args.assignee).toLowerCase();
1101
+ candidates = candidates.filter((candidate) => getReviewAssignee(candidate.tags)?.toLowerCase() === assignee);
1102
+ }
1103
+ const total = candidates.length;
1104
+ const offset = Math.max(0, Number(args.offset) || 0);
1105
+ const limit = Math.max(1, Number(args.limit) || 20);
1106
+ const page = candidates.slice(offset, offset + limit);
1107
+ const isCompact = args.compact === true;
1108
+ const lines = page.length === 0
1109
+ ? ['No candidates found.']
1110
+ : [
1111
+ `${total} candidate(s)${total > limit ? ` (showing ${offset + 1}-${offset + page.length})` : ''}:`,
1112
+ ...page.map((candidate) => {
1113
+ if (isCompact) {
1114
+ return `[${candidate.id}] ${candidate.type}/${candidate.status}: ${candidate.text.slice(0, 120)}${candidate.text.length > 120 ? '…' : ''}`;
1115
+ }
1116
+ const assignee = getReviewAssignee(candidate.tags);
1117
+ const tags = candidate.tags.length ? ` [${candidate.tags.join(', ')}]` : '';
1118
+ const assigneeLabel = assignee ? ` assignee=${assignee}` : '';
1119
+ return `[${candidate.id}] ${candidate.type}/${candidate.status}${assigneeLabel}: ${candidate.text}${tags}`;
1120
+ }),
1121
+ ];
1122
+ return {
1123
+ content: [{ type: 'text', text: lines.join('\n') }],
1124
+ structuredContent: { total, offset, limit, candidates: page },
1125
+ };
1126
+ }
1127
+ if (name === 'bclaw_get_capabilities') {
1128
+ const allCapabilities = listCapabilities(cwd);
1129
+ const filtered = allCapabilities.filter((cap) => {
1130
+ const categoryFilter = args.category;
1131
+ const tagsFilter = args.tags;
1132
+ if (categoryFilter && cap.category !== categoryFilter)
1133
+ return false;
1134
+ if (tagsFilter && tagsFilter.length > 0) {
1135
+ if (!tagsFilter.every((tag) => cap.tags.includes(tag)))
1136
+ return false;
1137
+ }
1138
+ return true;
1139
+ });
1140
+ const lines = [`Capabilities (${filtered.length}):`];
1141
+ filtered.forEach((cap) => {
1142
+ lines.push(`\n[${cap.id}] ${cap.name}`);
1143
+ lines.push(` Category: ${cap.category}`);
1144
+ lines.push(` Author: ${cap.author}`);
1145
+ if (cap.tags.length > 0) {
1146
+ lines.push(` Tags: ${cap.tags.join(', ')}`);
1147
+ }
1148
+ });
1149
+ return {
1150
+ content: [{ type: 'text', text: lines.join('\n') || 'No capabilities found.' }],
1151
+ structuredContent: { total: filtered.length, capabilities: filtered },
1152
+ };
1153
+ }
1154
+ if (name === 'bclaw_list_tools') {
1155
+ const allTools = listRegistryTools(cwd);
1156
+ const filtered = allTools.filter((tool) => {
1157
+ const typeFilter = args.type;
1158
+ const tagsFilter = args.tags;
1159
+ if (typeFilter && tool.type !== typeFilter)
1160
+ return false;
1161
+ if (tagsFilter && tagsFilter.length > 0) {
1162
+ if (!tagsFilter.every((tag) => tool.tags.includes(tag)))
1163
+ return false;
1164
+ }
1165
+ return true;
1166
+ });
1167
+ const lines = [`Tools (${filtered.length}):`];
1168
+ filtered.forEach((tool) => {
1169
+ lines.push(`\n[${tool.id}] ${tool.name}`);
1170
+ lines.push(` Type: ${tool.type}`);
1171
+ lines.push(` Author: ${tool.author}`);
1172
+ if (tool.tags.length > 0) {
1173
+ lines.push(` Tags: ${tool.tags.join(', ')}`);
1174
+ }
1175
+ });
1176
+ return {
1177
+ content: [{ type: 'text', text: lines.join('\n') || 'No tools found.' }],
1178
+ structuredContent: { total: filtered.length, tools: filtered },
1179
+ };
1180
+ }
1181
+ if (name === 'bclaw_search_tools') {
1182
+ const query = String(args.query ?? '');
1183
+ if (!query) {
1184
+ throw new Error('Missing required argument: query');
1185
+ }
1186
+ const allTools = listRegistryTools(cwd);
1187
+ const queryLower = query.toLowerCase();
1188
+ const filtered = allTools.filter((tool) => {
1189
+ const typeFilter = args.type;
1190
+ const tagsFilter = args.tags;
1191
+ if (typeFilter && tool.type !== typeFilter)
1192
+ return false;
1193
+ if (tagsFilter && tagsFilter.length > 0) {
1194
+ if (!tagsFilter.every((tag) => tool.tags.includes(tag)))
1195
+ return false;
1196
+ }
1197
+ return (tool.name.toLowerCase().includes(queryLower) ||
1198
+ tool.description.toLowerCase().includes(queryLower) ||
1199
+ tool.tags.some((tag) => tag.toLowerCase().includes(queryLower)));
1200
+ });
1201
+ const lines = [`Search results for '${query}' (${filtered.length} tool(s)):`];
1202
+ filtered.forEach((tool) => {
1203
+ lines.push(`\n[${tool.id}] ${tool.name}`);
1204
+ lines.push(` Type: ${tool.type}`);
1205
+ });
1206
+ return {
1207
+ content: [{ type: 'text', text: lines.join('\n') || 'No tools found.' }],
1208
+ structuredContent: { query, total: filtered.length, tools: filtered },
1209
+ };
1210
+ }
1211
+ if (name === 'bclaw_get_discovery') {
1212
+ const refresh = args.refresh !== false; // default: true
1213
+ const noSave = args.noSave;
1214
+ let profile;
1215
+ if (!refresh) {
1216
+ profile = loadDiscoveryProfile(cwd);
1217
+ }
1218
+ if (!profile) {
1219
+ profile = buildProjectDiscovery({ cwd });
1220
+ if (!noSave) {
1221
+ saveDiscoveryProfile(profile, cwd);
1222
+ }
1223
+ }
1224
+ return {
1225
+ content: [{ type: 'text', text: renderDiscoverySummary(profile) }],
1226
+ structuredContent: { ...profile, schema_version: SCHEMA_VERSION },
1227
+ };
1228
+ }
1229
+ if (name === 'bclaw_conflict_check') {
1230
+ const agentNameArg = args.agent;
1231
+ const agentIdArg = args.agentId;
1232
+ const currentAgentName = agentNameArg ?? resolveCurrentAgentName(cwd);
1233
+ const allClaimsForCheck = listClaims(cwd).filter((c) => c.status === 'active');
1234
+ const myClaimsForCheck = allClaimsForCheck.filter((c) => agentIdArg ? c.agent_id === agentIdArg : c.agent === currentAgentName);
1235
+ const otherClaimsForCheck = allClaimsForCheck.filter((c) => agentIdArg ? c.agent_id !== agentIdArg : c.agent !== currentAgentName);
1236
+ const conflicts = [];
1237
+ for (const mine of myClaimsForCheck) {
1238
+ const myScopes = mine.scope.replace(/\\/g, '/').split(/\s+/);
1239
+ for (const other of otherClaimsForCheck) {
1240
+ const otherScopes = other.scope.replace(/\\/g, '/').split(/\s+/);
1241
+ for (const ms of myScopes) {
1242
+ for (const os of otherScopes) {
1243
+ if (ms === os || ms.startsWith(os + '/') || os.startsWith(ms + '/')) {
1244
+ conflicts.push({
1245
+ my_claim: mine.id, my_scope: mine.scope,
1246
+ other_claim: other.id, other_agent: other.agent, other_scope: other.scope,
1247
+ reason: ms === os ? `exact: ${ms}` : `overlap: ${ms} ↔ ${os}`,
1248
+ });
1249
+ }
1250
+ }
1251
+ }
1252
+ }
1253
+ }
1254
+ const text = conflicts.length === 0
1255
+ ? `No claim conflicts for ${currentAgentName}.`
1256
+ : `${conflicts.length} conflict(s) found:\n${conflicts.map((c) => ` ${c.my_scope} ↔ ${c.other_agent}:${c.other_scope} (${c.reason})`).join('\n')}`;
1257
+ return {
1258
+ content: [{ type: 'text', text }],
1259
+ structuredContent: { agent: currentAgentName, conflicts, total: conflicts.length, schema_version: SCHEMA_VERSION },
1260
+ };
1261
+ }
1262
+ if (name === 'bclaw_switch') {
1263
+ if (args.list === true) {
1264
+ try {
1265
+ const result = listAvailableProjects(cwd);
1266
+ const lines = result.projects.map(p => {
1267
+ const marker = p.active ? '→' : ' ';
1268
+ const label = p.name ? `${p.name} (${p.relative_path})` : p.relative_path;
1269
+ return `${marker} ${label}`;
1270
+ });
1271
+ return {
1272
+ content: [{ type: 'text', text: lines.length > 0 ? `Projects in workspace:\n${lines.join('\n')}` : 'No projects found.' }],
1273
+ structuredContent: { ...result, schema_version: SCHEMA_VERSION },
1274
+ };
1275
+ }
1276
+ catch (err) {
1277
+ return createToolErrorResponse('switch_error', err instanceof Error ? err.message : String(err));
1278
+ }
1279
+ }
1280
+ if (args.clear === true) {
1281
+ try {
1282
+ const session = loadCurrentSession(cwd);
1283
+ if (session?.active_project) {
1284
+ const { active_project: _removed, ...rest } = session;
1285
+ saveCurrentSession(rest, cwd);
1286
+ }
1287
+ return {
1288
+ content: [{ type: 'text', text: '✔ Active project cleared. Commands will use workspace root.' }],
1289
+ structuredContent: { cleared: true, schema_version: SCHEMA_VERSION },
1290
+ };
1291
+ }
1292
+ catch (err) {
1293
+ return createToolErrorResponse('switch_error', err instanceof Error ? err.message : String(err));
1294
+ }
1295
+ }
1296
+ const projectRef = args.project;
1297
+ if (!projectRef) {
1298
+ return createToolErrorResponse('validation_error', 'Missing required argument: project (or use list=true / clear=true)');
1299
+ }
1300
+ try {
1301
+ const result = switchProject(projectRef, { cwd, sessionOnly: true });
1302
+ const text = `✔ Switched to ${result.name ? `"${result.name}"` : result.path} (${result.scope}-scoped)`;
1303
+ return {
1304
+ content: [{ type: 'text', text }],
1305
+ structuredContent: { ...result, schema_version: SCHEMA_VERSION },
1306
+ };
1307
+ }
1308
+ catch (err) {
1309
+ return createToolErrorResponse('switch_error', err instanceof Error ? err.message : String(err));
1310
+ }
1311
+ }
1312
+ if (name === 'bclaw_who') {
1313
+ // loadAllSessions and gcStaleSessions imported at top of file
1314
+ const doGc = args.gc === true;
1315
+ const showAll = args.all === true;
1316
+ if (doGc) {
1317
+ const removed = gcStaleSessions(cwd);
1318
+ return {
1319
+ content: [{ type: 'text', text: `✔ Removed ${removed} stale session(s).` }],
1320
+ structuredContent: { gc: true, removed, schema_version: SCHEMA_VERSION },
1321
+ };
1322
+ }
1323
+ const allSessions = loadAllSessions(cwd);
1324
+ const ttlMs = 4 * 60 * 60 * 1000;
1325
+ const now = Date.now();
1326
+ const sessions = showAll
1327
+ ? allSessions
1328
+ : allSessions.filter((s) => (now - Date.parse(s.last_seen_at)) <= ttlMs);
1329
+ const activeClaims = listClaims(cwd).filter((c) => c.status === 'active');
1330
+ const output = sessions.map((s) => ({
1331
+ session_id: s.session_id,
1332
+ user: s.user ?? 'unknown',
1333
+ agent: s.agent,
1334
+ agent_id: s.agent_id,
1335
+ project: s.active_project?.name ?? s.active_project?.path ?? null,
1336
+ claims: activeClaims.filter((c) => c.agent_id === s.agent_id).length,
1337
+ last_seen_at: s.last_seen_at,
1338
+ stale: (now - Date.parse(s.last_seen_at)) > ttlMs,
1339
+ }));
1340
+ const lines = sessions.length === 0
1341
+ ? 'No active sessions.'
1342
+ : output.map((s) => `${s.user} | ${s.agent} | ${s.project ?? '(root)'} | ${s.claims} claims | ${s.stale ? 'stale' : 'active'}`).join('\n');
1343
+ return {
1344
+ content: [{ type: 'text', text: lines }],
1345
+ structuredContent: { sessions: output, total: output.length, schema_version: SCHEMA_VERSION },
1346
+ };
1347
+ }
1348
+ if (name === 'bclaw_check_policy') {
1349
+ const scope = String(args.scope ?? '').trim();
1350
+ if (!scope) {
1351
+ return { content: [{ type: 'text', text: 'Error: missing required argument: scope' }] };
1352
+ }
1353
+ const result = checkPolicy({
1354
+ scope,
1355
+ agent: args.agent ?? resolveCurrentAgentName(cwd),
1356
+ agentId: args.agentId,
1357
+ action: args.action,
1358
+ cwd,
1359
+ });
1360
+ const parts = [];
1361
+ const status = result.allowed ? '✔ ALLOWED' : '✘ BLOCKED';
1362
+ parts.push(`Policy check for "${scope}": ${status}`);
1363
+ if (result.blocks.length > 0) {
1364
+ parts.push('');
1365
+ parts.push('Blocks:');
1366
+ for (const b of result.blocks) {
1367
+ parts.push(` ✘ [${b.kind}] ${b.message}`);
1368
+ }
1369
+ }
1370
+ if (result.warnings.length > 0) {
1371
+ parts.push('');
1372
+ parts.push('Warnings:');
1373
+ for (const w of result.warnings) {
1374
+ const idLabel = w.id ? ` (${w.id})` : '';
1375
+ parts.push(` ⚠ [${w.kind}]${idLabel} ${w.message}`);
1376
+ }
1377
+ }
1378
+ if (result.governance_context.active_instructions.length > 0) {
1379
+ parts.push('');
1380
+ parts.push(`Governance: ${result.governance_context.active_instructions.length} active instruction(s)`);
1381
+ for (const ins of result.governance_context.active_instructions) {
1382
+ const layerLabel = ins.layer === 'global' ? '[global]' : `[${ins.layer}:${ins.scope ?? '*'}]`;
1383
+ parts.push(` ${layerLabel} ${ins.text.slice(0, 150)}${ins.text.length > 150 ? '…' : ''}`);
1384
+ }
1385
+ }
1386
+ return {
1387
+ content: [{ type: 'text', text: parts.join('\n') }],
1388
+ structuredContent: {
1389
+ allowed: result.allowed,
1390
+ blocks: result.blocks,
1391
+ warnings: result.warnings,
1392
+ governance_context: {
1393
+ active_instructions_count: result.governance_context.active_instructions.length,
1394
+ matching_constraints_count: result.governance_context.matching_constraints.length,
1395
+ matching_traps_count: result.governance_context.matching_traps.length,
1396
+ active_claims_on_scope: result.governance_context.active_claims_on_scope.map(c => ({
1397
+ id: c.id, agent: c.agent, scope: c.scope, description: c.description,
1398
+ })),
1399
+ },
1400
+ schema_version: SCHEMA_VERSION,
1401
+ },
1402
+ };
1403
+ }
1404
+ if (name === 'bclaw_doctor') {
1405
+ // Capture doctor JSON output by redirecting console.log
1406
+ const captured = [];
1407
+ const origLog = console.log;
1408
+ const origWarn = console.warn;
1409
+ const origError = console.error;
1410
+ console.log = (...a) => captured.push(a.join(' '));
1411
+ console.warn = (...a) => captured.push(a.join(' '));
1412
+ console.error = (...a) => captured.push(a.join(' '));
1413
+ try {
1414
+ runDoctor({ json: true, cwd, migrationCheck: args.migrationCheck });
1415
+ }
1416
+ finally {
1417
+ console.log = origLog;
1418
+ console.warn = origWarn;
1419
+ console.error = origError;
1420
+ }
1421
+ const jsonStr = captured.join('\n');
1422
+ let structured = {};
1423
+ try {
1424
+ structured = JSON.parse(jsonStr);
1425
+ }
1426
+ catch { /* non-JSON fallback */ }
1427
+ const ok = structured.ok;
1428
+ const checks = structured.checks ?? [];
1429
+ const errors = checks.filter(c => c.status === 'error');
1430
+ const warns = checks.filter(c => c.status === 'warn');
1431
+ const summary = ok
1432
+ ? `✔ All ${checks.length} checks passed.`
1433
+ : `${errors.length} error(s), ${warns.length} warning(s) out of ${checks.length} checks.`;
1434
+ return {
1435
+ content: [{ type: 'text', text: summary }],
1436
+ structuredContent: { ...structured, schema_version: SCHEMA_VERSION },
1437
+ };
1438
+ }
1439
+ if (name === 'bclaw_history') {
1440
+ const id = String(args.id ?? '').trim();
1441
+ if (!id)
1442
+ throw new Error('Missing required argument: id');
1443
+ const entries = readAuditLog({ itemId: id }, cwd);
1444
+ const lines = [`History for ${id} — ${entries.length} event(s):`];
1445
+ for (const e of entries) {
1446
+ const reason = e.reason ? ` | ${e.reason}` : '';
1447
+ lines.push(` ${e.timestamp} [${e.actor}] ${e.action}${reason}`);
1448
+ }
1449
+ return {
1450
+ content: [{ type: 'text', text: lines.join('\n') }],
1451
+ structuredContent: { id, total: entries.length, entries, schema_version: SCHEMA_VERSION },
1452
+ };
1453
+ }
1454
+ if (name === 'bclaw_audit') {
1455
+ // Governance mode
1456
+ if (args.governance === true) {
1457
+ const report = buildGovernanceReport({
1458
+ scope: args.scope,
1459
+ agent: args.actor,
1460
+ since: args.since,
1461
+ cwd,
1462
+ });
1463
+ const markdown = renderGovernanceMarkdown(report);
1464
+ return {
1465
+ content: [{ type: 'text', text: markdown }],
1466
+ structuredContent: { ...report, schema_version: SCHEMA_VERSION },
1467
+ };
1468
+ }
1469
+ // Chronological mode (default)
1470
+ const limit = args.limit ?? 20;
1471
+ const entries = readAuditLog({
1472
+ since: args.since,
1473
+ actor: args.actor,
1474
+ action: args.action,
1475
+ }, cwd);
1476
+ const sliced = entries.slice(-limit);
1477
+ const lines = [`Audit log — showing ${sliced.length} of ${entries.length} entries:`];
1478
+ for (const e of sliced) {
1479
+ const itemInfo = e.item_id ? ` → ${e.item_id}` : '';
1480
+ const typeInfo = e.item_type ? ` (${e.item_type})` : '';
1481
+ const scopeInfo = e.scope ? ` scope:${e.scope}` : '';
1482
+ const reason = e.reason ? ` | ${e.reason}` : '';
1483
+ lines.push(` ${e.timestamp} [${e.actor}] ${e.action}${itemInfo}${typeInfo}${scopeInfo}${reason}`);
1484
+ }
1485
+ return {
1486
+ content: [{ type: 'text', text: lines.join('\n') }],
1487
+ structuredContent: { total: entries.length, returned: sliced.length, entries: sliced, schema_version: SCHEMA_VERSION },
1488
+ };
1489
+ }
1490
+ if (name === 'bclaw_dispatch_analysis') {
1491
+ const analysis = analyzeSequence(cwd);
1492
+ if (!analysis) {
1493
+ return {
1494
+ content: [{ type: 'text', text: 'No active sequence found.' }],
1495
+ structuredContent: { active_sequence: false, schema_version: SCHEMA_VERSION },
1496
+ };
1497
+ }
1498
+ const lanesFilter = args.lanes;
1499
+ const lines = [`Dispatch analysis — Sequence: ${analysis.sequence.name}`];
1500
+ lines.push('');
1501
+ // Ready lanes
1502
+ let ready = analysis.ready;
1503
+ if (lanesFilter?.length)
1504
+ ready = ready.filter(r => r.lane && lanesFilter.includes(r.lane));
1505
+ lines.push(`🟢 Ready (${ready.length}):`);
1506
+ for (const r of ready) {
1507
+ const lane = r.lane ? ` [${r.lane}]` : '';
1508
+ const assignee = r.plan.assignee ? ` → ${r.plan.assignee}` : '';
1509
+ lines.push(` ${r.plan.short_label ?? r.plan.id}${lane}${assignee} — ${r.plan.text.slice(0, 80)}`);
1510
+ lines.push(` ${r.reason}`);
1511
+ }
1512
+ // Active lanes
1513
+ let active = analysis.active;
1514
+ if (lanesFilter?.length)
1515
+ active = active.filter(a => a.lane && lanesFilter.includes(a.lane));
1516
+ if (active.length > 0) {
1517
+ lines.push('');
1518
+ lines.push(`🔵 Active (${active.length}):`);
1519
+ for (const a of active) {
1520
+ const lane = a.lane ? ` [${a.lane}]` : '';
1521
+ // Mirror dispatch.ts: pre-adoption lanes render as "pending adoption",
1522
+ // degraded liveness states get a [TAG] suffix.
1523
+ let status = 'working';
1524
+ let livenessTag = '';
1525
+ if (a.liveness === 'young' && !a.claim.session_id) {
1526
+ status = 'pending adoption';
1527
+ }
1528
+ else if (a.liveness && a.liveness !== 'live' && a.liveness !== 'young') {
1529
+ livenessTag = ` [${a.liveness.toUpperCase()}]`;
1530
+ }
1531
+ lines.push(` ${a.plan.short_label ?? a.plan.id}${lane} — ${a.agent} ${status}${livenessTag}`);
1532
+ }
1533
+ }
1534
+ // Blocked lanes
1535
+ let blocked = analysis.blocked;
1536
+ if (lanesFilter?.length)
1537
+ blocked = blocked.filter(b => b.lane && lanesFilter.includes(b.lane));
1538
+ if (blocked.length > 0) {
1539
+ lines.push('');
1540
+ lines.push(`🔴 Blocked (${blocked.length}):`);
1541
+ for (const b of blocked) {
1542
+ const lane = b.lane ? ` [${b.lane}]` : '';
1543
+ lines.push(` ${b.item.planId}${lane} — ${b.reason}`);
1544
+ }
1545
+ }
1546
+ // Done
1547
+ if (analysis.done.length > 0) {
1548
+ lines.push('');
1549
+ lines.push(`✅ Done (${analysis.done.length})`);
1550
+ }
1551
+ // Available agents
1552
+ lines.push('');
1553
+ lines.push(`Available agents: ${analysis.available_agents.length > 0 ? analysis.available_agents.join(', ') : '(none)'}`);
1554
+ return {
1555
+ content: [{ type: 'text', text: lines.join('\n') }],
1556
+ structuredContent: { ...analysis, schema_version: SCHEMA_VERSION },
1557
+ };
1558
+ }
1559
+ if (name === 'bclaw_read_inbox') {
1560
+ const agentName = args.agent ?? resolveCurrentAgentName(cwd);
1561
+ const markAsRead = args.markAsRead === true; // default: false — reading doesn't imply processing
1562
+ const result = readInbox({
1563
+ agent: agentName,
1564
+ status: args.status,
1565
+ type: args.type,
1566
+ thread_id: args.thread_id,
1567
+ limit: args.limit,
1568
+ offset: args.offset,
1569
+ markAsRead,
1570
+ }, cwd);
1571
+ const lines = [`Inbox for ${agentName} — ${result.total} message(s):`];
1572
+ for (const msg of result.messages) {
1573
+ const ack = msg.requires_ack ? ' [ACK required]' : '';
1574
+ const thread = msg.thread_id ? ` thread:${msg.thread_id}` : '';
1575
+ lines.push(` [${msg.short_label ?? msg.id}] ${msg.type} from ${msg.from} (${msg.status})${ack}${thread}`);
1576
+ lines.push(` ${msg.text.slice(0, 200)}${msg.text.length > 200 ? '...' : ''}`);
1577
+ }
1578
+ if (result.messages.length === 0) {
1579
+ lines.push(' (no messages)');
1580
+ }
1581
+ return {
1582
+ content: [{ type: 'text', text: lines.join('\n') }],
1583
+ structuredContent: { ...result, schema_version: SCHEMA_VERSION },
1584
+ };
1585
+ }
1586
+ if (name === 'bclaw_context') {
1587
+ // Phase 3 slice 3c — unified dispatcher. See docs/concepts/mcp-governance.md
1588
+ // for the stability contract of the advanced tier.
1589
+ const kind = String(args.kind ?? '');
1590
+ switch (kind) {
1591
+ case 'memory':
1592
+ return handleMcpReadToolCall('bclaw_get_context', args, context);
1593
+ case 'execution':
1594
+ return handleMcpReadToolCall('bclaw_get_execution_context', args, context);
1595
+ case 'board':
1596
+ return handleMcpReadToolCall('bclaw_get_agent_board', args, context);
1597
+ case 'board_summary':
1598
+ return handleMcpReadToolCall('bclaw_get_agent_board_summary', args, context);
1599
+ case 'delta': {
1600
+ const since = args.since;
1601
+ if (typeof since !== 'string' || !since) {
1602
+ throw new Error('bclaw_context(kind="delta") requires `since` (session_id).');
1603
+ }
1604
+ return handleMcpReadToolCall('bclaw_get_context', { ...args, since_session: since }, context);
1605
+ }
1606
+ default:
1607
+ throw new Error(`bclaw_context: unknown kind '${kind}'. Expected memory | execution | board | board_summary | delta.`);
1608
+ }
1609
+ }
1610
+ if (name === 'bclaw_get_thread') {
1611
+ const threadId = String(args.thread_id ?? '');
1612
+ if (!threadId) {
1613
+ throw new Error('Missing required argument: thread_id');
1614
+ }
1615
+ const messages = getThread(threadId, cwd);
1616
+ const lines = [`Thread ${threadId} — ${messages.length} message(s):`];
1617
+ for (const msg of messages) {
1618
+ lines.push(` [${msg.short_label ?? msg.id}] ${msg.from} → ${msg.to} (${msg.type}, ${msg.status})`);
1619
+ lines.push(` ${msg.text.slice(0, 200)}${msg.text.length > 200 ? '...' : ''}`);
1620
+ }
1621
+ return {
1622
+ content: [{ type: 'text', text: lines.join('\n') }],
1623
+ structuredContent: { thread_id: threadId, total: messages.length, messages, schema_version: SCHEMA_VERSION },
1624
+ };
1625
+ }
1626
+ throw new Error(`Unknown read tool: ${name}`);
1627
+ }
1628
+ //# sourceMappingURL=mcp-read-handlers.js.map