@phuetz/code-buddy 2.1.0 → 2.2.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.fr.md +2 -2
- package/README.md +3 -2
- package/codebuddy-runtime.json +4 -4
- package/dist/agent/execution/agent-executor.js +16 -5
- package/dist/agent/execution/tool-selection-strategy.d.ts +7 -0
- package/dist/agent/execution/tool-selection-strategy.js +13 -0
- package/dist/codebuddy/fleet-tool-defs.d.ts +1 -0
- package/dist/codebuddy/fleet-tool-defs.js +31 -0
- package/dist/codebuddy/providers/provider-openai-compat.d.ts +7 -0
- package/dist/codebuddy/providers/provider-openai-compat.js +28 -0
- package/dist/commands/handlers/missing-handlers.js +8 -0
- package/dist/commands/mcp.d.ts +13 -0
- package/dist/commands/mcp.js +62 -21
- package/dist/config/model-tools.js +99 -0
- package/dist/doctor/integrations.d.ts +18 -0
- package/dist/doctor/integrations.js +14 -0
- package/dist/errors/crash-handler.js +2 -0
- package/dist/mcp/client.d.ts +11 -0
- package/dist/mcp/client.js +62 -3
- package/dist/mcp/mcp-oauth-constants.d.ts +8 -0
- package/dist/mcp/mcp-oauth-constants.js +9 -0
- package/dist/mcp/mcp-oauth-provider.d.ts +80 -0
- package/dist/mcp/mcp-oauth-provider.js +241 -0
- package/dist/mcp/mcp-oauth.d.ts +60 -1
- package/dist/mcp/mcp-oauth.js +241 -65
- package/dist/mcp/transports.d.ts +4 -0
- package/dist/mcp/transports.js +52 -3
- package/dist/services/prompt-builder.js +3 -1
- package/dist/services/runtime-settings-context.d.ts +8 -0
- package/dist/services/runtime-settings-context.js +11 -1
- package/dist/tools/metadata.js +9 -0
- package/dist/tools/peer-tool-invoke-tool.d.ts +61 -0
- package/dist/tools/peer-tool-invoke-tool.js +396 -0
- package/dist/tools/registry/fleet-tools.d.ts +12 -3
- package/dist/tools/registry/fleet-tools.js +116 -4
- package/dist/tools/registry/index.d.ts +1 -1
- package/dist/tools/registry/index.js +2 -2
- package/dist/utils/config-validation/schema.d.ts +2 -2
- package/dist/utils/graceful-shutdown.d.ts +8 -0
- package/dist/utils/graceful-shutdown.js +23 -7
- package/package.json +1 -1
package/README.fr.md
CHANGED
|
@@ -6,10 +6,10 @@ Pour l’installation et les autres usages, consulter le [README principal](READ
|
|
|
6
6
|
|
|
7
7
|
## Nouveautés 2.1
|
|
8
8
|
|
|
9
|
-
La version 2.
|
|
9
|
+
La version 2.2 ajoute la connexion OAuth à ElevenLabs hébergé via MCP, fiabilise les commandes en script (interruption headless en code 130, `mcp add-json --yes`) et affiche le thème effectif dans `/status` ([notes 2.2.0](docs/RELEASE-NOTES-2.2.0.md)). La version 2.1 ajoutait un catalogue explicite de ressources, la recherche RagChat avec citations de pages, l’import de configurations MCP et un pont A2A JSON-RPC limité aux échanges texte documentés. Elle corrige aussi des problèmes de sessions terminal, de mémoire par fournisseur, de diagnostic de configuration et d’interface Cowork. Les [notes de version](docs/RELEASE-NOTES-2.1.0.md) détaillent les limites : pas de reprise automatique des tâches après panne, pas de compatibilité universelle ni de garantie de qualité OCR.
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
|
-
npm install -g @phuetz/code-buddy@2.
|
|
12
|
+
npm install -g @phuetz/code-buddy@2.2.0
|
|
13
13
|
buddy --version
|
|
14
14
|
buddy doctor --offline
|
|
15
15
|
```
|
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ configuration; enabling a feature does not replace its permission checks.
|
|
|
53
53
|
|
|
54
54
|
**New in 2.1:** explicit resource discovery and selection, RagChat search with page citations,
|
|
55
55
|
MCP configuration import, a bounded A2A JSON-RPC bridge, and fixes for terminal sessions,
|
|
56
|
-
configuration diagnostics and Cowork workflows. Read the [release notes](docs/RELEASE-NOTES-2.
|
|
56
|
+
configuration diagnostics and Cowork workflows. Read the [release notes](docs/RELEASE-NOTES-2.2.0.md)
|
|
57
57
|
for setup and the limits of each integration.
|
|
58
58
|
|
|
59
59
|
- **A multi-AI fleet hub.** Peers running `buddy server` observe each other's events and call each
|
|
@@ -261,7 +261,8 @@ to Apache 2.0 on 2030-08-31. Bundled Python skills stay MIT (see their `SKILL.md
|
|
|
261
261
|
## Documentation
|
|
262
262
|
|
|
263
263
|
- **[Getting started](docs/getting-started.md)** — first run, headless mode, sessions.
|
|
264
|
-
- **[Release notes 2.
|
|
264
|
+
- **[Release notes 2.2.0](docs/RELEASE-NOTES-2.2.0.md)** — current changes and validation limits.
|
|
265
|
+
- **[Release notes 2.1.0](docs/RELEASE-NOTES-2.1.0.md)** — previous release.
|
|
265
266
|
- [Release notes 2.0.0](docs/RELEASE-NOTES-2.0.0.md) — previous major release.
|
|
266
267
|
- [Install](docs/install.md) — published npm, Docker/VPS, the one-command installer.
|
|
267
268
|
- [Commands](docs/commands.md) · [Features](docs/features.md) · [Security](docs/security.md)
|
package/codebuddy-runtime.json
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"corePackage": {
|
|
4
4
|
"name": "@phuetz/code-buddy",
|
|
5
|
-
"version": "2.
|
|
5
|
+
"version": "2.2.0",
|
|
6
6
|
"description": "AI coding agent with terminal and HTTP interfaces, optional Cowork desktop integration, multi-provider routing, tools, fleet collaboration and inspectable memory. See release notes for supported integrations and limits."
|
|
7
7
|
},
|
|
8
|
-
"sourceRevision": "
|
|
8
|
+
"sourceRevision": "b5c50c189f69794ef73cbea1ef3b3a86c00b2b9f",
|
|
9
9
|
"sourceDirty": null,
|
|
10
10
|
"sourceRevisionOrigin": "env:GITHUB_SHA",
|
|
11
11
|
"distDigest": {
|
|
12
12
|
"algorithm": "sha256",
|
|
13
13
|
"scope": "dist-tree-code-without-maps-v1",
|
|
14
|
-
"value": "
|
|
15
|
-
"fileCount":
|
|
14
|
+
"value": "4c4f5752b460b3976153f23c1bbf6127f59897c27b3b4fbb4f17136332d6cae3",
|
|
15
|
+
"fileCount": 5197
|
|
16
16
|
},
|
|
17
17
|
"runtime": {
|
|
18
18
|
"kind": "codebuddy-core",
|
|
@@ -13,6 +13,7 @@ import { resolveFirstTokenStallTimeoutMs, resolveStallTimeoutMs, withStallGuard
|
|
|
13
13
|
import { startHeadlessPromptProgress } from "../../cli/headless-prompt-progress.js";
|
|
14
14
|
import { HEADLESS_LOCAL_COMPACT_ALWAYS_INCLUDE, HEADLESS_LOCAL_COMPACT_MAX_TOOLS, isHeadlessLocalPromptCompact, } from "../../config/headless-local-prompt.js";
|
|
15
15
|
import { normalizeHallucinatedLocalToolCall } from "../tool-handler.js";
|
|
16
|
+
import { mergeAlwaysInclude } from "./tool-selection-strategy.js";
|
|
16
17
|
import { ContextCompactionError } from "../../context/context-manager-v2.js";
|
|
17
18
|
import { logger } from "../../utils/logger.js";
|
|
18
19
|
import { getErrorMessage } from "../../errors/index.js";
|
|
@@ -1070,11 +1071,21 @@ export class AgentExecutor {
|
|
|
1070
1071
|
alwaysInclude: ['view_file', 'bash', 'search'],
|
|
1071
1072
|
};
|
|
1072
1073
|
}
|
|
1073
|
-
if (
|
|
1074
|
-
const { runtimeInspectionTools } = await import('../../services/runtime-settings-context.js');
|
|
1075
|
-
const inspectionTools =
|
|
1076
|
-
|
|
1077
|
-
|
|
1074
|
+
if (!codeResearch) {
|
|
1075
|
+
const { runtimeInspectionTools, connectedFleetSurfaceTools } = await import('../../services/runtime-settings-context.js');
|
|
1076
|
+
const inspectionTools = [...new Set([
|
|
1077
|
+
...(surface === 'cli' ? runtimeInspectionTools(turnQueryText) : []),
|
|
1078
|
+
...connectedFleetSurfaceTools(),
|
|
1079
|
+
])];
|
|
1080
|
+
if (inspectionTools.length) {
|
|
1081
|
+
// Fleet tools first (Qwen3.6 GGUF otherwise prefers bash and
|
|
1082
|
+
// shells list_peers) but they are ADDED to the guaranteed
|
|
1083
|
+
// default list, never substituted for it.
|
|
1084
|
+
const alwaysInclude = mergeAlwaysInclude(selectionOpts.alwaysInclude, inspectionTools);
|
|
1085
|
+
if (alwaysInclude) {
|
|
1086
|
+
selectionOpts = { ...selectionOpts, alwaysInclude };
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1078
1089
|
}
|
|
1079
1090
|
if (codeResearch) {
|
|
1080
1091
|
selectionOpts = { ...selectionOpts, alwaysInclude: ['self_describe'] };
|
|
@@ -71,6 +71,13 @@ export interface SelectionResult {
|
|
|
71
71
|
* Default configuration for tool selection
|
|
72
72
|
*/
|
|
73
73
|
export declare const DEFAULT_TOOL_SELECTION_CONFIG: ToolSelectionConfig;
|
|
74
|
+
/**
|
|
75
|
+
* Prepend extras onto the guaranteed alwaysInclude list.
|
|
76
|
+
* Empty extras leave `existing` unchanged (undefined keeps the strategy
|
|
77
|
+
* default). Unset `existing` is the default guaranteed list, not an empty
|
|
78
|
+
* replacement — fleet tools must be added, never substituted.
|
|
79
|
+
*/
|
|
80
|
+
export declare function mergeAlwaysInclude(existing: string[] | undefined, extras: string[]): string[] | undefined;
|
|
74
81
|
/**
|
|
75
82
|
* Tool Selection Strategy
|
|
76
83
|
*
|
|
@@ -56,6 +56,19 @@ export const DEFAULT_TOOL_SELECTION_CONFIG = {
|
|
|
56
56
|
enableCaching: true,
|
|
57
57
|
cacheTTLMs: 5 * 60 * 1000, // 5 minutes
|
|
58
58
|
};
|
|
59
|
+
/**
|
|
60
|
+
* Prepend extras onto the guaranteed alwaysInclude list.
|
|
61
|
+
* Empty extras leave `existing` unchanged (undefined keeps the strategy
|
|
62
|
+
* default). Unset `existing` is the default guaranteed list, not an empty
|
|
63
|
+
* replacement — fleet tools must be added, never substituted.
|
|
64
|
+
*/
|
|
65
|
+
export function mergeAlwaysInclude(existing, extras) {
|
|
66
|
+
if (extras.length === 0) {
|
|
67
|
+
return existing;
|
|
68
|
+
}
|
|
69
|
+
const base = existing ?? DEFAULT_TOOL_SELECTION_CONFIG.alwaysInclude;
|
|
70
|
+
return [...extras, ...base.filter((name) => !extras.includes(name))];
|
|
71
|
+
}
|
|
59
72
|
function requiredToolsForQuery(query) {
|
|
60
73
|
// A bare shared URL has almost no semantic words for TF-IDF/BM25 to match.
|
|
61
74
|
// Route YouTube links deterministically to both the local-first caption/video
|
|
@@ -11,5 +11,6 @@ export declare const PEER_DELEGATE_TOOL_DEF: CodeBuddyTool;
|
|
|
11
11
|
export declare const LIST_PEERS_TOOL_DEF: CodeBuddyTool;
|
|
12
12
|
export declare const PEER_CHAIN_TOOL_DEF: CodeBuddyTool;
|
|
13
13
|
export declare const ROUTE_PEER_TOOL_DEF: CodeBuddyTool;
|
|
14
|
+
export declare const PEER_TOOL_INVOKE_TOOL_DEF: CodeBuddyTool;
|
|
14
15
|
export declare const FLEET_ROOM_TOOL_DEF: CodeBuddyTool;
|
|
15
16
|
export declare const FLEET_TOOLS: CodeBuddyTool[];
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* because some call sites pull from one registry and some from the other.
|
|
8
8
|
*/
|
|
9
9
|
import { FLEET_DISPATCH_PROFILE_GUIDANCE_TEXT } from '../fleet/dispatch-profile.js';
|
|
10
|
+
import { PEER_TOOL_INVOKE_DESCRIPTION, PEER_TOOL_INVOKE_PARAM_DESCRIPTIONS, } from '../tools/peer-tool-invoke-tool.js';
|
|
10
11
|
const DISPATCH_PROFILE_PARAMETER_DESCRIPTION = 'Optional Fleet dispatch profile. When set, carries the operating posture through peer.chat ' +
|
|
11
12
|
'and returns peer-side policy metadata when supported. Selection guide: ' +
|
|
12
13
|
`${FLEET_DISPATCH_PROFILE_GUIDANCE_TEXT}.`;
|
|
@@ -188,6 +189,35 @@ export const ROUTE_PEER_TOOL_DEF = {
|
|
|
188
189
|
},
|
|
189
190
|
},
|
|
190
191
|
};
|
|
192
|
+
export const PEER_TOOL_INVOKE_TOOL_DEF = {
|
|
193
|
+
type: 'function',
|
|
194
|
+
function: {
|
|
195
|
+
name: 'peer_tool_invoke',
|
|
196
|
+
description: PEER_TOOL_INVOKE_DESCRIPTION,
|
|
197
|
+
parameters: {
|
|
198
|
+
type: 'object',
|
|
199
|
+
properties: {
|
|
200
|
+
peer: {
|
|
201
|
+
type: 'string',
|
|
202
|
+
description: PEER_TOOL_INVOKE_PARAM_DESCRIPTIONS.peer,
|
|
203
|
+
},
|
|
204
|
+
tool: {
|
|
205
|
+
type: 'string',
|
|
206
|
+
description: PEER_TOOL_INVOKE_PARAM_DESCRIPTIONS.tool,
|
|
207
|
+
},
|
|
208
|
+
args: {
|
|
209
|
+
type: 'object',
|
|
210
|
+
description: PEER_TOOL_INVOKE_PARAM_DESCRIPTIONS.args,
|
|
211
|
+
},
|
|
212
|
+
timeoutMs: {
|
|
213
|
+
type: 'number',
|
|
214
|
+
description: PEER_TOOL_INVOKE_PARAM_DESCRIPTIONS.timeoutMs,
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
required: ['peer', 'tool'],
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
};
|
|
191
221
|
export const FLEET_ROOM_TOOL_DEF = {
|
|
192
222
|
type: 'function',
|
|
193
223
|
function: {
|
|
@@ -214,6 +244,7 @@ export const FLEET_ROOM_TOOL_DEF = {
|
|
|
214
244
|
export const FLEET_TOOLS = [
|
|
215
245
|
FLEET_ROOM_TOOL_DEF,
|
|
216
246
|
PEER_DELEGATE_TOOL_DEF,
|
|
247
|
+
PEER_TOOL_INVOKE_TOOL_DEF,
|
|
217
248
|
PEER_CHAIN_TOOL_DEF,
|
|
218
249
|
LIST_PEERS_TOOL_DEF,
|
|
219
250
|
ROUTE_PEER_TOOL_DEF,
|
|
@@ -125,6 +125,13 @@ export declare class OpenAICompatProvider implements Provider {
|
|
|
125
125
|
* Can be overridden with GROK_FORCE_TOOLS=true for models that support function calling.
|
|
126
126
|
* Auto-enables tools for models known to support function calling.
|
|
127
127
|
*/
|
|
128
|
+
/**
|
|
129
|
+
* Trace whether `tools` / `tool_choice` actually leave the process. Local
|
|
130
|
+
* runtimes that trip `isLocalInference()` omit both fields — that is the
|
|
131
|
+
* second suspect when a model that emits tool calls in a raw bench falls
|
|
132
|
+
* back to `bash` inside the agent loop.
|
|
133
|
+
*/
|
|
134
|
+
private logToolPayload;
|
|
128
135
|
private isLocalInference;
|
|
129
136
|
/** xAI no longer accepts legacy search_parameters payloads. */
|
|
130
137
|
private isXaiProvider;
|
|
@@ -197,6 +197,7 @@ export class OpenAICompatProvider {
|
|
|
197
197
|
'llama3.2', // Alternative naming
|
|
198
198
|
'qwen2.5', // Qwen 2.5 supports tools
|
|
199
199
|
'qwen-2.5', // Alternative naming
|
|
200
|
+
'qwen3', // Qwen3 / Qwen3.6 GGUF (Lemonade) — includes() covers qwen3.5+
|
|
200
201
|
'mistral', // Mistral models support function calling
|
|
201
202
|
'mixtral', // Mixtral supports function calling
|
|
202
203
|
'command-r', // Cohere Command-R
|
|
@@ -450,6 +451,31 @@ export class OpenAICompatProvider {
|
|
|
450
451
|
* Can be overridden with GROK_FORCE_TOOLS=true for models that support function calling.
|
|
451
452
|
* Auto-enables tools for models known to support function calling.
|
|
452
453
|
*/
|
|
454
|
+
/**
|
|
455
|
+
* Trace whether `tools` / `tool_choice` actually leave the process. Local
|
|
456
|
+
* runtimes that trip `isLocalInference()` omit both fields — that is the
|
|
457
|
+
* second suspect when a model that emits tool calls in a raw bench falls
|
|
458
|
+
* back to `bash` inside the agent loop.
|
|
459
|
+
*/
|
|
460
|
+
logToolPayload(useTools, tools, toolChoice) {
|
|
461
|
+
const names = (tools ?? []).map((tool) => tool.function.name);
|
|
462
|
+
const payload = {
|
|
463
|
+
source: 'OpenAICompatProvider',
|
|
464
|
+
model: this.currentModel,
|
|
465
|
+
baseURL: this.baseURL,
|
|
466
|
+
localInference: this.isLocalInference(),
|
|
467
|
+
toolsPresent: useTools,
|
|
468
|
+
toolCount: useTools ? names.length : 0,
|
|
469
|
+
toolChoice: useTools ? (toolChoice ?? 'auto') : 'omitted',
|
|
470
|
+
toolNames: useTools ? names : [],
|
|
471
|
+
};
|
|
472
|
+
if (process.env.CODEBUDDY_LOG_LLM_TOOLS === 'true') {
|
|
473
|
+
logger.info('[openai-compat] chat tools payload', payload);
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
logger.debug('[openai-compat] chat tools payload', payload);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
453
479
|
isLocalInference() {
|
|
454
480
|
if (process.env.GROK_FORCE_TOOLS === 'true') {
|
|
455
481
|
return false;
|
|
@@ -801,6 +827,7 @@ export class OpenAICompatProvider {
|
|
|
801
827
|
async chat(messages, tools, opts = {}, searchOptions) {
|
|
802
828
|
try {
|
|
803
829
|
const useTools = !this.isLocalInference() && (tools?.length ?? 0) > 0;
|
|
830
|
+
this.logToolPayload(useTools, tools, opts.tool_choice);
|
|
804
831
|
// Strict-template local runtimes (Ollama/LM Studio/vLLM) require a single
|
|
805
832
|
// leading system message — merge any late/duplicate system injections
|
|
806
833
|
// before the tool-role conversion below.
|
|
@@ -965,6 +992,7 @@ export class OpenAICompatProvider {
|
|
|
965
992
|
};
|
|
966
993
|
try {
|
|
967
994
|
const useTools = !this.isLocalInference() && (tools?.length ?? 0) > 0;
|
|
995
|
+
this.logToolPayload(useTools, tools, opts.tool_choice);
|
|
968
996
|
// Strict-template local runtimes (Ollama/LM Studio/vLLM) require a single
|
|
969
997
|
// leading system message — merge any late/duplicate system injections
|
|
970
998
|
// before the tool-role conversion below.
|
|
@@ -811,6 +811,14 @@ export async function handleStatus(activeModel) {
|
|
|
811
811
|
// Current model
|
|
812
812
|
const currentModel = activeModel ?? (await import('../../utils/settings-manager.js')).getSettingsManager().getCurrentModel();
|
|
813
813
|
lines.push(` Model: ${currentModel}`);
|
|
814
|
+
try {
|
|
815
|
+
const { getThemeManager } = await import('../../themes/theme-manager.js');
|
|
816
|
+
const theme = getThemeManager().getCurrentTheme();
|
|
817
|
+
lines.push(` Theme: ${theme.name} (${theme.id})`);
|
|
818
|
+
}
|
|
819
|
+
catch (_err) {
|
|
820
|
+
lines.push(' Theme: unknown');
|
|
821
|
+
}
|
|
814
822
|
// Agent mode
|
|
815
823
|
try {
|
|
816
824
|
const { getOperatingModeManager } = await import('../../agent/operating-modes.js');
|
package/dist/commands/mcp.d.ts
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
export declare function confirmPrompt(prompt: string): Promise<boolean>;
|
|
3
|
+
/**
|
|
4
|
+
* How to confirm a stdio MCP add. `--yes` skips the prompt.
|
|
5
|
+
* Without a TTY and without `--yes`, abort instead of hanging on readline.
|
|
6
|
+
*/
|
|
7
|
+
export declare function stdioAddConfirmMode(yes: boolean, stdinIsTTY?: boolean | undefined): 'skip' | 'prompt' | 'abort';
|
|
8
|
+
/**
|
|
9
|
+
* `mcp test` / add / add-json only probe the server then persist config.
|
|
10
|
+
* Tear down the live client so a stdio child cannot keep the CLI event loop open.
|
|
11
|
+
* Same path as `buddy mcp test` (removeServer → transport.disconnect → reap PID).
|
|
12
|
+
*/
|
|
13
|
+
export declare function releaseMcpProbe(manager: {
|
|
14
|
+
removeServer(name: string): Promise<void>;
|
|
15
|
+
}, name: string): Promise<void>;
|
|
3
16
|
export interface MCPServeOptions {
|
|
4
17
|
allowWrite?: boolean;
|
|
5
18
|
tools?: string;
|
package/dist/commands/mcp.js
CHANGED
|
@@ -37,6 +37,30 @@ export function confirmPrompt(prompt) {
|
|
|
37
37
|
});
|
|
38
38
|
});
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* How to confirm a stdio MCP add. `--yes` skips the prompt.
|
|
42
|
+
* Without a TTY and without `--yes`, abort instead of hanging on readline.
|
|
43
|
+
*/
|
|
44
|
+
export function stdioAddConfirmMode(yes, stdinIsTTY = process.stdin.isTTY) {
|
|
45
|
+
if (yes)
|
|
46
|
+
return 'skip';
|
|
47
|
+
if (stdinIsTTY)
|
|
48
|
+
return 'prompt';
|
|
49
|
+
return 'abort';
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* `mcp test` / add / add-json only probe the server then persist config.
|
|
53
|
+
* Tear down the live client so a stdio child cannot keep the CLI event loop open.
|
|
54
|
+
* Same path as `buddy mcp test` (removeServer → transport.disconnect → reap PID).
|
|
55
|
+
*/
|
|
56
|
+
export async function releaseMcpProbe(manager, name) {
|
|
57
|
+
try {
|
|
58
|
+
await manager.removeServer(name);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Probe teardown must not mask the add/test result.
|
|
62
|
+
}
|
|
63
|
+
}
|
|
40
64
|
/** Start the distribution-facing stdio server. Logs stay on stderr via logger. */
|
|
41
65
|
export async function serveMCP(options = {}) {
|
|
42
66
|
const { CodeBuddyMCPServer } = await import('../mcp/mcp-server.js');
|
|
@@ -216,9 +240,9 @@ export function createMCPCommand() {
|
|
|
216
240
|
}
|
|
217
241
|
addMCPServer(preset);
|
|
218
242
|
console.log(chalk.green(`✓ Added predefined MCP server: ${name}`));
|
|
219
|
-
// Try to connect immediately
|
|
243
|
+
// Try to connect immediately, then drop the probe (same as `mcp test`).
|
|
244
|
+
const manager = getMCPManager();
|
|
220
245
|
try {
|
|
221
|
-
const manager = getMCPManager();
|
|
222
246
|
await manager.addServer(preset);
|
|
223
247
|
console.log(chalk.green(`✓ Connected to MCP server: ${name}`));
|
|
224
248
|
const tools = manager.getTools().filter(t => t.serverName === name);
|
|
@@ -228,6 +252,9 @@ export function createMCPCommand() {
|
|
|
228
252
|
console.log(chalk.yellow(`⚠ Server saved but connection failed: ${getErrorMessage(connectError)}`));
|
|
229
253
|
console.log(chalk.yellow(' Check your API key and try: buddy mcp test ' + name));
|
|
230
254
|
}
|
|
255
|
+
finally {
|
|
256
|
+
await releaseMcpProbe(manager, name);
|
|
257
|
+
}
|
|
231
258
|
return;
|
|
232
259
|
}
|
|
233
260
|
// Custom server
|
|
@@ -285,12 +312,16 @@ export function createMCPCommand() {
|
|
|
285
312
|
};
|
|
286
313
|
addMCPServer(config);
|
|
287
314
|
console.log(chalk.green(`✓ Added MCP server: ${name}`));
|
|
288
|
-
// Try to connect immediately
|
|
289
315
|
const manager = getMCPManager();
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
316
|
+
try {
|
|
317
|
+
await manager.addServer(config);
|
|
318
|
+
console.log(chalk.green(`✓ Connected to MCP server: ${name}`));
|
|
319
|
+
const tools = manager.getTools().filter(t => t.serverName === name);
|
|
320
|
+
console.log(chalk.blue(` Available tools: ${tools.length}`));
|
|
321
|
+
}
|
|
322
|
+
finally {
|
|
323
|
+
await releaseMcpProbe(manager, name);
|
|
324
|
+
}
|
|
294
325
|
}
|
|
295
326
|
catch (error) {
|
|
296
327
|
logger.error(chalk.red(`Error adding MCP server: ${getErrorMessage(error)}`));
|
|
@@ -301,7 +332,8 @@ export function createMCPCommand() {
|
|
|
301
332
|
mcpCommand
|
|
302
333
|
.command('add-json <name> <json>')
|
|
303
334
|
.description('Add an MCP server from JSON configuration')
|
|
304
|
-
.
|
|
335
|
+
.option('-y, --yes', 'skip confirmation for stdio MCP servers (required when stdin is not a TTY)')
|
|
336
|
+
.action(async (name, jsonConfig, options) => {
|
|
305
337
|
try {
|
|
306
338
|
let config;
|
|
307
339
|
try {
|
|
@@ -336,20 +368,31 @@ export function createMCPCommand() {
|
|
|
336
368
|
console.log(chalk.yellow('\nSecurity notice: This MCP server will execute a command on your system.'));
|
|
337
369
|
console.log(chalk.yellow(` Command: ${serverConfig.transport.command} ${(serverConfig.transport.args || []).join(' ')}`));
|
|
338
370
|
console.log(chalk.yellow(' Only add MCP servers from trusted sources.\n'));
|
|
339
|
-
const
|
|
340
|
-
if (
|
|
341
|
-
|
|
342
|
-
|
|
371
|
+
const mode = stdioAddConfirmMode(options.yes === true);
|
|
372
|
+
if (mode === 'abort') {
|
|
373
|
+
logger.error(chalk.red('Error: stdin is not a TTY. Re-run with --yes to confirm adding this stdio MCP server.'));
|
|
374
|
+
process.exit(1);
|
|
375
|
+
}
|
|
376
|
+
if (mode === 'prompt') {
|
|
377
|
+
const confirmed = await confirmPrompt('Do you want to proceed? (y/N): ');
|
|
378
|
+
if (!confirmed) {
|
|
379
|
+
console.log('MCP server addition cancelled.');
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
343
382
|
}
|
|
344
383
|
}
|
|
345
384
|
addMCPServer(serverConfig);
|
|
346
385
|
console.log(chalk.green(`✓ Added MCP server: ${name}`));
|
|
347
|
-
// Try to connect immediately
|
|
348
386
|
const manager = getMCPManager();
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
387
|
+
try {
|
|
388
|
+
await manager.addServer(serverConfig);
|
|
389
|
+
console.log(chalk.green(`✓ Connected to MCP server: ${name}`));
|
|
390
|
+
const tools = manager.getTools().filter(t => t.serverName === name);
|
|
391
|
+
console.log(chalk.blue(` Available tools: ${tools.length}`));
|
|
392
|
+
}
|
|
393
|
+
finally {
|
|
394
|
+
await releaseMcpProbe(manager, name);
|
|
395
|
+
}
|
|
353
396
|
}
|
|
354
397
|
catch (error) {
|
|
355
398
|
logger.error(chalk.red(`Error adding MCP server: ${getErrorMessage(error)}`));
|
|
@@ -519,11 +562,9 @@ export function createMCPCommand() {
|
|
|
519
562
|
process.exit(1);
|
|
520
563
|
}
|
|
521
564
|
finally {
|
|
522
|
-
//
|
|
523
|
-
// transport so stdio children and their database/network handles do not
|
|
524
|
-
// keep the CLI process alive after a successful discovery.
|
|
565
|
+
// Probe only: drop the client/stdio child so the CLI can exit.
|
|
525
566
|
if (connected) {
|
|
526
|
-
await manager
|
|
567
|
+
await releaseMcpProbe(manager, name);
|
|
527
568
|
}
|
|
528
569
|
}
|
|
529
570
|
});
|
|
@@ -376,6 +376,26 @@ const DEFAULT_MODEL_CONFIGS = [
|
|
|
376
376
|
patchFormat: 'search_replace',
|
|
377
377
|
strengths: ['french'],
|
|
378
378
|
},
|
|
379
|
+
// Lemonade GGUF ids (`Qwen3.6-35B-A3B-MTP-GGUF`) must win over the hosted
|
|
380
|
+
// `qwen3.6*` row below. That hosted row is a 262k OpenRouter ceiling with the
|
|
381
|
+
// default (standard) prompt profile — in the agent loop it drowns the model
|
|
382
|
+
// in schemas and the model shells `list_peers` via `bash` instead of calling
|
|
383
|
+
// `peer_tool_invoke` (mission 8: 33.7 tok/s raw tools OK, 9× bash in-loop).
|
|
384
|
+
// Match is case-insensitive (`matchModel` /i). Keep the same lite surface as
|
|
385
|
+
// `gemma4*` which completed the fleet recipe.
|
|
386
|
+
{
|
|
387
|
+
model: 'Qwen3.6-*-GGUF*',
|
|
388
|
+
strengths: ['code', 'french'],
|
|
389
|
+
supportsReasoning: true,
|
|
390
|
+
supportsToolCalls: true,
|
|
391
|
+
supportsVision: false,
|
|
392
|
+
contextWindow: 32768,
|
|
393
|
+
maxOutputTokens: 8192,
|
|
394
|
+
maxToolRounds: 20,
|
|
395
|
+
disabledTools: ['apply_patch', 'browser', 'computer_control'],
|
|
396
|
+
patchFormat: 'search_replace',
|
|
397
|
+
promptProfile: 'lite',
|
|
398
|
+
},
|
|
379
399
|
// Qwen 3.5 → 3.7 hébergés (262 144 chez tous les fournisseurs OpenRouter). Un runtime local
|
|
380
400
|
// qui en sert moins abaisse la valeur via la découverte Ollama/LM Studio.
|
|
381
401
|
{
|
|
@@ -896,6 +916,56 @@ const DEFAULT_MODEL_CONFIGS = [
|
|
|
896
916
|
patchFormat: 'full_file',
|
|
897
917
|
promptProfile: 'lite',
|
|
898
918
|
},
|
|
919
|
+
{
|
|
920
|
+
model: 'qwen2.5:14b*',
|
|
921
|
+
strengths: ['french'],
|
|
922
|
+
supportsReasoning: false,
|
|
923
|
+
supportsToolCalls: true,
|
|
924
|
+
supportsVision: false,
|
|
925
|
+
contextWindow: 32768,
|
|
926
|
+
maxOutputTokens: 4096,
|
|
927
|
+
patchFormat: 'search_replace',
|
|
928
|
+
},
|
|
929
|
+
{
|
|
930
|
+
model: 'qwen2.5:32b*',
|
|
931
|
+
strengths: ['french'],
|
|
932
|
+
supportsReasoning: false,
|
|
933
|
+
supportsToolCalls: true,
|
|
934
|
+
supportsVision: false,
|
|
935
|
+
contextWindow: 32768,
|
|
936
|
+
maxOutputTokens: 8192,
|
|
937
|
+
patchFormat: 'search_replace',
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
model: 'qwen2.5:72b*',
|
|
941
|
+
strengths: ['french'],
|
|
942
|
+
supportsReasoning: false,
|
|
943
|
+
supportsToolCalls: true,
|
|
944
|
+
supportsVision: false,
|
|
945
|
+
contextWindow: 32768,
|
|
946
|
+
maxOutputTokens: 8192,
|
|
947
|
+
patchFormat: 'search_replace',
|
|
948
|
+
},
|
|
949
|
+
{
|
|
950
|
+
model: 'qwen2.5-coder:14b*',
|
|
951
|
+
strengths: ['french', 'code'],
|
|
952
|
+
supportsReasoning: false,
|
|
953
|
+
supportsToolCalls: true,
|
|
954
|
+
supportsVision: false,
|
|
955
|
+
contextWindow: 32768,
|
|
956
|
+
maxOutputTokens: 4096,
|
|
957
|
+
patchFormat: 'search_replace',
|
|
958
|
+
},
|
|
959
|
+
{
|
|
960
|
+
model: 'qwen2.5-coder:32b*',
|
|
961
|
+
strengths: ['french', 'code'],
|
|
962
|
+
supportsReasoning: false,
|
|
963
|
+
supportsToolCalls: true,
|
|
964
|
+
supportsVision: false,
|
|
965
|
+
contextWindow: 32768,
|
|
966
|
+
maxOutputTokens: 8192,
|
|
967
|
+
patchFormat: 'search_replace',
|
|
968
|
+
},
|
|
899
969
|
{
|
|
900
970
|
model: 'qwen2.5*',
|
|
901
971
|
strengths: ['french'],
|
|
@@ -904,6 +974,7 @@ const DEFAULT_MODEL_CONFIGS = [
|
|
|
904
974
|
// tool_calls) via Ollama, so the agent can't execute them — it stays
|
|
905
975
|
// chat-only. Verified flaky against scripts/autonomy-lab/ (unlike qwen3,
|
|
906
976
|
// which does emit structured calls). Use qwen3+ for autonomous editing.
|
|
977
|
+
// Explicitly split by size: 14b+ are promoted above, <14b stay gated.
|
|
907
978
|
supportsToolCalls: false,
|
|
908
979
|
supportsVision: false,
|
|
909
980
|
contextWindow: 32768,
|
|
@@ -962,9 +1033,37 @@ const DEFAULT_MODEL_CONFIGS = [
|
|
|
962
1033
|
patchFormat: 'full_file',
|
|
963
1034
|
promptProfile: 'lite',
|
|
964
1035
|
},
|
|
1036
|
+
{
|
|
1037
|
+
model: 'llama3.1:70b*',
|
|
1038
|
+
supportsReasoning: false,
|
|
1039
|
+
supportsToolCalls: true,
|
|
1040
|
+
supportsVision: false,
|
|
1041
|
+
contextWindow: 131072,
|
|
1042
|
+
maxOutputTokens: 8192,
|
|
1043
|
+
patchFormat: 'search_replace',
|
|
1044
|
+
},
|
|
1045
|
+
{
|
|
1046
|
+
model: 'llama3.1:405b*',
|
|
1047
|
+
supportsReasoning: false,
|
|
1048
|
+
supportsToolCalls: true,
|
|
1049
|
+
supportsVision: false,
|
|
1050
|
+
contextWindow: 131072,
|
|
1051
|
+
maxOutputTokens: 8192,
|
|
1052
|
+
patchFormat: 'search_replace',
|
|
1053
|
+
},
|
|
1054
|
+
{
|
|
1055
|
+
model: 'llama3.3*',
|
|
1056
|
+
supportsReasoning: false,
|
|
1057
|
+
supportsToolCalls: true,
|
|
1058
|
+
supportsVision: false,
|
|
1059
|
+
contextWindow: 131072,
|
|
1060
|
+
maxOutputTokens: 8192,
|
|
1061
|
+
patchFormat: 'search_replace',
|
|
1062
|
+
},
|
|
965
1063
|
{
|
|
966
1064
|
model: 'llama3*',
|
|
967
1065
|
supportsReasoning: false,
|
|
1066
|
+
// Explicitly split by size: 70b+ promoted above, 8b and below stay gated.
|
|
968
1067
|
supportsToolCalls: false,
|
|
969
1068
|
supportsVision: false,
|
|
970
1069
|
contextWindow: 8192,
|
|
@@ -117,6 +117,16 @@ export declare const doctorJsonReportSchema: z.ZodObject<{
|
|
|
117
117
|
success: boolean;
|
|
118
118
|
action: string;
|
|
119
119
|
}>, "many">>;
|
|
120
|
+
theme: z.ZodObject<{
|
|
121
|
+
id: z.ZodString;
|
|
122
|
+
name: z.ZodString;
|
|
123
|
+
}, "strict", z.ZodTypeAny, {
|
|
124
|
+
name: string;
|
|
125
|
+
id: string;
|
|
126
|
+
}, {
|
|
127
|
+
name: string;
|
|
128
|
+
id: string;
|
|
129
|
+
}>;
|
|
120
130
|
}, "strict", z.ZodTypeAny, {
|
|
121
131
|
version: 1;
|
|
122
132
|
summary: {
|
|
@@ -126,6 +136,10 @@ export declare const doctorJsonReportSchema: z.ZodObject<{
|
|
|
126
136
|
optionalNotInstalled: number;
|
|
127
137
|
};
|
|
128
138
|
offline: boolean;
|
|
139
|
+
theme: {
|
|
140
|
+
name: string;
|
|
141
|
+
id: string;
|
|
142
|
+
};
|
|
129
143
|
checks: {
|
|
130
144
|
name: string;
|
|
131
145
|
message: string;
|
|
@@ -150,6 +164,10 @@ export declare const doctorJsonReportSchema: z.ZodObject<{
|
|
|
150
164
|
optionalNotInstalled: number;
|
|
151
165
|
};
|
|
152
166
|
offline: boolean;
|
|
167
|
+
theme: {
|
|
168
|
+
name: string;
|
|
169
|
+
id: string;
|
|
170
|
+
};
|
|
153
171
|
checks: {
|
|
154
172
|
name: string;
|
|
155
173
|
message: string;
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* Every probe is fail-soft: a broken source becomes a `warn` check, never a crash.
|
|
12
12
|
*/
|
|
13
13
|
import { z } from 'zod';
|
|
14
|
+
import { getThemeManager } from '../themes/theme-manager.js';
|
|
14
15
|
const KNOWN_MCP_TRANSPORTS = new Set(['stdio', 'http', 'sse', 'streamable_http']);
|
|
15
16
|
function check(id, name, status, message, optional) {
|
|
16
17
|
return { id, section: 'integrations', name, status, message, ...(optional ? { optional } : {}) };
|
|
@@ -195,12 +196,24 @@ export const doctorJsonReportSchema = z.object({
|
|
|
195
196
|
optional: z.boolean(),
|
|
196
197
|
}).strict()),
|
|
197
198
|
fixes: z.array(z.object({ success: z.boolean(), message: z.string(), action: z.string() }).strict()).optional(),
|
|
199
|
+
theme: z.object({
|
|
200
|
+
id: z.string(),
|
|
201
|
+
name: z.string(),
|
|
202
|
+
}).strict(),
|
|
198
203
|
}).strict();
|
|
199
204
|
export function slugifyCheckName(name) {
|
|
200
205
|
return name.toLowerCase().normalize('NFKD').replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'check';
|
|
201
206
|
}
|
|
202
207
|
export function buildDoctorJsonReport(checks, summary, options) {
|
|
203
208
|
const seen = new Map();
|
|
209
|
+
let theme = { id: 'unknown', name: 'unknown' };
|
|
210
|
+
try {
|
|
211
|
+
const current = getThemeManager().getCurrentTheme();
|
|
212
|
+
theme = { id: current.id, name: current.name };
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
// Theme manager is optional for doctor JSON consumers.
|
|
216
|
+
}
|
|
204
217
|
const report = {
|
|
205
218
|
version: 1,
|
|
206
219
|
generatedAt: (options.now ?? new Date()).toISOString(),
|
|
@@ -221,6 +234,7 @@ export function buildDoctorJsonReport(checks, summary, options) {
|
|
|
221
234
|
};
|
|
222
235
|
}),
|
|
223
236
|
...(options.fixes ? { fixes: options.fixes } : {}),
|
|
237
|
+
theme,
|
|
224
238
|
};
|
|
225
239
|
return doctorJsonReportSchema.parse(report);
|
|
226
240
|
}
|