@bike4mind/cli 0.2.37 → 0.2.38-docs-clawdbot-comparison-analysis.20242
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{artifactExtractor-QIDCYO4J.js → artifactExtractor-K6BLNO4G.js} +1 -1
- package/dist/{chunk-LJG44UT3.js → chunk-5V7IRWCZ.js} +2 -2
- package/dist/{chunk-AOP6RYDY.js → chunk-AZPDIAG7.js} +2 -2
- package/dist/{chunk-EY65E4W4.js → chunk-DF2SGZ6Z.js} +114 -15
- package/dist/{chunk-NJQYWIDJ.js → chunk-G3PPBZQR.js} +2 -2
- package/dist/{chunk-FACWWYF7.js → chunk-LSALEKGH.js} +8 -6
- package/dist/{chunk-5YLGNW2B.js → chunk-Q3TLK7O5.js} +175 -54
- package/dist/{chunk-H7RVLAQD.js → chunk-VNZXDZRP.js} +1 -1
- package/dist/{chunk-XIB66EG7.js → chunk-XWMNGOD4.js} +341 -332
- package/dist/commands/doctorCommand.js +1 -1
- package/dist/commands/headlessCommand.js +20 -18
- package/dist/commands/mcpCommand.js +2 -2
- package/dist/commands/updateCommand.js +1 -1
- package/dist/{create-IFIWNV4T.js → create-VGLP6SQO.js} +3 -3
- package/dist/index.js +97 -37
- package/dist/{llmMarkdownGenerator-7D3C33BP.js → llmMarkdownGenerator-I2LUZ5JN.js} +1 -1
- package/dist/{markdownGenerator-C2QW7FBH.js → markdownGenerator-7R5VE2DT.js} +1 -1
- package/dist/{mementoService-ABCCDW6T.js → mementoService-3JRERDXH.js} +3 -3
- package/dist/{src-ZPD2SIIU.js → src-5EEW25MH.js} +25 -1
- package/dist/{src-D6KSYF2W.js → src-6KYFWLTR.js} +4 -2
- package/dist/{subtractCredits-QKF6HTX3.js → subtractCredits-ULETUGGP.js} +3 -3
- package/package.json +8 -6
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
getOpenWeatherKey,
|
|
5
5
|
getSerperKey,
|
|
6
6
|
getWolframAlphaKey
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-G3PPBZQR.js";
|
|
8
8
|
import {
|
|
9
9
|
BFLImageService,
|
|
10
10
|
BaseStorage,
|
|
@@ -16,14 +16,14 @@ import {
|
|
|
16
16
|
OpenAIBackend,
|
|
17
17
|
OpenAIImageService,
|
|
18
18
|
XAIImageService
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-Q3TLK7O5.js";
|
|
20
20
|
import {
|
|
21
21
|
Logger
|
|
22
22
|
} from "./chunk-PFBYGCOW.js";
|
|
23
23
|
import {
|
|
24
24
|
ConfigStore,
|
|
25
25
|
logger
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-VNZXDZRP.js";
|
|
27
27
|
import {
|
|
28
28
|
AiEvents,
|
|
29
29
|
ApiKeyEvents,
|
|
@@ -77,10 +77,11 @@ import {
|
|
|
77
77
|
VideoModels,
|
|
78
78
|
XAI_IMAGE_MODELS,
|
|
79
79
|
b4mLLMTools,
|
|
80
|
+
getDataLakeTags,
|
|
80
81
|
getMcpProviderMetadata,
|
|
81
82
|
getViewById,
|
|
82
83
|
resolveNavigationIntents
|
|
83
|
-
} from "./chunk-
|
|
84
|
+
} from "./chunk-DF2SGZ6Z.js";
|
|
84
85
|
|
|
85
86
|
// src/utils/fileSearch.ts
|
|
86
87
|
import * as fs from "fs";
|
|
@@ -1846,7 +1847,8 @@ ${options.context}` : this.getSystemPrompt()
|
|
|
1846
1847
|
temperature,
|
|
1847
1848
|
abortSignal: options.signal,
|
|
1848
1849
|
tool_choice: this.context.toolChoice,
|
|
1849
|
-
executeTools: false
|
|
1850
|
+
executeTools: false,
|
|
1851
|
+
thinking: this.context.thinking
|
|
1850
1852
|
},
|
|
1851
1853
|
async (texts, completionInfo) => {
|
|
1852
1854
|
for (const text of texts) {
|
|
@@ -2146,34 +2148,20 @@ Remember: You are an autonomous AGENT. Act independently and solve problems proa
|
|
|
2146
2148
|
}
|
|
2147
2149
|
}
|
|
2148
2150
|
/**
|
|
2149
|
-
* Build and append tool call/result messages for the conversation history
|
|
2151
|
+
* Build and append tool call/result messages for the conversation history.
|
|
2152
|
+
* Delegates to the backend's pushToolMessages so each provider formats
|
|
2153
|
+
* messages according to its own API requirements.
|
|
2150
2154
|
*/
|
|
2151
2155
|
appendToolMessages(messages, toolUse, observation, thinkingBlocks) {
|
|
2152
2156
|
const params = this.parseToolArguments(toolUse.arguments);
|
|
2153
|
-
const
|
|
2154
|
-
const
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
}
|
|
2162
|
-
];
|
|
2163
|
-
messages.push({
|
|
2164
|
-
role: "assistant",
|
|
2165
|
-
content: assistantContent
|
|
2166
|
-
});
|
|
2167
|
-
messages.push({
|
|
2168
|
-
role: "user",
|
|
2169
|
-
content: [
|
|
2170
|
-
{
|
|
2171
|
-
type: "tool_result",
|
|
2172
|
-
tool_use_id: msgToolCallId,
|
|
2173
|
-
content: observation
|
|
2174
|
-
}
|
|
2175
|
-
]
|
|
2176
|
-
});
|
|
2157
|
+
const toolId = toolUse.id || `${toolUse.name}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
2158
|
+
const parameters = typeof toolUse.arguments === "string" ? toolUse.arguments : JSON.stringify(params);
|
|
2159
|
+
this.context.llm.pushToolMessages(
|
|
2160
|
+
messages,
|
|
2161
|
+
{ id: toolId, name: toolUse.name, parameters },
|
|
2162
|
+
observation,
|
|
2163
|
+
thinkingBlocks
|
|
2164
|
+
);
|
|
2177
2165
|
}
|
|
2178
2166
|
};
|
|
2179
2167
|
|
|
@@ -8745,7 +8733,7 @@ var knowledgeBaseSearchTool = {
|
|
|
8745
8733
|
toolFn: async (value) => {
|
|
8746
8734
|
const params = value;
|
|
8747
8735
|
const { query, tags, file_type, max_results = 5 } = params;
|
|
8748
|
-
context.logger.log("\u{1F4DA} Knowledge Base Search:
|
|
8736
|
+
context.logger.log("\u{1F4DA} Knowledge Base Search: userId:", context.userId, "query:", query, "tags:", tags);
|
|
8749
8737
|
if (!context.db.fabfiles) {
|
|
8750
8738
|
context.logger.error("\u274C Knowledge Base Search: fabfiles repository not available");
|
|
8751
8739
|
return "Knowledge base search is not available at this time.";
|
|
@@ -8768,10 +8756,12 @@ var knowledgeBaseSearchTool = {
|
|
|
8768
8756
|
// Search across fileName + tags + notes for better recall
|
|
8769
8757
|
includeShared: true,
|
|
8770
8758
|
// Include owned + explicitly shared + org-shared files
|
|
8771
|
-
userGroups: context.user.groups || []
|
|
8759
|
+
userGroups: context.user.groups || [],
|
|
8772
8760
|
// Pass user's groups for org-level sharing
|
|
8761
|
+
dataLakeTags: getDataLakeTags(context.user.tags || [])
|
|
8762
|
+
// Include data lake files user has access to
|
|
8773
8763
|
});
|
|
8774
|
-
context.logger.log("\u{1F4DA} Knowledge Base Search: Found", searchResults.data.length, "results");
|
|
8764
|
+
context.logger.log("\u{1F4DA} Knowledge Base Search: Found", searchResults.data.length, "of", searchResults.total, "results. Files:", searchResults.data.map((f) => f.fileName));
|
|
8775
8765
|
return formatSearchResults(searchResults.data);
|
|
8776
8766
|
} catch (error) {
|
|
8777
8767
|
context.logger.error("\u274C Knowledge Base Search: Error during search:", error);
|
|
@@ -8791,7 +8781,7 @@ var knowledgeBaseSearchTool = {
|
|
|
8791
8781
|
tags: {
|
|
8792
8782
|
type: "array",
|
|
8793
8783
|
items: { type: "string" },
|
|
8794
|
-
description: 'Optional: filter results by tag names. Supports partial matching. For optimization docs, use tags like "opti:family:scheduling", "opti:QUBO", "opti:solver:highs",
|
|
8784
|
+
description: 'Optional: filter results by tag names. Supports partial matching. For optimization docs, use tags like "opti:family:scheduling", "opti:QUBO", "opti:solver:highs". For IonQ sales intelligence, use tags like "ionq:vertical:pharma", "ionq:competitor:ibm", "ionq:type:product-specs", "ionq:stage:discovery", "ionq:offering:forte". Any matching tag qualifies the file.'
|
|
8795
8785
|
},
|
|
8796
8786
|
file_type: {
|
|
8797
8787
|
type: "string",
|
|
@@ -8840,7 +8830,7 @@ var knowledgeBaseRetrieveTool = {
|
|
|
8840
8830
|
if (file) {
|
|
8841
8831
|
files = [file];
|
|
8842
8832
|
} else {
|
|
8843
|
-
const searchResults = await context.db.fabfiles.search(context.userId, file_id, { tags: [], shared: false }, { page: 1, limit: 1 }, { by: "fileName", direction: "asc" }, { textSearch: true, includeShared: true, userGroups: context.user.groups || [] });
|
|
8833
|
+
const searchResults = await context.db.fabfiles.search(context.userId, file_id, { tags: [], shared: false }, { page: 1, limit: 1 }, { by: "fileName", direction: "asc" }, { textSearch: true, includeShared: true, userGroups: context.user.groups || [], dataLakeTags: getDataLakeTags(context.user.tags || []) });
|
|
8844
8834
|
files = searchResults.data;
|
|
8845
8835
|
}
|
|
8846
8836
|
if (files.length === 0) {
|
|
@@ -8848,7 +8838,7 @@ var knowledgeBaseRetrieveTool = {
|
|
|
8848
8838
|
}
|
|
8849
8839
|
}
|
|
8850
8840
|
if (files.length === 0 && (tags?.length || query)) {
|
|
8851
|
-
const searchResults = await context.db.fabfiles.search(context.userId, query || "", { tags: tags || [], shared: false }, { page: 1, limit: 5 }, { by: "fileName", direction: "asc" }, { textSearch: true, includeShared: true, userGroups: context.user.groups || [] });
|
|
8841
|
+
const searchResults = await context.db.fabfiles.search(context.userId, query || "", { tags: tags || [], shared: false }, { page: 1, limit: 5 }, { by: "fileName", direction: "asc" }, { textSearch: true, includeShared: true, userGroups: context.user.groups || [], dataLakeTags: getDataLakeTags(context.user.tags || []) });
|
|
8852
8842
|
files = searchResults.data;
|
|
8853
8843
|
if (files.length === 0) {
|
|
8854
8844
|
const searchDesc = [query && `query "${query}"`, tags?.length && `tags [${tags.join(", ")}]`].filter(Boolean).join(" and ");
|
|
@@ -8904,7 +8894,7 @@ Chunks: ${chunks.length} | Characters: ${charLabel}
|
|
|
8904
8894
|
tags: {
|
|
8905
8895
|
type: "array",
|
|
8906
8896
|
items: { type: "string" },
|
|
8907
|
-
description: 'Filter documents by tags. For optimization docs, use tags like "opti:family:scheduling", "opti:solver:highs",
|
|
8897
|
+
description: 'Filter documents by tags. For optimization docs, use tags like "opti:family:scheduling", "opti:solver:highs". For IonQ sales intelligence, use tags like "ionq:vertical:pharma", "ionq:competitor:ibm", "ionq:type:product-specs", "ionq:offering:forte".'
|
|
8908
8898
|
},
|
|
8909
8899
|
query: {
|
|
8910
8900
|
type: "string",
|
|
@@ -13280,232 +13270,64 @@ var generateMcpTools = async (mcpData) => {
|
|
|
13280
13270
|
Logger.debug(`\u{1F527} generateMcpTools: Generated ${result.length} tool implementations for ${mcpData.serverName}`);
|
|
13281
13271
|
return result;
|
|
13282
13272
|
};
|
|
13283
|
-
|
|
13284
|
-
|
|
13285
|
-
|
|
13286
|
-
|
|
13287
|
-
|
|
13288
|
-
|
|
13289
|
-
|
|
13290
|
-
|
|
13291
|
-
|
|
13292
|
-
|
|
13293
|
-
|
|
13294
|
-
|
|
13295
|
-
|
|
13296
|
-
|
|
13297
|
-
|
|
13298
|
-
|
|
13299
|
-
|
|
13300
|
-
|
|
13301
|
-
|
|
13302
|
-
|
|
13303
|
-
|
|
13304
|
-
|
|
13305
|
-
|
|
13306
|
-
|
|
13307
|
-
|
|
13308
|
-
|
|
13309
|
-
|
|
13310
|
-
|
|
13311
|
-
|
|
13312
|
-
|
|
13313
|
-
|
|
13314
|
-
|
|
13315
|
-
|
|
13316
|
-
|
|
13317
|
-
|
|
13318
|
-
|
|
13319
|
-
|
|
13320
|
-
|
|
13321
|
-
|
|
13322
|
-
### Positive Observations
|
|
13323
|
-
- Well-implemented patterns worth noting (optional)
|
|
13324
|
-
|
|
13325
|
-
Focus on actionable, specific feedback referencing exact code locations. Your review will be used by the main agent.`
|
|
13326
|
-
});
|
|
13327
|
-
|
|
13328
|
-
// ../../b4m-core/packages/services/dist/src/llm/agents/ProjectManagerAgent.js
|
|
13329
|
-
var ProjectManagerAgent = (config) => ({
|
|
13330
|
-
name: "project_manager",
|
|
13331
|
-
description: "Project management via Jira and Confluence (create issues, search, update status, manage attachments, write docs). ALWAYS delegate Jira/Confluence requests to this agent \u2014 you do not have direct access to these tools",
|
|
13332
|
-
model: config?.model ?? ChatModels.CLAUDE_4_6_SONNET_BEDROCK,
|
|
13333
|
-
fallbackModels: [ChatModels.GPT4_1, ChatModels.GPT4_1_MINI],
|
|
13334
|
-
defaultThoroughness: config?.defaultThoroughness ?? "medium",
|
|
13335
|
-
maxIterations: { quick: 3, medium: 8, very_thorough: 15 },
|
|
13336
|
-
allowedTools: [...config?.extraAllowedTools ?? []],
|
|
13337
|
-
deniedTools: [...config?.extraDeniedTools ?? []],
|
|
13338
|
-
exclusiveMcpServers: ["atlassian"],
|
|
13339
|
-
systemPrompt: `You are a project management specialist with access to Jira and Confluence. Your job is to help manage projects, issues, documentation, and team workflows.
|
|
13340
|
-
|
|
13341
|
-
## Capabilities
|
|
13342
|
-
|
|
13343
|
-
### Jira
|
|
13344
|
-
- Search for issues using JQL
|
|
13345
|
-
- Create, update, and transition issues
|
|
13346
|
-
- Add comments and manage watchers
|
|
13347
|
-
- List projects and issue types
|
|
13348
|
-
- List, upload, download, and delete attachments on issues
|
|
13349
|
-
|
|
13350
|
-
### Confluence
|
|
13351
|
-
- Search for documentation
|
|
13352
|
-
- Create and update pages
|
|
13353
|
-
- Browse spaces and page hierarchies
|
|
13354
|
-
- List, upload, download, and delete attachments on pages
|
|
13355
|
-
|
|
13356
|
-
### Account & Identity
|
|
13357
|
-
- Check connected Jira account using \`atlassian__jira_get_current_user\`
|
|
13358
|
-
- Check connected Confluence account using \`atlassian__confluence_get_current_user\`
|
|
13359
|
-
- Look up users by name or email using \`atlassian__jira_search_users\`
|
|
13360
|
-
|
|
13361
|
-
## Best Practices
|
|
13362
|
-
1. When searching Jira, use precise JQL queries (e.g., \`project = PROJ AND status = "In Progress"\`)
|
|
13363
|
-
2. When creating issues, always check available issue types first with \`atlassian__jira_list_issue_types\`
|
|
13364
|
-
3. When updating issue status, use \`atlassian__jira_update_issue_transition\` with the target status name
|
|
13365
|
-
4. When creating Confluence pages, use the user's personal space if no space is specified
|
|
13366
|
-
5. Always confirm destructive operations (delete) with the user before proceeding
|
|
13367
|
-
6. When asked about the connected Atlassian account call BOTH \`atlassian__jira_get_current_user\` and \`atlassian__confluence_get_current_user\` to show the full picture. Present results clearly labeled by product, and note if either service is not connected. NEVER say you don't have access to account information \u2014 you DO have these tools.
|
|
13368
|
-
7. When asked specifically about "Jira account" or "Confluence account" only, use the respective tool alone.
|
|
13369
|
-
8. When asked about attachments, ALWAYS use the attachment tools \u2014 never guess or fabricate attachment details. For uploading files shared in Slack, pass the \`fabFileId\` from the message context to \`atlassian__jira_upload_attachment\` or \`atlassian__confluence_upload_attachment\`.
|
|
13370
|
-
|
|
13371
|
-
## Output Format
|
|
13372
|
-
Provide a clear summary of actions taken:
|
|
13373
|
-
1. What was done (created, updated, searched, etc.)
|
|
13374
|
-
2. Links or keys to relevant items (e.g., PROJ-123)
|
|
13375
|
-
3. Any issues or warnings encountered
|
|
13376
|
-
|
|
13377
|
-
Be precise with issue keys and project names. Your results will be used by the main agent.`
|
|
13378
|
-
});
|
|
13379
|
-
|
|
13380
|
-
// ../../b4m-core/packages/services/dist/src/llm/agents/GithubManagerAgent.js
|
|
13381
|
-
var GithubManagerAgent = (config) => ({
|
|
13382
|
-
name: "github_manager",
|
|
13383
|
-
description: "GitHub operations (issues, pull requests, code search, branches, workflows, reviews). ALWAYS delegate GitHub requests to this agent \u2014 you do not have direct access to these tools",
|
|
13384
|
-
model: config?.model ?? ChatModels.CLAUDE_4_6_SONNET_BEDROCK,
|
|
13385
|
-
fallbackModels: [ChatModels.GPT4_1, ChatModels.GPT4_1_MINI],
|
|
13386
|
-
defaultThoroughness: config?.defaultThoroughness ?? "medium",
|
|
13387
|
-
maxIterations: { quick: 3, medium: 8, very_thorough: 15 },
|
|
13388
|
-
allowedTools: [...config?.extraAllowedTools ?? []],
|
|
13389
|
-
deniedTools: [...config?.extraDeniedTools ?? []],
|
|
13390
|
-
exclusiveMcpServers: ["github"],
|
|
13391
|
-
systemPrompt: `You are a GitHub specialist with access to GitHub's API. Your job is to help manage repositories, issues, pull requests, code search, branches, and CI/CD workflows.
|
|
13392
|
-
|
|
13393
|
-
## First Step: ALWAYS Resolve Repository Context Automatically
|
|
13394
|
-
Before doing ANYTHING else, call the \`github__current_user\` tool. The response includes:
|
|
13395
|
-
- **selected_repositories**: The list of repositories the user has enabled for AI access (in \`owner/repo\` format)
|
|
13396
|
-
- **user**: The authenticated user's profile (login, name, etc.)
|
|
13397
|
-
|
|
13398
|
-
### Repository Resolution Rules (CRITICAL)
|
|
13399
|
-
You MUST use the selected_repositories list to automatically resolve the owner and repo. NEVER ask the user for the org, owner, or full repository path. Instead:
|
|
13400
|
-
|
|
13401
|
-
1. **Exact match**: If the user says a repo name that exactly matches a repo name in selected_repositories, use it immediately.
|
|
13402
|
-
2. **Partial/fuzzy match**: If the user provides a partial name, match it against any repo in selected_repositories whose name contains that term (e.g., if the user says "lumina" and selected_repositories contains \`SomeOrg/lumina5\`, use that).
|
|
13403
|
-
3. **Single repo shortcut**: If only one repository is selected, ALWAYS default to it \u2014 no questions asked.
|
|
13404
|
-
4. **Multiple matches**: Only if multiple selected repos match the user's term AND you truly cannot disambiguate, list the matching repos and ask which one. Do NOT list repos that don't match.
|
|
13405
|
-
5. **No match**: If nothing in selected_repositories matches, tell the user which repos are available and ask them to clarify.
|
|
13406
|
-
|
|
13407
|
-
**NEVER ask the user to "paste the GitHub URL" or provide the org/owner name.** You already have this information from \`github__current_user\`. The owner and repo are embedded in the \`owner/repo\` format of each selected repository entry.
|
|
13408
|
-
|
|
13409
|
-
### Defaults for Ambiguous Requests
|
|
13410
|
-
- **Timezone**: Default to UTC unless the user specifies otherwise.
|
|
13411
|
-
- **PR/Issue state**: Default to \`open\` unless the user specifies otherwise.
|
|
13412
|
-
- **Scope**: All matching repos unless the user narrows it down.
|
|
13413
|
-
- **When in doubt, act**: Prefer making reasonable assumptions and proceeding over asking clarifying questions. You can always note your assumptions in the response.
|
|
13414
|
-
|
|
13415
|
-
## Capabilities
|
|
13416
|
-
|
|
13417
|
-
### Issues
|
|
13418
|
-
- Create, update, and search issues
|
|
13419
|
-
- Add comments and manage labels
|
|
13420
|
-
- List and filter issues by state, assignee, labels
|
|
13421
|
-
|
|
13422
|
-
### Pull Requests
|
|
13423
|
-
- Create, update, and list pull requests
|
|
13424
|
-
- Get PR diffs, files changed, and review status
|
|
13425
|
-
- Merge pull requests and manage reviews
|
|
13426
|
-
- Request reviews from team members
|
|
13427
|
-
|
|
13428
|
-
### Code & Repository
|
|
13429
|
-
- Search code across repositories
|
|
13430
|
-
- Get file contents and commit history
|
|
13431
|
-
- Create and list branches and tags
|
|
13432
|
-
- Fork repositories
|
|
13433
|
-
|
|
13434
|
-
### CI/CD & Workflows
|
|
13435
|
-
- List and monitor workflow runs
|
|
13436
|
-
- Get job logs for debugging failures
|
|
13437
|
-
- Re-run failed jobs or entire workflows
|
|
13438
|
-
- Download workflow artifacts
|
|
13439
|
-
|
|
13440
|
-
### Notifications
|
|
13441
|
-
- List and manage GitHub notifications
|
|
13442
|
-
- Mark notifications as read/done
|
|
13443
|
-
|
|
13444
|
-
## Best Practices
|
|
13445
|
-
1. When searching issues or PRs, use GitHub search syntax (e.g., \`is:open label:bug assignee:username\`)
|
|
13446
|
-
2. When creating PRs, always include a clear title and description
|
|
13447
|
-
3. When reviewing PR changes, use \`get_pull_request_diff\` or \`get_pull_request_files\` for context
|
|
13448
|
-
4. For CI/CD debugging, use \`get_job_logs\` with \`failed_only=true\` to focus on failures
|
|
13449
|
-
5. Always include links to issues/PRs in your responses (e.g., owner/repo#123)
|
|
13450
|
-
|
|
13451
|
-
## Output Format
|
|
13452
|
-
Provide a clear summary of actions taken:
|
|
13453
|
-
1. What was done (created, searched, merged, etc.)
|
|
13454
|
-
2. Always provide links to relevant items (e.g., owner/repo#123, PR URLs)
|
|
13455
|
-
3. Any issues or warnings encountered
|
|
13456
|
-
|
|
13457
|
-
Be precise with repository names, issue numbers, and PR numbers. Your results will be used by the main agent.
|
|
13458
|
-
|
|
13459
|
-
## Write Operations & Confirmation
|
|
13460
|
-
Write tools (create issue, update issue, comment, merge PR, etc.) have a built-in confirmation system. When you call them, they return a preview with Confirm/Cancel buttons \u2014 the user clicks to execute. **NEVER ask the user for text confirmation before calling a write tool.** Just call the tool immediately. The tool handles confirmation automatically.
|
|
13461
|
-
|
|
13462
|
-
**CRITICAL: When a write tool returns \`"confirmation_required": true\`, the action has NOT been executed yet.** It is a preview awaiting user confirmation via buttons. Your summary MUST say the action is **awaiting confirmation**, NOT that it was completed. Example:
|
|
13463
|
-
- \u2705 "A comment preview is ready on PR #123. Click Confirm below to post it."
|
|
13464
|
-
- \u274C "Done! The comment has been posted on PR #123." (WRONG \u2014 it hasn't been posted yet)`
|
|
13465
|
-
});
|
|
13466
|
-
|
|
13467
|
-
// ../../b4m-core/packages/services/dist/src/llm/agents/ServerAgentStore.js
|
|
13468
|
-
var ServerAgentStore = class {
|
|
13469
|
-
constructor() {
|
|
13470
|
-
const builtInAgents = [
|
|
13471
|
-
// For now disable explore and plan agents as it is much faster if the parent llm handles this.
|
|
13472
|
-
// ExploreAgent(),
|
|
13473
|
-
// PlanAgent(),
|
|
13474
|
-
CodeReviewAgent(),
|
|
13475
|
-
ProjectManagerAgent(),
|
|
13476
|
-
GithubManagerAgent()
|
|
13477
|
-
];
|
|
13478
|
-
this.agents = new Map(builtInAgents.map((a) => [a.name, a]));
|
|
13479
|
-
}
|
|
13480
|
-
getAgent(name) {
|
|
13481
|
-
return this.agents.get(name);
|
|
13482
|
-
}
|
|
13483
|
-
getAllAgents() {
|
|
13484
|
-
return Array.from(this.agents.values());
|
|
13485
|
-
}
|
|
13486
|
-
getAgentNames() {
|
|
13487
|
-
return Array.from(this.agents.keys());
|
|
13488
|
-
}
|
|
13489
|
-
hasAgent(name) {
|
|
13490
|
-
return this.agents.has(name);
|
|
13491
|
-
}
|
|
13492
|
-
/**
|
|
13493
|
-
* Get the deduplicated list of MCP server names that are exclusive to agents.
|
|
13494
|
-
* Derived from each agent's `exclusiveMcpServers` field.
|
|
13495
|
-
*/
|
|
13496
|
-
getExclusiveMcpServers() {
|
|
13497
|
-
const servers = /* @__PURE__ */ new Set();
|
|
13498
|
-
for (const agent of this.agents.values()) {
|
|
13499
|
-
if (agent.exclusiveMcpServers) {
|
|
13500
|
-
for (const s of agent.exclusiveMcpServers) {
|
|
13501
|
-
servers.add(s);
|
|
13273
|
+
var generateMcpToolsFromCache = (serverName, cachedTools, callTool) => {
|
|
13274
|
+
const normalizedServerName = serverName.toLowerCase();
|
|
13275
|
+
const result = cachedTools.map((item) => {
|
|
13276
|
+
const { name: originalToolName, ...rest } = item;
|
|
13277
|
+
const namespacedToolName = `${normalizedServerName}__${originalToolName}`;
|
|
13278
|
+
const providerMetadata = getMcpProviderMetadata(normalizedServerName);
|
|
13279
|
+
const fallbackDescription = providerMetadata?.defaultToolDescriptions?.[originalToolName] ?? "";
|
|
13280
|
+
const parameters = normalizeToolParameters(rest);
|
|
13281
|
+
const optionTools = {
|
|
13282
|
+
toolFn: async (args) => {
|
|
13283
|
+
Logger.debug(`Calling ${originalToolName} tool via ${serverName}`, args);
|
|
13284
|
+
try {
|
|
13285
|
+
const toolResult = await callTool(originalToolName, args);
|
|
13286
|
+
const contentBlocks = toolResult?.content;
|
|
13287
|
+
if (Array.isArray(contentBlocks) && contentBlocks.length > 0) {
|
|
13288
|
+
const normalized = contentBlocks.map((entry) => {
|
|
13289
|
+
if (entry && typeof entry === "object" && "text" in entry) {
|
|
13290
|
+
return entry.text;
|
|
13291
|
+
}
|
|
13292
|
+
return JSON.stringify(entry);
|
|
13293
|
+
}).join("\n");
|
|
13294
|
+
Logger.debug(`[Tool Result] ${originalToolName}:`, normalized);
|
|
13295
|
+
return normalized;
|
|
13296
|
+
}
|
|
13297
|
+
const serialized = typeof toolResult === "string" ? toolResult : JSON.stringify(toolResult);
|
|
13298
|
+
Logger.debug(`[Tool Result] Unexpected format for ${originalToolName}, returning serialized output`);
|
|
13299
|
+
return serialized;
|
|
13300
|
+
} catch (error) {
|
|
13301
|
+
if (normalizedServerName === "atlassian") {
|
|
13302
|
+
const errorName = error instanceof Error ? error.name : "";
|
|
13303
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
13304
|
+
const isTokenError = errorName === "AtlassianReconnectRequiredError" || errorMessage.includes("401") || errorMessage.includes("403") || errorMessage.includes("unauthorized") || errorMessage.includes("expired");
|
|
13305
|
+
if (isTokenError) {
|
|
13306
|
+
console.warn(`Atlassian token may be expired for tool ${originalToolName}, error:`, errorMessage);
|
|
13307
|
+
return ATLASSIAN_RECONNECT_MESSAGE;
|
|
13308
|
+
}
|
|
13309
|
+
}
|
|
13310
|
+
throw error;
|
|
13502
13311
|
}
|
|
13312
|
+
},
|
|
13313
|
+
toolSchema: {
|
|
13314
|
+
name: namespacedToolName,
|
|
13315
|
+
description: rest.description || fallbackDescription,
|
|
13316
|
+
parameters
|
|
13503
13317
|
}
|
|
13504
|
-
}
|
|
13505
|
-
return
|
|
13506
|
-
|
|
13318
|
+
};
|
|
13319
|
+
return {
|
|
13320
|
+
name: namespacedToolName,
|
|
13321
|
+
...optionTools,
|
|
13322
|
+
_isMcpTool: true
|
|
13323
|
+
};
|
|
13324
|
+
});
|
|
13325
|
+
Logger.debug(`\u{1F527} generateMcpToolsFromCache: Generated ${result.length} tool implementations for ${serverName} (from cache)`);
|
|
13326
|
+
return result;
|
|
13507
13327
|
};
|
|
13508
|
-
|
|
13328
|
+
|
|
13329
|
+
// ../../b4m-core/packages/services/dist/src/llm/agents/ServerSubagentOrchestrator.js
|
|
13330
|
+
var SUBAGENT_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
13509
13331
|
|
|
13510
13332
|
// ../../b4m-core/packages/services/dist/src/llm/ChatCompletionProcess.js
|
|
13511
13333
|
import throttle2 from "lodash/throttle.js";
|
|
@@ -15238,6 +15060,12 @@ function substituteArguments(template, args) {
|
|
|
15238
15060
|
return result;
|
|
15239
15061
|
}
|
|
15240
15062
|
|
|
15063
|
+
// src/utils/mcpAdapter.ts
|
|
15064
|
+
import { createHash } from "crypto";
|
|
15065
|
+
import { readFile as readFile2, writeFile, mkdir } from "fs/promises";
|
|
15066
|
+
import { homedir as homedir4 } from "os";
|
|
15067
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
15068
|
+
|
|
15241
15069
|
// ../../b4m-core/packages/mcp/dist/src/client.js
|
|
15242
15070
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
15243
15071
|
import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
|
|
@@ -15251,22 +15079,17 @@ var MCPClient = class {
|
|
|
15251
15079
|
customCommand;
|
|
15252
15080
|
customArgs;
|
|
15253
15081
|
suppressStderr;
|
|
15082
|
+
onStderrLine;
|
|
15254
15083
|
tools = [];
|
|
15255
15084
|
serverName;
|
|
15256
|
-
constructor({ envVariables, name,
|
|
15085
|
+
constructor({ envVariables, name, command, args, suppressStderr = false, onStderrLine }) {
|
|
15257
15086
|
this.mcp = new Client2({ name: "mcp-client-cli", version: "1.0.0" });
|
|
15258
15087
|
this.envVariables = [...envVariables];
|
|
15259
15088
|
this.serverName = name;
|
|
15260
15089
|
this.customCommand = command;
|
|
15261
15090
|
this.customArgs = args;
|
|
15262
15091
|
this.suppressStderr = suppressStderr;
|
|
15263
|
-
|
|
15264
|
-
const reposJson = JSON.stringify(selectedRepositories);
|
|
15265
|
-
this.envVariables.push({
|
|
15266
|
-
key: "SELECTED_REPOSITORIES",
|
|
15267
|
-
value: reposJson
|
|
15268
|
-
});
|
|
15269
|
-
}
|
|
15092
|
+
this.onStderrLine = onStderrLine;
|
|
15270
15093
|
}
|
|
15271
15094
|
async connectToServer() {
|
|
15272
15095
|
try {
|
|
@@ -15305,6 +15128,7 @@ var MCPClient = class {
|
|
|
15305
15128
|
args = [serverScriptPath];
|
|
15306
15129
|
console.log(`[MCP] Using server: ${this.serverName} at ${serverScriptPath}`);
|
|
15307
15130
|
}
|
|
15131
|
+
const stderrMode = this.suppressStderr ? "ignore" : this.onStderrLine ? "pipe" : void 0;
|
|
15308
15132
|
const transportConfig = {
|
|
15309
15133
|
command,
|
|
15310
15134
|
args,
|
|
@@ -15312,14 +15136,16 @@ var MCPClient = class {
|
|
|
15312
15136
|
...Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] !== void 0)),
|
|
15313
15137
|
...envVarsObject
|
|
15314
15138
|
},
|
|
15315
|
-
|
|
15316
|
-
...this.suppressStderr && { stderr: "ignore" }
|
|
15139
|
+
...stderrMode && { stderr: stderrMode }
|
|
15317
15140
|
};
|
|
15318
15141
|
this.transport = new StdioClientTransport(transportConfig);
|
|
15319
15142
|
this.transport.onerror = (error) => {
|
|
15320
15143
|
console.error(`[MCP] Transport error for ${this.serverName}:`, error);
|
|
15321
15144
|
};
|
|
15322
15145
|
await this.mcp.connect(this.transport);
|
|
15146
|
+
if (this.onStderrLine && this.transport.stderr) {
|
|
15147
|
+
this.readStderr(this.transport.stderr);
|
|
15148
|
+
}
|
|
15323
15149
|
await new Promise((resolve3) => setTimeout(resolve3, 100));
|
|
15324
15150
|
const toolsResult = await this.mcp.listTools();
|
|
15325
15151
|
this.tools = toolsResult.tools.map((tool) => {
|
|
@@ -15341,6 +15167,30 @@ var MCPClient = class {
|
|
|
15341
15167
|
throw e;
|
|
15342
15168
|
}
|
|
15343
15169
|
}
|
|
15170
|
+
/**
|
|
15171
|
+
* Read lines from the child process stderr stream and dispatch to the callback.
|
|
15172
|
+
* Uses Node.js stream events; errors are silently caught (stream closes on disconnect).
|
|
15173
|
+
*/
|
|
15174
|
+
readStderr(stream) {
|
|
15175
|
+
let buffer = "";
|
|
15176
|
+
stream.setEncoding("utf8");
|
|
15177
|
+
stream.on("data", (chunk) => {
|
|
15178
|
+
buffer += chunk;
|
|
15179
|
+
const lines = buffer.split("\n");
|
|
15180
|
+
buffer = lines.pop() || "";
|
|
15181
|
+
for (const line of lines) {
|
|
15182
|
+
const trimmed = line.trim();
|
|
15183
|
+
if (trimmed)
|
|
15184
|
+
this.onStderrLine?.(trimmed);
|
|
15185
|
+
}
|
|
15186
|
+
});
|
|
15187
|
+
stream.on("end", () => {
|
|
15188
|
+
if (buffer.trim())
|
|
15189
|
+
this.onStderrLine?.(buffer.trim());
|
|
15190
|
+
});
|
|
15191
|
+
stream.on("error", () => {
|
|
15192
|
+
});
|
|
15193
|
+
}
|
|
15344
15194
|
async callTool(toolName, toolArgs) {
|
|
15345
15195
|
try {
|
|
15346
15196
|
const args = toolArgs && typeof toolArgs === "object" ? toolArgs : void 0;
|
|
@@ -15376,14 +15226,30 @@ var MCPClient = class {
|
|
|
15376
15226
|
};
|
|
15377
15227
|
|
|
15378
15228
|
// src/utils/mcpAdapter.ts
|
|
15229
|
+
var CACHE_VERSION = 1;
|
|
15230
|
+
var CACHE_FILE = join3(homedir4(), ".bike4mind", "mcp-schema-cache.json");
|
|
15379
15231
|
var McpManager = class {
|
|
15380
15232
|
constructor(config) {
|
|
15381
15233
|
this.servers = /* @__PURE__ */ new Map();
|
|
15382
15234
|
this.connectionStates = /* @__PURE__ */ new Map();
|
|
15235
|
+
/** Per-server deferred promise resolved once the background connection is ready */
|
|
15236
|
+
this.connectionReady = /* @__PURE__ */ new Map();
|
|
15237
|
+
/**
|
|
15238
|
+
* Serializes background cache saves so concurrent writes don't race.
|
|
15239
|
+
* JSON.stringify is evaluated at run-time, always capturing the latest cache state.
|
|
15240
|
+
*/
|
|
15241
|
+
this.backgroundSaveQueue = Promise.resolve();
|
|
15383
15242
|
this.config = config;
|
|
15384
15243
|
}
|
|
15244
|
+
/** Subscribe to background connection state changes for live UI updates. */
|
|
15245
|
+
setOnStateChange(callback) {
|
|
15246
|
+
this.onStateChange = callback;
|
|
15247
|
+
}
|
|
15385
15248
|
/**
|
|
15386
|
-
* Initialize
|
|
15249
|
+
* Initialize MCP servers with schema caching.
|
|
15250
|
+
*
|
|
15251
|
+
* - Cache hit → tools registered immediately from cache; server connected in background
|
|
15252
|
+
* - Cache miss → server connected eagerly (blocks); result cached for next run
|
|
15387
15253
|
*/
|
|
15388
15254
|
async initialize() {
|
|
15389
15255
|
const enabledServers = this.config.mcpServers.filter((s) => s.enabled);
|
|
@@ -15392,47 +15258,96 @@ var McpManager = class {
|
|
|
15392
15258
|
return;
|
|
15393
15259
|
}
|
|
15394
15260
|
logger.debug(`\u{1F4E1} Initializing ${enabledServers.length} MCP server(s)...`);
|
|
15395
|
-
const
|
|
15396
|
-
const
|
|
15397
|
-
|
|
15398
|
-
|
|
15399
|
-
|
|
15261
|
+
const cache = await this.loadCache();
|
|
15262
|
+
const configuredNames = new Set(this.config.mcpServers.map((s) => s.name));
|
|
15263
|
+
let pruned = false;
|
|
15264
|
+
for (const name of Object.keys(cache.servers)) {
|
|
15265
|
+
if (!configuredNames.has(name)) {
|
|
15266
|
+
delete cache.servers[name];
|
|
15267
|
+
pruned = true;
|
|
15268
|
+
}
|
|
15269
|
+
}
|
|
15270
|
+
const eagerConnections = [];
|
|
15271
|
+
for (const serverConfig of enabledServers) {
|
|
15272
|
+
const configHash = this.hashServerConfig(serverConfig);
|
|
15273
|
+
const cachedEntry = cache.servers[serverConfig.name];
|
|
15274
|
+
if (cachedEntry && cachedEntry.configHash === configHash) {
|
|
15275
|
+
this.registerFromCache(serverConfig, cachedEntry);
|
|
15276
|
+
this.connectBackground(serverConfig, cache);
|
|
15277
|
+
} else {
|
|
15278
|
+
eagerConnections.push(this.connectEager(serverConfig, cache));
|
|
15279
|
+
}
|
|
15280
|
+
}
|
|
15281
|
+
if (eagerConnections.length > 0) {
|
|
15282
|
+
await Promise.allSettled(eagerConnections);
|
|
15283
|
+
await this.saveCache(cache);
|
|
15284
|
+
} else if (pruned) {
|
|
15285
|
+
await this.saveCache(cache);
|
|
15400
15286
|
}
|
|
15401
|
-
|
|
15402
|
-
|
|
15287
|
+
const connected = [...this.connectionStates.values()].filter((s) => s === "connected").length;
|
|
15288
|
+
const pending = [...this.connectionStates.values()].filter((s) => s === "connecting").length;
|
|
15289
|
+
if (connected > 0 || pending > 0) {
|
|
15290
|
+
logger.debug(`\u2705 ${connected} MCP server(s) ready${pending > 0 ? `, ${pending} connecting in background` : ""}`);
|
|
15403
15291
|
}
|
|
15404
15292
|
}
|
|
15293
|
+
// ─── Private: connection strategies ────────────────────────────────────────
|
|
15405
15294
|
/**
|
|
15406
|
-
*
|
|
15295
|
+
* Register tool stubs from cache immediately. callTool lazily awaits the
|
|
15296
|
+
* background connection before forwarding to the real client.
|
|
15407
15297
|
*/
|
|
15408
|
-
|
|
15298
|
+
registerFromCache(serverConfig, entry) {
|
|
15409
15299
|
this.connectionStates.set(serverConfig.name, "connecting");
|
|
15300
|
+
let resolveReady;
|
|
15301
|
+
let rejectReady;
|
|
15302
|
+
const promise = new Promise((res, rej) => {
|
|
15303
|
+
resolveReady = res;
|
|
15304
|
+
rejectReady = rej;
|
|
15305
|
+
});
|
|
15306
|
+
promise.catch(() => {
|
|
15307
|
+
});
|
|
15308
|
+
this.connectionReady.set(serverConfig.name, { resolve: resolveReady, reject: rejectReady, promise });
|
|
15309
|
+
const callTool = async (name, args) => {
|
|
15310
|
+
await promise;
|
|
15311
|
+
return this.servers.get(serverConfig.name).client.callTool(name, args);
|
|
15312
|
+
};
|
|
15313
|
+
const tools = generateMcpToolsFromCache(serverConfig.name, entry.tools, callTool);
|
|
15314
|
+
this.servers.set(serverConfig.name, { name: serverConfig.name, client: null, tools });
|
|
15315
|
+
logger.debug(`\u{1F4CB} ${entry.tools.length} tools for ${serverConfig.name} loaded from cache`);
|
|
15316
|
+
}
|
|
15317
|
+
/**
|
|
15318
|
+
* Spawn the server process in the background. Resolves the per-server
|
|
15319
|
+
* deferred promise when ready so pending callTool invocations can proceed.
|
|
15320
|
+
*/
|
|
15321
|
+
connectBackground(serverConfig, cache) {
|
|
15322
|
+
this.doConnect(serverConfig).then(({ client }) => {
|
|
15323
|
+
const instance = this.servers.get(serverConfig.name);
|
|
15324
|
+
if (instance) {
|
|
15325
|
+
instance.client = client;
|
|
15326
|
+
}
|
|
15327
|
+
this.connectionStates.set(serverConfig.name, "connected");
|
|
15328
|
+
this.connectionReady.get(serverConfig.name)?.resolve();
|
|
15329
|
+
this.onStateChange?.();
|
|
15330
|
+
logger.debug(`\u2705 Background connection to ${serverConfig.name} established`);
|
|
15331
|
+
this.writeCacheEntry(cache, serverConfig, client.tools);
|
|
15332
|
+
this.scheduleBackgroundSave(cache);
|
|
15333
|
+
}).catch((err) => {
|
|
15334
|
+
this.connectionStates.set(serverConfig.name, "failed");
|
|
15335
|
+
this.connectionReady.get(serverConfig.name)?.reject(err);
|
|
15336
|
+
this.onStateChange?.();
|
|
15337
|
+
logger.debug(`\u274C Background connection to ${serverConfig.name} failed: ${err}`);
|
|
15338
|
+
});
|
|
15339
|
+
}
|
|
15340
|
+
/**
|
|
15341
|
+
* Connect eagerly (blocks initialize). Populates servers map and updates cache.
|
|
15342
|
+
*/
|
|
15343
|
+
async connectEager(serverConfig, cache) {
|
|
15344
|
+
this.connectionStates.set(serverConfig.name, "connecting");
|
|
15345
|
+
logger.debug(`\u{1F504} Connecting to ${serverConfig.name}...`);
|
|
15410
15346
|
try {
|
|
15411
|
-
|
|
15412
|
-
|
|
15413
|
-
key,
|
|
15414
|
-
value
|
|
15415
|
-
}));
|
|
15416
|
-
const client = new MCPClient({
|
|
15417
|
-
envVariables,
|
|
15418
|
-
name: serverConfig.name,
|
|
15419
|
-
command: serverConfig.command,
|
|
15420
|
-
args: serverConfig.args,
|
|
15421
|
-
suppressStderr: true
|
|
15422
|
-
});
|
|
15423
|
-
await client.connectToServer();
|
|
15424
|
-
const mcpData = {
|
|
15425
|
-
serverName: serverConfig.name,
|
|
15426
|
-
getTools: async () => client.tools,
|
|
15427
|
-
callTool: async (name, args) => client.callTool(name, args)
|
|
15428
|
-
};
|
|
15429
|
-
const tools = await generateMcpTools(mcpData);
|
|
15430
|
-
this.servers.set(serverConfig.name, {
|
|
15431
|
-
name: serverConfig.name,
|
|
15432
|
-
client,
|
|
15433
|
-
tools
|
|
15434
|
-
});
|
|
15347
|
+
const { client, tools } = await this.doConnect(serverConfig);
|
|
15348
|
+
this.servers.set(serverConfig.name, { name: serverConfig.name, client, tools });
|
|
15435
15349
|
this.connectionStates.set(serverConfig.name, "connected");
|
|
15350
|
+
this.writeCacheEntry(cache, serverConfig, client.tools);
|
|
15436
15351
|
logger.debug(`\u2705 Connected to ${serverConfig.name} (${tools.length} tools)`);
|
|
15437
15352
|
} catch (error) {
|
|
15438
15353
|
this.connectionStates.set(serverConfig.name, "failed");
|
|
@@ -15440,8 +15355,82 @@ var McpManager = class {
|
|
|
15440
15355
|
}
|
|
15441
15356
|
}
|
|
15442
15357
|
/**
|
|
15443
|
-
*
|
|
15358
|
+
* Shared: spawn and handshake with an MCP server process.
|
|
15359
|
+
*/
|
|
15360
|
+
async doConnect(serverConfig) {
|
|
15361
|
+
const envVariables = Object.entries(serverConfig.env).map(([key, value]) => ({ key, value }));
|
|
15362
|
+
const client = new MCPClient({
|
|
15363
|
+
envVariables,
|
|
15364
|
+
name: serverConfig.name,
|
|
15365
|
+
command: serverConfig.command,
|
|
15366
|
+
args: serverConfig.args,
|
|
15367
|
+
suppressStderr: true
|
|
15368
|
+
});
|
|
15369
|
+
await client.connectToServer();
|
|
15370
|
+
const mcpData = {
|
|
15371
|
+
serverName: serverConfig.name,
|
|
15372
|
+
getTools: async () => client.tools,
|
|
15373
|
+
// any: generateMcpTools accepts a loose duck-type; MCPClient.callTool return type
|
|
15374
|
+
// doesn't match the internal interface exactly, but runtime behaviour is correct.
|
|
15375
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
15376
|
+
callTool: async (name, args) => client.callTool(name, args)
|
|
15377
|
+
};
|
|
15378
|
+
const tools = await generateMcpTools(mcpData);
|
|
15379
|
+
return { client, tools };
|
|
15380
|
+
}
|
|
15381
|
+
// ─── Private: cache helpers ──────────────────────────────────────────────────
|
|
15382
|
+
async loadCache() {
|
|
15383
|
+
try {
|
|
15384
|
+
const raw = await readFile2(CACHE_FILE, "utf-8");
|
|
15385
|
+
const parsed = JSON.parse(raw);
|
|
15386
|
+
if (parsed.version === CACHE_VERSION) {
|
|
15387
|
+
return parsed;
|
|
15388
|
+
}
|
|
15389
|
+
} catch {
|
|
15390
|
+
}
|
|
15391
|
+
return { version: CACHE_VERSION, servers: {} };
|
|
15392
|
+
}
|
|
15393
|
+
async saveCache(cache) {
|
|
15394
|
+
try {
|
|
15395
|
+
await mkdir(dirname2(CACHE_FILE), { recursive: true });
|
|
15396
|
+
await writeFile(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
|
|
15397
|
+
} catch (error) {
|
|
15398
|
+
logger.debug(`\u26A0\uFE0F Failed to save MCP schema cache: ${error}`);
|
|
15399
|
+
}
|
|
15400
|
+
}
|
|
15401
|
+
/**
|
|
15402
|
+
* Enqueue a background cache save. Saves are serialized so concurrent background
|
|
15403
|
+
* connections can't interleave writes and lose each other's entries. JSON.stringify
|
|
15404
|
+
* is evaluated when the queued write runs, so it always captures the latest state.
|
|
15444
15405
|
*/
|
|
15406
|
+
scheduleBackgroundSave(cache) {
|
|
15407
|
+
this.backgroundSaveQueue = this.backgroundSaveQueue.then(() => this.saveCache(cache)).catch(() => {
|
|
15408
|
+
});
|
|
15409
|
+
}
|
|
15410
|
+
writeCacheEntry(cache, serverConfig, rawTools) {
|
|
15411
|
+
cache.servers[serverConfig.name] = {
|
|
15412
|
+
configHash: this.hashServerConfig(serverConfig),
|
|
15413
|
+
tools: rawTools.map((t) => {
|
|
15414
|
+
const tool = t;
|
|
15415
|
+
return {
|
|
15416
|
+
name: t.name,
|
|
15417
|
+
description: tool.description,
|
|
15418
|
+
// Normalize both MCP SDK casing variants to input_schema for storage
|
|
15419
|
+
input_schema: tool.inputSchema ?? tool.input_schema
|
|
15420
|
+
};
|
|
15421
|
+
}),
|
|
15422
|
+
cachedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15423
|
+
};
|
|
15424
|
+
}
|
|
15425
|
+
hashServerConfig(serverConfig) {
|
|
15426
|
+
const key = JSON.stringify({
|
|
15427
|
+
command: serverConfig.command,
|
|
15428
|
+
args: serverConfig.args,
|
|
15429
|
+
env: serverConfig.env
|
|
15430
|
+
});
|
|
15431
|
+
return createHash("sha256").update(key).digest("hex").slice(0, 16);
|
|
15432
|
+
}
|
|
15433
|
+
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
15445
15434
|
getTools() {
|
|
15446
15435
|
const allTools = [];
|
|
15447
15436
|
for (const server of this.servers.values()) {
|
|
@@ -15449,24 +15438,17 @@ var McpManager = class {
|
|
|
15449
15438
|
}
|
|
15450
15439
|
return allTools;
|
|
15451
15440
|
}
|
|
15452
|
-
/**
|
|
15453
|
-
* Get tool count by server
|
|
15454
|
-
*/
|
|
15455
15441
|
getToolCount() {
|
|
15456
15442
|
return Array.from(this.servers.values()).map((server) => ({
|
|
15457
15443
|
serverName: server.name,
|
|
15458
15444
|
count: server.tools.length
|
|
15459
15445
|
}));
|
|
15460
15446
|
}
|
|
15461
|
-
/**
|
|
15462
|
-
* Disconnect from all MCP servers
|
|
15463
|
-
*/
|
|
15464
15447
|
async disconnect() {
|
|
15465
|
-
if (this.servers.size === 0)
|
|
15466
|
-
return;
|
|
15467
|
-
}
|
|
15448
|
+
if (this.servers.size === 0) return;
|
|
15468
15449
|
logger.debug(`\u{1F50C} Disconnecting from ${this.servers.size} MCP server(s)...`);
|
|
15469
15450
|
const disconnectPromises = Array.from(this.servers.values()).map(async (server) => {
|
|
15451
|
+
if (!server.client) return;
|
|
15470
15452
|
try {
|
|
15471
15453
|
await server.client.disconnect();
|
|
15472
15454
|
logger.debug(`\u2705 Disconnected from ${server.name}`);
|
|
@@ -15476,27 +15458,17 @@ var McpManager = class {
|
|
|
15476
15458
|
});
|
|
15477
15459
|
await Promise.allSettled(disconnectPromises);
|
|
15478
15460
|
this.servers.clear();
|
|
15461
|
+
this.connectionReady.clear();
|
|
15479
15462
|
}
|
|
15480
|
-
/**
|
|
15481
|
-
* Check if any MCP servers are connected
|
|
15482
|
-
*/
|
|
15483
15463
|
hasServers() {
|
|
15484
15464
|
return this.servers.size > 0;
|
|
15485
15465
|
}
|
|
15486
|
-
/**
|
|
15487
|
-
* Get list of connected server names
|
|
15488
|
-
*/
|
|
15489
15466
|
getServerNames() {
|
|
15490
15467
|
return Array.from(this.servers.keys());
|
|
15491
15468
|
}
|
|
15492
|
-
/**
|
|
15493
|
-
* Get connection state for a server
|
|
15494
|
-
*/
|
|
15495
15469
|
getConnectionState(serverName) {
|
|
15496
15470
|
const serverConfig = this.config.mcpServers.find((s) => s.name === serverName);
|
|
15497
|
-
if (!serverConfig?.enabled)
|
|
15498
|
-
return "disabled";
|
|
15499
|
-
}
|
|
15471
|
+
if (!serverConfig?.enabled) return "disabled";
|
|
15500
15472
|
return this.connectionStates.get(serverName) || "connecting";
|
|
15501
15473
|
}
|
|
15502
15474
|
};
|
|
@@ -15904,6 +15876,9 @@ var ServerLlmBackend = class {
|
|
|
15904
15876
|
});
|
|
15905
15877
|
});
|
|
15906
15878
|
}
|
|
15879
|
+
pushToolMessages(_messages, _tool, _result) {
|
|
15880
|
+
throw new Error("ServerLlmBackend does not support pushToolMessages \u2014 tools are executed server-side");
|
|
15881
|
+
}
|
|
15907
15882
|
/**
|
|
15908
15883
|
* Get available models from server
|
|
15909
15884
|
* Fetches from /api/models and filters for CLI-compatible models
|
|
@@ -16123,6 +16098,9 @@ var WebSocketLlmBackend = class {
|
|
|
16123
16098
|
});
|
|
16124
16099
|
});
|
|
16125
16100
|
}
|
|
16101
|
+
pushToolMessages(_messages, _tool, _result) {
|
|
16102
|
+
throw new Error("WebSocketLlmBackend does not support pushToolMessages \u2014 tools are executed server-side");
|
|
16103
|
+
}
|
|
16126
16104
|
/**
|
|
16127
16105
|
* Get available models from server (REST call, not streaming).
|
|
16128
16106
|
* Delegates to ApiClient -- same as ServerLlmBackend.
|
|
@@ -16163,6 +16141,9 @@ var WebSocketLlmBackend = class {
|
|
|
16163
16141
|
};
|
|
16164
16142
|
|
|
16165
16143
|
// src/ws/WebSocketConnectionManager.ts
|
|
16144
|
+
import WsWebSocket from "ws";
|
|
16145
|
+
var useWsPolyfill = typeof globalThis.WebSocket === "undefined";
|
|
16146
|
+
var WS = useWsPolyfill ? WsWebSocket : globalThis.WebSocket;
|
|
16166
16147
|
var WebSocketConnectionManager = class {
|
|
16167
16148
|
constructor(wsUrl, getToken) {
|
|
16168
16149
|
this.ws = null;
|
|
@@ -16170,6 +16151,7 @@ var WebSocketConnectionManager = class {
|
|
|
16170
16151
|
this.reconnectAttempts = 0;
|
|
16171
16152
|
this.maxReconnectDelay = 3e4;
|
|
16172
16153
|
this.handlers = /* @__PURE__ */ new Map();
|
|
16154
|
+
this.actionHandlers = /* @__PURE__ */ new Map();
|
|
16173
16155
|
this.disconnectHandlers = /* @__PURE__ */ new Set();
|
|
16174
16156
|
this.reconnectTimer = null;
|
|
16175
16157
|
this.connected = false;
|
|
@@ -16192,7 +16174,13 @@ var WebSocketConnectionManager = class {
|
|
|
16192
16174
|
}
|
|
16193
16175
|
return new Promise((resolve3, reject) => {
|
|
16194
16176
|
logger.debug(`[WS] Connecting to ${this.wsUrl}...`);
|
|
16195
|
-
|
|
16177
|
+
if (useWsPolyfill) {
|
|
16178
|
+
this.ws = new WsWebSocket(this.wsUrl, {
|
|
16179
|
+
headers: { "Sec-WebSocket-Protocol": `access_token.${token}` }
|
|
16180
|
+
});
|
|
16181
|
+
} else {
|
|
16182
|
+
this.ws = new WS(this.wsUrl, [`access_token.${token}`]);
|
|
16183
|
+
}
|
|
16196
16184
|
this.ws.onopen = () => {
|
|
16197
16185
|
logger.debug("[WS] Connected");
|
|
16198
16186
|
this.connected = true;
|
|
@@ -16209,7 +16197,12 @@ var WebSocketConnectionManager = class {
|
|
|
16209
16197
|
if (requestId && this.handlers.has(requestId)) {
|
|
16210
16198
|
this.handlers.get(requestId)(message);
|
|
16211
16199
|
} else {
|
|
16212
|
-
|
|
16200
|
+
const action = message.action;
|
|
16201
|
+
if (action && this.actionHandlers.has(action)) {
|
|
16202
|
+
this.actionHandlers.get(action)(message);
|
|
16203
|
+
} else {
|
|
16204
|
+
logger.debug(`[WS] Unhandled message: ${action || "unknown"}`);
|
|
16205
|
+
}
|
|
16213
16206
|
}
|
|
16214
16207
|
} catch (err) {
|
|
16215
16208
|
logger.debug(`[WS] Failed to parse message: ${err}`);
|
|
@@ -16224,11 +16217,13 @@ var WebSocketConnectionManager = class {
|
|
|
16224
16217
|
}
|
|
16225
16218
|
};
|
|
16226
16219
|
this.ws.onerror = (err) => {
|
|
16227
|
-
|
|
16220
|
+
const underlying = err.error;
|
|
16221
|
+
const detail = underlying?.message || String(err);
|
|
16222
|
+
logger.debug(`[WS] Error: ${detail}`);
|
|
16228
16223
|
if (this.connecting) {
|
|
16229
16224
|
this.connecting = false;
|
|
16230
16225
|
this.connected = false;
|
|
16231
|
-
reject(new Error(
|
|
16226
|
+
reject(new Error(`WebSocket connection failed: ${detail}`));
|
|
16232
16227
|
}
|
|
16233
16228
|
};
|
|
16234
16229
|
});
|
|
@@ -16241,7 +16236,7 @@ var WebSocketConnectionManager = class {
|
|
|
16241
16236
|
* Send a JSON message over the WebSocket connection.
|
|
16242
16237
|
*/
|
|
16243
16238
|
send(data) {
|
|
16244
|
-
if (!this.ws || this.ws.readyState !==
|
|
16239
|
+
if (!this.ws || this.ws.readyState !== WS.OPEN) {
|
|
16245
16240
|
throw new Error("WebSocket is not connected");
|
|
16246
16241
|
}
|
|
16247
16242
|
const payload = JSON.stringify(data);
|
|
@@ -16264,6 +16259,19 @@ var WebSocketConnectionManager = class {
|
|
|
16264
16259
|
offRequest(requestId) {
|
|
16265
16260
|
this.handlers.delete(requestId);
|
|
16266
16261
|
}
|
|
16262
|
+
/**
|
|
16263
|
+
* Register a handler for messages matching a specific action type.
|
|
16264
|
+
* Used for server-pushed commands like keep_command.
|
|
16265
|
+
*/
|
|
16266
|
+
onAction(action, handler) {
|
|
16267
|
+
this.actionHandlers.set(action, handler);
|
|
16268
|
+
}
|
|
16269
|
+
/**
|
|
16270
|
+
* Remove a handler for a specific action type.
|
|
16271
|
+
*/
|
|
16272
|
+
offAction(action) {
|
|
16273
|
+
this.actionHandlers.delete(action);
|
|
16274
|
+
}
|
|
16267
16275
|
/**
|
|
16268
16276
|
* Register a handler that fires when the connection drops.
|
|
16269
16277
|
*/
|
|
@@ -16287,13 +16295,14 @@ var WebSocketConnectionManager = class {
|
|
|
16287
16295
|
this.ws = null;
|
|
16288
16296
|
}
|
|
16289
16297
|
this.handlers.clear();
|
|
16298
|
+
this.actionHandlers.clear();
|
|
16290
16299
|
this.disconnectHandlers.clear();
|
|
16291
16300
|
}
|
|
16292
16301
|
startHeartbeat() {
|
|
16293
16302
|
this.stopHeartbeat();
|
|
16294
16303
|
this.heartbeatInterval = setInterval(
|
|
16295
16304
|
() => {
|
|
16296
|
-
if (this.ws && this.ws.readyState ===
|
|
16305
|
+
if (this.ws && this.ws.readyState === WS.OPEN) {
|
|
16297
16306
|
this.ws.send(JSON.stringify({ action: "heartbeat" }));
|
|
16298
16307
|
logger.debug("[WS] Heartbeat sent");
|
|
16299
16308
|
}
|