@epoch-agent/runtime 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -34
- package/dist/index.d.ts +619 -37
- package/dist/index.js +771 -73
- package/package.json +9 -9
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { loadCommandDefinitions, SLASH_COMMANDS_DIAG_MODULE, createToolScope, expandCommand, RecentWorkspaces, clearModelCache, discoverModels, ScheduleStore, isWithinWindow, acquireScheduleLock, triggerPeriodMs, ScheduleRecorder, pruneRecordings, suggestRuleFromApproval, needsAllowlist, isNonInteractive, loadPlugins, assertLevelAllowed, discoverProject, ProviderRouter, ProviderError, PROVIDER_NO_CREDENTIAL, SessionManager, GoalService, MemoryManager, MemoryReviewer, resolveSkillDirs, SkillSystem, SkillLearner, HookManager, loadHookSources, ApprovalCache, PermissionManager, resolvePolicyDirs, resolvePoliciesDir, loadPolicyFiles, checkShadowing, compileRuleLists, ContextCompressor, compressionOptions, TrackerDB, getConfigOrigins, mapOperationType, affectedPaths, workspaceDisplayPath, persistParts, persistToolResults, ToolRegistry, createTodoTool, createGoalTool, createMemoryTool, createCodeExecTool, createRunCodeTool, createSkillViewTool, createToolSearchTool, createToolRevealState, createAskQuestionTool, resolveWorkspace, unloadedInstructionFiles, resolveProjectRoot, loadConfig, CodeSandbox, CheckpointManager, createDelegateTool, findRole, resolveInWorkspace, listWorkspaceFiles, loadRoleDefinitions, mergeRoles, AGENT_ROLE_DIAG_MODULE, builtinRoles, unknownRoleTools, BLOCKED_TOOLS, blockedRoleTools, unknownRoleSkills, BudgetStore, mergeHeadlessPolicy, describeHeadlessPolicy, importSkills, previewSkillImport, removeSessionArtifacts, gateForTool, managedSwitchNotes, originOf, EpochConfigSchema, defaultConfig, SettingsFileSchema, isWritableSettingKey, writeSettingValue, SETTING_WRITE_APPLY, resolveSettings, SETTING_WRITE_LAYERS, readProviderKeyEnv, validateSchedule, createAllowAllGate, setTrustGate, TrustManager, createTrustGate, createDenyAllGate, getImportGate, setImportGate, createAllowAllImportGate, ImportTrustStore, createDenyAllImportGate, createImportGate, getCapability, restoreParts, createSessionSearchTools, PlanModeState, createPlanModeTools, toolTableTokens, explainSetting, settingWriteTargets, uninstallPlugin, readMarketplaces, searchMarketplaces, resolveEntrySource, resolveSource, readPluginRecords, createRoleFile, AgentLoop, resolveArtifactRetention, roleScopedProvider, createRoleScope, revealScopedProvider, withGoalPrompt, withGoalCompression, ScheduleRegistrar, resolveScheduleBackend, updateMarketplace, addMarketplace, SETTINGS_LAYER_ORDER, resolveMarketplaceRef, BudgetGuard, installPlugin, currentEpochVersion } from '@epoch-agent/core';
|
|
1
|
+
import { loadCommandDefinitions, SLASH_COMMANDS_DIAG_MODULE, createToolScope, expandCommand, RecentWorkspaces, clearModelCache, discoverModels, ScheduleStore, isWithinWindow, acquireScheduleLock, triggerPeriodMs, ScheduleRecorder, pruneRecordings, suggestRuleFromApproval, needsAllowlist, isNonInteractive, loadPlugins, assertLevelAllowed, discoverProject, ProviderRouter, ProviderError, PROVIDER_NO_CREDENTIAL, SessionManager, GoalService, MemoryManager, MemoryReviewer, resolveSkillDirs, SkillSystem, SkillLearner, HookManager, loadHookSources, ApprovalCache, PermissionManager, resolvePolicyDirs, resolvePoliciesDir, loadPolicyFiles, checkShadowing, compileRuleLists, ContextCompressor, compressionOptions, TrackerDB, getConfigOrigins, resolveComplianceDirs, resolveComplianceDir, loadLexiconFiles, ComplianceEngine, DEFAULT_COMPLIANCE_SETTINGS, mapOperationType, affectedPaths, workspaceDisplayPath, persistParts, persistToolResults, ToolRegistry, createTodoTool, createGoalTool, createMemoryTool, createCodeExecTool, createRunCodeTool, createSkillViewTool, createSkillWriteTools, createToolSearchTool, createToolRevealState, createAskQuestionTool, resolveWorkspace, unloadedInstructionFiles, resolveProjectRoot, loadConfig, CodeSandbox, CheckpointManager, createDelegateTool, findRole, systemOpenArgv, resolveInWorkspace, listWorkspaceFiles, loadRoleDefinitions, mergeRoles, AGENT_ROLE_DIAG_MODULE, builtinRoles, unknownRoleTools, BLOCKED_TOOLS, blockedRoleTools, unknownRoleSkills, BudgetStore, mergeHeadlessPolicy, describeHeadlessPolicy, importSkills, previewSkillImport, removeSessionArtifacts, gateForTool, managedSwitchNotes, originOf, EpochConfigSchema, defaultConfig, SettingsFileSchema, isWritableSettingKey, writeSettingValue, SETTING_WRITE_APPLY, resolveSettings, SETTING_WRITE_LAYERS, readProviderKeyEnv, validateSchedule, createAllowAllGate, setTrustGate, TrustManager, createTrustGate, createDenyAllGate, getImportGate, setImportGate, createAllowAllImportGate, ImportTrustStore, createDenyAllImportGate, createImportGate, getCapability, restoreParts, createSessionSearchTools, PlanModeState, createPlanModeTools, toolTableTokens, explainSetting, settingWriteTargets, settingValueKind, settingValueChoices, uninstallPlugin, readMarketplaces, searchMarketplaces, resolveEntrySource, resolveSource, readPluginRecords, createRoleFile, AgentLoop, resolveArtifactRetention, roleScopedProvider, createRoleScope, revealScopedProvider, withGoalPrompt, withGoalCompression, ScheduleRegistrar, resolveScheduleBackend, updateMarketplace, addMarketplace, SETTINGS_LAYER_ORDER, resolveMarketplaceRef, BudgetGuard, installPlugin, currentEpochVersion } from '@epoch-agent/core';
|
|
2
2
|
export { DEFAULT_GOAL_MAX_ROUNDS, MAX_GOAL_MAX_ROUNDS, MAX_OBJECTIVE_CHARS, MAX_REASON_CHARS, SANDBOX_COVERS, SANDBOX_EXCLUDES, SCHEDULE_DEFAULTS, SCHEDULE_DEFAULT_ALLOWLIST, nextRunAt, readRecording, skillIndexDescription } from '@epoch-agent/core';
|
|
3
|
-
import { projectCommandsDir, commandsDir, t, issueDetails, resolveHomeDir, configPath, readKey, setScalar, setSectionField, mcpConfigPath, workspacesPath, dbPath, automationLogsDir, pluginsStatePath, safeInit, memoriesDir, skillsDir, claudeUserSettingsPath, hooksPath, artifactsDir, resolveRipgrep, withTimeout, createProcessTable, marketplacesPath, pluginsDir, agentsDir, budgetStatePath, createSecretStore, setSecretStore, prefetchProviderSecrets, setSecretValues, migrateEnvSecrets,
|
|
3
|
+
import { projectCommandsDir, commandsDir, t, issueDetails, resolveHomeDir, configPath, readKey, setScalar, setSectionField, mcpConfigPath, workspacesPath, writeProviderSecret, envPath, maskApiKey, dbPath, automationLogsDir, pluginsStatePath, safeInit, memoriesDir, skillsDir, claudeUserSettingsPath, hooksPath, artifactsDir, resolveRipgrep, withTimeout, createProcessTable, marketplacesPath, pluginsDir, agentsDir, budgetStatePath, createSecretStore, setSecretStore, prefetchProviderSecrets, setSecretValues, migrateEnvSecrets, setDataKey, parseDataKey, generateDataKey, i18nDiagnostics, automationWorkDir, trustPath, trustedImportsPath, setShell, probeShell } from '@epoch-agent/infra';
|
|
4
4
|
export { t } from '@epoch-agent/infra';
|
|
5
|
-
import { isAbsolute, dirname, delimiter, resolve, join } from 'path';
|
|
5
|
+
import { isAbsolute, dirname, delimiter, resolve, join, relative, sep } from 'path';
|
|
6
6
|
import { AGENT_ROLE_NAME_PATTERN, isValidAgentRoleName, apiKeyEnvVar, getProviderInfo, PROVIDER_INFOS, parseModelRef, contentToText, assistantTurnText, systemNote, ToolExposure, isMentionableFilePath, DiagnosticSink, METRIC, API_KEY_ENV_VARS, MCP_DATA_KEY_NAME, isPermissionLevel, NOOP_TELEMETRY, DEFAULT_AGENT_ROLE } from '@epoch-agent/protocol';
|
|
7
|
-
import { existsSync, mkdirSync, writeFileSync, readFileSync, statSync, renameSync, rmSync, appendFileSync } from 'fs';
|
|
7
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, statSync, renameSync, rmSync, realpathSync, openSync, readSync, closeSync, appendFileSync } from 'fs';
|
|
8
8
|
import { randomUUID, createHash } from 'crypto';
|
|
9
9
|
import { parseMcpConfig, McpRegistry, McpAuthStore, mcpAuthStatus, loginToMcpServer, logoutFromMcpServer } from '@epoch-agent/plugin-mcp';
|
|
10
10
|
import { filePlugin } from '@epoch-agent/plugin-file';
|
|
@@ -58,7 +58,7 @@ function wireCommands(opts) {
|
|
|
58
58
|
"no-command-loaded"
|
|
59
59
|
);
|
|
60
60
|
}
|
|
61
|
-
const available = new Set(toolProvider.list().map((
|
|
61
|
+
const available = new Set(toolProvider.list().map((t25) => t25.name));
|
|
62
62
|
for (const cmd of summary.commands) {
|
|
63
63
|
const unknown = (cmd.allowedTools ?? []).filter((name) => !available.has(name));
|
|
64
64
|
if (unknown.length > 0) {
|
|
@@ -589,6 +589,223 @@ var init_host_preset = __esm({
|
|
|
589
589
|
HOST_PRESET_MODULE = "HostPreset";
|
|
590
590
|
}
|
|
591
591
|
});
|
|
592
|
+
function fail(message, start, code, suggestion) {
|
|
593
|
+
return {
|
|
594
|
+
success: false,
|
|
595
|
+
output: "",
|
|
596
|
+
error: { code, message, ...suggestion ? { suggestion } : {} },
|
|
597
|
+
duration: Date.now() - start
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
function done(output, start) {
|
|
601
|
+
return { success: true, output, duration: Date.now() - start };
|
|
602
|
+
}
|
|
603
|
+
function str(args, key) {
|
|
604
|
+
const raw = args[key];
|
|
605
|
+
return typeof raw === "string" ? raw : "";
|
|
606
|
+
}
|
|
607
|
+
function toAddInput(args) {
|
|
608
|
+
const name = str(args, "name").trim();
|
|
609
|
+
if (!name) return "name must not be empty";
|
|
610
|
+
const transport = str(args, "transport").trim();
|
|
611
|
+
if (transport !== "stdio" && transport !== "sse" && transport !== "http") {
|
|
612
|
+
return `transport must be one of "stdio" / "sse" / "http", got ${JSON.stringify(transport)}`;
|
|
613
|
+
}
|
|
614
|
+
const rawArgs = args["args"];
|
|
615
|
+
if (rawArgs !== void 0 && !isStringArray(rawArgs)) {
|
|
616
|
+
return "args must be an array of strings";
|
|
617
|
+
}
|
|
618
|
+
const rawEnv = args["env"];
|
|
619
|
+
if (rawEnv !== void 0 && !isStringRecord(rawEnv)) {
|
|
620
|
+
return "env must be an object whose values are all strings";
|
|
621
|
+
}
|
|
622
|
+
const command = str(args, "command").trim();
|
|
623
|
+
const url = str(args, "url").trim();
|
|
624
|
+
return {
|
|
625
|
+
name,
|
|
626
|
+
transport,
|
|
627
|
+
...command ? { command } : {},
|
|
628
|
+
...rawArgs === void 0 ? {} : { args: rawArgs },
|
|
629
|
+
...url ? { url } : {},
|
|
630
|
+
...rawEnv === void 0 ? {} : { env: rawEnv }
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
function isStringArray(value) {
|
|
634
|
+
return Array.isArray(value) && value.every((v) => typeof v === "string");
|
|
635
|
+
}
|
|
636
|
+
function isStringRecord(value) {
|
|
637
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
638
|
+
return Object.values(value).every((v) => typeof v === "string");
|
|
639
|
+
}
|
|
640
|
+
function listTool(deps) {
|
|
641
|
+
return {
|
|
642
|
+
name: "mcp_list",
|
|
643
|
+
description: "\u5217\u51FA\u8FD9\u4E2A\u8FDB\u7A0B\u91CC\u6240\u6709 MCP server \u7684\u72B6\u6001\uFF1A\u540D\u5B57\u3001\u6765\u6E90\u3001\u8FDE\u4E0A\u6CA1\u6709\u3001\u51E0\u4E2A\u5DE5\u5177\u3001\u4E0A\u6B21\u9519\u5728\u54EA\u3002\u6539\u4E00\u53F0\u4E4B\u524D\u5148\u770B\u4E00\u773C\u3002\nsource \u51B3\u5B9A\u6539\u4E0D\u6539\u5F97\u52A8\uFF1A\u53EA\u6709 user \u90A3\u6279\u662F\u7528\u6237\u914D\u7684\uFF1Bhost / plugin \u4E24\u6279\u4E0D\u5728 mcp.json \u91CC\uFF0C\u6539\u4E0D\u4E86\u3002\n\u26A0\uFE0F \u4E0D\u56DE command / url / env \u2014\u2014 \u90A3\u51E0\u683C\u5E38\u85CF\u4EE4\u724C\uFF0C\u8981\u770B\u8BA9\u7528\u6237\u81EA\u5DF1\u6253\u5F00 ~/.epoch/mcp.json\u3002",
|
|
644
|
+
parameters: { type: "object", properties: {} },
|
|
645
|
+
exposure: ToolExposure.Direct,
|
|
646
|
+
supportsParallel: true,
|
|
647
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
648
|
+
operation: "file_read",
|
|
649
|
+
describeTarget: () => MCP_LIST_TOOL,
|
|
650
|
+
execute() {
|
|
651
|
+
const start = Date.now();
|
|
652
|
+
const rows = deps.servers();
|
|
653
|
+
if (rows.length === 0) {
|
|
654
|
+
return Promise.resolve(
|
|
655
|
+
done("No MCP servers are configured in this process. Use mcp_add to add one.", start)
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
const lines = rows.map((s) => {
|
|
659
|
+
const state = s.connected ? "connected" : "disconnected";
|
|
660
|
+
const login = s.needsLogin === true ? ", needs login" : "";
|
|
661
|
+
const err = s.lastError === void 0 ? "" : ` \u2014 ${s.lastError}`;
|
|
662
|
+
return `${s.name} [${s.source}] ${state}, ${s.toolCount} tools${login}${err}`;
|
|
663
|
+
});
|
|
664
|
+
return Promise.resolve(done(lines.join("\n"), start));
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
function runWrite(deps, args, kind) {
|
|
669
|
+
const start = Date.now();
|
|
670
|
+
const input = toAddInput(args);
|
|
671
|
+
if (typeof input === "string") return Promise.resolve(fail(input, start, 3201));
|
|
672
|
+
const call = kind === "add" ? deps.control.add(input) : deps.control.update(input);
|
|
673
|
+
return call.then((outcome) => {
|
|
674
|
+
if (!outcome.ok) {
|
|
675
|
+
return fail(
|
|
676
|
+
`${outcome.reason}: ${outcome.detail}`,
|
|
677
|
+
start,
|
|
678
|
+
3202,
|
|
679
|
+
HINT_BY_REASON[outcome.reason]
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
return done(describeWrite(kind, outcome.configPath, outcome.status), start);
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
function describeWrite(kind, configPath2, status) {
|
|
686
|
+
const verb = kind === "add" ? "Added" : "Updated";
|
|
687
|
+
const why = status.lastError === void 0 ? "" : ` \u2014 ${status.lastError}`;
|
|
688
|
+
const login = status.needsLogin === true ? " It needs a login: run `epoch mcp login`." : "";
|
|
689
|
+
const live = status.connected ? `It is connected and exposes ${status.toolCount} tools now.` : `It is NOT connected right now${why}. The config is saved either way \u2014 the server may just not be up yet.`;
|
|
690
|
+
return `${verb} MCP server "${status.name}" in ${configPath2}. ${live}${login}`;
|
|
691
|
+
}
|
|
692
|
+
function addTool(deps) {
|
|
693
|
+
return {
|
|
694
|
+
name: "mcp_add",
|
|
695
|
+
description: "\u52A0\u4E00\u53F0 MCP server\uFF1A\u5199\u8FDB ~/.epoch/mcp.json\uFF0C\u7136\u540E\u5F53\u573A\u8FDE\u4E00\u6B21\u3002\n\u26A0\uFE0F \u5F71\u54CD\u7684\u4E0D\u53EA\u662F\u8FD9\u6BB5\u4F1A\u8BDD \u2014\u2014 \u90A3\u53F0\u4EE5\u540E\u6BCF\u4E2A epoch \u8FDB\u7A0B\u90FD\u4F1A\u8FDE\u5B83\uFF08stdio \u90A3\u6863\u8981\u8D77\u5B50\u8FDB\u7A0B\uFF09\u3002\u52A0\u4E4B\u524D\u5148\u8DDF\u7528\u6237\u8BF4\u6E05\u4F60\u8981\u52A0\u4EC0\u4E48\u3002\n\u8FDE\u4E0D\u4E0A\u4E0D\u7B97\u5931\u8D25\uFF08\u914D\u7F6E\u5BF9\u3001\u5BF9\u9762\u6CA1\u8D77\u6765\u7167\u6837\u52A0\u5F97\u8FDB\u53BB\uFF0C\u56DE\u6267\u4F1A\u8BF4\u6E05\uFF09\u3002\u540D\u5B57\u88AB\u5360\u4F1A\u88AB\u62D2 \u2014\u2014 \u90A3\u65F6\u6539\u7528 mcp_update\u3002",
|
|
696
|
+
parameters: {
|
|
697
|
+
type: "object",
|
|
698
|
+
properties: SERVER_PROPERTIES,
|
|
699
|
+
required: ["name", "transport"]
|
|
700
|
+
},
|
|
701
|
+
exposure: ToolExposure.Direct,
|
|
702
|
+
supportsParallel: false,
|
|
703
|
+
annotations: ADMIN_ANNOTATIONS,
|
|
704
|
+
operation: "command",
|
|
705
|
+
describeTarget: (args) => `${MCP_ADD_TOOL}:${String(args["name"] ?? "")} ${String(args["command"] ?? args["url"] ?? "")}`.trim(),
|
|
706
|
+
execute: (args) => runWrite(deps, args, "add")
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
function updateTool(deps) {
|
|
710
|
+
return {
|
|
711
|
+
name: "mcp_update",
|
|
712
|
+
description: "\u6539\u4E00\u53F0\u5DF2\u6709 MCP server\uFF1A\u5C31\u5730\u6362\u6389 ~/.epoch/mcp.json \u91CC\u90A3\u4E00\u6BB5\uFF0C\u518D\u6309\u65B0\u914D\u7F6E\u91CD\u8FDE\u5B83\u3002\u522B\u7684 server \u548C\u6587\u4EF6\u91CC\u7684\u6CE8\u91CA\u6392\u7248\u4E00\u4E2A\u5B57\u8282\u4E0D\u52A8\u3002\n\u26A0\uFE0F \u6536\u7684\u662F**\u6574\u5957**\u914D\u7F6E\u4E0D\u662F\u8865\u4E01 \u2014\u2014 \u6CA1\u7ED9\u7684\u683C\u5B50\uFF08args / url / env\uFF09\u4F1A\u88AB\u5F53\u6210\u300C\u4E0D\u8981\u4E86\u300D\uFF0C\u8981\u4FDD\u4F4F\u7684\u6BCF\u6B21\u90FD\u5F97\u539F\u6837\u518D\u7ED9\u3002\n\u540D\u5B57\u6539\u4E0D\u4E86\uFF08\u90A3\u7B49\u4E8E\u6362\u4E00\u6279\u5DE5\u5177\u540D\uFF0C\u8981\u6539\u540D\u5C31 mcp_remove \u518D mcp_add\uFF09\u3002\u6587\u4EF6\u91CC\u6CA1\u8FD9\u4E2A\u540D\u5B57\u4F1A\u88AB\u62D2\u3002",
|
|
713
|
+
parameters: {
|
|
714
|
+
type: "object",
|
|
715
|
+
properties: SERVER_PROPERTIES,
|
|
716
|
+
required: ["name", "transport"]
|
|
717
|
+
},
|
|
718
|
+
exposure: ToolExposure.Direct,
|
|
719
|
+
supportsParallel: false,
|
|
720
|
+
annotations: ADMIN_ANNOTATIONS,
|
|
721
|
+
operation: "command",
|
|
722
|
+
describeTarget: (args) => `${MCP_UPDATE_TOOL}:${String(args["name"] ?? "")} ${String(args["command"] ?? args["url"] ?? "")}`.trim(),
|
|
723
|
+
execute: (args) => runWrite(deps, args, "update")
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
function removeTool(deps) {
|
|
727
|
+
return {
|
|
728
|
+
name: "mcp_remove",
|
|
729
|
+
description: "\u5220\u6389\u4E00\u53F0 MCP server\uFF1A\u4ECE ~/.epoch/mcp.json \u91CC\u53BB\u6389\u90A3\u4E00\u6BB5\uFF0C\u5E76\u65AD\u5F00\u5B83\u3001\u628A\u5B83\u90A3\u6279\u5DE5\u5177\u6458\u6389\u3002\u522B\u7684 server \u548C\u6CE8\u91CA\u6392\u7248\u4E00\u4E2A\u5B57\u8282\u4E0D\u52A8\u3002\n\u26A0\uFE0F \u4E0D\u53EF\u64A4\u9500 \u2014\u2014 \u90A3\u4E00\u6BB5\u914D\u7F6E\uFF08\u542B env \u91CC\u7684\u4EE4\u724C\uFF09\u4ECE\u6587\u4EF6\u91CC\u6D88\u5931\uFF0C\u8981\u56DE\u6765\u53EA\u80FD\u91CD\u586B\u3002\u5220\u4E4B\u524D\u5148\u8DDF\u7528\u6237\u786E\u8BA4\u3002\n\u6587\u4EF6\u91CC\u6CA1\u8FD9\u4E2A\u540D\u5B57\u4F1A\u88AB\u62D2\uFF08host / plugin \u90A3\u4E24\u6279\u5220\u4E0D\u6389\uFF09\u3002",
|
|
730
|
+
parameters: {
|
|
731
|
+
type: "object",
|
|
732
|
+
properties: { name: SERVER_PROPERTIES.name },
|
|
733
|
+
required: ["name"]
|
|
734
|
+
},
|
|
735
|
+
exposure: ToolExposure.Direct,
|
|
736
|
+
supportsParallel: false,
|
|
737
|
+
annotations: ADMIN_ANNOTATIONS,
|
|
738
|
+
operation: "command",
|
|
739
|
+
describeTarget: (args) => `${MCP_REMOVE_TOOL}:${String(args["name"] ?? "")}`,
|
|
740
|
+
async execute(args) {
|
|
741
|
+
const start = Date.now();
|
|
742
|
+
const name = str(args, "name").trim();
|
|
743
|
+
if (!name) return fail("name must not be empty", start, 3201);
|
|
744
|
+
const outcome = await deps.control.remove(name);
|
|
745
|
+
if (!outcome.ok) {
|
|
746
|
+
return fail(
|
|
747
|
+
`${outcome.reason}: ${outcome.detail}`,
|
|
748
|
+
start,
|
|
749
|
+
3202,
|
|
750
|
+
HINT_BY_REASON[outcome.reason]
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
return done(
|
|
754
|
+
`removed MCP server "${name}" from ${outcome.configPath}. Its tools are gone from this process.`,
|
|
755
|
+
start
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
function createMcpTools(deps) {
|
|
761
|
+
return [listTool(deps), addTool(deps), updateTool(deps), removeTool(deps)];
|
|
762
|
+
}
|
|
763
|
+
var MCP_LIST_TOOL, MCP_ADD_TOOL, MCP_UPDATE_TOOL, MCP_REMOVE_TOOL, SERVER_PROPERTIES, ADMIN_ANNOTATIONS, HINT_BY_REASON;
|
|
764
|
+
var init_mcp_tools = __esm({
|
|
765
|
+
"src/mcp-tools.ts"() {
|
|
766
|
+
MCP_LIST_TOOL = "mcp_list";
|
|
767
|
+
MCP_ADD_TOOL = "mcp_add";
|
|
768
|
+
MCP_UPDATE_TOOL = "mcp_update";
|
|
769
|
+
MCP_REMOVE_TOOL = "mcp_remove";
|
|
770
|
+
SERVER_PROPERTIES = {
|
|
771
|
+
name: {
|
|
772
|
+
type: "string",
|
|
773
|
+
description: "server \u540D\u3002\u4F1A\u539F\u6837\u53D8\u6210\u5DE5\u5177\u540D\u7684\u4E00\u90E8\u5206\uFF08mcp__<name>__<\u5DE5\u5177>\uFF09"
|
|
774
|
+
},
|
|
775
|
+
transport: {
|
|
776
|
+
type: "string",
|
|
777
|
+
enum: ["stdio", "sse", "http"],
|
|
778
|
+
description: "\u4F20\u8F93\u7C7B\u578B\uFF1Astdio \u8D77\u4E00\u4E2A\u672C\u5730\u5B50\u8FDB\u7A0B\uFF0Csse / http \u8FDE\u4E00\u4E2A\u5730\u5740"
|
|
779
|
+
},
|
|
780
|
+
command: { type: "string", description: "stdio \u90A3\u4E00\u6863\u7684\u53EF\u6267\u884C\u6587\u4EF6\uFF0C\u5982 npx" },
|
|
781
|
+
args: {
|
|
782
|
+
type: "array",
|
|
783
|
+
items: { type: "string" },
|
|
784
|
+
description: "stdio \u90A3\u4E00\u6863\u7684\u547D\u4EE4\u884C\u53C2\u6570\uFF0C\u5143\u7D20\u5FC5\u987B\u5168\u662F\u5B57\u7B26\u4E32"
|
|
785
|
+
},
|
|
786
|
+
url: { type: "string", description: "sse / http \u90A3\u4E24\u6863\u7684\u5730\u5740" },
|
|
787
|
+
env: {
|
|
788
|
+
type: "object",
|
|
789
|
+
additionalProperties: { type: "string" },
|
|
790
|
+
description: "\u5B50\u8FDB\u7A0B\u73AF\u5883\u53D8\u91CF\uFF0C\u503C\u5FC5\u987B\u5168\u662F\u5B57\u7B26\u4E32"
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
ADMIN_ANNOTATIONS = {
|
|
794
|
+
readOnlyHint: false,
|
|
795
|
+
destructiveHint: true,
|
|
796
|
+
idempotentHint: false,
|
|
797
|
+
openWorldHint: true
|
|
798
|
+
};
|
|
799
|
+
HINT_BY_REASON = {
|
|
800
|
+
"invalid-name": 'Use only letters, digits, "_" and "-", starting with a letter or digit (max 48 chars) \u2014 the name becomes part of every tool name this server exposes.',
|
|
801
|
+
"invalid-config": 'stdio needs "command"; sse and http need a valid "url". Fix that one field and retry.',
|
|
802
|
+
duplicate: "That name is already taken \u2014 call mcp_list, then use mcp_update instead of mcp_add.",
|
|
803
|
+
"not-found": "That name is not in ~/.epoch/mcp.json \u2014 call mcp_list first, or use mcp_add.",
|
|
804
|
+
unwritable: "The config file could not be read, written, or parsed. Do not retry \u2014 tell the user to look at ~/.epoch/mcp.json themselves.",
|
|
805
|
+
"apply-failed": "The config file was written but this process could not be brought in line. The change takes effect on the next epoch start."
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
});
|
|
592
809
|
function providerLabel(info, lang) {
|
|
593
810
|
switch (info.type) {
|
|
594
811
|
case "ollama":
|
|
@@ -607,12 +824,16 @@ function buildModelCatalogControl(opts) {
|
|
|
607
824
|
};
|
|
608
825
|
return {
|
|
609
826
|
providers() {
|
|
610
|
-
return PROVIDER_INFOS.filter((p) => p.interactive).map((p) =>
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
827
|
+
return PROVIDER_INFOS.filter((p) => p.interactive).map((p) => {
|
|
828
|
+
const key = p.apiKeyEnv ? keyOf(p.apiKeyEnv) : "";
|
|
829
|
+
return {
|
|
830
|
+
type: p.type,
|
|
831
|
+
label: p.label,
|
|
832
|
+
envVar: p.apiKeyEnv,
|
|
833
|
+
hasKey: p.apiKeyEnv === null || key !== "",
|
|
834
|
+
keyHint: key === "" ? null : maskApiKey(key)
|
|
835
|
+
};
|
|
836
|
+
});
|
|
616
837
|
},
|
|
617
838
|
async discover(provider, discoverOpts) {
|
|
618
839
|
const envVar = getProviderInfo(provider)?.apiKeyEnv ?? null;
|
|
@@ -632,6 +853,19 @@ function buildModelCatalogControl(opts) {
|
|
|
632
853
|
source: result.source,
|
|
633
854
|
status: probeStatus(result, { needsKey: envVar !== null, hasKey: apiKey !== "" })
|
|
634
855
|
};
|
|
856
|
+
},
|
|
857
|
+
async setKey(provider, apiKey) {
|
|
858
|
+
const envVar = getProviderInfo(provider)?.apiKeyEnv ?? null;
|
|
859
|
+
if (!envVar) throw new Error(t("provider.key_not_applicable", { provider }));
|
|
860
|
+
const written = await writeProviderSecret(envVar, apiKey, envPath(homeDir));
|
|
861
|
+
clearModelCache(homeDir, provider);
|
|
862
|
+
return {
|
|
863
|
+
envVar,
|
|
864
|
+
backend: written.backend,
|
|
865
|
+
encrypted: written.encrypted,
|
|
866
|
+
envPath: written.envPath,
|
|
867
|
+
keyHint: maskApiKey(apiKey)
|
|
868
|
+
};
|
|
635
869
|
}
|
|
636
870
|
};
|
|
637
871
|
}
|
|
@@ -1204,7 +1438,7 @@ async function runOnce(store, def, opts, startedAt, now) {
|
|
|
1204
1438
|
});
|
|
1205
1439
|
return { status: "failed", exitCode: SCHEDULE_EXIT_CODES.FAILURE, run, detail: reason2 };
|
|
1206
1440
|
}
|
|
1207
|
-
const level = runtime.permissions.setLevel(def.permission);
|
|
1441
|
+
const level = runtime.permissions.setLevel(def.permission, { remember: false });
|
|
1208
1442
|
if (!level.ok) {
|
|
1209
1443
|
const reason2 = `managed-policy: ${def.permission}`;
|
|
1210
1444
|
const run = store.recordRun({
|
|
@@ -1498,7 +1732,6 @@ function resolveModelCapability(config, diags) {
|
|
|
1498
1732
|
const cap = getCapability(config.model, config.homeDir);
|
|
1499
1733
|
return {
|
|
1500
1734
|
contextLength: cap.contextLength,
|
|
1501
|
-
supportsImages: cap.supportsImages,
|
|
1502
1735
|
...cap.pricing ? { pricing: cap.pricing } : {}
|
|
1503
1736
|
};
|
|
1504
1737
|
} catch {
|
|
@@ -1507,7 +1740,7 @@ function resolveModelCapability(config, diags) {
|
|
|
1507
1740
|
t("boot.no_model_metadata", { model: config.model }),
|
|
1508
1741
|
"no-model-metadata"
|
|
1509
1742
|
);
|
|
1510
|
-
return { contextLength: config.contextLength
|
|
1743
|
+
return { contextLength: config.contextLength };
|
|
1511
1744
|
}
|
|
1512
1745
|
}
|
|
1513
1746
|
function reportUtilityModel(info, diags) {
|
|
@@ -1534,7 +1767,7 @@ function buildTrust(config, workDir, diags, interactive) {
|
|
|
1534
1767
|
const gate2 = createAllowAllGate(t("boot.trust_disabled"));
|
|
1535
1768
|
setTrustGate(gate2);
|
|
1536
1769
|
const trust2 = gate2.decide(workDir);
|
|
1537
|
-
diags.
|
|
1770
|
+
diags.skipped("Trust", trust2.reason);
|
|
1538
1771
|
const disabled = safeInit("Trust", () => new TrustManager(trustPath(config.homeDir)), diags);
|
|
1539
1772
|
for (const err of disabled?.loadErrors ?? []) diags.warn("Trust", err, "trust-load-error");
|
|
1540
1773
|
return { manager: disabled, trust: trust2, root, gate: gate2 };
|
|
@@ -1630,11 +1863,19 @@ function buildServices(baseConfig, diags, workDir = process.cwd(), opts = {}) {
|
|
|
1630
1863
|
}
|
|
1631
1864
|
}
|
|
1632
1865
|
if (provider) provider.refreshMetadata();
|
|
1633
|
-
const
|
|
1634
|
-
|
|
1635
|
-
|
|
1866
|
+
const router = provider;
|
|
1867
|
+
const providerBridge = router ? {
|
|
1868
|
+
generate: router.generate.bind(router),
|
|
1869
|
+
stream: router.stream.bind(router),
|
|
1636
1870
|
get capabilities() {
|
|
1637
|
-
|
|
1871
|
+
try {
|
|
1872
|
+
return { supportsImages: router.getCapability().supportsImages };
|
|
1873
|
+
} catch {
|
|
1874
|
+
return { supportsImages: false };
|
|
1875
|
+
}
|
|
1876
|
+
},
|
|
1877
|
+
get modelId() {
|
|
1878
|
+
return router.getSelection().model;
|
|
1638
1879
|
}
|
|
1639
1880
|
} : null;
|
|
1640
1881
|
const session = safeInit("Session", () => new SessionManager(config.dbPath), diags);
|
|
@@ -1765,6 +2006,7 @@ function buildServices(baseConfig, diags, workDir = process.cwd(), opts = {}) {
|
|
|
1765
2006
|
}
|
|
1766
2007
|
}
|
|
1767
2008
|
}
|
|
2009
|
+
const compliance = buildCompliance(config, projectContext.rootDir, trust.trusted, diags);
|
|
1768
2010
|
let contextEngine = null;
|
|
1769
2011
|
if (provider) {
|
|
1770
2012
|
reportUtilityModel(provider.getUtilityInfo(), diags);
|
|
@@ -1816,9 +2058,53 @@ function buildServices(baseConfig, diags, workDir = process.cwd(), opts = {}) {
|
|
|
1816
2058
|
policy: policyStatus,
|
|
1817
2059
|
hook: hookStatus,
|
|
1818
2060
|
plugins,
|
|
2061
|
+
compliance,
|
|
1819
2062
|
cleanups
|
|
1820
2063
|
};
|
|
1821
2064
|
}
|
|
2065
|
+
function buildCompliance(config, projectRoot, trusted, diags) {
|
|
2066
|
+
const settings = resolveComplianceSettings(config.compliance);
|
|
2067
|
+
if (!settings.enabled) {
|
|
2068
|
+
diags.skipped("Compliance", t("boot.compliance_off"));
|
|
2069
|
+
return null;
|
|
2070
|
+
}
|
|
2071
|
+
const rules = [];
|
|
2072
|
+
const exempt = [];
|
|
2073
|
+
try {
|
|
2074
|
+
const dirs = resolveComplianceDirs({
|
|
2075
|
+
userDir: resolveComplianceDir(config.homeDir),
|
|
2076
|
+
...projectRoot ? { projectRoot } : {},
|
|
2077
|
+
trusted,
|
|
2078
|
+
diags
|
|
2079
|
+
});
|
|
2080
|
+
for (const spec of dirs.dirs) {
|
|
2081
|
+
const loaded = loadLexiconFiles(spec.dir, spec.source, diags);
|
|
2082
|
+
rules.push(...loaded.rules);
|
|
2083
|
+
exempt.push(...loaded.exempt);
|
|
2084
|
+
}
|
|
2085
|
+
} catch {
|
|
2086
|
+
diags.warn("Compliance", t("boot.compliance_load_failed"), "compliance-load-failed");
|
|
2087
|
+
}
|
|
2088
|
+
const engine = new ComplianceEngine({ settings, rules, exempt });
|
|
2089
|
+
diags.ok(
|
|
2090
|
+
"Compliance",
|
|
2091
|
+
t("boot.compliance_ready", {
|
|
2092
|
+
active: engine.activeRuleCount,
|
|
2093
|
+
builtin: engine.counts.builtin,
|
|
2094
|
+
external: engine.counts.user + engine.counts.project
|
|
2095
|
+
})
|
|
2096
|
+
);
|
|
2097
|
+
return engine;
|
|
2098
|
+
}
|
|
2099
|
+
function resolveComplianceSettings(raw) {
|
|
2100
|
+
return {
|
|
2101
|
+
enabled: raw?.enabled ?? DEFAULT_COMPLIANCE_SETTINGS.enabled,
|
|
2102
|
+
scope: { ...DEFAULT_COMPLIANCE_SETTINGS.scope, ...raw?.scope },
|
|
2103
|
+
skipCodeBlocks: raw?.skipCodeBlocks ?? DEFAULT_COMPLIANCE_SETTINGS.skipCodeBlocks,
|
|
2104
|
+
maxHoldChars: raw?.maxHoldChars ?? DEFAULT_COMPLIANCE_SETTINGS.maxHoldChars,
|
|
2105
|
+
actions: { ...DEFAULT_COMPLIANCE_SETTINGS.actions, ...raw?.actions }
|
|
2106
|
+
};
|
|
2107
|
+
}
|
|
1822
2108
|
var init_services = __esm({
|
|
1823
2109
|
"src/services.ts"() {
|
|
1824
2110
|
}
|
|
@@ -1966,11 +2252,11 @@ var init_agent_session = __esm({
|
|
|
1966
2252
|
}
|
|
1967
2253
|
};
|
|
1968
2254
|
return {
|
|
1969
|
-
onApprovalRequest: (req) => new Promise((
|
|
1970
|
-
enqueue({ kind: "approval", req, resolve:
|
|
2255
|
+
onApprovalRequest: (req) => new Promise((resolve4) => {
|
|
2256
|
+
enqueue({ kind: "approval", req, resolve: resolve4 });
|
|
1971
2257
|
}),
|
|
1972
|
-
onQuestionRequest: (req) => new Promise((
|
|
1973
|
-
enqueue({ kind: "question", req, resolve:
|
|
2258
|
+
onQuestionRequest: (req) => new Promise((resolve4) => {
|
|
2259
|
+
enqueue({ kind: "question", req, resolve: resolve4 });
|
|
1974
2260
|
}),
|
|
1975
2261
|
attach: (session) => {
|
|
1976
2262
|
target = session;
|
|
@@ -2090,8 +2376,8 @@ var init_agent_session = __esm({
|
|
|
2090
2376
|
}
|
|
2091
2377
|
continue;
|
|
2092
2378
|
}
|
|
2093
|
-
const { value: event, done } = await pendingNext;
|
|
2094
|
-
if (
|
|
2379
|
+
const { value: event, done: done2 } = await pendingNext;
|
|
2380
|
+
if (done2) break;
|
|
2095
2381
|
pendingNext = iter.next();
|
|
2096
2382
|
if (event.type === "text-delta") assistantText += event.text;
|
|
2097
2383
|
turn.observe(event);
|
|
@@ -2208,7 +2494,7 @@ function buildSessionGuards(input) {
|
|
|
2208
2494
|
permission,
|
|
2209
2495
|
planState,
|
|
2210
2496
|
managed,
|
|
2211
|
-
|
|
2497
|
+
defaultLevel,
|
|
2212
2498
|
requestApproval,
|
|
2213
2499
|
interactive,
|
|
2214
2500
|
deferredTools
|
|
@@ -2218,7 +2504,7 @@ function buildSessionGuards(input) {
|
|
|
2218
2504
|
const reveal = own ? createToolRevealState() : bootstrapReveal;
|
|
2219
2505
|
return {
|
|
2220
2506
|
permission: perm,
|
|
2221
|
-
permissions: perm ? buildSessionPermissions(perm, managed, state) : null,
|
|
2507
|
+
permissions: perm ? buildSessionPermissions(perm, managed, state, defaultLevel) : null,
|
|
2222
2508
|
plan: state ? buildPlanControl(state) : null,
|
|
2223
2509
|
planState: state,
|
|
2224
2510
|
tools: own ? [
|
|
@@ -2233,22 +2519,24 @@ function buildSessionGuards(input) {
|
|
|
2233
2519
|
bootstrap: () => bootstrapScope,
|
|
2234
2520
|
open: () => {
|
|
2235
2521
|
if (!permission) return scopeOf(null, null, true);
|
|
2236
|
-
const own = permission.scoped(
|
|
2522
|
+
const own = permission.scoped(defaultLevel.get());
|
|
2237
2523
|
return scopeOf(own, planState ? new PlanModeState(own) : null, true);
|
|
2238
2524
|
}
|
|
2239
2525
|
};
|
|
2240
2526
|
}
|
|
2241
|
-
function buildSessionPermissions(permission, managed, planState) {
|
|
2527
|
+
function buildSessionPermissions(permission, managed, planState, defaultLevel) {
|
|
2242
2528
|
return {
|
|
2243
2529
|
level: () => permission.getLevel(),
|
|
2244
2530
|
managed: () => ({ bypassDisabled: managed.disableBypassPermissionsMode }),
|
|
2245
|
-
setLevel: (level) => {
|
|
2531
|
+
setLevel: (level, opts) => {
|
|
2246
2532
|
if (level === "bypass" && managed.disableBypassPermissionsMode) {
|
|
2247
2533
|
return { ok: false, reason: "managed-bypass-disabled" };
|
|
2248
2534
|
}
|
|
2249
2535
|
permission.setLevel(level);
|
|
2250
2536
|
planState?.forget();
|
|
2251
|
-
return { ok: true };
|
|
2537
|
+
if (!opts.remember) return { ok: true };
|
|
2538
|
+
const failure = defaultLevel.remember(level);
|
|
2539
|
+
return failure ? { ok: true, notRemembered: failure } : { ok: true };
|
|
2252
2540
|
}
|
|
2253
2541
|
};
|
|
2254
2542
|
}
|
|
@@ -2699,9 +2987,6 @@ var init_mcp_config_apply = __esm({
|
|
|
2699
2987
|
init_mcp_config_file();
|
|
2700
2988
|
}
|
|
2701
2989
|
});
|
|
2702
|
-
function isWritableName(name) {
|
|
2703
|
-
return /^[A-Za-z0-9][A-Za-z0-9_-]{0,47}$/.test(name);
|
|
2704
|
-
}
|
|
2705
2990
|
function skipTrivia(raw, from) {
|
|
2706
2991
|
let i = from;
|
|
2707
2992
|
for (; ; ) {
|
|
@@ -2765,13 +3050,15 @@ function walkObject(raw, braceAt) {
|
|
|
2765
3050
|
const entries = [];
|
|
2766
3051
|
let i = skipTrivia(raw, braceAt + 1);
|
|
2767
3052
|
while (i < raw.length && raw[i] !== "}") {
|
|
3053
|
+
const keyAt = i;
|
|
2768
3054
|
const key = readString(raw, i);
|
|
2769
3055
|
if (!key) return { entries, clean: false };
|
|
2770
3056
|
const afterKey = skipTrivia(raw, key[1]);
|
|
2771
3057
|
if (raw[afterKey] !== ":") return { entries, clean: false };
|
|
2772
3058
|
const valueAt2 = skipTrivia(raw, afterKey + 1);
|
|
2773
|
-
|
|
2774
|
-
|
|
3059
|
+
const valueEnd = skipValue(raw, valueAt2);
|
|
3060
|
+
entries.push({ key: key[0], keyAt, valueAt: valueAt2, valueEnd });
|
|
3061
|
+
i = skipTrivia(raw, valueEnd);
|
|
2775
3062
|
if (raw[i] === ",") i = skipTrivia(raw, i + 1);
|
|
2776
3063
|
else if (raw[i] !== "}") return { entries, clean: false };
|
|
2777
3064
|
}
|
|
@@ -2789,10 +3076,14 @@ function locate(raw) {
|
|
|
2789
3076
|
}
|
|
2790
3077
|
const inner = walkObject(raw, servers.valueAt);
|
|
2791
3078
|
if (!inner.clean) return { ok: false, detail: t("mcp_write.servers_unreadable") };
|
|
2792
|
-
const keys = inner.entries.map((e) => e.key);
|
|
2793
3079
|
return {
|
|
2794
3080
|
ok: true,
|
|
2795
|
-
landing: {
|
|
3081
|
+
landing: {
|
|
3082
|
+
braceAt: servers.valueAt,
|
|
3083
|
+
hasMembers: inner.entries.length > 0,
|
|
3084
|
+
entries: inner.entries,
|
|
3085
|
+
kind: "servers"
|
|
3086
|
+
}
|
|
2796
3087
|
};
|
|
2797
3088
|
}
|
|
2798
3089
|
if (top.entries.some((e) => e.key === "mcpServers")) {
|
|
@@ -2800,15 +3091,29 @@ function locate(raw) {
|
|
|
2800
3091
|
}
|
|
2801
3092
|
return {
|
|
2802
3093
|
ok: true,
|
|
2803
|
-
landing: { braceAt: start, hasMembers: top.entries.length > 0,
|
|
3094
|
+
landing: { braceAt: start, hasMembers: top.entries.length > 0, entries: [], kind: "top" }
|
|
2804
3095
|
};
|
|
2805
3096
|
}
|
|
2806
3097
|
function indentOf(raw) {
|
|
2807
3098
|
return /\n([ \t]+)\S/.exec(raw)?.[1] ?? " ";
|
|
2808
3099
|
}
|
|
2809
|
-
function
|
|
2810
|
-
const
|
|
2811
|
-
|
|
3100
|
+
function eolOf(raw) {
|
|
3101
|
+
const at = raw.indexOf("\n");
|
|
3102
|
+
if (at < 0) return "\n";
|
|
3103
|
+
return raw[at - 1] === "\r" ? "\r\n" : "\n";
|
|
3104
|
+
}
|
|
3105
|
+
function withEol(block, eol) {
|
|
3106
|
+
return eol === "\n" ? block : block.replaceAll("\n", eol);
|
|
3107
|
+
}
|
|
3108
|
+
var init_mcp_json_scan = __esm({
|
|
3109
|
+
"src/mcp-json-scan.ts"() {
|
|
3110
|
+
}
|
|
3111
|
+
});
|
|
3112
|
+
function isWritableName(name) {
|
|
3113
|
+
return /^[A-Za-z0-9][A-Za-z0-9_-]{0,47}$/.test(name);
|
|
3114
|
+
}
|
|
3115
|
+
function renderEntry(input, indent, pad = indent.repeat(2)) {
|
|
3116
|
+
const inner = pad + indent;
|
|
2812
3117
|
const fields = [`${inner}"transport": ${JSON.stringify(input.transport)}`];
|
|
2813
3118
|
if (input.command) fields.push(`${inner}"command": ${JSON.stringify(input.command)}`);
|
|
2814
3119
|
if (input.args && input.args.length > 0) {
|
|
@@ -2832,7 +3137,7 @@ function insertServerText(raw, input) {
|
|
|
2832
3137
|
const scan = locate(raw);
|
|
2833
3138
|
if (!scan.ok) return { ok: false, reason: "unwritable", detail: scan.detail };
|
|
2834
3139
|
const { landing } = scan;
|
|
2835
|
-
if (landing.
|
|
3140
|
+
if (landing.entries.some((e) => e.key === input.name)) {
|
|
2836
3141
|
return { ok: false, reason: "duplicate", detail: input.name };
|
|
2837
3142
|
}
|
|
2838
3143
|
const indent = indentOf(raw);
|
|
@@ -2844,7 +3149,8 @@ ${indent}"servers": {
|
|
|
2844
3149
|
${entry}
|
|
2845
3150
|
${indent}}${landing.hasMembers ? "," : "\n"}`;
|
|
2846
3151
|
const at = landing.braceAt + 1;
|
|
2847
|
-
|
|
3152
|
+
const eol = eolOf(raw);
|
|
3153
|
+
return { ok: true, text: raw.slice(0, at) + withEol(block, eol) + raw.slice(at) };
|
|
2848
3154
|
}
|
|
2849
3155
|
function writeMcpServer(input, opts = {}) {
|
|
2850
3156
|
if (!isWritableName(input.name)) {
|
|
@@ -2853,17 +3159,8 @@ function writeMcpServer(input, opts = {}) {
|
|
|
2853
3159
|
for (const name of opts.taken ?? []) {
|
|
2854
3160
|
if (name === input.name) return { ok: false, reason: "duplicate", detail: input.name };
|
|
2855
3161
|
}
|
|
2856
|
-
const
|
|
2857
|
-
|
|
2858
|
-
"(add-form)"
|
|
2859
|
-
);
|
|
2860
|
-
if (probe.servers.length === 0) {
|
|
2861
|
-
return {
|
|
2862
|
-
ok: false,
|
|
2863
|
-
reason: "invalid-config",
|
|
2864
|
-
detail: probe.issues.map((i) => i.message).join("; ") || t("mcp_write.invalid")
|
|
2865
|
-
};
|
|
2866
|
-
}
|
|
3162
|
+
const invalid = validateAddInput(input);
|
|
3163
|
+
if (invalid !== null) return { ok: false, reason: "invalid-config", detail: invalid };
|
|
2867
3164
|
const configPath2 = mcpConfigPath(opts.homeDir);
|
|
2868
3165
|
let raw;
|
|
2869
3166
|
try {
|
|
@@ -2881,6 +3178,14 @@ function writeMcpServer(input, opts = {}) {
|
|
|
2881
3178
|
}
|
|
2882
3179
|
return { ok: true, configPath: configPath2 };
|
|
2883
3180
|
}
|
|
3181
|
+
function validateAddInput(input, lang) {
|
|
3182
|
+
const probe = parseMcpConfig(
|
|
3183
|
+
JSON.stringify({ servers: { [input.name]: rawFields(input) } }),
|
|
3184
|
+
"(add-form)"
|
|
3185
|
+
);
|
|
3186
|
+
if (probe.servers.length > 0) return null;
|
|
3187
|
+
return probe.issues.map((i) => i.message).join("; ") || t("mcp_write.invalid", {}, lang);
|
|
3188
|
+
}
|
|
2884
3189
|
function rawFields(input) {
|
|
2885
3190
|
return {
|
|
2886
3191
|
transport: input.transport,
|
|
@@ -2907,9 +3212,172 @@ function toServerConfig(input) {
|
|
|
2907
3212
|
var EMPTY_CONFIG;
|
|
2908
3213
|
var init_mcp_config_write = __esm({
|
|
2909
3214
|
"src/mcp-config-write.ts"() {
|
|
3215
|
+
init_mcp_json_scan();
|
|
2910
3216
|
EMPTY_CONFIG = '{\n "servers": {}\n}\n';
|
|
2911
3217
|
}
|
|
2912
3218
|
});
|
|
3219
|
+
function skipSpaces(raw, from) {
|
|
3220
|
+
let i = from;
|
|
3221
|
+
while (i < raw.length && /[ \t\r\n]/.test(raw[i])) i++;
|
|
3222
|
+
return i;
|
|
3223
|
+
}
|
|
3224
|
+
function lineStart(raw, at) {
|
|
3225
|
+
return raw.lastIndexOf("\n", at - 1) + 1;
|
|
3226
|
+
}
|
|
3227
|
+
function lineEnd(raw, at) {
|
|
3228
|
+
const nl = raw.indexOf("\n", at);
|
|
3229
|
+
return nl === -1 ? raw.length : nl;
|
|
3230
|
+
}
|
|
3231
|
+
function padBefore(raw, at) {
|
|
3232
|
+
const ls = lineStart(raw, at);
|
|
3233
|
+
const head = raw.slice(ls, at);
|
|
3234
|
+
return /^[ \t]*$/.test(head) ? head : "";
|
|
3235
|
+
}
|
|
3236
|
+
function findEntry(raw, name) {
|
|
3237
|
+
const scan = locate(raw);
|
|
3238
|
+
if (!scan.ok) return { detail: scan.detail };
|
|
3239
|
+
const entries = scan.landing.entries;
|
|
3240
|
+
const index = entries.findIndex((e) => e.key === name);
|
|
3241
|
+
return index === -1 ? null : { entries, index };
|
|
3242
|
+
}
|
|
3243
|
+
function replaceServerText(raw, input) {
|
|
3244
|
+
const found = findEntry(raw, input.name);
|
|
3245
|
+
if (found !== null && "detail" in found) {
|
|
3246
|
+
return { ok: false, reason: "unwritable", detail: found.detail };
|
|
3247
|
+
}
|
|
3248
|
+
if (found === null) return { ok: false, reason: "invalid-name", detail: input.name };
|
|
3249
|
+
const target = found.entries[found.index];
|
|
3250
|
+
const pad = padBefore(raw, target.keyAt);
|
|
3251
|
+
const entry = renderEntry(input, indentOf(raw), pad);
|
|
3252
|
+
const block = withEol(entry.slice(pad.length), eolOf(raw));
|
|
3253
|
+
const text = raw.slice(0, target.keyAt) + block + raw.slice(target.valueEnd);
|
|
3254
|
+
return { ok: true, text };
|
|
3255
|
+
}
|
|
3256
|
+
function removeCuts(raw, entries, index) {
|
|
3257
|
+
const self = entries[index];
|
|
3258
|
+
const afterValue = skipTrivia(raw, self.valueEnd);
|
|
3259
|
+
if (raw[afterValue] === ",") {
|
|
3260
|
+
return skipSpaces(raw, self.valueEnd) === afterValue ? [tidyLine(raw, self.keyAt, afterValue + 1)] : [
|
|
3261
|
+
[self.keyAt, self.valueEnd],
|
|
3262
|
+
[afterValue, afterValue + 1]
|
|
3263
|
+
];
|
|
3264
|
+
}
|
|
3265
|
+
const prev = index > 0 ? entries[index - 1] : void 0;
|
|
3266
|
+
const prevComma = prev === void 0 ? -1 : skipTrivia(raw, prev.valueEnd);
|
|
3267
|
+
if (prevComma < 0 || raw[prevComma] !== ",") {
|
|
3268
|
+
return [tidyLine(raw, self.keyAt, self.valueEnd)];
|
|
3269
|
+
}
|
|
3270
|
+
return [
|
|
3271
|
+
[prevComma, prevComma + 1],
|
|
3272
|
+
[lineStartIfBlank(raw, self.keyAt), self.valueEnd]
|
|
3273
|
+
];
|
|
3274
|
+
}
|
|
3275
|
+
function tidyLine(raw, keyAt, to) {
|
|
3276
|
+
const ls = lineStart(raw, keyAt);
|
|
3277
|
+
const le = lineEnd(raw, to);
|
|
3278
|
+
const headBlank = /^[ \t]*$/.test(raw.slice(ls, keyAt));
|
|
3279
|
+
const tailBlank = /^[ \t\r]*$/.test(raw.slice(to, le));
|
|
3280
|
+
if (!headBlank || !tailBlank) return [keyAt, to];
|
|
3281
|
+
return [ls, Math.min(le + 1, raw.length)];
|
|
3282
|
+
}
|
|
3283
|
+
function lineStartIfBlank(raw, at) {
|
|
3284
|
+
const ls = lineStart(raw, at);
|
|
3285
|
+
return /^[ \t]*$/.test(raw.slice(ls, at)) ? ls : at;
|
|
3286
|
+
}
|
|
3287
|
+
function removeServerText(raw, name) {
|
|
3288
|
+
const found = findEntry(raw, name);
|
|
3289
|
+
if (found !== null && "detail" in found) {
|
|
3290
|
+
return { ok: false, reason: "unwritable", detail: found.detail };
|
|
3291
|
+
}
|
|
3292
|
+
if (found === null) return { ok: false, reason: "invalid-name", detail: name };
|
|
3293
|
+
const cuts = [...removeCuts(raw, found.entries, found.index)].sort((a, b) => b[0] - a[0]);
|
|
3294
|
+
let text = raw;
|
|
3295
|
+
for (const [from, to] of cuts) text = text.slice(0, from) + text.slice(to);
|
|
3296
|
+
return { ok: true, text };
|
|
3297
|
+
}
|
|
3298
|
+
function commit(splice, expectKeys, opts) {
|
|
3299
|
+
const configPath2 = mcpConfigPath(opts.homeDir);
|
|
3300
|
+
if (!existsSync(configPath2)) return { ok: false, reason: "not-found", detail: configPath2 };
|
|
3301
|
+
let raw;
|
|
3302
|
+
try {
|
|
3303
|
+
raw = readFileSync(configPath2, "utf-8");
|
|
3304
|
+
} catch (err) {
|
|
3305
|
+
return { ok: false, reason: "unwritable", detail: describe5(err) };
|
|
3306
|
+
}
|
|
3307
|
+
const before = locate(raw);
|
|
3308
|
+
if (!before.ok) return { ok: false, reason: "unwritable", detail: before.detail };
|
|
3309
|
+
const spliced = splice(raw);
|
|
3310
|
+
if (!spliced.ok) {
|
|
3311
|
+
return spliced.reason === "invalid-name" ? { ok: false, reason: "not-found", detail: spliced.detail } : spliced;
|
|
3312
|
+
}
|
|
3313
|
+
const bad = checkSpliced(
|
|
3314
|
+
spliced.text,
|
|
3315
|
+
expectKeys(before.landing.entries.map((e) => e.key)),
|
|
3316
|
+
opts.lang
|
|
3317
|
+
);
|
|
3318
|
+
if (bad !== null) return { ok: false, reason: "unwritable", detail: bad };
|
|
3319
|
+
return write(configPath2, raw, spliced.text);
|
|
3320
|
+
}
|
|
3321
|
+
function checkSpliced(text, expect, lang) {
|
|
3322
|
+
const after = locate(text);
|
|
3323
|
+
if (!after.ok) return after.detail;
|
|
3324
|
+
const got = after.landing.entries.map((e) => e.key);
|
|
3325
|
+
if (got.length !== expect.length || got.some((k, i) => k !== expect[i])) {
|
|
3326
|
+
return `${t("mcp_write.invalid", {}, lang)} (servers: [${got.join(", ")}] != [${expect.join(", ")}])`;
|
|
3327
|
+
}
|
|
3328
|
+
return null;
|
|
3329
|
+
}
|
|
3330
|
+
function write(configPath2, expectDisk, body) {
|
|
3331
|
+
let disk;
|
|
3332
|
+
try {
|
|
3333
|
+
disk = readFileSync(configPath2, "utf-8");
|
|
3334
|
+
} catch (err) {
|
|
3335
|
+
return { ok: false, reason: "unwritable", detail: describe5(err) };
|
|
3336
|
+
}
|
|
3337
|
+
if (disk !== expectDisk) {
|
|
3338
|
+
return { ok: false, reason: "unwritable", detail: t("mcp_config.stale_disk") };
|
|
3339
|
+
}
|
|
3340
|
+
const tmp = `${configPath2}.${process.pid}.tmp`;
|
|
3341
|
+
try {
|
|
3342
|
+
mkdirSync(dirname(configPath2), { recursive: true });
|
|
3343
|
+
const mode = statSync(configPath2).mode & 511;
|
|
3344
|
+
try {
|
|
3345
|
+
writeFileSync(tmp, body, { encoding: "utf-8", mode });
|
|
3346
|
+
renameSync(tmp, configPath2);
|
|
3347
|
+
} catch (err) {
|
|
3348
|
+
rmSync(tmp, { force: true });
|
|
3349
|
+
throw err;
|
|
3350
|
+
}
|
|
3351
|
+
} catch (err) {
|
|
3352
|
+
return { ok: false, reason: "unwritable", detail: describe5(err) };
|
|
3353
|
+
}
|
|
3354
|
+
return { ok: true, configPath: configPath2 };
|
|
3355
|
+
}
|
|
3356
|
+
function updateMcpServer(input, opts = {}) {
|
|
3357
|
+
const invalid = validateAddInput(input, opts.lang);
|
|
3358
|
+
if (invalid !== null) return { ok: false, reason: "invalid-config", detail: invalid };
|
|
3359
|
+
return commit(
|
|
3360
|
+
(raw) => replaceServerText(raw, input),
|
|
3361
|
+
(before) => before,
|
|
3362
|
+
opts
|
|
3363
|
+
);
|
|
3364
|
+
}
|
|
3365
|
+
function removeMcpServer(name, opts = {}) {
|
|
3366
|
+
return commit(
|
|
3367
|
+
(raw) => removeServerText(raw, name),
|
|
3368
|
+
(before) => before.filter((k) => k !== name),
|
|
3369
|
+
opts
|
|
3370
|
+
);
|
|
3371
|
+
}
|
|
3372
|
+
function describe5(err) {
|
|
3373
|
+
return err instanceof Error ? err.message : String(err);
|
|
3374
|
+
}
|
|
3375
|
+
var init_mcp_config_edit = __esm({
|
|
3376
|
+
"src/mcp-config-edit.ts"() {
|
|
3377
|
+
init_mcp_config_write();
|
|
3378
|
+
init_mcp_json_scan();
|
|
3379
|
+
}
|
|
3380
|
+
});
|
|
2913
3381
|
function foldToolMode(mode) {
|
|
2914
3382
|
if (mode === "both") return "both";
|
|
2915
3383
|
if (mode === "code") return "code";
|
|
@@ -2948,6 +3416,12 @@ function registerLocalTools(input) {
|
|
|
2948
3416
|
const rg = resolveRipgrep();
|
|
2949
3417
|
if (rg.mode === "missing") {
|
|
2950
3418
|
diags.warn("Ripgrep", t("boot.ripgrep_missing", { hint: rg.hint ?? "" }), "ripgrep-missing");
|
|
3419
|
+
} else if (rg.mode === "not-executable") {
|
|
3420
|
+
diags.warn(
|
|
3421
|
+
"Ripgrep",
|
|
3422
|
+
t("boot.ripgrep_not_executable", { hint: rg.hint ?? "" }),
|
|
3423
|
+
"ripgrep-not-executable"
|
|
3424
|
+
);
|
|
2951
3425
|
} else if (rg.mode === "system") {
|
|
2952
3426
|
diags.ok("Ripgrep", t("boot.ripgrep_system"));
|
|
2953
3427
|
} else {
|
|
@@ -2976,6 +3450,7 @@ function registerLocalTools(input) {
|
|
|
2976
3450
|
registry.registerTool(createRunCodeTool());
|
|
2977
3451
|
}
|
|
2978
3452
|
if (skills) registry.registerTool(createSkillViewTool({ skills }));
|
|
3453
|
+
if (skills) registry.registerTools(createSkillWriteTools({ skills }));
|
|
2979
3454
|
if (input.deferMcp === true) {
|
|
2980
3455
|
registry.registerTool(
|
|
2981
3456
|
createToolSearchTool({
|
|
@@ -3063,7 +3538,7 @@ function createToolSync(mcp, registry, defer) {
|
|
|
3063
3538
|
registry.unregisterBySource(`mcp:${server}`);
|
|
3064
3539
|
registerMcpBatch(
|
|
3065
3540
|
registry,
|
|
3066
|
-
fresh.filter((
|
|
3541
|
+
fresh.filter((t25) => t25.name.startsWith(prefix)),
|
|
3067
3542
|
server,
|
|
3068
3543
|
defer
|
|
3069
3544
|
);
|
|
@@ -3132,6 +3607,32 @@ async function addServer(mcp, sync, input, homeDir) {
|
|
|
3132
3607
|
}
|
|
3133
3608
|
};
|
|
3134
3609
|
}
|
|
3610
|
+
async function updateServer(mcp, sync, input, homeDir) {
|
|
3611
|
+
const written = updateMcpServer(input, { homeDir });
|
|
3612
|
+
if (!written.ok) return written;
|
|
3613
|
+
await mcp.disconnect(input.name);
|
|
3614
|
+
const config = toServerConfig(input);
|
|
3615
|
+
const [status] = await mcp.connectAll([config], "user");
|
|
3616
|
+
await sync.replace(input.name);
|
|
3617
|
+
return {
|
|
3618
|
+
ok: true,
|
|
3619
|
+
configPath: written.configPath,
|
|
3620
|
+
status: status ?? mcp.getStatus(input.name) ?? {
|
|
3621
|
+
name: input.name,
|
|
3622
|
+
connected: false,
|
|
3623
|
+
toolCount: 0,
|
|
3624
|
+
reconnectAttempts: 0,
|
|
3625
|
+
source: "user"
|
|
3626
|
+
}
|
|
3627
|
+
};
|
|
3628
|
+
}
|
|
3629
|
+
async function removeServer(mcp, sync, name, homeDir) {
|
|
3630
|
+
const written = removeMcpServer(name, { homeDir });
|
|
3631
|
+
if (!written.ok) return written;
|
|
3632
|
+
await mcp.disconnect(name);
|
|
3633
|
+
await sync.replace(name);
|
|
3634
|
+
return { ok: true, configPath: written.configPath };
|
|
3635
|
+
}
|
|
3135
3636
|
async function connectAllServers(mcp, batches, budgetMs = MCP_CONNECT_BUDGET_MS) {
|
|
3136
3637
|
const all = await withTimeout(
|
|
3137
3638
|
Promise.all(batches.map((b) => mcp.connectAll([...b.servers], b.source))),
|
|
@@ -3179,6 +3680,12 @@ async function registerMcpTools(registry, homeDir, diags, injected = [], defer =
|
|
|
3179
3680
|
if (outcome.ok) wireToolRefresh(mcp, registry, sync);
|
|
3180
3681
|
return outcome;
|
|
3181
3682
|
},
|
|
3683
|
+
update: async (input) => {
|
|
3684
|
+
const outcome = await updateServer(mcp, sync, input, homeDir);
|
|
3685
|
+
if (outcome.ok) wireToolRefresh(mcp, registry, sync);
|
|
3686
|
+
return outcome;
|
|
3687
|
+
},
|
|
3688
|
+
remove: (name) => removeServer(mcp, sync, name, homeDir),
|
|
3182
3689
|
readConfig: () => readMcpConfigFile({ homeDir }),
|
|
3183
3690
|
writeConfig: (text, revision, lang) => writeMcpConfigFile(text, {
|
|
3184
3691
|
revision,
|
|
@@ -3228,7 +3735,7 @@ async function registerMcpTools(registry, homeDir, diags, injected = [], defer =
|
|
|
3228
3735
|
);
|
|
3229
3736
|
continue;
|
|
3230
3737
|
}
|
|
3231
|
-
const own = mcpTools.filter((
|
|
3738
|
+
const own = mcpTools.filter((t25) => t25.name.startsWith(`mcp__${st.name}__`));
|
|
3232
3739
|
const registered = dropOverlongToolNames(
|
|
3233
3740
|
own,
|
|
3234
3741
|
`MCP ${st.name}`,
|
|
@@ -3257,6 +3764,7 @@ var init_tools = __esm({
|
|
|
3257
3764
|
"src/tools.ts"() {
|
|
3258
3765
|
init_host_capabilities();
|
|
3259
3766
|
init_mcp_config_apply();
|
|
3767
|
+
init_mcp_config_edit();
|
|
3260
3768
|
init_mcp_config_file();
|
|
3261
3769
|
init_mcp_config_write();
|
|
3262
3770
|
MCP_CONNECT_BUDGET_MS = 15e3;
|
|
@@ -3443,6 +3951,7 @@ var init_session_factory = __esm({
|
|
|
3443
3951
|
contextLength: services.contextLength,
|
|
3444
3952
|
artifactRetention: resolveArtifactRetention(config.artifacts),
|
|
3445
3953
|
sandboxTerminal: config.sandbox?.terminal !== false,
|
|
3954
|
+
compliance: this.opts.compliance ?? void 0,
|
|
3446
3955
|
...role ? { role: () => ({ name: role.name, ...role.prompt ? { prompt: role.prompt } : {} }) } : {},
|
|
3447
3956
|
...permission ? { permissionManager: permission } : {},
|
|
3448
3957
|
...services.hookManager ? { hookManager: services.hookManager } : {},
|
|
@@ -3524,6 +4033,7 @@ ${context}` : goal,
|
|
|
3524
4033
|
multimodal: { homeDir: config.homeDir },
|
|
3525
4034
|
artifactRetention: resolveArtifactRetention(config.artifacts),
|
|
3526
4035
|
sandboxTerminal: config.sandbox?.terminal !== false,
|
|
4036
|
+
compliance: opts.compliance ?? void 0,
|
|
3527
4037
|
maxTurns: active?.maxTurns ?? config.maxTurns,
|
|
3528
4038
|
role: () => {
|
|
3529
4039
|
const now = roleScope.current;
|
|
@@ -3636,6 +4146,9 @@ function toProvider(router) {
|
|
|
3636
4146
|
} catch {
|
|
3637
4147
|
return { supportsImages: false };
|
|
3638
4148
|
}
|
|
4149
|
+
},
|
|
4150
|
+
get modelId() {
|
|
4151
|
+
return router.getSelection().model;
|
|
3639
4152
|
}
|
|
3640
4153
|
};
|
|
3641
4154
|
}
|
|
@@ -4018,20 +4531,96 @@ var init_utility = __esm({
|
|
|
4018
4531
|
LABEL_SHAPE = /^[a-z0-9_-]{1,32}$/;
|
|
4019
4532
|
}
|
|
4020
4533
|
});
|
|
4534
|
+
function isSystemOpenable(path) {
|
|
4535
|
+
const dot = path.lastIndexOf(".");
|
|
4536
|
+
if (dot < 0) return false;
|
|
4537
|
+
return SYSTEM_OPENABLE_EXTENSIONS.has(path.slice(dot).toLowerCase());
|
|
4538
|
+
}
|
|
4539
|
+
function toWorkspaceRelative(root, userPath) {
|
|
4540
|
+
const trimmed = userPath.trim();
|
|
4541
|
+
if (trimmed === "") return null;
|
|
4542
|
+
if (!isAbsolute(trimmed)) return trimmed.replace(/^\.\/+/, "") || null;
|
|
4543
|
+
const rel = relative(resolve(root), resolve(trimmed));
|
|
4544
|
+
if (rel === "" || rel.startsWith("..")) return null;
|
|
4545
|
+
return rel.split(sep).join("/");
|
|
4546
|
+
}
|
|
4547
|
+
function insideRoot(root, abs) {
|
|
4548
|
+
let realRoot;
|
|
4549
|
+
try {
|
|
4550
|
+
realRoot = realpathSync(root);
|
|
4551
|
+
} catch {
|
|
4552
|
+
return false;
|
|
4553
|
+
}
|
|
4554
|
+
const rel = relative(realRoot, abs);
|
|
4555
|
+
return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
|
|
4556
|
+
}
|
|
4557
|
+
function sniffKind(absPath) {
|
|
4558
|
+
let fd;
|
|
4559
|
+
try {
|
|
4560
|
+
fd = openSync(absPath, "r");
|
|
4561
|
+
const buf = Buffer.alloc(8192);
|
|
4562
|
+
const read = readSync(fd, buf, 0, buf.length, 0);
|
|
4563
|
+
return buf.subarray(0, read).includes(0) ? "binary" : "text";
|
|
4564
|
+
} catch {
|
|
4565
|
+
return null;
|
|
4566
|
+
} finally {
|
|
4567
|
+
if (fd !== void 0) {
|
|
4568
|
+
try {
|
|
4569
|
+
closeSync(fd);
|
|
4570
|
+
} catch {
|
|
4571
|
+
}
|
|
4572
|
+
}
|
|
4573
|
+
}
|
|
4574
|
+
}
|
|
4021
4575
|
function withinWorkspace(resolveInWorkspace2, root, rel) {
|
|
4022
4576
|
return resolveInWorkspace2(root, rel) !== null;
|
|
4023
4577
|
}
|
|
4024
4578
|
function buildWorkspaceFilesControl(deps, permission) {
|
|
4025
4579
|
let cache = null;
|
|
4026
4580
|
const now = () => Date.now();
|
|
4581
|
+
function statFileImpl(path, root) {
|
|
4582
|
+
const rel = toWorkspaceRelative(root, path);
|
|
4583
|
+
if (rel === null) return { ok: false, reason: "denied" };
|
|
4584
|
+
const full = deps.resolveInWorkspace(root, rel);
|
|
4585
|
+
if (!full) return { ok: false, reason: "denied" };
|
|
4586
|
+
const decision = permission?.check({
|
|
4587
|
+
type: "file_read",
|
|
4588
|
+
detail: rel,
|
|
4589
|
+
workDir: root,
|
|
4590
|
+
target: full,
|
|
4591
|
+
toolName: "file_read"
|
|
4592
|
+
});
|
|
4593
|
+
if (permission && !decision?.allowed) return { ok: false, reason: "denied" };
|
|
4594
|
+
let stat;
|
|
4595
|
+
try {
|
|
4596
|
+
stat = statSync(full);
|
|
4597
|
+
} catch (err) {
|
|
4598
|
+
const code = err.code;
|
|
4599
|
+
return {
|
|
4600
|
+
ok: false,
|
|
4601
|
+
reason: code === "ENOENT" || code === "ENOTDIR" ? "missing" : "unreadable"
|
|
4602
|
+
};
|
|
4603
|
+
}
|
|
4604
|
+
if (!stat.isFile()) return { ok: false, reason: "not-a-file" };
|
|
4605
|
+
let absPath;
|
|
4606
|
+
try {
|
|
4607
|
+
absPath = realpathSync(full);
|
|
4608
|
+
} catch {
|
|
4609
|
+
return { ok: false, reason: "unreadable" };
|
|
4610
|
+
}
|
|
4611
|
+
if (!insideRoot(root, absPath)) return { ok: false, reason: "denied" };
|
|
4612
|
+
const kind = sniffKind(absPath);
|
|
4613
|
+
if (kind === null) return { ok: false, reason: "unreadable" };
|
|
4614
|
+
return { ok: true, rel, absPath, bytes: stat.size, mtimeMs: stat.mtimeMs, kind };
|
|
4615
|
+
}
|
|
4027
4616
|
function computeList(root) {
|
|
4028
4617
|
const { files, truncated } = deps.listFiles(root);
|
|
4029
4618
|
return { files, truncated, at: now(), root };
|
|
4030
4619
|
}
|
|
4031
4620
|
function listFor(root) {
|
|
4032
|
-
const
|
|
4621
|
+
const t25 = now();
|
|
4033
4622
|
if (!cache || cache.root !== root) cache = computeList(root);
|
|
4034
|
-
else if (
|
|
4623
|
+
else if (t25 - cache.at > LIST_TTL_MS) cache = computeList(root);
|
|
4035
4624
|
return cache;
|
|
4036
4625
|
}
|
|
4037
4626
|
return {
|
|
@@ -4090,12 +4679,55 @@ function buildWorkspaceFilesControl(deps, permission) {
|
|
|
4090
4679
|
return { ok: false, reason: "binary", bytes: stat.size };
|
|
4091
4680
|
}
|
|
4092
4681
|
return { ok: true, text: raw.toString("utf8"), bytes: stat.size };
|
|
4682
|
+
},
|
|
4683
|
+
statFile: statFileImpl,
|
|
4684
|
+
planSystemOpen(path, root) {
|
|
4685
|
+
const stat = statFileImpl(path, root);
|
|
4686
|
+
if (!stat.ok) return { ok: false, reason: stat.reason };
|
|
4687
|
+
if (!isSystemOpenable(stat.absPath)) return { ok: false, reason: "extension-not-allowed" };
|
|
4688
|
+
const { command, args } = deps.systemOpenArgv(stat.absPath);
|
|
4689
|
+
return { ok: true, command, args, absPath: stat.absPath };
|
|
4093
4690
|
}
|
|
4094
4691
|
};
|
|
4095
4692
|
}
|
|
4096
|
-
var LIST_TTL_MS;
|
|
4693
|
+
var SYSTEM_OPENABLE_EXTENSIONS, LIST_TTL_MS;
|
|
4097
4694
|
var init_workspace_files = __esm({
|
|
4098
4695
|
"src/workspace-files.ts"() {
|
|
4696
|
+
SYSTEM_OPENABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
4697
|
+
".doc",
|
|
4698
|
+
".docx",
|
|
4699
|
+
".xls",
|
|
4700
|
+
".xlsx",
|
|
4701
|
+
".ppt",
|
|
4702
|
+
".pptx",
|
|
4703
|
+
".rtf",
|
|
4704
|
+
".odt",
|
|
4705
|
+
".ods",
|
|
4706
|
+
".odp",
|
|
4707
|
+
".pages",
|
|
4708
|
+
".numbers",
|
|
4709
|
+
".key",
|
|
4710
|
+
".pdf",
|
|
4711
|
+
".tif",
|
|
4712
|
+
".tiff",
|
|
4713
|
+
".heic",
|
|
4714
|
+
".heif",
|
|
4715
|
+
".psd",
|
|
4716
|
+
".sketch",
|
|
4717
|
+
".fig",
|
|
4718
|
+
".mp4",
|
|
4719
|
+
".mov",
|
|
4720
|
+
".m4v",
|
|
4721
|
+
".mkv",
|
|
4722
|
+
".avi",
|
|
4723
|
+
".webm",
|
|
4724
|
+
".mp3",
|
|
4725
|
+
".wav",
|
|
4726
|
+
".m4a",
|
|
4727
|
+
".flac",
|
|
4728
|
+
".aac",
|
|
4729
|
+
".ogg"
|
|
4730
|
+
]);
|
|
4099
4731
|
LIST_TTL_MS = 6e4;
|
|
4100
4732
|
}
|
|
4101
4733
|
});
|
|
@@ -4414,6 +5046,9 @@ async function assembleRuntime(opts, opened, processes) {
|
|
|
4414
5046
|
config.tools?.deferMcp === true
|
|
4415
5047
|
);
|
|
4416
5048
|
cleanups.push(mcpWiring.dispose);
|
|
5049
|
+
registry.registerTools(
|
|
5050
|
+
createMcpTools({ control: mcpWiring.control, servers: () => mcpWiring.status() })
|
|
5051
|
+
);
|
|
4417
5052
|
const bridges = new SessionBridges(opts.onApprovalRequest);
|
|
4418
5053
|
const interactive = !services.permission?.isNonInteractive();
|
|
4419
5054
|
const requestPlanApproval = (req, sessionId2) => bridges.approve(sessionId2, req);
|
|
@@ -4423,13 +5058,14 @@ async function assembleRuntime(opts, opened, processes) {
|
|
|
4423
5058
|
requestPlanApproval,
|
|
4424
5059
|
interactive
|
|
4425
5060
|
);
|
|
5061
|
+
const defaultLevel = buildDefaultLevelSlot(services);
|
|
4426
5062
|
const guards = buildSessionGuards({
|
|
4427
5063
|
permission: services.permission,
|
|
4428
5064
|
planState,
|
|
4429
5065
|
managed: services.managedPolicy,
|
|
4430
5066
|
deferredTools: () => registry.byExposure(ToolExposure.Deferred),
|
|
4431
5067
|
bootstrapReveal,
|
|
4432
|
-
|
|
5068
|
+
defaultLevel,
|
|
4433
5069
|
requestApproval: requestPlanApproval,
|
|
4434
5070
|
interactive
|
|
4435
5071
|
});
|
|
@@ -4503,6 +5139,7 @@ async function assembleRuntime(opts, opened, processes) {
|
|
|
4503
5139
|
deferredTools: () => registry.byExposure(ToolExposure.Deferred),
|
|
4504
5140
|
codeModeTools: () => registry.codeModeDeclared(),
|
|
4505
5141
|
telemetry,
|
|
5142
|
+
compliance: services.compliance,
|
|
4506
5143
|
budget: budgetWiring,
|
|
4507
5144
|
...opts.onApprovalRequest ? { hostApproval: opts.onApprovalRequest } : {},
|
|
4508
5145
|
workspaces,
|
|
@@ -4604,7 +5241,7 @@ async function assembleRuntime(opts, opened, processes) {
|
|
|
4604
5241
|
sessionStore: store ?? null,
|
|
4605
5242
|
sessionReferences: services.session?.references ?? null,
|
|
4606
5243
|
workspaceFiles: buildWorkspaceFilesControl(
|
|
4607
|
-
{ listFiles: listWorkspaceFiles, resolveInWorkspace },
|
|
5244
|
+
{ listFiles: listWorkspaceFiles, resolveInWorkspace, systemOpenArgv },
|
|
4608
5245
|
services.permission
|
|
4609
5246
|
),
|
|
4610
5247
|
checkpoints: bootstrap?.checkpoints ?? buildCheckpointControl(checkpoints, null, () => sessionId),
|
|
@@ -4657,7 +5294,7 @@ async function assembleRuntime(opts, opened, processes) {
|
|
|
4657
5294
|
skillIndexResidency: (role) => services.skill?.indexResidency({
|
|
4658
5295
|
only: role === null ? void 0 : findRole(agentRoles, role)?.skills
|
|
4659
5296
|
}) ?? /* @__PURE__ */ new Map(),
|
|
4660
|
-
|
|
5297
|
+
skillWrite: buildSkillControl(services),
|
|
4661
5298
|
roleWrite: createRoleWriteControl({
|
|
4662
5299
|
userDir: agentsDir(config.homeDir),
|
|
4663
5300
|
roles: () => agentRoles,
|
|
@@ -4945,6 +5582,34 @@ function buildSkillControl(services) {
|
|
|
4945
5582
|
const outcome = await importSkills(source, { ...opts, token });
|
|
4946
5583
|
if (outcome.ok && outcome.imported.length > 0) services.skill?.invalidateCache();
|
|
4947
5584
|
return outcome;
|
|
5585
|
+
},
|
|
5586
|
+
remove: async (name, lang) => {
|
|
5587
|
+
const skill = services.skill;
|
|
5588
|
+
if (!skill) {
|
|
5589
|
+
return {
|
|
5590
|
+
ok: false,
|
|
5591
|
+
reason: "missing",
|
|
5592
|
+
detail: t("skill.remove_no_system", void 0, lang)
|
|
5593
|
+
};
|
|
5594
|
+
}
|
|
5595
|
+
const found = skill.list().find((s) => s.name === name);
|
|
5596
|
+
if (!found) {
|
|
5597
|
+
return {
|
|
5598
|
+
ok: false,
|
|
5599
|
+
reason: "not-found",
|
|
5600
|
+
detail: t("skill.remove_unknown", { name }, lang)
|
|
5601
|
+
};
|
|
5602
|
+
}
|
|
5603
|
+
try {
|
|
5604
|
+
const path = await skill.delete(name);
|
|
5605
|
+
return { ok: true, name, path };
|
|
5606
|
+
} catch (err) {
|
|
5607
|
+
return {
|
|
5608
|
+
ok: false,
|
|
5609
|
+
reason: found.scope === "user" ? "failed" : "readonly",
|
|
5610
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
5611
|
+
};
|
|
5612
|
+
}
|
|
4948
5613
|
}
|
|
4949
5614
|
};
|
|
4950
5615
|
}
|
|
@@ -5080,7 +5745,7 @@ function buildPermissionsControl(services, bootstrap, tools) {
|
|
|
5080
5745
|
bypassDisabled: p.disableBypassPermissionsMode
|
|
5081
5746
|
};
|
|
5082
5747
|
},
|
|
5083
|
-
setLevel: (level) => bootstrap?.setLevel(level) ?? { ok: true },
|
|
5748
|
+
setLevel: (level, opts) => bootstrap?.setLevel(level, opts) ?? { ok: true },
|
|
5084
5749
|
gates: (level) => {
|
|
5085
5750
|
const rules = compileRuleLists(services.config.permissions);
|
|
5086
5751
|
const isolation = services.permission?.getSandboxIsolation() ?? "process";
|
|
@@ -5125,15 +5790,20 @@ function buildSettingsControl(services, workspaces, opts) {
|
|
|
5125
5790
|
row.scalar ? { key: row.scalar, layers: scope.layers } : void 0
|
|
5126
5791
|
);
|
|
5127
5792
|
const overridable = row.scalar !== void 0;
|
|
5793
|
+
const writes = scope.paths === null ? [] : settingWriteTargets({
|
|
5794
|
+
key: row.key,
|
|
5795
|
+
winner: chain.layer,
|
|
5796
|
+
paths: scope.paths,
|
|
5797
|
+
trusted: scope.trusted
|
|
5798
|
+
});
|
|
5799
|
+
const kind = writes.length === 0 ? null : settingValueKind(row.key);
|
|
5800
|
+
const choices = writes.length === 0 ? null : settingValueChoices(row.key);
|
|
5128
5801
|
return {
|
|
5129
5802
|
...chain,
|
|
5130
5803
|
overridable,
|
|
5131
|
-
writes
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
paths: scope.paths,
|
|
5135
|
-
trusted: scope.trusted
|
|
5136
|
-
})
|
|
5804
|
+
writes,
|
|
5805
|
+
...kind === null ? {} : { valueKind: kind },
|
|
5806
|
+
...choices === null ? {} : { choices }
|
|
5137
5807
|
};
|
|
5138
5808
|
});
|
|
5139
5809
|
};
|
|
@@ -5152,7 +5822,7 @@ function buildSettingsControl(services, workspaces, opts) {
|
|
|
5152
5822
|
value: input.value
|
|
5153
5823
|
});
|
|
5154
5824
|
if (!outcome.ok) return { ok: false, reason: outcome.reason, path };
|
|
5155
|
-
const target = rows(input.sessionId).find((row) => row.key === input.key)?.writes.find((
|
|
5825
|
+
const target = rows(input.sessionId).find((row) => row.key === input.key)?.writes.find((write2) => write2.layer === input.layer);
|
|
5156
5826
|
return {
|
|
5157
5827
|
ok: true,
|
|
5158
5828
|
key: input.key,
|
|
@@ -5203,6 +5873,31 @@ function writePathsOf(layers) {
|
|
|
5203
5873
|
}
|
|
5204
5874
|
return paths;
|
|
5205
5875
|
}
|
|
5876
|
+
function buildDefaultLevelSlot(services) {
|
|
5877
|
+
let current = services.permissionLevel;
|
|
5878
|
+
return {
|
|
5879
|
+
get: () => current,
|
|
5880
|
+
remember: (level) => {
|
|
5881
|
+
current = level;
|
|
5882
|
+
const paths = writePathsOf(services.settingsLayers);
|
|
5883
|
+
if (!paths) return { reason: "no-user-config" };
|
|
5884
|
+
const outcome = writeSettingValue({
|
|
5885
|
+
layer: "user",
|
|
5886
|
+
path: paths.user,
|
|
5887
|
+
key: "permission",
|
|
5888
|
+
value: level
|
|
5889
|
+
});
|
|
5890
|
+
if (!outcome.ok) {
|
|
5891
|
+
const reason2 = outcome.reason === "unwritable-file" ? "unwritable" : "io";
|
|
5892
|
+
return { reason: reason2, path: paths.user };
|
|
5893
|
+
}
|
|
5894
|
+
if (originOf(services.configOrigins, "permission") === "env") {
|
|
5895
|
+
return { reason: "env-pinned", path: paths.user };
|
|
5896
|
+
}
|
|
5897
|
+
return void 0;
|
|
5898
|
+
}
|
|
5899
|
+
};
|
|
5900
|
+
}
|
|
5206
5901
|
function valueAt(config, key) {
|
|
5207
5902
|
let cursor = config;
|
|
5208
5903
|
for (const segment of key.split(".")) {
|
|
@@ -5236,6 +5931,7 @@ var init_build = __esm({
|
|
|
5236
5931
|
init_host_capabilities();
|
|
5237
5932
|
init_host_marketplaces();
|
|
5238
5933
|
init_host_preset();
|
|
5934
|
+
init_mcp_tools();
|
|
5239
5935
|
init_model_catalog();
|
|
5240
5936
|
init_plugin_control();
|
|
5241
5937
|
init_plugin_mcp_servers();
|
|
@@ -5339,7 +6035,7 @@ async function probeMcpServer(name, opts = {}) {
|
|
|
5339
6035
|
const tools = status?.connected ? await registry.getAllTools() : [];
|
|
5340
6036
|
return {
|
|
5341
6037
|
connected: status?.connected ?? false,
|
|
5342
|
-
toolNames: tools.map((
|
|
6038
|
+
toolNames: tools.map((t25) => t25.name),
|
|
5343
6039
|
...status?.lastError ? { error: status.lastError } : {},
|
|
5344
6040
|
needsLogin: status?.needsLogin ?? false
|
|
5345
6041
|
};
|
|
@@ -5360,5 +6056,7 @@ async function requireServer(name, opts) {
|
|
|
5360
6056
|
// src/index.ts
|
|
5361
6057
|
init_mcp_config_file();
|
|
5362
6058
|
init_mcp_config_apply();
|
|
6059
|
+
init_mcp_config_edit();
|
|
6060
|
+
init_mcp_tools();
|
|
5363
6061
|
|
|
5364
|
-
export { AgentRoleError, AgentSession, ApprovalRelay, HOST_CAPABILITIES_MODULE, HOST_MARKETPLACE_MODULE, HOST_PRESET_MODULE, QuestionRelay, SCHEDULE_EXIT_CODES, SessionStore, SessionWorkspaces, applyHostPreset, applyMcpConfig, buildModelCatalogControl, buildRuntime, buildServices, buildWorkspaceFilesControl, collectFileChanges, createScheduleControl, createUserActions, ensureSecretsReady, exitCodeFor, fireSchedule, listMcpServers, mcpConfigRevision, mcpLogin, mcpLogout, probeMcpServer, providerLabel, readMcpConfigFile, registerAskQuestion, registerLocalTools, registerMcpTools, serializeStream, splitDirList, toSerializable, utilityBudgetKey, wireCommands, wireHostCapabilities, wirePluginMcpServers, wireWorkspace, withModelScope, withRoleScope, withToolScope, writeMcpConfigFile, writeMcpServer };
|
|
6062
|
+
export { AgentRoleError, AgentSession, ApprovalRelay, HOST_CAPABILITIES_MODULE, HOST_MARKETPLACE_MODULE, HOST_PRESET_MODULE, MCP_ADD_TOOL, MCP_LIST_TOOL, MCP_REMOVE_TOOL, MCP_UPDATE_TOOL, QuestionRelay, SCHEDULE_EXIT_CODES, SessionStore, SessionWorkspaces, applyHostPreset, applyMcpConfig, buildModelCatalogControl, buildRuntime, buildServices, buildWorkspaceFilesControl, collectFileChanges, createMcpTools, createScheduleControl, createUserActions, ensureSecretsReady, exitCodeFor, fireSchedule, isSystemOpenable, listMcpServers, mcpConfigRevision, mcpLogin, mcpLogout, probeMcpServer, providerLabel, readMcpConfigFile, registerAskQuestion, registerLocalTools, registerMcpTools, removeMcpServer, removeServerText, replaceServerText, serializeStream, splitDirList, toSerializable, updateMcpServer, utilityBudgetKey, wireCommands, wireHostCapabilities, wirePluginMcpServers, wireWorkspace, withModelScope, withRoleScope, withToolScope, writeMcpConfigFile, writeMcpServer };
|