brainclaw 1.9.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +631 -499
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/cli.js +18 -1
- package/dist/commands/code-map.js +129 -0
- package/dist/commands/codev.js +7 -0
- package/dist/commands/harvest.js +1 -1
- package/dist/commands/hooks.js +73 -73
- package/dist/commands/init.js +1 -1
- package/dist/commands/install-hooks.js +78 -78
- package/dist/commands/mcp-read-handlers.js +57 -14
- package/dist/commands/mcp.js +200 -13
- package/dist/commands/run-profile.js +3 -2
- package/dist/commands/switch.js +125 -93
- package/dist/commands/version.js +1 -1
- package/dist/core/agent-capability.js +19 -4
- package/dist/core/agent-files.js +131 -119
- package/dist/core/code-map/backend.js +123 -0
- package/dist/core/code-map/core.js +81 -0
- package/dist/core/code-map/drafts.js +2 -0
- package/dist/core/code-map/extractor.js +29 -0
- package/dist/core/code-map/finalizer.js +191 -0
- package/dist/core/code-map/freshness.js +108 -0
- package/dist/core/code-map/ids.js +0 -0
- package/dist/core/code-map/importable.js +35 -0
- package/dist/core/code-map/indexes.js +197 -0
- package/dist/core/code-map/lang/java/imports.scm +17 -0
- package/dist/core/code-map/lang/java/index.js +254 -0
- package/dist/core/code-map/lang/java/tags.scm +48 -0
- package/dist/core/code-map/lang/php/imports.scm +21 -0
- package/dist/core/code-map/lang/php/index.js +251 -0
- package/dist/core/code-map/lang/php/tags.scm +44 -0
- package/dist/core/code-map/lang/provider.js +9 -0
- package/dist/core/code-map/lang/providers.js +24 -0
- package/dist/core/code-map/lang/python/imports.scm +90 -0
- package/dist/core/code-map/lang/python/index.js +364 -0
- package/dist/core/code-map/lang/python/tags.scm +81 -0
- package/dist/core/code-map/lang/query-runtime.js +374 -0
- package/dist/core/code-map/lang/registry.js +125 -0
- package/dist/core/code-map/lang/typescript/imports.scm +90 -0
- package/dist/core/code-map/lang/typescript/index.js +306 -0
- package/dist/core/code-map/lang/typescript/tags.js.scm +106 -0
- package/dist/core/code-map/lang/typescript/tags.scm +151 -0
- package/dist/core/code-map/lock.js +210 -0
- package/dist/core/code-map/materialized.js +51 -0
- package/dist/core/code-map/memory-reader.js +59 -0
- package/dist/core/code-map/paths.js +53 -0
- package/dist/core/code-map/query.js +568 -0
- package/dist/core/code-map/refresh.js +0 -0
- package/dist/core/code-map/resolve.js +177 -0
- package/dist/core/code-map/store.js +206 -0
- package/dist/core/code-map/types.js +288 -0
- package/dist/core/code-map/vocabulary.js +57 -0
- package/dist/core/code-map/wasm-loader.js +294 -0
- package/dist/core/code-map/work-section.js +206 -0
- package/dist/core/codev-prompts.js +38 -38
- package/dist/core/codev-rounds.js +4 -0
- 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/dispatcher.js +1 -1
- package/dist/core/entity-operations.js +29 -3
- package/dist/core/execution-adapters.js +11 -10
- package/dist/core/execution-profile.js +58 -0
- package/dist/core/execution.js +1 -1
- package/dist/core/facade-schema.js +9 -0
- package/dist/core/instruction-templates.js +2 -0
- package/dist/core/loops/verbs.js +0 -1
- package/dist/core/mcp-command-resolution.js +3 -1
- package/dist/core/messaging.js +2 -2
- package/dist/core/protocol-skills.js +164 -164
- package/dist/core/runtime-signals.js +1 -1
- package/dist/core/search.js +19 -2
- package/dist/core/security-guard.js +207 -207
- package/dist/core/spawn-check.js +16 -2
- package/dist/core/staleness.js +1 -1
- package/dist/core/store-resolution.js +67 -11
- package/dist/core/worktree.js +18 -18
- package/dist/facts.js +9 -5
- package/dist/facts.json +8 -4
- package/dist/vendor/web-tree-sitter/tree-sitter.js +3980 -0
- package/dist/vendor/web-tree-sitter/tree-sitter.wasm +0 -0
- package/dist/wasm/tree-sitter-java.wasm +0 -0
- package/dist/wasm/tree-sitter-javascript.wasm +0 -0
- package/dist/wasm/tree-sitter-php.wasm +0 -0
- package/dist/wasm/tree-sitter-python.wasm +0 -0
- package/dist/wasm/tree-sitter-tsx.wasm +0 -0
- package/dist/wasm/tree-sitter-typescript.wasm +0 -0
- package/dist/wasm/tree-sitter.wasm +0 -0
- package/docs/PROTOCOL.md +1 -1
- package/docs/adapters/openclaw.md +43 -43
- package/docs/architecture/project-refs.md +328 -328
- package/docs/cli.md +2131 -2093
- package/docs/code-map.md +198 -0
- package/docs/concepts/coordination.md +52 -52
- package/docs/concepts/coordinator-runbook.md +129 -129
- package/docs/concepts/dispatch-lifecycle.md +245 -245
- package/docs/concepts/event-log-store.md +928 -928
- 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 +84 -84
- package/docs/concepts/multi-agent-workflows.md +167 -167
- package/docs/concepts/observer-protocol.md +361 -361
- package/docs/concepts/plans-and-claims.md +217 -217
- package/docs/concepts/project-md-convention.md +35 -35
- package/docs/concepts/runtime-notes.md +38 -38
- package/docs/concepts/troubleshooting.md +254 -254
- package/docs/concepts/workspace-bootstrapping.md +142 -142
- 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 +158 -158
- package/docs/integrations/claude-code.md +23 -23
- package/docs/integrations/cline.md +77 -77
- package/docs/integrations/continue.md +55 -55
- package/docs/integrations/copilot.md +68 -68
- package/docs/integrations/cursor.md +23 -23
- package/docs/integrations/kilocode.md +72 -72
- package/docs/integrations/mcp.md +385 -378
- package/docs/integrations/mistral-vibe.md +122 -122
- package/docs/integrations/openclaw.md +92 -92
- package/docs/integrations/opencode.md +84 -84
- package/docs/integrations/overview.md +115 -115
- package/docs/integrations/roo.md +71 -71
- package/docs/integrations/windsurf.md +77 -77
- package/docs/mcp-schema-changelog.md +364 -356
- 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 +183 -183
- package/docs/release-maintenance.md +79 -79
- package/docs/reputation.md +52 -52
- package/docs/review.md +45 -45
- package/docs/security.md +212 -212
- package/docs/server-operations.md +118 -118
- package/docs/storage.md +106 -106
- package/package.json +86 -66
- package/docs/concepts/event-log-store-critique-A.md +0 -333
- package/docs/concepts/event-log-store-critique-B.md +0 -353
- package/docs/concepts/event-log-store-phase0-measurements.md +0 -58
- package/docs/concepts/event-log-store-proposal-A.md +0 -365
- package/docs/concepts/event-log-store-proposal-B.md +0 -404
- package/docs/concepts/identity-model-proposal.md +0 -371
|
@@ -6,7 +6,7 @@ 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';
|
|
@@ -27,13 +27,13 @@ import { checkPolicy } from '../core/policy.js';
|
|
|
27
27
|
import { buildGovernanceReport, renderGovernanceMarkdown } from '../core/governance.js';
|
|
28
28
|
import { inferProjectFromTarget, loadInstructions, resolveInstructions } from '../core/instructions.js';
|
|
29
29
|
import { buildReputationSnapshot, toPublicReputationSummary } from '../core/reputation.js';
|
|
30
|
-
import { search } from '../core/search.js';
|
|
30
|
+
import { countLegacySearchMatches, search } from '../core/search.js';
|
|
31
31
|
import { buildEstimationReport } from './estimation-report.js';
|
|
32
32
|
import { runDoctor } from './doctor.js';
|
|
33
33
|
import { buildProjectDiscovery, saveDiscoveryProfile, loadDiscoveryProfile, renderDiscoverySummary } from '../core/project-discovery.js';
|
|
34
34
|
import { listCapabilities, listTools as listRegistryTools } from '../core/registries.js';
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
35
|
+
import { listAvailableProjectsForSession, switchProject } from './switch.js';
|
|
36
|
+
import { resolveEffectiveCwdInfo } from '../core/store-resolution.js';
|
|
37
37
|
import { resolveProjectCwd } from '../core/cross-project.js';
|
|
38
38
|
import { readUnseenEvents, buildNotificationSummary } from '../core/event-log.js';
|
|
39
39
|
import { boundListResult, DEFAULT_FIND_CHAR_BUDGET } from '../core/entity-operations.js';
|
|
@@ -100,9 +100,12 @@ function getReviewAssignee(tags) {
|
|
|
100
100
|
}
|
|
101
101
|
export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
102
102
|
const baseCwd = context.cwd ?? process.cwd();
|
|
103
|
-
|
|
104
|
-
? baseCwd
|
|
105
|
-
:
|
|
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;
|
|
106
109
|
// If a project param is provided, resolve it to an actual cwd override.
|
|
107
110
|
// resolveProjectCwd unifies cross_project_links (siblings/peers) AND
|
|
108
111
|
// workspace store-chain children. Throws on unknown project — surfaces
|
|
@@ -115,6 +118,14 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
115
118
|
let projectRoutingApplied = false;
|
|
116
119
|
if (targetProjectArg) {
|
|
117
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
|
+
}
|
|
118
129
|
projectRoutingApplied = true;
|
|
119
130
|
}
|
|
120
131
|
if (name === 'bclaw_get_context') {
|
|
@@ -631,13 +642,25 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
631
642
|
}
|
|
632
643
|
const offset = Math.max(0, Number(args.offset) || 0);
|
|
633
644
|
const limit = typeof args.limit === 'number' ? args.limit : 10;
|
|
645
|
+
const includeLegacy = args.includeLegacy === true || (typeof args.filter === 'object' && args.filter?.includeLegacy === true);
|
|
634
646
|
const allResults = search({
|
|
635
647
|
query,
|
|
636
648
|
section: (args.section ?? args.type),
|
|
637
649
|
since: args.since,
|
|
638
|
-
maxResults:
|
|
650
|
+
maxResults: Number.MAX_SAFE_INTEGER,
|
|
651
|
+
includeLegacy,
|
|
639
652
|
cwd,
|
|
640
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
|
+
});
|
|
641
664
|
const total = allResults.length;
|
|
642
665
|
const page = allResults.slice(offset, offset + limit);
|
|
643
666
|
// trp#449 class — bound the page by size (pln#542). budget_tokens tightens
|
|
@@ -647,16 +670,34 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
647
670
|
const bounded = boundListResult({ entity: 'search_result', total, items: page }, offset, charBudget);
|
|
648
671
|
const lines = bounded.items.map((result) => `[${result.id}] (${result.section}) score=${result.score.toFixed(2)}: ${result.text.slice(0, 120)}`);
|
|
649
672
|
const nextActions = bounded.has_more
|
|
650
|
-
? [{
|
|
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
|
+
}]
|
|
651
688
|
: [];
|
|
689
|
+
const legacyNote = excludedLegacy > 0 ? ` (${excludedLegacy} legacy result(s) excluded; pass includeLegacy=true to include them)` : '';
|
|
652
690
|
return {
|
|
653
|
-
content: [{ type: 'text', text: bounded.items.length > 0 ? lines.join('\n') :
|
|
691
|
+
content: [{ type: 'text', text: bounded.items.length > 0 ? `${lines.join('\n')}${legacyNote}` : `No results found.${legacyNote}` }],
|
|
654
692
|
structuredContent: {
|
|
655
693
|
total,
|
|
656
694
|
offset,
|
|
657
695
|
limit,
|
|
658
696
|
results: bounded.items,
|
|
659
697
|
returned: bounded.returned,
|
|
698
|
+
excluded_legacy: excludedLegacy,
|
|
699
|
+
resolved_project: resolvedProject ?? { path: cwd },
|
|
700
|
+
active_source: activeSource,
|
|
660
701
|
has_more: bounded.has_more,
|
|
661
702
|
...(bounded.next_offset !== undefined ? { next_offset: bounded.next_offset } : {}),
|
|
662
703
|
...(bounded.omitted_for_size ? { omitted_for_size: bounded.omitted_for_size } : {}),
|
|
@@ -1034,7 +1075,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1034
1075
|
}
|
|
1035
1076
|
catch { /* defensive: never block events query on reconcile failure */ }
|
|
1036
1077
|
}
|
|
1037
|
-
|
|
1078
|
+
const events = queryRuntimeEvents({
|
|
1038
1079
|
...(id ? { id } : {}),
|
|
1039
1080
|
...(assignmentId ? { assignment_id: assignmentId } : {}),
|
|
1040
1081
|
...(runId ? { run_id: runId } : {}),
|
|
@@ -1422,7 +1463,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1422
1463
|
if (name === 'bclaw_switch') {
|
|
1423
1464
|
if (args.list === true) {
|
|
1424
1465
|
try {
|
|
1425
|
-
const result =
|
|
1466
|
+
const result = listAvailableProjectsForSession(cwd, context.connectionSessionId);
|
|
1426
1467
|
const lines = result.projects.map(p => {
|
|
1427
1468
|
const marker = p.active ? '→' : ' ';
|
|
1428
1469
|
const label = p.name ? `${p.name} (${p.relative_path})` : p.relative_path;
|
|
@@ -1439,7 +1480,9 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1439
1480
|
}
|
|
1440
1481
|
if (args.clear === true) {
|
|
1441
1482
|
try {
|
|
1442
|
-
const session =
|
|
1483
|
+
const session = context.connectionSessionId
|
|
1484
|
+
? loadSessionById(context.connectionSessionId, cwd)
|
|
1485
|
+
: loadCurrentSession(cwd);
|
|
1443
1486
|
if (session?.active_project) {
|
|
1444
1487
|
const { active_project: _removed, ...rest } = session;
|
|
1445
1488
|
saveCurrentSession(rest, cwd);
|
|
@@ -1458,7 +1501,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1458
1501
|
return createToolErrorResponse('validation_error', 'Missing required argument: project (or use list=true / clear=true)');
|
|
1459
1502
|
}
|
|
1460
1503
|
try {
|
|
1461
|
-
const result = switchProject(projectRef, { cwd, sessionOnly: true });
|
|
1504
|
+
const result = switchProject(projectRef, { cwd, sessionOnly: true, sessionId: context.connectionSessionId });
|
|
1462
1505
|
const text = `✔ Switched to ${result.name ? `"${result.name}"` : result.path} (${result.scope}-scoped)`;
|
|
1463
1506
|
return {
|
|
1464
1507
|
content: [{ type: 'text', text }],
|
package/dist/commands/mcp.js
CHANGED
|
@@ -39,7 +39,7 @@ import { createCapability, createTool as createRegistryTool } from '../core/regi
|
|
|
39
39
|
import { detectAiAgent } from '../core/ai-agent-detection.js';
|
|
40
40
|
import { checkGitPresence, scanGitRepos, parseRoots, parseRepoSelection, parseAgentSelection, getDetectedSetupAgentNames, getInstalledAgentNames, runGlobalInstall, initReposAndConfigureAgents, readSetupState, ALL_KNOWN_AGENTS, } from './setup.js';
|
|
41
41
|
import { buildAgentInventory } from '../core/agent-inventory.js';
|
|
42
|
-
import { resolveEffectiveCwd, resolveProjectRef, resolveTargetStore } from '../core/store-resolution.js';
|
|
42
|
+
import { resolveEffectiveCwd, resolveEffectiveCwdInfo, resolveProjectRef, resolveTargetStore } from '../core/store-resolution.js';
|
|
43
43
|
import { assessBootstrapNeed, probeForQuickSetup, buildQuickSetupProbeResponse, buildOnboardingPreview, resolveEmptyMemoryRecommendation } from '../core/setup-flow.js';
|
|
44
44
|
import { ensureUserStore, resolveHomeDir } from '../core/setup-state.js';
|
|
45
45
|
import { createPlan, addStep as addStepOp, completeStep as completeStepOp, updateStep as updateStepOp, deleteStep as deleteStepOp, deletePlan as deletePlanOp } from '../core/operations/plan.js';
|
|
@@ -48,6 +48,7 @@ import { dispatch, dispatchReview, generateDispatchBrief } from '../core/dispatc
|
|
|
48
48
|
import { deleteMemoryItem, updateMemoryItem } from '../core/operations/memory-mutation.js';
|
|
49
49
|
import { assessMemoryPressure, buildCompactionTemplate, applyCompaction } from '../core/gc-semantic.js';
|
|
50
50
|
import { WorkRequestSchema, CoordinateRequestSchema } from '../core/facade-schema.js';
|
|
51
|
+
import { codeMapWorkSection, codeMapRefreshNextActions } from '../core/code-map/work-section.js';
|
|
51
52
|
import { getSpawnableAgents, getCapabilityProfile, buildInvokeCommand, validateAgentForDispatch } from '../core/agent-capability.js';
|
|
52
53
|
import { attemptExecution } from '../core/execution.js';
|
|
53
54
|
import { createAgentRun, transitionAgentRun } from '../core/agentruns.js';
|
|
@@ -167,6 +168,8 @@ export const MCP_READ_TOOLS = [
|
|
|
167
168
|
type: { type: 'string', description: 'Filter by section: decisions, constraints, traps, handoffs, candidates, plans, sequences.' },
|
|
168
169
|
section: { type: 'string', description: 'Filter by section (state, candidates, runtime).' },
|
|
169
170
|
since: { type: 'string', description: 'Filter items created after this ISO date.' },
|
|
171
|
+
project: { type: 'string', description: 'Optional project name/path to search. Defaults to the active project.' },
|
|
172
|
+
includeLegacy: { type: 'boolean', description: 'Include records with provenance.kind="legacy" (default false). Response reports excluded_legacy when false.' },
|
|
170
173
|
limit: { type: 'number', description: 'Maximum number of results to return (default 10).' },
|
|
171
174
|
offset: { type: 'number', description: 'Number of results to skip (for pagination).' },
|
|
172
175
|
budget_tokens: { type: 'number', description: 'Optional token budget for the result page (~4 chars/token). The page is size-bounded; has_more/next_offset advertise the rest.' },
|
|
@@ -447,8 +450,54 @@ export const MCP_READ_TOOLS = [
|
|
|
447
450
|
required: ['target_id'],
|
|
448
451
|
},
|
|
449
452
|
},
|
|
453
|
+
{
|
|
454
|
+
name: 'bclaw_code_status',
|
|
455
|
+
description: 'Code Map status for this project: store presence, freshness badge (fresh / stale_changed_files / stale_extractor / stale_grammar / partial / missing_index), and index stats (files, nodes, edges). Read-only; never refreshes. Pair with bclaw_code_refresh when freshness is missing_index or stale.',
|
|
456
|
+
annotations: { tier: 'standard', category: 'discovery', headlessApproval: 'auto' },
|
|
457
|
+
inputSchema: {
|
|
458
|
+
type: 'object',
|
|
459
|
+
properties: {},
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
name: 'bclaw_code_find',
|
|
464
|
+
description: 'Search the Code Map symbol index for a query (function/class/component/hook/type names). Returns ranked matches with path + score, plus a freshness_badge from the lazy read-path check. Read-only; never refreshes — a missing_index badge means run bclaw_code_refresh first.',
|
|
465
|
+
annotations: { tier: 'standard', category: 'discovery', headlessApproval: 'auto' },
|
|
466
|
+
inputSchema: {
|
|
467
|
+
type: 'object',
|
|
468
|
+
properties: {
|
|
469
|
+
query: { type: 'string', description: 'Symbol or token to search for (e.g. "App", "useAuth", "dispatch").' },
|
|
470
|
+
limit: { type: 'number', description: 'Max matches to return.' },
|
|
471
|
+
},
|
|
472
|
+
required: ['query'],
|
|
473
|
+
},
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
name: 'bclaw_code_brief',
|
|
477
|
+
description: 'Before editing, ask Code Map what to read: returns a ranked suggested_files_to_read list (cap 12) for a symbol or path, related brainclaw memory (cap 5), and a freshness_badge. Read-only; never refreshes.',
|
|
478
|
+
annotations: { tier: 'standard', category: 'discovery', headlessApproval: 'auto' },
|
|
479
|
+
inputSchema: {
|
|
480
|
+
type: 'object',
|
|
481
|
+
properties: {
|
|
482
|
+
target: { type: 'string', description: 'Symbol name or file path to build a reading brief for.' },
|
|
483
|
+
limit: { type: 'number', description: 'Max suggested files (hard-capped at 12 by the spec).' },
|
|
484
|
+
},
|
|
485
|
+
required: ['target'],
|
|
486
|
+
},
|
|
487
|
+
},
|
|
450
488
|
];
|
|
451
489
|
const MCP_WRITE_TOOLS = [
|
|
490
|
+
{
|
|
491
|
+
name: 'bclaw_code_refresh',
|
|
492
|
+
description: 'Rebuild the Code Map index for this project (Tree-sitter parse + shards + indexes, behind the per-project lock). scope="changed" (default) reparses changed files; scope="all" does a full refresh + compaction. A live competing lock fails fast with a clear status — refresh never blocks. Returns the resulting freshness_badge.',
|
|
493
|
+
annotations: { tier: 'standard', category: 'discovery', headlessApproval: 'prompt' },
|
|
494
|
+
inputSchema: {
|
|
495
|
+
type: 'object',
|
|
496
|
+
properties: {
|
|
497
|
+
scope: { type: 'string', enum: ['changed', 'all'], description: 'changed (default) reparses changed files only; all does a full refresh with orphan compaction.' },
|
|
498
|
+
},
|
|
499
|
+
},
|
|
500
|
+
},
|
|
452
501
|
{
|
|
453
502
|
name: 'bclaw_dispatch',
|
|
454
503
|
description: 'Unified dispatch entry for sequence-lane parallelization (parallelize plans across lanes). To open a NEW review of a commit/branch, use `bclaw_coordinate(intent="review", open_loop=true, targetAgents=[…])` instead — bclaw_dispatch is for sequence-driven execution, NOT for opening new reviews. `intent` discriminator: analysis (sequence lane status, read-only), execute (default — analyze + generate briefs + send to agents), review (routes an EXISTING already-reflected handoff to a reviewer — only for handoffs produced by `session-end --reflect-handoff` or similar). Consolidates the legacy bclaw_dispatch_analysis / bclaw_dispatch / bclaw_dispatch_review. Returns FacadeResponse; for verification semantics see the same response-validation guidance documented on `bclaw_coordinate`.',
|
|
@@ -1782,6 +1831,32 @@ function explicitSessionIdFromEnv() {
|
|
|
1782
1831
|
|| process.env.CLAUDE_SESSION_ID?.trim()
|
|
1783
1832
|
|| process.env.COPILOT_SESSION_ID?.trim();
|
|
1784
1833
|
}
|
|
1834
|
+
function projectInfoForCwd(cwd) {
|
|
1835
|
+
try {
|
|
1836
|
+
const config = loadConfig(cwd);
|
|
1837
|
+
return { path: cwd, name: config.project_name };
|
|
1838
|
+
}
|
|
1839
|
+
catch {
|
|
1840
|
+
return { path: cwd };
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
function scopeMetadataForTarget(args, targetCwd, effectiveScope) {
|
|
1844
|
+
const hasExplicitProject = typeof args.project === 'string' && args.project.trim().length > 0;
|
|
1845
|
+
return {
|
|
1846
|
+
resolved_project: projectInfoForCwd(targetCwd),
|
|
1847
|
+
active_source: hasExplicitProject ? 'explicit' : effectiveScope.active_source,
|
|
1848
|
+
};
|
|
1849
|
+
}
|
|
1850
|
+
function renderProvenanceFilterNote(result) {
|
|
1851
|
+
const parts = [];
|
|
1852
|
+
if ((result.excluded_legacy ?? 0) > 0) {
|
|
1853
|
+
parts.push(`${result.excluded_legacy} legacy item(s) excluded (pass filter.includeLegacy=true to include them)`);
|
|
1854
|
+
}
|
|
1855
|
+
if ((result.excluded_low_confidence_auto_reflect ?? 0) > 0) {
|
|
1856
|
+
parts.push(`${result.excluded_low_confidence_auto_reflect} low-confidence auto_reflect item(s) excluded (lower filter.minAutoReflectConfidence to include them)`);
|
|
1857
|
+
}
|
|
1858
|
+
return parts.length > 0 ? parts.join('; ') : undefined;
|
|
1859
|
+
}
|
|
1785
1860
|
export function parseMcpLine(line) {
|
|
1786
1861
|
let parsed;
|
|
1787
1862
|
try {
|
|
@@ -2557,6 +2632,11 @@ import { handleMcpReadToolCall } from './mcp-read-handlers.js';
|
|
|
2557
2632
|
export { handleMcpReadToolCall };
|
|
2558
2633
|
async function _executeMcpToolCallInner(payload) {
|
|
2559
2634
|
const { name, args, cwd, connectionSessionId } = payload;
|
|
2635
|
+
const scopeInfo = payload.effectiveScope ?? {
|
|
2636
|
+
cwd,
|
|
2637
|
+
active_source: 'cwd',
|
|
2638
|
+
resolved_project: projectInfoForCwd(cwd),
|
|
2639
|
+
};
|
|
2560
2640
|
try {
|
|
2561
2641
|
if (isLegacyMcpToolFacadeDisabled(name)) {
|
|
2562
2642
|
return { response: createLegacyMcpToolDisabledResponse() };
|
|
@@ -2566,9 +2646,62 @@ async function _executeMcpToolCallInner(payload) {
|
|
|
2566
2646
|
const { handleCheckSecurity } = await import('./check-security-mcp.js');
|
|
2567
2647
|
return { response: toolResponse(await handleCheckSecurity(args, cwd)) };
|
|
2568
2648
|
}
|
|
2649
|
+
// Code Map tools (spec §9). These delegate to the async JsonlBackend, so
|
|
2650
|
+
// they are handled here rather than via the synchronous read-tool path.
|
|
2651
|
+
// status/find/brief are reads; refresh is a write (prompt approval).
|
|
2652
|
+
if (name === 'bclaw_code_status' || name === 'bclaw_code_find' || name === 'bclaw_code_brief' || name === 'bclaw_code_refresh') {
|
|
2653
|
+
const { JsonlBackend } = await import('../core/code-map/backend.js');
|
|
2654
|
+
const be = new JsonlBackend();
|
|
2655
|
+
if (name === 'bclaw_code_status') {
|
|
2656
|
+
const status = await be.status({ cwd });
|
|
2657
|
+
return {
|
|
2658
|
+
response: toolResponse({
|
|
2659
|
+
content: [{ type: 'text', text: `Code Map: ${status.store_exists ? 'store present' : 'no store'} — freshness=${status.freshness_badge.status}` }],
|
|
2660
|
+
structuredContent: { ...status, freshness_badge: status.freshness_badge },
|
|
2661
|
+
}),
|
|
2662
|
+
};
|
|
2663
|
+
}
|
|
2664
|
+
if (name === 'bclaw_code_refresh') {
|
|
2665
|
+
const scope = args.scope === 'all' ? 'all' : 'changed';
|
|
2666
|
+
const result = await be.refresh({ scope, cwd });
|
|
2667
|
+
return {
|
|
2668
|
+
response: toolResponse({
|
|
2669
|
+
content: [{ type: 'text', text: `Code Map refresh [${result.scope}]: ran=${result.ran} freshness=${result.freshness_badge.status}${result.lock_status ? ` (${result.lock_status})` : ''}` }],
|
|
2670
|
+
structuredContent: { ...result, freshness_badge: result.freshness_badge },
|
|
2671
|
+
}),
|
|
2672
|
+
};
|
|
2673
|
+
}
|
|
2674
|
+
if (name === 'bclaw_code_find') {
|
|
2675
|
+
const query = typeof args.query === 'string' ? args.query : '';
|
|
2676
|
+
if (!query.trim()) {
|
|
2677
|
+
return { response: createToolErrorResponse('validation_error', 'bclaw_code_find requires a non-empty query.') };
|
|
2678
|
+
}
|
|
2679
|
+
const limit = typeof args.limit === 'number' ? args.limit : undefined;
|
|
2680
|
+
const result = await be.find({ query, limit, cwd });
|
|
2681
|
+
return {
|
|
2682
|
+
response: toolResponse({
|
|
2683
|
+
content: [{ type: 'text', text: `Code Map find "${result.query}": ${result.matches.length} match(es), freshness=${result.freshness_badge.status}` }],
|
|
2684
|
+
structuredContent: { ...result, freshness_badge: result.freshness_badge },
|
|
2685
|
+
}),
|
|
2686
|
+
};
|
|
2687
|
+
}
|
|
2688
|
+
// bclaw_code_brief
|
|
2689
|
+
const target = typeof args.target === 'string' ? args.target : '';
|
|
2690
|
+
if (!target.trim()) {
|
|
2691
|
+
return { response: createToolErrorResponse('validation_error', 'bclaw_code_brief requires a non-empty target.') };
|
|
2692
|
+
}
|
|
2693
|
+
const limit = typeof args.limit === 'number' ? args.limit : undefined;
|
|
2694
|
+
const result = await be.brief({ target, limit, cwd });
|
|
2695
|
+
return {
|
|
2696
|
+
response: toolResponse({
|
|
2697
|
+
content: [{ type: 'text', text: `Code Map brief "${result.target}": ${result.suggested_files_to_read.length} file(s) to read, freshness=${result.freshness_badge.status}` }],
|
|
2698
|
+
structuredContent: { ...result, freshness_badge: result.freshness_badge },
|
|
2699
|
+
}),
|
|
2700
|
+
};
|
|
2701
|
+
}
|
|
2569
2702
|
if (MCP_READ_TOOLS.some((tool) => tool.name === name) || LEGACY_READ_TOOL_HANDLERS.has(name)) {
|
|
2570
2703
|
return {
|
|
2571
|
-
response: appendLegacyMcpToolWarning(toolResponse(handleMcpReadToolCall(name, args, { cwd })), name),
|
|
2704
|
+
response: appendLegacyMcpToolWarning(toolResponse(handleMcpReadToolCall(name, args, { cwd, connectionSessionId, effectiveScope: scopeInfo })), name),
|
|
2572
2705
|
};
|
|
2573
2706
|
}
|
|
2574
2707
|
// Resolve model once for all write operations
|
|
@@ -4821,6 +4954,26 @@ async function _executeMcpToolCallInner(payload) {
|
|
|
4821
4954
|
nextActions.push({ tool: 'bclaw_context', args: { kind: 'memory' }, when: 'to read the full changed items behind context_diff' });
|
|
4822
4955
|
}
|
|
4823
4956
|
nextActions.push({ tool: 'bclaw_quick_capture', args: { text: '<finding>', type: '<decision|trap|constraint|note>' }, when: 'capture decisions/traps as you work' });
|
|
4957
|
+
// Code Map opt-in section (spec §10). Single live seam: returns null
|
|
4958
|
+
// (and does no work) unless the project's Code Map manifest carries
|
|
4959
|
+
// code_map_enabled:true. Never refreshes; bounded ≤2500ms on an active
|
|
4960
|
+
// lock; surfaces a missing_index hint or stale results with the badge.
|
|
4961
|
+
let codeMapSection = null;
|
|
4962
|
+
try {
|
|
4963
|
+
codeMapSection = await codeMapWorkSection(targetCwd, {
|
|
4964
|
+
query: workReq.scope ?? workReq.contextTarget,
|
|
4965
|
+
});
|
|
4966
|
+
}
|
|
4967
|
+
catch {
|
|
4968
|
+
// Best-effort: Code Map must never break or block bclaw_work.
|
|
4969
|
+
}
|
|
4970
|
+
// Code Map onboarding/freshness nudge (pln#588): promote the actionable
|
|
4971
|
+
// refresh to a first-class next_action so a fresh or stale project's first
|
|
4972
|
+
// bclaw_work TELLS the agent to build/update the index — the passive
|
|
4973
|
+
// missing_index hint alone was easy to skip. Still never refreshes here.
|
|
4974
|
+
for (const action of codeMapRefreshNextActions(codeMapSection)) {
|
|
4975
|
+
nextActions.push(action);
|
|
4976
|
+
}
|
|
4824
4977
|
const facadeResponse = {
|
|
4825
4978
|
status: 'ok',
|
|
4826
4979
|
intent: workReq.intent,
|
|
@@ -4835,6 +4988,7 @@ async function _executeMcpToolCallInner(payload) {
|
|
|
4835
4988
|
bootstrap_verdict: bootstrapVerdict,
|
|
4836
4989
|
next_action: nextAction,
|
|
4837
4990
|
next_actions: nextActions,
|
|
4991
|
+
...(codeMapSection ? { code_map: codeMapSection } : {}),
|
|
4838
4992
|
};
|
|
4839
4993
|
const summaryParts = [`✔ bclaw_work [${workReq.intent}] session=${sessionResult.session_id}`];
|
|
4840
4994
|
if (claimId)
|
|
@@ -6214,6 +6368,7 @@ async function _executeMcpToolCallInner(payload) {
|
|
|
6214
6368
|
try {
|
|
6215
6369
|
const entity = String(args.entity ?? '');
|
|
6216
6370
|
const targetCwd = resolveProjectCwd(args.project, cwd);
|
|
6371
|
+
const targetScope = scopeMetadataForTarget(args, targetCwd, scopeInfo);
|
|
6217
6372
|
// pln#460 follow-up — some MCP clients (notably Claude Code with a
|
|
6218
6373
|
// tool schema that declares `filter: { type: 'object' }` without a
|
|
6219
6374
|
// sub-property schema) stringify the filter object before shipping
|
|
@@ -6269,10 +6424,10 @@ async function _executeMcpToolCallInner(payload) {
|
|
|
6269
6424
|
const bounded = boundListResult(result, offset, charBudget);
|
|
6270
6425
|
const warnings = collectLoadValidationWarnings(entity, targetCwd);
|
|
6271
6426
|
const nextActions = [
|
|
6272
|
-
{ tool: 'bclaw_get', args: { entity, id: '<id from items>' }, when: 'to read one item in full' },
|
|
6427
|
+
{ tool: 'bclaw_get', args: { entity, id: '<id from items>', ...(args.project ? { project: args.project } : {}), ...(args.budget_tokens ? { budget_tokens: args.budget_tokens } : {}) }, when: 'to read one item in full' },
|
|
6273
6428
|
];
|
|
6274
6429
|
if (bounded.has_more) {
|
|
6275
|
-
nextActions.push({ tool: 'bclaw_find', args: { entity, filter: { ...filter, offset: bounded.next_offset } }, when: 'to fetch the next page' });
|
|
6430
|
+
nextActions.push({ tool: 'bclaw_find', args: { entity, filter: { ...filter, offset: bounded.next_offset }, ...(args.project ? { project: args.project } : {}), ...(args.budget_tokens ? { budget_tokens: args.budget_tokens } : {}) }, when: 'to fetch the next page' });
|
|
6276
6431
|
}
|
|
6277
6432
|
// structuredContent is the canonical MCP return channel that clients
|
|
6278
6433
|
// (VS Code extension, Codex, etc.) read for machine-parseable data.
|
|
@@ -6281,10 +6436,20 @@ async function _executeMcpToolCallInner(payload) {
|
|
|
6281
6436
|
// `result.items` arrived as undefined on the client — the root cause
|
|
6282
6437
|
// of the VS Code Backlog section rendering empty.
|
|
6283
6438
|
const moreNote = bounded.has_more ? ` (returned ${bounded.returned}; ${result.total - bounded.returned} more — offset ${bounded.next_offset})` : '';
|
|
6439
|
+
const provenanceFilterNote = renderProvenanceFilterNote(result);
|
|
6440
|
+
const legacyNote = provenanceFilterNote
|
|
6441
|
+
? `; ${provenanceFilterNote}`
|
|
6442
|
+
: '';
|
|
6284
6443
|
return {
|
|
6285
6444
|
response: toolResponse({
|
|
6286
|
-
content: [{ type: 'text', text: `✔ ${result.total} ${entity} item(s)${moreNote}` }],
|
|
6287
|
-
structuredContent: {
|
|
6445
|
+
content: [{ type: 'text', text: `✔ ${result.total} ${entity} item(s)${moreNote}${legacyNote}` }],
|
|
6446
|
+
structuredContent: {
|
|
6447
|
+
...bounded,
|
|
6448
|
+
warnings,
|
|
6449
|
+
resolved_project: targetScope.resolved_project,
|
|
6450
|
+
active_source: targetScope.active_source,
|
|
6451
|
+
next_actions: nextActions,
|
|
6452
|
+
},
|
|
6288
6453
|
}),
|
|
6289
6454
|
};
|
|
6290
6455
|
}
|
|
@@ -6361,6 +6526,7 @@ async function _executeMcpToolCallInner(payload) {
|
|
|
6361
6526
|
}
|
|
6362
6527
|
const rawData = (args.data ?? {});
|
|
6363
6528
|
const targetCwd = resolveProjectCwd(args.project, cwd);
|
|
6529
|
+
const targetScope = scopeMetadataForTarget(args, targetCwd, scopeInfo);
|
|
6364
6530
|
// Auto-fill identity fields. Without this, a caller who omits author/agent
|
|
6365
6531
|
// creates a schema-invalid record that is silently dropped on read and
|
|
6366
6532
|
// GC'd from disk on the next mutation.
|
|
@@ -6392,7 +6558,11 @@ async function _executeMcpToolCallInner(payload) {
|
|
|
6392
6558
|
return {
|
|
6393
6559
|
response: toolResponse({
|
|
6394
6560
|
content: [{ type: 'text', text: `✔ created ${entity} ${result.id}` }],
|
|
6395
|
-
structuredContent: {
|
|
6561
|
+
structuredContent: {
|
|
6562
|
+
...result,
|
|
6563
|
+
resolved_project: targetScope.resolved_project,
|
|
6564
|
+
active_source: targetScope.active_source,
|
|
6565
|
+
},
|
|
6396
6566
|
}),
|
|
6397
6567
|
};
|
|
6398
6568
|
}
|
|
@@ -6406,13 +6576,18 @@ async function _executeMcpToolCallInner(payload) {
|
|
|
6406
6576
|
const id = String(args.id ?? '');
|
|
6407
6577
|
const patch = (args.patch ?? {});
|
|
6408
6578
|
const targetCwd = resolveProjectCwd(args.project, cwd);
|
|
6579
|
+
const targetScope = scopeMetadataForTarget(args, targetCwd, scopeInfo);
|
|
6409
6580
|
const { agent_name, agent_id } = resolveCanonicalAuthor(args, cwd, connectionSessionId);
|
|
6410
6581
|
const result = updateEntity(entity, id, patch, targetCwd);
|
|
6411
6582
|
appendAuditEntry({ actor: agent_name, ...(agent_id ? { actor_id: agent_id } : {}), action: 'update', item_id: id, item_type: entity }, targetCwd);
|
|
6412
6583
|
return {
|
|
6413
6584
|
response: toolResponse({
|
|
6414
6585
|
content: [{ type: 'text', text: `✔ updated ${entity} ${id}` }],
|
|
6415
|
-
structuredContent: {
|
|
6586
|
+
structuredContent: {
|
|
6587
|
+
...result,
|
|
6588
|
+
resolved_project: targetScope.resolved_project,
|
|
6589
|
+
active_source: targetScope.active_source,
|
|
6590
|
+
},
|
|
6416
6591
|
}),
|
|
6417
6592
|
};
|
|
6418
6593
|
}
|
|
@@ -6426,13 +6601,18 @@ async function _executeMcpToolCallInner(payload) {
|
|
|
6426
6601
|
const id = String(args.id ?? '');
|
|
6427
6602
|
const purge = args.purge === true;
|
|
6428
6603
|
const targetCwd = resolveProjectCwd(args.project, cwd);
|
|
6604
|
+
const targetScope = scopeMetadataForTarget(args, targetCwd, scopeInfo);
|
|
6429
6605
|
const { agent_name, agent_id } = resolveCanonicalAuthor(args, cwd, connectionSessionId);
|
|
6430
6606
|
const result = removeEntity(entity, id, targetCwd, purge);
|
|
6431
6607
|
appendAuditEntry({ actor: agent_name, ...(agent_id ? { actor_id: agent_id } : {}), action: 'delete', item_id: id, item_type: entity, reason: purge ? 'purged' : 'archived' }, targetCwd);
|
|
6432
6608
|
return {
|
|
6433
6609
|
response: toolResponse({
|
|
6434
6610
|
content: [{ type: 'text', text: `✔ removed ${entity} ${id}` }],
|
|
6435
|
-
structuredContent: {
|
|
6611
|
+
structuredContent: {
|
|
6612
|
+
...result,
|
|
6613
|
+
resolved_project: targetScope.resolved_project,
|
|
6614
|
+
active_source: targetScope.active_source,
|
|
6615
|
+
},
|
|
6436
6616
|
}),
|
|
6437
6617
|
};
|
|
6438
6618
|
}
|
|
@@ -6454,13 +6634,18 @@ async function _executeMcpToolCallInner(payload) {
|
|
|
6454
6634
|
const to = String(args.to ?? '');
|
|
6455
6635
|
const reason = args.reason;
|
|
6456
6636
|
const targetCwd = resolveProjectCwd(args.project, cwd);
|
|
6637
|
+
const targetScope = scopeMetadataForTarget(args, targetCwd, scopeInfo);
|
|
6457
6638
|
const { agent_name, agent_id } = resolveCanonicalAuthor(args, cwd, connectionSessionId);
|
|
6458
6639
|
const result = transitionEntity(entity, id, to, targetCwd, reason);
|
|
6459
6640
|
appendAuditEntry({ actor: agent_name, ...(agent_id ? { actor_id: agent_id } : {}), action: 'update', item_id: id, item_type: entity, reason: `transition ${result.from} → ${to}${reason ? ` (${reason})` : ''}` }, targetCwd);
|
|
6460
6641
|
return {
|
|
6461
6642
|
response: toolResponse({
|
|
6462
6643
|
content: [{ type: 'text', text: `✔ ${entity} ${id}: ${result.from} → ${to}` }],
|
|
6463
|
-
structuredContent: {
|
|
6644
|
+
structuredContent: {
|
|
6645
|
+
...result,
|
|
6646
|
+
resolved_project: targetScope.resolved_project,
|
|
6647
|
+
active_source: targetScope.active_source,
|
|
6648
|
+
},
|
|
6464
6649
|
}),
|
|
6465
6650
|
};
|
|
6466
6651
|
}
|
|
@@ -6504,9 +6689,10 @@ async function _executeMcpToolCallInner(payload) {
|
|
|
6504
6689
|
*/
|
|
6505
6690
|
export async function executeMcpToolCall(payload) {
|
|
6506
6691
|
const baseCwd = payload.cwd;
|
|
6507
|
-
const
|
|
6508
|
-
? baseCwd
|
|
6509
|
-
:
|
|
6692
|
+
const effective = payload.name === 'bclaw_switch'
|
|
6693
|
+
? { cwd: baseCwd, active_source: 'cwd', resolved_project: undefined }
|
|
6694
|
+
: resolveEffectiveCwdInfo({ baseCwd, sessionId: payload.connectionSessionId });
|
|
6695
|
+
const cwd = effective.cwd;
|
|
6510
6696
|
const envClaimId = process.env.BRAINCLAW_CLAIM_ID?.trim() || undefined;
|
|
6511
6697
|
// ── Auto-session ────────────────────────────────────────────────────────────
|
|
6512
6698
|
let autoSessionId;
|
|
@@ -6564,6 +6750,7 @@ export async function executeMcpToolCall(payload) {
|
|
|
6564
6750
|
...payload,
|
|
6565
6751
|
cwd,
|
|
6566
6752
|
connectionSessionId: effectiveConnectionSessionId,
|
|
6753
|
+
effectiveScope: effective,
|
|
6567
6754
|
});
|
|
6568
6755
|
// Apply legacy deprecation warning uniformly (Phase 3 slice 3g). Read tools
|
|
6569
6756
|
// already get it at line 2560; write tools historically did not. This
|
|
@@ -37,8 +37,9 @@ export function runRunProfile(profileName, options = {}) {
|
|
|
37
37
|
console.error(`Unknown agent: ${options.agent}. Using profile invoke template.`);
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
-
// Replace {prompt} placeholder with the profile prompt
|
|
41
|
-
|
|
40
|
+
// Replace {prompt} placeholder with the profile prompt. Escape backslashes
|
|
41
|
+
// before quotes so a backslash in the prompt can't break out of the quoting.
|
|
42
|
+
const command = invoke.replace(/\{prompt\}/g, profile.prompt.replace(/\\/g, '\\\\').replace(/"/g, '\\"'));
|
|
42
43
|
if (options.dry) {
|
|
43
44
|
console.log(`[dry-run] Profile: ${profile.name}`);
|
|
44
45
|
console.log(`[dry-run] Command: ${command}`);
|