brainclaw 1.8.0 → 1.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +592 -505
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/cli.js +138 -13
- package/dist/commands/add-step.js +1 -1
- package/dist/commands/bootstrap.js +2 -26
- package/dist/commands/check-security-mcp.js +50 -33
- package/dist/commands/check-security.js +86 -43
- package/dist/commands/claim.js +22 -21
- package/dist/commands/confirm.js +26 -0
- package/dist/commands/context-diff.js +1 -1
- package/dist/commands/dispatch-watch.js +142 -0
- package/dist/commands/doctor.js +113 -2
- package/dist/commands/estimation-report.js +115 -16
- package/dist/commands/harvest.js +286 -23
- package/dist/commands/hooks.js +73 -73
- package/dist/commands/init.js +124 -22
- package/dist/commands/install-hooks.js +78 -78
- package/dist/commands/loops-handlers.js +4 -0
- package/dist/commands/mcp-read-handlers.js +253 -41
- package/dist/commands/mcp.js +664 -102
- package/dist/commands/memory.js +21 -17
- package/dist/commands/migrate.js +81 -17
- package/dist/commands/prune.js +78 -4
- package/dist/commands/reflect.js +26 -20
- package/dist/commands/register-agent.js +57 -1
- package/dist/commands/repair.js +20 -0
- package/dist/commands/session-end.js +15 -6
- package/dist/commands/session-start.js +18 -1
- package/dist/commands/setup-security.js +39 -18
- package/dist/commands/setup.js +26 -27
- package/dist/commands/stale.js +16 -2
- package/dist/commands/switch.js +26 -5
- package/dist/commands/uninstall.js +126 -34
- package/dist/commands/update-step.js +6 -0
- package/dist/commands/version.js +1 -1
- package/dist/commands/worktree.js +60 -0
- package/dist/core/actions.js +12 -3
- package/dist/core/agent-capability.js +30 -17
- package/dist/core/agent-files.js +963 -666
- package/dist/core/agent-integrations.js +0 -3
- package/dist/core/agent-inventory.js +67 -0
- package/dist/core/agent-registry.js +163 -29
- package/dist/core/agentrun-reconciler.js +33 -2
- package/dist/core/agentruns.js +7 -1
- package/dist/core/ai-agent-detection.js +31 -44
- package/dist/core/archival.js +15 -9
- package/dist/core/assignment-reconciler.js +56 -0
- package/dist/core/assignment-sweeper.js +127 -4
- package/dist/core/assignments.js +69 -11
- package/dist/core/bootstrap.js +233 -67
- package/dist/core/brainclaw-version.js +22 -0
- package/dist/core/candidates.js +21 -1
- package/dist/core/claims.js +313 -150
- package/dist/core/codev-prompts.js +38 -38
- package/dist/core/config.js +6 -1
- package/dist/core/context-diff.js +148 -20
- package/dist/core/context.js +129 -8
- package/dist/core/coordination.js +22 -3
- package/dist/core/default-profiles/doctor.yaml +11 -11
- package/dist/core/default-profiles/janitor.yaml +11 -11
- package/dist/core/default-profiles/onboarder.yaml +11 -11
- package/dist/core/default-profiles/reviewer.yaml +13 -13
- package/dist/core/dispatch-status.js +79 -5
- package/dist/core/dispatcher.js +65 -12
- package/dist/core/entity-operations.js +74 -27
- package/dist/core/entity-registry.js +31 -5
- package/dist/core/event-log.js +138 -21
- package/dist/core/events/checkpoint.js +258 -0
- package/dist/core/events/genesis.js +220 -0
- package/dist/core/events/journal.js +507 -0
- package/dist/core/events/materialize.js +126 -0
- package/dist/core/events/registry-post-image.js +110 -0
- package/dist/core/events/verify.js +109 -0
- package/dist/core/execution-adapters.js +23 -0
- package/dist/core/execution.js +1 -1
- package/dist/core/facade-schema.js +38 -0
- package/dist/core/gc-semantic.js +130 -5
- package/dist/core/handoff-snapshot.js +68 -0
- package/dist/core/ids.js +19 -8
- package/dist/core/instruction-templates.js +34 -115
- package/dist/core/io.js +39 -3
- package/dist/core/json-store.js +10 -1
- package/dist/core/lock.js +153 -28
- package/dist/core/loops/bootstrap-acquire.js +25 -1
- package/dist/core/loops/facade-schema.js +2 -0
- package/dist/core/loops/hooks/survey-signals-baseline.js +36 -0
- package/dist/core/loops/index.js +1 -0
- package/dist/core/loops/presets/bootstrap.js +7 -0
- package/dist/core/loops/store.js +17 -0
- package/dist/core/loops/verbs.js +24 -2
- package/dist/core/markdown.js +8 -76
- package/dist/core/mcp-command-resolution.js +245 -0
- package/dist/core/memory-compactor.js +5 -3
- package/dist/core/memory-lifecycle.js +282 -0
- package/dist/core/merge-risk.js +150 -0
- package/dist/core/messaging.js +10 -3
- package/dist/core/migration.js +11 -1
- package/dist/core/observer-mode.js +26 -0
- package/dist/core/operations/memory-mutation.js +90 -65
- package/dist/core/operations/plan.js +27 -1
- package/dist/core/protocol-skills.js +210 -0
- package/dist/core/reflection-safety.js +6 -7
- package/dist/core/reputation.js +84 -2
- package/dist/core/runtime-signals.js +72 -10
- package/dist/core/runtime.js +84 -1
- package/dist/core/schema.js +114 -0
- package/dist/core/search.js +19 -2
- package/dist/core/security-detectors.js +125 -0
- package/dist/core/security-extract.js +189 -0
- package/dist/core/security-guard.js +217 -139
- package/dist/core/security-packages.js +121 -0
- package/dist/core/security-scoring.js +76 -9
- package/dist/core/security.js +34 -2
- package/dist/core/sequence.js +11 -2
- package/dist/core/setup-flow.js +141 -13
- package/dist/core/spawn-check.js +16 -2
- package/dist/core/staleness.js +73 -2
- package/dist/core/state.js +250 -54
- package/dist/core/store-resolution.js +45 -12
- package/dist/core/worktree.js +90 -26
- package/dist/facts.js +8 -8
- package/dist/facts.json +7 -7
- package/docs/PROTOCOL.md +223 -0
- package/docs/adapters/openclaw.md +43 -43
- package/docs/architecture/project-refs.md +328 -328
- package/docs/cli.md +2097 -2096
- package/docs/concepts/coordination.md +52 -52
- package/docs/concepts/coordinator-runbook.md +129 -0
- package/docs/concepts/dispatch-lifecycle.md +245 -245
- package/docs/concepts/event-log-store.md +928 -0
- package/docs/concepts/ideation-loop.md +317 -317
- package/docs/concepts/loop-engine.md +520 -511
- package/docs/concepts/mcp-governance.md +268 -268
- package/docs/concepts/memory.md +89 -88
- package/docs/concepts/multi-agent-workflows.md +167 -167
- package/docs/concepts/observer-protocol.md +361 -0
- package/docs/concepts/parallel-merge-protocol.md +71 -0
- package/docs/concepts/plans-and-claims.md +217 -174
- package/docs/concepts/project-md-convention.md +35 -35
- package/docs/concepts/runtime-notes.md +38 -38
- package/docs/concepts/skills.md +78 -0
- package/docs/concepts/troubleshooting.md +254 -254
- package/docs/concepts/workspace-bootstrapping.md +142 -81
- package/docs/context-format-changelog.md +35 -35
- package/docs/context-format.md +48 -48
- package/docs/index.md +65 -65
- package/docs/integrations/agents.md +162 -162
- package/docs/integrations/claude-code.md +23 -23
- package/docs/integrations/cline.md +87 -88
- package/docs/integrations/codex.md +2 -2
- package/docs/integrations/continue.md +60 -60
- package/docs/integrations/copilot.md +82 -80
- package/docs/integrations/cursor.md +23 -23
- package/docs/integrations/kilocode.md +72 -72
- package/docs/integrations/mcp.md +377 -377
- package/docs/integrations/mistral-vibe.md +122 -122
- package/docs/integrations/openclaw.md +99 -98
- package/docs/integrations/opencode.md +84 -84
- package/docs/integrations/overview.md +122 -122
- package/docs/integrations/roo.md +74 -74
- package/docs/integrations/windsurf.md +83 -83
- package/docs/mcp-schema-changelog.md +360 -329
- package/docs/playbooks/integration/index.md +121 -121
- package/docs/playbooks/orchestration.md +37 -0
- package/docs/playbooks/productivity/index.md +99 -99
- package/docs/playbooks/team/index.md +117 -117
- package/docs/product/agent-first-model.md +184 -184
- package/docs/product/entity-model-audit.md +462 -462
- package/docs/product/positioning.md +86 -86
- package/docs/quickstart-existing-project.md +107 -107
- package/docs/quickstart.md +148 -147
- package/docs/release-maintenance.md +79 -79
- package/docs/reputation.md +52 -52
- package/docs/review.md +45 -45
- package/docs/security.md +212 -53
- package/docs/server-operations.md +118 -118
- package/docs/storage.md +110 -108
- package/package.json +86 -69
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { applyBootstrapImport, renderBootstrapInterview, renderBootstrapSummary, runBootstrapProfile, uninstallBootstrapImport } from '../core/bootstrap.js';
|
|
2
2
|
import { buildAgentToolingContext, renderAgentToolingSummary } from '../core/agent-context.js';
|
|
3
|
-
import { buildCoordinationSnapshot } from '../core/coordination.js';
|
|
3
|
+
import { buildCoordinationSnapshot, buildCrossProjectSnapshot } from '../core/coordination.js';
|
|
4
4
|
import { scanDescendantPlans } from './list-plans.js';
|
|
5
5
|
import { buildContext } from '../core/context.js';
|
|
6
6
|
import { buildExecutionContext, renderExecutionContextSummary } from '../core/execution-context.js';
|
|
7
7
|
import { checkBrainclawInstallableUpdate, renderBrainclawInstallableUpdateNotice } from '../core/brainclaw-version.js';
|
|
8
8
|
import { loadConfig } from '../core/config.js';
|
|
9
|
-
import { loadAllSessions, loadCurrentSession, saveCurrentSession, gcStaleSessions } from '../core/identity.js';
|
|
9
|
+
import { loadAllSessions, loadCurrentSession, loadSessionById, saveCurrentSession, gcStaleSessions } from '../core/identity.js';
|
|
10
10
|
import { loadState } from '../core/state.js';
|
|
11
11
|
import { listArchivedCandidates, listCandidates, resolvedSource } from '../core/candidates.js';
|
|
12
12
|
import { listClaims, assessClaimLiveness } from '../core/claims.js';
|
|
13
13
|
import { listAssignments } from '../core/assignments.js';
|
|
14
14
|
import { listAgentRuns } from '../core/agentruns.js';
|
|
15
15
|
import { reconcileAgentRun } from '../core/agentrun-reconciler.js';
|
|
16
|
+
import { isObserverMode } from '../core/observer-mode.js';
|
|
16
17
|
import { getDispatchStatus } from '../core/dispatch-status.js';
|
|
17
18
|
import { listActionRequired } from '../core/actions.js';
|
|
18
19
|
import { queryRuntimeEvents } from '../core/events.js';
|
|
@@ -26,15 +27,17 @@ import { checkPolicy } from '../core/policy.js';
|
|
|
26
27
|
import { buildGovernanceReport, renderGovernanceMarkdown } from '../core/governance.js';
|
|
27
28
|
import { inferProjectFromTarget, loadInstructions, resolveInstructions } from '../core/instructions.js';
|
|
28
29
|
import { buildReputationSnapshot, toPublicReputationSummary } from '../core/reputation.js';
|
|
29
|
-
import { search } from '../core/search.js';
|
|
30
|
+
import { countLegacySearchMatches, search } from '../core/search.js';
|
|
30
31
|
import { buildEstimationReport } from './estimation-report.js';
|
|
31
32
|
import { runDoctor } from './doctor.js';
|
|
32
33
|
import { buildProjectDiscovery, saveDiscoveryProfile, loadDiscoveryProfile, renderDiscoverySummary } from '../core/project-discovery.js';
|
|
33
34
|
import { listCapabilities, listTools as listRegistryTools } from '../core/registries.js';
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
35
|
+
import { listAvailableProjectsForSession, switchProject } from './switch.js';
|
|
36
|
+
import { resolveEffectiveCwdInfo } from '../core/store-resolution.js';
|
|
36
37
|
import { resolveProjectCwd } from '../core/cross-project.js';
|
|
37
38
|
import { readUnseenEvents, buildNotificationSummary } from '../core/event-log.js';
|
|
39
|
+
import { boundListResult, DEFAULT_FIND_CHAR_BUDGET } from '../core/entity-operations.js';
|
|
40
|
+
import { handoffDiffPreviewNote } from '../core/handoff-snapshot.js';
|
|
38
41
|
import { BootstrapInterviewAnswerSchema, AssignmentStatusSchema, AgentRunStatusSchema, AgentRunTransportSchema, ActionRequiredStatusSchema, ActionRequiredKindSchema } from '../core/schema.js';
|
|
39
42
|
import { SCHEMA_VERSION, createToolErrorResponse, normaliseFormat, renderContextForMcp, } from './mcp.js';
|
|
40
43
|
function normalizeBootstrapInterviewAnswersArg(value) {
|
|
@@ -60,6 +63,33 @@ function normalizeBootstrapInterviewAudienceArg(value) {
|
|
|
60
63
|
}
|
|
61
64
|
return 'any';
|
|
62
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* trp#449 class (pln#542): bound an arbitrary object payload by repeatedly
|
|
68
|
+
* halving its largest array field until the serialized size fits the budget.
|
|
69
|
+
* Returns the bounded payload plus a per-field omitted count so the caller
|
|
70
|
+
* can advertise what was dropped.
|
|
71
|
+
*/
|
|
72
|
+
function boundObjectArrays(payload, charBudget) {
|
|
73
|
+
const omitted = {};
|
|
74
|
+
let current = { ...payload };
|
|
75
|
+
while (JSON.stringify(current).length > charBudget) {
|
|
76
|
+
let largestKey;
|
|
77
|
+
let largestLen = 1;
|
|
78
|
+
for (const [key, value] of Object.entries(current)) {
|
|
79
|
+
if (Array.isArray(value) && value.length > largestLen) {
|
|
80
|
+
largestKey = key;
|
|
81
|
+
largestLen = value.length;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (!largestKey)
|
|
85
|
+
break;
|
|
86
|
+
const arr = current[largestKey];
|
|
87
|
+
const newLen = Math.max(1, Math.floor(arr.length / 2));
|
|
88
|
+
omitted[largestKey] = (omitted[largestKey] ?? 0) + (arr.length - newLen);
|
|
89
|
+
current = { ...current, [largestKey]: arr.slice(0, newLen) };
|
|
90
|
+
}
|
|
91
|
+
return { payload: current, omitted };
|
|
92
|
+
}
|
|
63
93
|
function getReviewAssignee(tags) {
|
|
64
94
|
for (const tag of tags) {
|
|
65
95
|
if (tag.startsWith('assignee:')) {
|
|
@@ -70,20 +100,38 @@ function getReviewAssignee(tags) {
|
|
|
70
100
|
}
|
|
71
101
|
export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
72
102
|
const baseCwd = context.cwd ?? process.cwd();
|
|
73
|
-
|
|
74
|
-
? baseCwd
|
|
75
|
-
:
|
|
103
|
+
const effective = name === 'bclaw_switch'
|
|
104
|
+
? { cwd: baseCwd, active_source: 'cwd', resolved_project: undefined }
|
|
105
|
+
: context.effectiveScope ?? resolveEffectiveCwdInfo({ baseCwd, sessionId: context.connectionSessionId });
|
|
106
|
+
let cwd = effective.cwd;
|
|
107
|
+
let activeSource = effective.active_source;
|
|
108
|
+
let resolvedProject = effective.resolved_project;
|
|
76
109
|
// If a project param is provided, resolve it to an actual cwd override.
|
|
77
110
|
// resolveProjectCwd unifies cross_project_links (siblings/peers) AND
|
|
78
111
|
// workspace store-chain children. Throws on unknown project — surfaces
|
|
79
112
|
// visibly as a tool error rather than silently falling back to the
|
|
80
113
|
// current project, which would mislead the caller.
|
|
114
|
+
// Precedence rule: once routing succeeds, per-handler "project as filter"
|
|
115
|
+
// logic is skipped (routing already scopes to the right store) — pln#359.
|
|
81
116
|
const projectArg = args.project;
|
|
82
117
|
const targetProjectArg = name === 'bclaw_switch' ? undefined : projectArg;
|
|
118
|
+
let projectRoutingApplied = false;
|
|
83
119
|
if (targetProjectArg) {
|
|
84
120
|
cwd = resolveProjectCwd(targetProjectArg, cwd);
|
|
121
|
+
activeSource = 'explicit';
|
|
122
|
+
try {
|
|
123
|
+
const config = loadConfig(cwd);
|
|
124
|
+
resolvedProject = { path: cwd, name: config.project_name };
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
resolvedProject = { path: cwd };
|
|
128
|
+
}
|
|
129
|
+
projectRoutingApplied = true;
|
|
85
130
|
}
|
|
86
131
|
if (name === 'bclaw_get_context') {
|
|
132
|
+
// pln#542: budget_tokens caps the relevance-ranked fill (~4 chars/token).
|
|
133
|
+
// Explicit maxChars wins when both are given.
|
|
134
|
+
const budgetTokens = typeof args.budget_tokens === 'number' && args.budget_tokens > 0 ? args.budget_tokens : undefined;
|
|
87
135
|
const result = buildContext({
|
|
88
136
|
target: args.path,
|
|
89
137
|
project: targetProjectArg,
|
|
@@ -93,7 +141,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
93
141
|
profile: args.profile,
|
|
94
142
|
includePending: args.includePending,
|
|
95
143
|
maxItems: args.maxItems,
|
|
96
|
-
maxChars: args.maxChars,
|
|
144
|
+
maxChars: args.maxChars ?? (budgetTokens ? budgetTokens * 4 : undefined),
|
|
97
145
|
digest: args.digest,
|
|
98
146
|
sinceSession: args.since_session,
|
|
99
147
|
bootstrap: args.bootstrap,
|
|
@@ -133,10 +181,22 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
133
181
|
suggestions.push('\n💡 Tip: Use bclaw_get_capabilities, bclaw_list_tools, or bclaw_search_tools for detailed discovery');
|
|
134
182
|
enrichedContent = content + suggestions.join('\n');
|
|
135
183
|
}
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
184
|
+
// Unseen-event notifications. When buildContext already computed the
|
|
185
|
+
// event-cursor diff (the converged novelty mechanism, pln#542), reuse its
|
|
186
|
+
// histogram — the cursor was advanced by that read, so a second
|
|
187
|
+
// readUnseenEvents would observe nothing.
|
|
188
|
+
let notifications;
|
|
189
|
+
let unseenEventCount;
|
|
190
|
+
if (result.context_diff?.source === 'event_cursor') {
|
|
191
|
+
notifications = result.context_diff.event_summary;
|
|
192
|
+
unseenEventCount = result.context_diff.unseen_event_count;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
const agentName = args.agent ?? resolveCurrentAgentName(cwd);
|
|
196
|
+
const unseenEvents = readUnseenEvents(agentName, cwd);
|
|
197
|
+
notifications = buildNotificationSummary(unseenEvents);
|
|
198
|
+
unseenEventCount = unseenEvents.length;
|
|
199
|
+
}
|
|
140
200
|
return {
|
|
141
201
|
content: [{ type: 'text', text: enrichedContent || 'No relevant memory found.' }],
|
|
142
202
|
structuredContent: {
|
|
@@ -151,7 +211,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
151
211
|
name: tool.name,
|
|
152
212
|
type: tool.type,
|
|
153
213
|
})),
|
|
154
|
-
...(notifications ? { pending_notifications: notifications, unseen_event_count:
|
|
214
|
+
...(notifications ? { pending_notifications: notifications, unseen_event_count: unseenEventCount } : {}),
|
|
155
215
|
},
|
|
156
216
|
};
|
|
157
217
|
}
|
|
@@ -197,12 +257,38 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
197
257
|
lines.push(`Suggestion: ${suggestion}`);
|
|
198
258
|
}
|
|
199
259
|
}
|
|
200
|
-
|
|
201
|
-
|
|
260
|
+
// trp#449 class (pln#542): the embedded git diff is unbounded — cap it so
|
|
261
|
+
// a large handoff snapshot never overflows the MCP token budget.
|
|
262
|
+
// budget_tokens tightens the cap (~4 chars/token).
|
|
263
|
+
const handoffBudgetTokens = typeof args.budget_tokens === 'number' && args.budget_tokens > 0 ? args.budget_tokens : undefined;
|
|
264
|
+
const diffCharBudget = handoffBudgetTokens ? Math.min(handoffBudgetTokens * 4, DEFAULT_FIND_CHAR_BUDGET) : DEFAULT_FIND_CHAR_BUDGET;
|
|
265
|
+
let boundedHandoff = handoff;
|
|
266
|
+
let diffTruncated = false;
|
|
267
|
+
if (handoff.snapshot?.diff && handoff.snapshot.diff.length > diffCharBudget) {
|
|
268
|
+
diffTruncated = true;
|
|
269
|
+
boundedHandoff = {
|
|
270
|
+
...handoff,
|
|
271
|
+
snapshot: {
|
|
272
|
+
...handoff.snapshot,
|
|
273
|
+
diff: `${handoff.snapshot.diff.slice(0, diffCharBudget)}\n… [diff truncated to ${diffCharBudget} chars — read the worktree branch for the full diff]`,
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
if (boundedHandoff.snapshot?.diff) {
|
|
278
|
+
lines.push('', 'Uncommitted Git Diff:', boundedHandoff.snapshot.diff);
|
|
279
|
+
// pln#569 — the inline diff is a capped preview when a digest is present;
|
|
280
|
+
// tell the reader the full diff lives on the worktree branch.
|
|
281
|
+
const note = handoffDiffPreviewNote(boundedHandoff.snapshot);
|
|
282
|
+
if (!diffTruncated && note)
|
|
283
|
+
lines.push(note);
|
|
202
284
|
}
|
|
203
285
|
return {
|
|
204
286
|
content: [{ type: 'text', text: lines.join('\n') }],
|
|
205
|
-
structuredContent: {
|
|
287
|
+
structuredContent: {
|
|
288
|
+
handoff: boundedHandoff,
|
|
289
|
+
...(diffTruncated ? { diff_truncated: true } : {}),
|
|
290
|
+
schema_version: SCHEMA_VERSION,
|
|
291
|
+
},
|
|
206
292
|
};
|
|
207
293
|
}
|
|
208
294
|
if (name === 'bclaw_bootstrap') {
|
|
@@ -210,6 +296,23 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
210
296
|
if (args.apply && args.uninstall) {
|
|
211
297
|
throw new Error('bclaw_bootstrap does not allow apply and uninstall at the same time.');
|
|
212
298
|
}
|
|
299
|
+
// Mirror the CLI confirmAction gate: apply/uninstall mutate canonical
|
|
300
|
+
// memory, so they refuse without an explicit yes:true instead of relying
|
|
301
|
+
// on the host honouring the headlessApproval annotation.
|
|
302
|
+
if ((args.apply || args.uninstall) && args.yes !== true) {
|
|
303
|
+
const action = args.uninstall ? 'uninstall' : 'apply';
|
|
304
|
+
return {
|
|
305
|
+
content: [{
|
|
306
|
+
type: 'text',
|
|
307
|
+
text: `bclaw_bootstrap ${action} modifies canonical memory and requires explicit confirmation. Confirm with the user, then re-call with yes: true. No changes were made.`,
|
|
308
|
+
}],
|
|
309
|
+
structuredContent: {
|
|
310
|
+
confirmation_required: true,
|
|
311
|
+
action,
|
|
312
|
+
schema_version: SCHEMA_VERSION,
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
}
|
|
213
316
|
if (args.uninstall) {
|
|
214
317
|
const result = uninstallBootstrapImport(cwd);
|
|
215
318
|
const text = !result.receipt
|
|
@@ -371,11 +474,34 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
371
474
|
return !plan || plan.status === 'todo';
|
|
372
475
|
}).length
|
|
373
476
|
: 0;
|
|
477
|
+
// pln#559 step 3 — attention_required is now the FULL composite the
|
|
478
|
+
// Attention section already displays in the extension: pending actions
|
|
479
|
+
// + non-auto (human-review) candidates + blocked assignments + stale
|
|
480
|
+
// runs. The 2026-06-10 calibration: the badge previously read only
|
|
481
|
+
// pendingActions and chronically undercounted while the Attention
|
|
482
|
+
// section showed N >> badge. The badge must NEVER be smaller than the
|
|
483
|
+
// section header it represents.
|
|
484
|
+
const pendingCandidates = listCandidates('pending', cwd);
|
|
485
|
+
const pendingHumanCandidates = pendingCandidates.filter((c) => resolvedSource(c) !== 'auto');
|
|
486
|
+
const allAssignments = listAssignments(cwd);
|
|
487
|
+
const blockedAssignments = allAssignments.filter((a) => a.status === 'blocked');
|
|
488
|
+
const allRuns = listAgentRuns(cwd);
|
|
489
|
+
const staleRuns = allRuns.filter((r) => r.status === 'blocked' || r.status === 'waiting_input' || r.status === 'failed');
|
|
490
|
+
const attentionRequiredComposite = pendingActions.length +
|
|
491
|
+
pendingHumanCandidates.length +
|
|
492
|
+
blockedAssignments.length +
|
|
493
|
+
staleRuns.length;
|
|
374
494
|
const summary = {
|
|
375
495
|
project_id: config.project_id,
|
|
376
496
|
agent,
|
|
377
497
|
current_host: currentHost,
|
|
378
|
-
attention_required:
|
|
498
|
+
attention_required: attentionRequiredComposite,
|
|
499
|
+
attention_breakdown: {
|
|
500
|
+
pending_actions: pendingActions.length,
|
|
501
|
+
pending_human_candidates: pendingHumanCandidates.length,
|
|
502
|
+
blocked_assignments: blockedAssignments.length,
|
|
503
|
+
stale_runs: staleRuns.length,
|
|
504
|
+
},
|
|
379
505
|
in_progress: activeClaims.length,
|
|
380
506
|
plans: {
|
|
381
507
|
in_progress: state.plan_items.filter((p) => p.status === 'in_progress').length,
|
|
@@ -491,9 +617,22 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
491
617
|
lines.push(`- ${other.name}: ${other.claim_count} claim(s) on ${other.scopes.join(', ')}`);
|
|
492
618
|
}
|
|
493
619
|
}
|
|
620
|
+
// trp#449 class (pln#542): the board aggregates many unbounded arrays —
|
|
621
|
+
// bound the structured payload by size. budget_tokens tightens the cap.
|
|
622
|
+
const boardBudgetTokens = typeof args.budget_tokens === 'number' && args.budget_tokens > 0 ? args.budget_tokens : undefined;
|
|
623
|
+
const boardCharBudget = boardBudgetTokens ? Math.min(boardBudgetTokens * 4, DEFAULT_FIND_CHAR_BUDGET) : DEFAULT_FIND_CHAR_BUDGET;
|
|
624
|
+
const { payload: boundedBoard, omitted } = boundObjectArrays(board, boardCharBudget);
|
|
494
625
|
return {
|
|
495
626
|
content: [{ type: 'text', text: lines.join('\n') }],
|
|
496
|
-
structuredContent: {
|
|
627
|
+
structuredContent: {
|
|
628
|
+
...boundedBoard,
|
|
629
|
+
...(Object.keys(omitted).length > 0
|
|
630
|
+
? {
|
|
631
|
+
omitted_for_size: omitted,
|
|
632
|
+
hint: `Payload size-bounded: ${Object.entries(omitted).map(([k, n]) => `${n} ${k}`).join(', ')} omitted. Use bclaw_find(entity=…) with filters to read the full lists.`,
|
|
633
|
+
}
|
|
634
|
+
: {}),
|
|
635
|
+
},
|
|
497
636
|
};
|
|
498
637
|
}
|
|
499
638
|
if (name === 'bclaw_search') {
|
|
@@ -503,19 +642,68 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
503
642
|
}
|
|
504
643
|
const offset = Math.max(0, Number(args.offset) || 0);
|
|
505
644
|
const limit = typeof args.limit === 'number' ? args.limit : 10;
|
|
645
|
+
const includeLegacy = args.includeLegacy === true || (typeof args.filter === 'object' && args.filter?.includeLegacy === true);
|
|
506
646
|
const allResults = search({
|
|
507
647
|
query,
|
|
508
648
|
section: (args.section ?? args.type),
|
|
509
649
|
since: args.since,
|
|
510
|
-
maxResults:
|
|
650
|
+
maxResults: Number.MAX_SAFE_INTEGER,
|
|
651
|
+
includeLegacy,
|
|
511
652
|
cwd,
|
|
512
653
|
});
|
|
654
|
+
const excludedLegacy = includeLegacy
|
|
655
|
+
? 0
|
|
656
|
+
: countLegacySearchMatches({
|
|
657
|
+
query,
|
|
658
|
+
section: (args.section ?? args.type),
|
|
659
|
+
since: args.since,
|
|
660
|
+
maxResults: offset + limit,
|
|
661
|
+
includeLegacy: true,
|
|
662
|
+
cwd,
|
|
663
|
+
});
|
|
513
664
|
const total = allResults.length;
|
|
514
665
|
const page = allResults.slice(offset, offset + limit);
|
|
515
|
-
|
|
666
|
+
// trp#449 class — bound the page by size (pln#542). budget_tokens tightens
|
|
667
|
+
// the cap (~4 chars/token); the default mirrors bclaw_find's budget.
|
|
668
|
+
const budgetTokens = typeof args.budget_tokens === 'number' && args.budget_tokens > 0 ? args.budget_tokens : undefined;
|
|
669
|
+
const charBudget = budgetTokens ? Math.min(budgetTokens * 4, DEFAULT_FIND_CHAR_BUDGET) : DEFAULT_FIND_CHAR_BUDGET;
|
|
670
|
+
const bounded = boundListResult({ entity: 'search_result', total, items: page }, offset, charBudget);
|
|
671
|
+
const lines = bounded.items.map((result) => `[${result.id}] (${result.section}) score=${result.score.toFixed(2)}: ${result.text.slice(0, 120)}`);
|
|
672
|
+
const nextActions = bounded.has_more
|
|
673
|
+
? [{
|
|
674
|
+
tool: 'bclaw_search',
|
|
675
|
+
args: {
|
|
676
|
+
query,
|
|
677
|
+
offset: bounded.next_offset,
|
|
678
|
+
limit,
|
|
679
|
+
...(args.project ? { project: args.project } : {}),
|
|
680
|
+
...(args.section ? { section: args.section } : {}),
|
|
681
|
+
...(args.type ? { type: args.type } : {}),
|
|
682
|
+
...(args.since ? { since: args.since } : {}),
|
|
683
|
+
...(args.budget_tokens ? { budget_tokens: args.budget_tokens } : {}),
|
|
684
|
+
...(includeLegacy ? { includeLegacy: true } : {}),
|
|
685
|
+
},
|
|
686
|
+
when: 'to fetch the next page',
|
|
687
|
+
}]
|
|
688
|
+
: [];
|
|
689
|
+
const legacyNote = excludedLegacy > 0 ? ` (${excludedLegacy} legacy result(s) excluded; pass includeLegacy=true to include them)` : '';
|
|
516
690
|
return {
|
|
517
|
-
content: [{ type: 'text', text:
|
|
518
|
-
structuredContent: {
|
|
691
|
+
content: [{ type: 'text', text: bounded.items.length > 0 ? `${lines.join('\n')}${legacyNote}` : `No results found.${legacyNote}` }],
|
|
692
|
+
structuredContent: {
|
|
693
|
+
total,
|
|
694
|
+
offset,
|
|
695
|
+
limit,
|
|
696
|
+
results: bounded.items,
|
|
697
|
+
returned: bounded.returned,
|
|
698
|
+
excluded_legacy: excludedLegacy,
|
|
699
|
+
resolved_project: resolvedProject ?? { path: cwd },
|
|
700
|
+
active_source: activeSource,
|
|
701
|
+
has_more: bounded.has_more,
|
|
702
|
+
...(bounded.next_offset !== undefined ? { next_offset: bounded.next_offset } : {}),
|
|
703
|
+
...(bounded.omitted_for_size ? { omitted_for_size: bounded.omitted_for_size } : {}),
|
|
704
|
+
...(bounded.hint ? { hint: bounded.hint } : {}),
|
|
705
|
+
next_actions: nextActions,
|
|
706
|
+
},
|
|
519
707
|
};
|
|
520
708
|
}
|
|
521
709
|
if (name === 'bclaw_estimation_report') {
|
|
@@ -563,7 +751,10 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
563
751
|
const assignee = String(args.assignee).toLowerCase();
|
|
564
752
|
plans = plans.filter((plan) => plan.assignee?.toLowerCase() === assignee);
|
|
565
753
|
}
|
|
566
|
-
if (args.project) {
|
|
754
|
+
if (args.project && !projectRoutingApplied) {
|
|
755
|
+
// Only filter by plan.project metadata when routing was NOT applied.
|
|
756
|
+
// When routing succeeded, cwd is already scoped to the target project store,
|
|
757
|
+
// so filtering by plan.project label would incorrectly narrow results.
|
|
567
758
|
const project = String(args.project).toLowerCase();
|
|
568
759
|
plans = plans.filter((plan) => plan.project?.toLowerCase() === project);
|
|
569
760
|
}
|
|
@@ -863,23 +1054,28 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
863
1054
|
// committed but never called bclaw_assignment_update) and surfaces
|
|
864
1055
|
// delivered_but_unverified for spawns past the 60s grace with no
|
|
865
1056
|
// life-sign — see runtime_note run_77e65e77 for the empirical case.
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
reconcileAgentRun(
|
|
1057
|
+
// Observer mode (BRAINCLAW_OBSERVER=1) suppresses this pre-read sweep —
|
|
1058
|
+
// a dashboard fetching assignment events must not be allowed to transition
|
|
1059
|
+
// agent_run records as a side effect of the read.
|
|
1060
|
+
if (!isObserverMode()) {
|
|
1061
|
+
try {
|
|
1062
|
+
if (runId) {
|
|
1063
|
+
reconcileAgentRun(runId, cwd);
|
|
873
1064
|
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1065
|
+
else if (assignmentId) {
|
|
1066
|
+
for (const run of listAgentRuns(cwd, { assignment_id: assignmentId })) {
|
|
1067
|
+
reconcileAgentRun(run.id, cwd);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
else if (claimId) {
|
|
1071
|
+
for (const run of listAgentRuns(cwd, { claim_id: claimId })) {
|
|
1072
|
+
reconcileAgentRun(run.id, cwd);
|
|
1073
|
+
}
|
|
878
1074
|
}
|
|
879
1075
|
}
|
|
1076
|
+
catch { /* defensive: never block events query on reconcile failure */ }
|
|
880
1077
|
}
|
|
881
|
-
|
|
882
|
-
let events = queryRuntimeEvents({
|
|
1078
|
+
const events = queryRuntimeEvents({
|
|
883
1079
|
...(id ? { id } : {}),
|
|
884
1080
|
...(assignmentId ? { assignment_id: assignmentId } : {}),
|
|
885
1081
|
...(runId ? { run_id: runId } : {}),
|
|
@@ -1267,7 +1463,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1267
1463
|
if (name === 'bclaw_switch') {
|
|
1268
1464
|
if (args.list === true) {
|
|
1269
1465
|
try {
|
|
1270
|
-
const result =
|
|
1466
|
+
const result = listAvailableProjectsForSession(cwd, context.connectionSessionId);
|
|
1271
1467
|
const lines = result.projects.map(p => {
|
|
1272
1468
|
const marker = p.active ? '→' : ' ';
|
|
1273
1469
|
const label = p.name ? `${p.name} (${p.relative_path})` : p.relative_path;
|
|
@@ -1284,7 +1480,9 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1284
1480
|
}
|
|
1285
1481
|
if (args.clear === true) {
|
|
1286
1482
|
try {
|
|
1287
|
-
const session =
|
|
1483
|
+
const session = context.connectionSessionId
|
|
1484
|
+
? loadSessionById(context.connectionSessionId, cwd)
|
|
1485
|
+
: loadCurrentSession(cwd);
|
|
1288
1486
|
if (session?.active_project) {
|
|
1289
1487
|
const { active_project: _removed, ...rest } = session;
|
|
1290
1488
|
saveCurrentSession(rest, cwd);
|
|
@@ -1303,7 +1501,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1303
1501
|
return createToolErrorResponse('validation_error', 'Missing required argument: project (or use list=true / clear=true)');
|
|
1304
1502
|
}
|
|
1305
1503
|
try {
|
|
1306
|
-
const result = switchProject(projectRef, { cwd, sessionOnly: true });
|
|
1504
|
+
const result = switchProject(projectRef, { cwd, sessionOnly: true, sessionId: context.connectionSessionId });
|
|
1307
1505
|
const text = `✔ Switched to ${result.name ? `"${result.name}"` : result.path} (${result.scope}-scoped)`;
|
|
1308
1506
|
return {
|
|
1309
1507
|
content: [{ type: 'text', text }],
|
|
@@ -1601,6 +1799,17 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1601
1799
|
return handleMcpReadToolCall('bclaw_get_agent_board', args, context);
|
|
1602
1800
|
case 'board_summary':
|
|
1603
1801
|
return handleMcpReadToolCall('bclaw_get_agent_board_summary', args, context);
|
|
1802
|
+
case 'cross_project': {
|
|
1803
|
+
// pln#558 step 3 — lightweight endpoint for the VS Code extension's
|
|
1804
|
+
// SYSTEM section: returns linked_projects + incoming_signals only,
|
|
1805
|
+
// so the dashboard no longer pulls the full coordination snapshot
|
|
1806
|
+
// just to render two summary lists.
|
|
1807
|
+
const snap = buildCrossProjectSnapshot(cwd);
|
|
1808
|
+
return {
|
|
1809
|
+
content: [{ type: 'text', text: JSON.stringify(snap, null, 2) }],
|
|
1810
|
+
structuredContent: snap,
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1604
1813
|
case 'delta': {
|
|
1605
1814
|
const since = args.since;
|
|
1606
1815
|
if (typeof since !== 'string' || !since) {
|
|
@@ -1609,7 +1818,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1609
1818
|
return handleMcpReadToolCall('bclaw_get_context', { ...args, since_session: since }, context);
|
|
1610
1819
|
}
|
|
1611
1820
|
default:
|
|
1612
|
-
throw new Error(`bclaw_context: unknown kind '${kind}'. Expected memory | execution | board | board_summary | delta.`);
|
|
1821
|
+
throw new Error(`bclaw_context: unknown kind '${kind}'. Expected memory | execution | board | board_summary | cross_project | delta.`);
|
|
1613
1822
|
}
|
|
1614
1823
|
}
|
|
1615
1824
|
if (name === 'bclaw_get_thread') {
|
|
@@ -1635,11 +1844,13 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1635
1844
|
}
|
|
1636
1845
|
const tailLogLines = typeof args.tail_log_lines === 'number' ? args.tail_log_lines : undefined;
|
|
1637
1846
|
const stallThresholdMs = typeof args.stall_threshold_ms === 'number' ? args.stall_threshold_ms : undefined;
|
|
1847
|
+
const baseRef = typeof args.base_ref === 'string' && args.base_ref ? args.base_ref : undefined;
|
|
1638
1848
|
const status = getDispatchStatus({
|
|
1639
1849
|
target_id: targetId,
|
|
1640
1850
|
cwd,
|
|
1641
1851
|
tail_log_lines: tailLogLines,
|
|
1642
1852
|
stall_threshold_ms: stallThresholdMs,
|
|
1853
|
+
base_ref: baseRef,
|
|
1643
1854
|
});
|
|
1644
1855
|
// Text view: short, single-screen summary so an agent can decide what to do
|
|
1645
1856
|
// without parsing the structured payload.
|
|
@@ -1658,6 +1869,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1658
1869
|
`Runtime: pid=${status.runtime.pid ?? '-'} alive=${status.runtime.pid_alive ?? 'unknown'} ack=${status.runtime.ack_file.exists}`,
|
|
1659
1870
|
` stdout: ${status.runtime.log_files.stdout?.exists ? `${status.runtime.log_files.stdout.size_bytes}B` : 'absent'}`,
|
|
1660
1871
|
` stderr: ${status.runtime.log_files.stderr?.exists ? `${status.runtime.log_files.stderr.size_bytes}B` : 'absent'}`,
|
|
1872
|
+
` git: commits_ahead=${status.runtime.commits_ahead ?? 'n/a'} dirty_tracked=${status.runtime.dirty_tracked ?? 'n/a'}`,
|
|
1661
1873
|
];
|
|
1662
1874
|
return {
|
|
1663
1875
|
content: [{ type: 'text', text: lines.join('\n') }],
|