@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/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, envPath, setDataKey, parseDataKey, generateDataKey, i18nDiagnostics, automationWorkDir, trustPath, trustedImportsPath, setShell, probeShell } from '@epoch-agent/infra';
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((t23) => t23.name));
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
- type: p.type,
612
- label: p.label,
613
- envVar: p.apiKeyEnv,
614
- hasKey: p.apiKeyEnv === null || keyOf(p.apiKeyEnv) !== ""
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, supportsImages: false };
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.warn("Trust", trust2.reason, "trust-disabled");
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 providerBridge = provider ? {
1634
- generate: provider.generate.bind(provider),
1635
- stream: provider.stream.bind(provider),
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
- return { supportsImages: capability.supportsImages };
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((resolve3) => {
1970
- enqueue({ kind: "approval", req, resolve: resolve3 });
2255
+ onApprovalRequest: (req) => new Promise((resolve4) => {
2256
+ enqueue({ kind: "approval", req, resolve: resolve4 });
1971
2257
  }),
1972
- onQuestionRequest: (req) => new Promise((resolve3) => {
1973
- enqueue({ kind: "question", req, resolve: resolve3 });
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 (done) break;
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
- startLevel,
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(startLevel);
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
- entries.push({ key: key[0], valueAt: valueAt2 });
2774
- i = skipTrivia(raw, skipValue(raw, valueAt2));
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: { braceAt: servers.valueAt, hasMembers: keys.length > 0, keys, kind: "servers" }
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, keys: [], kind: "top" }
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 renderEntry(input, indent) {
2810
- const pad = indent.repeat(2);
2811
- const inner = indent.repeat(3);
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.keys.includes(input.name)) {
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
- return { ok: true, text: raw.slice(0, at) + block + raw.slice(at) };
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 probe = parseMcpConfig(
2857
- JSON.stringify({ servers: { [input.name]: rawFields(input) } }),
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((t23) => t23.name.startsWith(prefix)),
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((t23) => t23.name.startsWith(`mcp__${st.name}__`));
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 t23 = now();
4621
+ const t25 = now();
4033
4622
  if (!cache || cache.root !== root) cache = computeList(root);
4034
- else if (t23 - cache.at > LIST_TTL_MS) cache = computeList(root);
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
- startLevel: services.permissionLevel,
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
- skillImport: buildSkillControl(services),
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: scope.paths === null ? [] : settingWriteTargets({
5132
- winner: chain.layer,
5133
- overridable,
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((write) => write.layer === input.layer);
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((t23) => t23.name),
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 };