@yeaft/webchat-agent 1.0.409 → 1.0.410
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/connection/message-router.js +24 -2
- package/llm-config-cli.js +36 -3
- package/local-runtime/server/handlers/agent-output.js +14 -0
- package/local-runtime/server/handlers/agent-sync.js +6 -1
- package/local-runtime/server/handlers/client-conversation.js +4 -0
- package/local-runtime/server/handlers/client-misc.js +27 -0
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +166 -64
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +2 -2
- package/local-runtime/web/style.bundle.css +1 -1
- package/local-runtime/web/style.bundle.css.gz +0 -0
- package/package.json +1 -1
- package/yeaft/config-api.js +109 -48
- package/yeaft/config.js +56 -9
- package/yeaft/engine.js +98 -43
- package/yeaft/plugins.js +170 -0
- package/yeaft/session.js +7 -4
- package/yeaft/sessions/feature-flag.js +42 -9
- package/yeaft/tools/mcp-tools.js +1 -0
- package/yeaft/tools/registry.js +51 -7
- package/yeaft/tools/types.js +6 -0
- package/yeaft/web-bridge.js +317 -89
- package/yeaft/work-center/runner.js +5 -1
package/yeaft/plugins.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* plugins.js — Agent-level selectable Yeaft capabilities.
|
|
3
|
+
*
|
|
4
|
+
* An Agent owns installed tools, skills, and MCP configuration. Missing plugin
|
|
5
|
+
* fields retain historical behavior (everything enabled); explicit arrays are
|
|
6
|
+
* allowlists and may intentionally be empty.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
function normalizeNameList(value, field) {
|
|
10
|
+
if (value === undefined) return undefined;
|
|
11
|
+
if (!Array.isArray(value)) throw new Error(`plugins.${field} must be an array`);
|
|
12
|
+
|
|
13
|
+
const names = [];
|
|
14
|
+
const seen = new Set();
|
|
15
|
+
for (const raw of value) {
|
|
16
|
+
if (typeof raw !== 'string' || !raw.trim()) {
|
|
17
|
+
throw new Error(`plugins.${field} entries must be non-empty strings`);
|
|
18
|
+
}
|
|
19
|
+
const name = raw.trim();
|
|
20
|
+
if (!seen.has(name)) {
|
|
21
|
+
seen.add(name);
|
|
22
|
+
names.push(name);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return names;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Normalise persisted Agent plugin config while preserving inheritance. */
|
|
29
|
+
export function normalizePluginConfig(value) {
|
|
30
|
+
// Only an omitted field inherits the legacy all-enabled behavior. An
|
|
31
|
+
// explicit `null` is persisted schema, not absence, and must be rejected
|
|
32
|
+
// so every reader can fail closed consistently.
|
|
33
|
+
if (value === undefined) return {};
|
|
34
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
35
|
+
throw new Error('plugins must be an object');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const out = {};
|
|
39
|
+
for (const field of ['tools', 'skills', 'mcpServers']) {
|
|
40
|
+
const names = normalizeNameList(value[field], field);
|
|
41
|
+
if (names !== undefined) out[field] = names;
|
|
42
|
+
}
|
|
43
|
+
for (const key of Object.keys(value)) {
|
|
44
|
+
if (!['tools', 'skills', 'mcpServers'].includes(key)) {
|
|
45
|
+
throw new Error(`unknown plugins key: ${key}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Return an explicit deny-all policy for a persisted plugins schema error.
|
|
53
|
+
* This is deliberately distinct from `{}`, whose missing fields inherit the
|
|
54
|
+
* historical all-enabled behavior.
|
|
55
|
+
*/
|
|
56
|
+
export function createDenyAllPluginConfig() {
|
|
57
|
+
return { tools: [], skills: [], mcpServers: [] };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function isPluginNameEnabled(plugins, field, name) {
|
|
61
|
+
if (!plugins || !Array.isArray(plugins[field])) return true;
|
|
62
|
+
return plugins[field].includes(name);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Keep the configured MCP catalog separate from the runtime connection set.
|
|
67
|
+
* The catalog must retain disabled servers so users can enable them later;
|
|
68
|
+
* runtimes must receive only the effective allowlisted subset.
|
|
69
|
+
*
|
|
70
|
+
* The caller owns the raw configured catalog; this helper never connects it.
|
|
71
|
+
*/
|
|
72
|
+
export function resolveMcpPluginPolicy(mcpConfig, plugins) {
|
|
73
|
+
const configured = {
|
|
74
|
+
...(mcpConfig || {}),
|
|
75
|
+
servers: Array.isArray(mcpConfig?.servers) ? mcpConfig.servers : [],
|
|
76
|
+
};
|
|
77
|
+
if (!Array.isArray(plugins?.mcpServers)) {
|
|
78
|
+
return { configured, effective: configured };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const allowed = new Set(plugins.mcpServers);
|
|
82
|
+
return {
|
|
83
|
+
configured,
|
|
84
|
+
effective: {
|
|
85
|
+
...configured,
|
|
86
|
+
servers: configured.servers.filter(server => allowed.has(server?.name)),
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Live delegating view over a SkillManager. It does not mutate the shared
|
|
93
|
+
* manager because Sessions and project runtimes reuse that manager.
|
|
94
|
+
*/
|
|
95
|
+
export function createPluginSkillManager(skillManager, plugins) {
|
|
96
|
+
if (!skillManager) return null;
|
|
97
|
+
const hasExplicitSkills = Array.isArray(plugins?.skills);
|
|
98
|
+
const allowed = hasExplicitSkills ? new Set(plugins.skills) : null;
|
|
99
|
+
const isAllowed = name => !allowed || allowed.has(name);
|
|
100
|
+
const has = name => isAllowed(name) && !!skillManager.has?.(name);
|
|
101
|
+
const list = (...args) => (skillManager.list?.(...args) || [])
|
|
102
|
+
.filter(skill => isAllowed(skill?.name));
|
|
103
|
+
const get = name => has(name) ? skillManager.get?.(name) || null : null;
|
|
104
|
+
const resolve = name => has(name) ? skillManager.resolve?.(name) || null : null;
|
|
105
|
+
const view = (name, filePath) => has(name) ? skillManager.view?.(name, filePath) || null : null;
|
|
106
|
+
const findRelevant = (...args) => (skillManager.findRelevant?.(...args) || [])
|
|
107
|
+
.filter(skill => isAllowed(skill?.name));
|
|
108
|
+
const getPromptContent = name => has(name) ? skillManager.getPromptContent?.(name) || '' : '';
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
has,
|
|
112
|
+
get,
|
|
113
|
+
resolve,
|
|
114
|
+
list,
|
|
115
|
+
view,
|
|
116
|
+
findRelevant,
|
|
117
|
+
getPromptContent,
|
|
118
|
+
getRelevantPromptContent: (...args) => findRelevant(...args)
|
|
119
|
+
.map(skill => getPromptContent(skill.name))
|
|
120
|
+
.filter(Boolean)
|
|
121
|
+
.join('\n\n'),
|
|
122
|
+
listCategories: () => [...new Set(list().map(skill => skill.category).filter(Boolean))].sort(),
|
|
123
|
+
get size() { return list().length; },
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Build a browser-safe catalog from already discovered Agent assets. */
|
|
128
|
+
export function buildPluginCatalog({ toolRegistry, skillManager, mcpConfig, mcpManager } = {}) {
|
|
129
|
+
const tools = typeof toolRegistry?.getAllTools === 'function'
|
|
130
|
+
? toolRegistry.getAllTools()
|
|
131
|
+
.filter(tool => !tool?.mcpServer)
|
|
132
|
+
.map(tool => ({ id: tool.name, label: tool.name }))
|
|
133
|
+
.sort((a, b) => a.label.localeCompare(b.label))
|
|
134
|
+
: [];
|
|
135
|
+
|
|
136
|
+
const skills = typeof skillManager?.list === 'function'
|
|
137
|
+
? skillManager.list()
|
|
138
|
+
.map(skill => ({
|
|
139
|
+
id: skill.name,
|
|
140
|
+
label: skill.name,
|
|
141
|
+
description: skill.description || '',
|
|
142
|
+
category: skill.category || null,
|
|
143
|
+
}))
|
|
144
|
+
.sort((a, b) => a.label.localeCompare(b.label))
|
|
145
|
+
: [];
|
|
146
|
+
|
|
147
|
+
const statusByName = new Map((mcpManager?.status?.() || [])
|
|
148
|
+
.map(status => [status.name, status]));
|
|
149
|
+
// A configured catalog is authoritative, including an explicit empty array.
|
|
150
|
+
// Only callers that supplied no catalog at all use live status as a legacy
|
|
151
|
+
// fallback.
|
|
152
|
+
const configuredMcpServers = Array.isArray(mcpConfig?.servers)
|
|
153
|
+
? mcpConfig.servers
|
|
154
|
+
: (mcpManager?.status?.() || []).map(status => ({ name: status.name, command: '' }));
|
|
155
|
+
const mcpServers = configuredMcpServers
|
|
156
|
+
.filter(server => typeof server?.name === 'string' && server.name)
|
|
157
|
+
.map(server => {
|
|
158
|
+
const status = statusByName.get(server.name);
|
|
159
|
+
return {
|
|
160
|
+
id: server.name,
|
|
161
|
+
label: server.name,
|
|
162
|
+
description: server.command || '',
|
|
163
|
+
ready: status ? !!status.ready : null,
|
|
164
|
+
toolCount: status?.toolCount || 0,
|
|
165
|
+
};
|
|
166
|
+
})
|
|
167
|
+
.sort((a, b) => a.label.localeCompare(b.label));
|
|
168
|
+
|
|
169
|
+
return { tools, skills, mcpServers };
|
|
170
|
+
}
|
package/yeaft/session.js
CHANGED
|
@@ -23,6 +23,7 @@ import { recordAgentTokenUsage } from '../metrics.js';
|
|
|
23
23
|
import { ConversationStore, setDefaultRecentTurnsLimit } from './conversation/persist.js';
|
|
24
24
|
import { SkillManager, createSkillManager } from './skills.js';
|
|
25
25
|
import { MCPManager } from './mcp.js';
|
|
26
|
+
import { resolveMcpPluginPolicy } from './plugins.js';
|
|
26
27
|
import { createFullRegistry } from './tools/index.js';
|
|
27
28
|
import { buildMcpFlattenedTools } from './tools/mcp-tools.js';
|
|
28
29
|
import { Engine } from './engine.js';
|
|
@@ -395,19 +396,21 @@ export async function loadSession(options = {}) {
|
|
|
395
396
|
// present, is only a project tier overlay plus the storage root.
|
|
396
397
|
const projectTierRoot = sessionWorkDir || process.cwd();
|
|
397
398
|
|
|
398
|
-
let
|
|
399
|
+
let loadedSkillManager;
|
|
399
400
|
if (skipSkills) {
|
|
400
401
|
// Pass the literal user-tier dir (matches the normal branch's tier 2)
|
|
401
402
|
// so any save/remove calls land in the same place users expect. New
|
|
402
403
|
// `SkillManager` API takes literal scan dirs — no auto-suffix of /skills.
|
|
403
|
-
|
|
404
|
+
loadedSkillManager = new SkillManager(join(configDir, 'skills'));
|
|
404
405
|
// Don't call .load() — empty skill manager
|
|
405
406
|
} else {
|
|
406
|
-
|
|
407
|
+
loadedSkillManager = createSkillManager(configDir, projectTierRoot);
|
|
407
408
|
}
|
|
409
|
+
const skillManager = loadedSkillManager;
|
|
408
410
|
|
|
409
411
|
// ─── 7. Connect MCP servers ────────────────────────────
|
|
410
|
-
const
|
|
412
|
+
const rawMcpConfig = loadMCPConfig(configDir, undefined, projectTierRoot);
|
|
413
|
+
const { effective: mcpConfig } = resolveMcpPluginPolicy(rawMcpConfig, config.plugins);
|
|
411
414
|
const mcpManager = new MCPManager();
|
|
412
415
|
let mcpStatus = { connected: [], failed: [] };
|
|
413
416
|
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* feature-flag.js — Reads `config.yeaft.multiVp.enabled` from ~/.yeaft/config.json.
|
|
3
3
|
*
|
|
4
|
-
* Per architecture §11: multi-VP
|
|
5
|
-
* gates UI entry points and (later) migration.
|
|
4
|
+
* Per architecture §11: multi-VP Sessions are opt-in for MVP. The flag
|
|
5
|
+
* gates UI entry points and (later) migration. The reader returns a plain
|
|
6
6
|
* boolean and never throws — missing/corrupt config falls back to `false`.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* The exported writer rejects an existing malformed config or invalid Plugin
|
|
9
|
+
* schema rather than replacing it, so it cannot reopen a fail-closed Agent
|
|
10
|
+
* capability policy through an unrelated feature-flag update.
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
import { existsSync, readFileSync } from 'fs';
|
|
13
14
|
import { join } from 'path';
|
|
15
|
+
import { normalizePluginConfig } from '../plugins.js';
|
|
14
16
|
import { writeAtomic } from '../storage/index.js';
|
|
15
17
|
|
|
16
18
|
const CONFIG_FILE = 'config.json';
|
|
@@ -20,12 +22,32 @@ function readConfig(yeaftDir) {
|
|
|
20
22
|
const path = join(yeaftDir, CONFIG_FILE);
|
|
21
23
|
if (!existsSync(path)) return {};
|
|
22
24
|
try {
|
|
23
|
-
|
|
25
|
+
const config = JSON.parse(readFileSync(path, 'utf8'));
|
|
26
|
+
return config && typeof config === 'object' && !Array.isArray(config) ? config : {};
|
|
24
27
|
} catch {
|
|
25
28
|
return {};
|
|
26
29
|
}
|
|
27
30
|
}
|
|
28
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Strict precondition for writes to the Agent-owned config document. Reads can
|
|
34
|
+
* remain tolerant because the flag is optional, but no mutation may replace a
|
|
35
|
+
* malformed root or a Plugin policy that the runtime must keep fail-closed.
|
|
36
|
+
*/
|
|
37
|
+
function readConfigForWrite(yeaftDir) {
|
|
38
|
+
const path = join(yeaftDir, CONFIG_FILE);
|
|
39
|
+
if (!existsSync(path)) return {};
|
|
40
|
+
const config = JSON.parse(readFileSync(path, 'utf8'));
|
|
41
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)
|
|
42
|
+
|| Object.getPrototypeOf(config) !== Object.prototype) {
|
|
43
|
+
throw new Error('config.json must contain an object');
|
|
44
|
+
}
|
|
45
|
+
if (Object.prototype.hasOwnProperty.call(config, 'plugins')) {
|
|
46
|
+
normalizePluginConfig(config.plugins);
|
|
47
|
+
}
|
|
48
|
+
return config;
|
|
49
|
+
}
|
|
50
|
+
|
|
29
51
|
export function isMultiVpEnabled(yeaftDir) {
|
|
30
52
|
const cfg = readConfig(yeaftDir);
|
|
31
53
|
let cur = cfg;
|
|
@@ -37,13 +59,24 @@ export function isMultiVpEnabled(yeaftDir) {
|
|
|
37
59
|
}
|
|
38
60
|
|
|
39
61
|
export function setMultiVpEnabled(yeaftDir, enabled) {
|
|
40
|
-
|
|
62
|
+
let cfg;
|
|
63
|
+
try {
|
|
64
|
+
cfg = readConfigForWrite(yeaftDir);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
return { error: `Failed to read config.json: ${err?.message || err}` };
|
|
67
|
+
}
|
|
41
68
|
let cur = cfg;
|
|
42
69
|
for (let i = 0; i < FLAG_PATH.length - 1; i++) {
|
|
43
70
|
const seg = FLAG_PATH[i];
|
|
44
|
-
if (!cur[seg] || typeof cur[seg] !== 'object') cur[seg] = {};
|
|
71
|
+
if (!cur[seg] || typeof cur[seg] !== 'object' || Array.isArray(cur[seg])) cur[seg] = {};
|
|
45
72
|
cur = cur[seg];
|
|
46
73
|
}
|
|
47
|
-
|
|
48
|
-
|
|
74
|
+
const nextValue = Boolean(enabled);
|
|
75
|
+
cur[FLAG_PATH[FLAG_PATH.length - 1]] = nextValue;
|
|
76
|
+
try {
|
|
77
|
+
writeAtomic(join(yeaftDir, CONFIG_FILE), JSON.stringify(cfg, null, 2));
|
|
78
|
+
} catch (err) {
|
|
79
|
+
return { error: `Failed to write config.json: ${err?.message || err}` };
|
|
80
|
+
}
|
|
81
|
+
return { enabled: nextValue };
|
|
49
82
|
}
|
package/yeaft/tools/mcp-tools.js
CHANGED
|
@@ -107,6 +107,7 @@ export function buildMcpFlattenedTools(mcpManager) {
|
|
|
107
107
|
|
|
108
108
|
return defineTool({
|
|
109
109
|
name: flattenedName,
|
|
110
|
+
mcpServer: t.server,
|
|
110
111
|
description: truncateDescription(
|
|
111
112
|
t.description || `MCP tool ${fullName.split('__').slice(1).join('__')} from server ${t.server}`
|
|
112
113
|
),
|
package/yeaft/tools/registry.js
CHANGED
|
@@ -290,6 +290,39 @@ export function isToolHiddenByCollabPolicy(toolName, policy) {
|
|
|
290
290
|
return normalized === COLLAB_TOOL_POLICY.SINGLE_VP && FORWARD_TOOL_NAMES.includes(toolName);
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
+
/**
|
|
294
|
+
* Normalise an optional Agent-level plugin selection. Missing category fields
|
|
295
|
+
* preserve historical behavior; explicit empty arrays disable that category.
|
|
296
|
+
*
|
|
297
|
+
* @param {object|null|undefined} plugins
|
|
298
|
+
* @returns {{ tools: Set<string>|null, mcpServers: Set<string>|null }}
|
|
299
|
+
*/
|
|
300
|
+
export function normalizePluginToolPolicy(plugins) {
|
|
301
|
+
const normalize = (value) => {
|
|
302
|
+
if (!Array.isArray(value)) return null;
|
|
303
|
+
return new Set(value
|
|
304
|
+
.filter(item => typeof item === 'string' && item.trim())
|
|
305
|
+
.map(item => item.trim()));
|
|
306
|
+
};
|
|
307
|
+
return {
|
|
308
|
+
tools: normalize(plugins?.tools),
|
|
309
|
+
mcpServers: normalize(plugins?.mcpServers),
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Check a canonical ToolDef against the Agent-level plugin selection. MCP
|
|
315
|
+
* tools are controlled by server name; built-ins use their canonical name.
|
|
316
|
+
*/
|
|
317
|
+
export function isToolHiddenByPluginPolicy(tool, plugins) {
|
|
318
|
+
if (!tool) return true;
|
|
319
|
+
const policy = normalizePluginToolPolicy(plugins);
|
|
320
|
+
if (tool.mcpServer) {
|
|
321
|
+
return policy.mcpServers !== null && !policy.mcpServers.has(tool.mcpServer);
|
|
322
|
+
}
|
|
323
|
+
return policy.tools !== null && !policy.tools.has(tool.name);
|
|
324
|
+
}
|
|
325
|
+
|
|
293
326
|
export class ToolRegistry {
|
|
294
327
|
/** @type {Map<string, import('./types.js').ToolDef>} */
|
|
295
328
|
#tools = new Map();
|
|
@@ -383,7 +416,7 @@ export class ToolRegistry {
|
|
|
383
416
|
* Sessions).
|
|
384
417
|
*
|
|
385
418
|
* @param {string} [language='en']
|
|
386
|
-
* @param {{ collabToolPolicy?: string, activeToolNames?: Set<string>|string[] }} [opts]
|
|
419
|
+
* @param {{ collabToolPolicy?: string, plugins?: object, activeToolNames?: Set<string>|string[] }} [opts]
|
|
387
420
|
* @returns {{ name: string, description: string, parameters: object }[]}
|
|
388
421
|
*/
|
|
389
422
|
getToolDefs(language = 'en', opts = {}) {
|
|
@@ -395,6 +428,7 @@ export class ToolRegistry {
|
|
|
395
428
|
return this.getAllTools()
|
|
396
429
|
.filter(t => !activeToolNames || activeToolNames.has(t.name))
|
|
397
430
|
.filter(t => !isToolHiddenByCollabPolicy(t.name, collabToolPolicy))
|
|
431
|
+
.filter(t => !isToolHiddenByPluginPolicy(t, opts?.plugins))
|
|
398
432
|
.map(t => {
|
|
399
433
|
return {
|
|
400
434
|
name: t.name,
|
|
@@ -413,7 +447,7 @@ export class ToolRegistry {
|
|
|
413
447
|
* may execute only when canonical `SpawnAgent` is active for this request.
|
|
414
448
|
*
|
|
415
449
|
* @param {string} name
|
|
416
|
-
* @param {{ collabToolPolicy?: string, activeToolNames?: Set<string>|string[] }} [opts]
|
|
450
|
+
* @param {{ collabToolPolicy?: string, plugins?: object, activeToolNames?: Set<string>|string[] }} [opts]
|
|
417
451
|
* @returns {boolean}
|
|
418
452
|
*/
|
|
419
453
|
isAllowed(name, opts = {}) {
|
|
@@ -423,16 +457,26 @@ export class ToolRegistry {
|
|
|
423
457
|
? opts.activeToolNames
|
|
424
458
|
: (Array.isArray(opts?.activeToolNames) ? new Set(opts.activeToolNames) : null);
|
|
425
459
|
if (activeToolNames && !activeToolNames.has(tool.name)) return false;
|
|
426
|
-
return !isToolHiddenByCollabPolicy(tool.name, opts?.collabToolPolicy)
|
|
460
|
+
return !isToolHiddenByCollabPolicy(tool.name, opts?.collabToolPolicy)
|
|
461
|
+
&& !isToolHiddenByPluginPolicy(tool, opts?.plugins);
|
|
427
462
|
}
|
|
428
463
|
|
|
429
464
|
/**
|
|
430
|
-
* Get
|
|
431
|
-
* so debug surfaces
|
|
465
|
+
* Get registered canonical tool names under an optional policy. Aliases are
|
|
466
|
+
* excluded so debug surfaces still show one row per real tool.
|
|
467
|
+
* @param {{ collabToolPolicy?: string, plugins?: object, activeToolNames?: Set<string>|string[] }} [opts]
|
|
432
468
|
* @returns {string[]}
|
|
433
469
|
*/
|
|
434
|
-
getToolNames() {
|
|
435
|
-
|
|
470
|
+
getToolNames(opts = {}) {
|
|
471
|
+
const collabToolPolicy = normalizeCollabToolPolicy(opts?.collabToolPolicy);
|
|
472
|
+
const activeToolNames = opts?.activeToolNames instanceof Set
|
|
473
|
+
? opts.activeToolNames
|
|
474
|
+
: (Array.isArray(opts?.activeToolNames) ? new Set(opts.activeToolNames) : null);
|
|
475
|
+
return this.getAllTools()
|
|
476
|
+
.filter(t => !activeToolNames || activeToolNames.has(t.name))
|
|
477
|
+
.filter(t => !isToolHiddenByCollabPolicy(t.name, collabToolPolicy))
|
|
478
|
+
.filter(t => !isToolHiddenByPluginPolicy(t, opts?.plugins))
|
|
479
|
+
.map(t => t.name);
|
|
436
480
|
}
|
|
437
481
|
|
|
438
482
|
/**
|
package/yeaft/tools/types.js
CHANGED
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
* @property {boolean | ((input?: object) => boolean)} [mayMutateWorkspaceAfterReturn] — may keep changing the workspace after execute() resolves; disables same-query read reuse
|
|
74
74
|
* @property {(input?: object) => boolean} [isDestructive] — destructive operation?
|
|
75
75
|
* @property {'json-error-envelope' | null} [errorOutput] — explicit returned-output error contract; null means only thrown errors fail
|
|
76
|
+
* @property {string} [mcpServer] — owning MCP server for flattened MCP tools
|
|
76
77
|
* @property {'external' | 'run'} [sideEffectScope] — whether mutations escape the current Run collector
|
|
77
78
|
*/
|
|
78
79
|
|
|
@@ -90,6 +91,7 @@
|
|
|
90
91
|
* mayMutateWorkspaceAfterReturn?: boolean | ((input?: object) => boolean),
|
|
91
92
|
* isDestructive?: (input?: object) => boolean,
|
|
92
93
|
* errorOutput?: 'json-error-envelope' | null,
|
|
94
|
+
* mcpServer?: string,
|
|
93
95
|
* sideEffectScope?: 'external' | 'run',
|
|
94
96
|
* timeoutMs?: number,
|
|
95
97
|
* }} def
|
|
@@ -107,6 +109,7 @@ export function defineTool({
|
|
|
107
109
|
mayMutateWorkspaceAfterReturn = false,
|
|
108
110
|
isDestructive = () => false,
|
|
109
111
|
errorOutput = 'json-error-envelope',
|
|
112
|
+
mcpServer,
|
|
110
113
|
sideEffectScope = 'external',
|
|
111
114
|
timeoutMs,
|
|
112
115
|
}) {
|
|
@@ -132,6 +135,9 @@ export function defineTool({
|
|
|
132
135
|
if (Array.isArray(aliases) && aliases.length > 0) {
|
|
133
136
|
def.aliases = aliases.slice();
|
|
134
137
|
}
|
|
138
|
+
if (typeof mcpServer === 'string' && mcpServer.trim()) {
|
|
139
|
+
def.mcpServer = mcpServer.trim();
|
|
140
|
+
}
|
|
135
141
|
// Only attach `timeoutMs` when the tool author opts in. Leaving it
|
|
136
142
|
// unset means ToolRegistry.execute uses DEFAULT_TOOL_TIMEOUT_MS — set
|
|
137
143
|
// to <= 0 to disable the per-tool timeout entirely.
|