@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.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GoalRoundResult, Goal, GoalWrite, GoalClear, Workspace, TrustDecision, ProjectContext, RecentWorkspace, TrustGate, RecentWorkspaces, PermissionManager, SessionManager, FtsHealth, FtsReclaim, AgentLoop, PluginCommandDir, InstallPreview, PluginRecord, PluginSourceType, PluginCounts, MarketplaceEntry, ScheduleRegistrar, HeadlessPolicy, CreateScheduleInput, ScheduleIssue, RegisterOutcome, UpdateScheduleInput, needsAllowlist, HookSourceStatus, ConfigOrigins, SkillDirSpec, ProviderRouter, AgentProvider, GoalService, MemoryManager, SkillSystem, SkillLearner, HookManager, ContextCompressor, TrackerDB, TrustManager, ModelPricing, LayerOutcome, ManagedPolicy, PluginArtifacts, CheckpointSummary, RewindPreview, RewindOutcome, ToolRevealState, CodeSandbox, ISkillSystem, ToolRegistry, QuestionAskFn, ResponseFormat, ProviderWarning, SettingValue, SettingLayer, SettingStep, SettingWriteTarget, SessionReferences, IsolationReport, RoleMergeNotice, SkillMeta, SkillIndexResidency, SkillImportResult, SkillImportPreview, SkillImportDone, ShadowFinding, BaseOrigin, PermissionAuditScope, PermissionAuditSnapshot, CachedApproval, ToolGate, SettingWriteLayer, SettingApply, SettingWriteFailure, PluginPathRef } from '@epoch-agent/core';
|
|
1
|
+
import { GoalRoundResult, Goal, GoalWrite, GoalClear, Workspace, TrustDecision, ProjectContext, RecentWorkspace, TrustGate, RecentWorkspaces, PermissionManager, SessionManager, FtsHealth, FtsReclaim, AgentLoop, PluginCommandDir, InstallPreview, PluginRecord, PluginSourceType, PluginCounts, MarketplaceEntry, ScheduleRegistrar, HeadlessPolicy, CreateScheduleInput, ScheduleIssue, RegisterOutcome, UpdateScheduleInput, needsAllowlist, HookSourceStatus, ConfigOrigins, SkillDirSpec, ProviderRouter, AgentProvider, GoalService, MemoryManager, SkillSystem, SkillLearner, HookManager, ContextCompressor, TrackerDB, TrustManager, ModelPricing, LayerOutcome, ManagedPolicy, PluginArtifacts, ComplianceEngine, CheckpointSummary, RewindPreview, RewindOutcome, ToolRevealState, CodeSandbox, ISkillSystem, ToolRegistry, QuestionAskFn, ResponseFormat, ProviderWarning, SettingValue, SettingLayer, SettingStep, SettingWriteTarget, SettingValueKind, SessionReferences, IsolationReport, RoleMergeNotice, SkillMeta, SkillIndexResidency, SkillImportResult, SkillImportPreview, SkillImportDone, ShadowFinding, BaseOrigin, PermissionAuditScope, PermissionAuditSnapshot, CachedApproval, ToolGate, SettingWriteLayer, SettingApply, SettingWriteFailure, PluginPathRef } 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
3
|
import { McpServerConfig, McpServerStatus, McpServerSource, McpTransport, McpAuthStatus } from '@epoch-agent/plugin-mcp';
|
|
4
4
|
export { McpAuthState, McpAuthStatus, McpServerConfig } from '@epoch-agent/plugin-mcp';
|
|
@@ -2204,6 +2204,14 @@ interface ProviderOption {
|
|
|
2204
2204
|
envVar: string | null;
|
|
2205
2205
|
/** 这台机器上这把 key 配好了没有。**不需要 key 的那家恒 `true`** */
|
|
2206
2206
|
hasKey: boolean;
|
|
2207
|
+
/**
|
|
2208
|
+
* 那把 key 长什么样 —— **脱敏后的**(`maskApiKey`)。没配 / 不需要时是 `null`。
|
|
2209
|
+
*
|
|
2210
|
+
* ⚠️ 这一格是**唯一**一处让解出来的 key 沾到外面的地方,而它沾出去的是
|
|
2211
|
+
* 一个不可逆的展示串。判据(以及「为什么一个布尔不够」)在网线那一份
|
|
2212
|
+
* `WireProviderOption.keyHint` 上。
|
|
2213
|
+
*/
|
|
2214
|
+
keyHint: string | null;
|
|
2207
2215
|
}
|
|
2208
2216
|
/** 探一家的结果。和网线那一份 `WireModelSuggestions` 逐字同形 */
|
|
2209
2217
|
interface ModelSuggestions {
|
|
@@ -2218,13 +2226,25 @@ interface DiscoverOptions {
|
|
|
2218
2226
|
/** 绕开磁盘缓存重新探。同 `epoch model --refresh` */
|
|
2219
2227
|
refresh?: boolean;
|
|
2220
2228
|
}
|
|
2229
|
+
/** {@link ModelCatalogControl.setKey} 写完之后,那把 key 落到哪儿了 */
|
|
2230
|
+
interface ProviderKeyWritten {
|
|
2231
|
+
/** 写进的那个环境变量名。回声一遍 —— 调用方手里只有 provider 类型 */
|
|
2232
|
+
envVar: string;
|
|
2233
|
+
/** `keychain` / `dpapi` / `libsecret` / `plaintext`。**必须回显给用户** */
|
|
2234
|
+
backend: string;
|
|
2235
|
+
encrypted: boolean;
|
|
2236
|
+
/** 明文降级时那个 `.env` 在哪儿;进了加密后端就是 `null` */
|
|
2237
|
+
envPath: string | null;
|
|
2238
|
+
/** 脱敏后的那把 key。⚠️ **不是原文**,判据同 {@link ProviderOption.keyHint} */
|
|
2239
|
+
keyHint: string;
|
|
2240
|
+
}
|
|
2221
2241
|
/**
|
|
2222
|
-
* 宿主看得见的那一面 ——
|
|
2242
|
+
* 宿主看得见的那一面 —— **两个问句加一个写**。
|
|
2223
2243
|
*
|
|
2224
2244
|
* `clearModelCache` 刻意不单独露出来:它唯一的正当用法就是「清掉再探一次」,
|
|
2225
2245
|
* 而那两步之间任何一次早退都会留下一个**被清空却没有重填**的缓存,
|
|
2226
2246
|
* 下一次探不通时用户看到的是「一个模型都没有」而不是上次那份。
|
|
2227
|
-
* 所以它被并进 {@link discover} 的 `refresh
|
|
2247
|
+
* 所以它被并进 {@link discover} 的 `refresh`(以及 {@link setKey} 的收尾)里,两步绑死。
|
|
2228
2248
|
* 同 `WorkspaceControl` 刻意不露 `seed()`、`McpControl` 只有 `reconnect()`。
|
|
2229
2249
|
*/
|
|
2230
2250
|
interface ModelCatalogControl {
|
|
@@ -2242,6 +2262,33 @@ interface ModelCatalogControl {
|
|
|
2242
2262
|
* 探这一家有哪些模型。**从不抛,也从不回 null** —— 见 {@link ModelSuggestions.status}。
|
|
2243
2263
|
*/
|
|
2244
2264
|
discover(provider: ProviderType, opts?: DiscoverOptions): Promise<ModelSuggestions>;
|
|
2265
|
+
/**
|
|
2266
|
+
* 给这一家配一把 key(2026-08-31)。落点由 `SecretStore` 的降级链决定,
|
|
2267
|
+
* 结果回声在 {@link ProviderKeyWritten} 上。
|
|
2268
|
+
*
|
|
2269
|
+
* ## ⚠️ 这一个和上面两个不同档:它**会抛**
|
|
2270
|
+
*
|
|
2271
|
+
* 上面两个是问句,问不到有一档「探不到」可落;这一个是**写**,
|
|
2272
|
+
* 而一次没落地的写没有「另一档结果」—— 静默吞掉等于告诉用户「配好了」
|
|
2273
|
+
* 而其实没有。两种抛法:
|
|
2274
|
+
*
|
|
2275
|
+
* - 这家**不要 key**(Ollama,`apiKeyEnv === null`)。这不是运行期故障,
|
|
2276
|
+
* 是调用方问错了问题 —— 收窄口上不该存在一个「配了也没用」的动作,
|
|
2277
|
+
* 所以它在这儿就断,不往下走到 store。
|
|
2278
|
+
* - `SecretStore` 那一发自己失败(钥匙串拒绝、磁盘满)。原样抛出去。
|
|
2279
|
+
*
|
|
2280
|
+
* ## 写完**顺手清掉这一家的模型缓存**,两步绑死
|
|
2281
|
+
*
|
|
2282
|
+
* 探测结果落盘缓存 24 小时。不清的话,用户刚配完 key 再探一次,
|
|
2283
|
+
* 拿到的还是没 key 那会儿回退出来的**静态目录** —— 而
|
|
2284
|
+
* `cli/src/commands/model.ts` 的文件头逐字记着那个后果:
|
|
2285
|
+
* 「后果不是『少几个模型』而是**列表完全不对**」。
|
|
2286
|
+
* 这一步不给调用方选,同 `discover` 的 `refresh` 不给单独的 `clear`。
|
|
2287
|
+
*
|
|
2288
|
+
* ⚠️ 「让本进程立刻看得见这把 key」那一步**不在这儿**,在 infra 的
|
|
2289
|
+
* `writeProviderSecret()` 里 —— `epoch model` 和这条路要的是同一件事。
|
|
2290
|
+
*/
|
|
2291
|
+
setKey(provider: ProviderType, apiKey: string): Promise<ProviderKeyWritten>;
|
|
2245
2292
|
}
|
|
2246
2293
|
interface ModelCatalogOptions {
|
|
2247
2294
|
/** 数据目录。缓存文件和凭据解析都按它分命名空间 */
|
|
@@ -2751,10 +2798,24 @@ interface RoleWriteControl {
|
|
|
2751
2798
|
*/
|
|
2752
2799
|
type LevelChangeResult = {
|
|
2753
2800
|
ok: true;
|
|
2801
|
+
/**
|
|
2802
|
+
* 「记住这一档」那一半的结局(2026-08-31)。**只在没记住时有值** ——
|
|
2803
|
+
* 记住了、或者这一发压根没要求记(`remember: false`)时是 `undefined`。
|
|
2804
|
+
*
|
|
2805
|
+
* ⚠️ 它**不影响 `ok`**:档位已经换成了。两件事分开报的判据在
|
|
2806
|
+
* {@link RememberDefaultLevel} 上。
|
|
2807
|
+
*/
|
|
2808
|
+
notRemembered?: RememberFailure;
|
|
2754
2809
|
} | {
|
|
2755
2810
|
ok: false;
|
|
2756
2811
|
reason: 'managed-bypass-disabled';
|
|
2757
2812
|
};
|
|
2813
|
+
/** 没记住的成因。**码不是句子**,逐字对应 `WirePermissionRememberFailure`(判据在那儿) */
|
|
2814
|
+
type RememberFailure = {
|
|
2815
|
+
reason: 'no-user-config' | 'unwritable' | 'io' | 'env-pinned';
|
|
2816
|
+
/** 本来要写的那个文件;说不出路径的那一档整个不带 */
|
|
2817
|
+
path?: string;
|
|
2818
|
+
};
|
|
2758
2819
|
/**
|
|
2759
2820
|
* Plan 模式的出口(方案 35)。
|
|
2760
2821
|
*
|
|
@@ -2819,7 +2880,7 @@ interface SessionPermissions {
|
|
|
2819
2880
|
bypassDisabled: boolean;
|
|
2820
2881
|
};
|
|
2821
2882
|
/**
|
|
2822
|
-
*
|
|
2883
|
+
* 切到某一档。**这是改权限档位的唯一入口**,四件事必须在这一次调用里
|
|
2823
2884
|
* 一起发生,顺序也是判据:
|
|
2824
2885
|
*
|
|
2825
2886
|
* 1. **托管挡 bypass**(方案 22 §2.6)。挡下时级别一个字不动,回 `ok:false`;
|
|
@@ -2827,11 +2888,20 @@ interface SessionPermissions {
|
|
|
2827
2888
|
* 3. **`plan.forget()`**:用户接管了方向盘,plan 模式记着的「进入前那一档」
|
|
2828
2889
|
* 当场作废。⚠️ 是 `forget()` 不是 `leave()`:后者会把级别**恢复**成进入前
|
|
2829
2890
|
* 那一档,正好覆盖掉用户这一下刚选的那个。
|
|
2891
|
+
* 4. **`opts.remember` 为真时把它记成新会话的起点**(2026-08-31)。
|
|
2892
|
+
* 排在第 3 步之后不是随手排的:记的是「用户此刻要的那一档」,
|
|
2893
|
+
* 而第 1 步可能已经把这一发整个否掉了。
|
|
2830
2894
|
*
|
|
2831
2895
|
* 判定住在这一处而不是每个宿主抄一份 —— 抄一份的具体代价是**第三步会被漏掉**,
|
|
2832
2896
|
* 而那件事只在「plan 模式 + 中途换档 + 批准计划」三件事凑齐时才看得见。
|
|
2897
|
+
*
|
|
2898
|
+
* @param opts.remember 这一发**算不算用户显式挑的**。必填,判据全文在
|
|
2899
|
+
* {@link DefaultLevelSlot} 那张三行表上 —— 一句话:定时任务的
|
|
2900
|
+
* `def.permission` 走的是同一个方法,而它绝不能改用户的默认档。
|
|
2833
2901
|
*/
|
|
2834
|
-
setLevel: (level: PermissionLevel
|
|
2902
|
+
setLevel: (level: PermissionLevel, opts: {
|
|
2903
|
+
remember: boolean;
|
|
2904
|
+
}) => LevelChangeResult;
|
|
2835
2905
|
}
|
|
2836
2906
|
|
|
2837
2907
|
/**
|
|
@@ -3021,7 +3091,9 @@ interface ExecutorRuntime {
|
|
|
3021
3091
|
} | null;
|
|
3022
3092
|
permission: PermissionManager | null;
|
|
3023
3093
|
permissions: {
|
|
3024
|
-
setLevel: (level: ScheduleDefinition['permission']
|
|
3094
|
+
setLevel: (level: ScheduleDefinition['permission'], opts: {
|
|
3095
|
+
remember: boolean;
|
|
3096
|
+
}) => LevelChangeResult;
|
|
3025
3097
|
rules: () => ReadonlyArray<{
|
|
3026
3098
|
bucket: 'allow' | 'ask' | 'deny';
|
|
3027
3099
|
rule: string;
|
|
@@ -3316,6 +3388,16 @@ interface Services {
|
|
|
3316
3388
|
*
|
|
3317
3389
|
* ⚠️ 它是**起点**不是现状:`permission.getLevel()` 才是「此刻是哪一档」,
|
|
3318
3390
|
* 而那一格从这一轮起一个会话一份。
|
|
3391
|
+
*
|
|
3392
|
+
* ⚠️ **2026-08-31:它是「起点的初值」,不再是起点本身。**
|
|
3393
|
+
* 起点从这一轮起会动(用户显式挑一档就跟着走),那一格是
|
|
3394
|
+
* `session-guard.ts` 的 `DefaultLevelSlot`,`build.ts` 用这个字段给它开局。
|
|
3395
|
+
* 上面那句「新建的会话从这一档起步」因此要读成「**第一个**新建的会话」——
|
|
3396
|
+
* 用户换过档之后,起点是他挑的那一档。
|
|
3397
|
+
*
|
|
3398
|
+
* 那句「不继承别的会话此刻那一档」**一个字都没变**:改起点的是「用户挑」这个
|
|
3399
|
+
* 动作,不是别的会话的现状(plan 模式和定时任务都改得动某个会话此刻那一档,
|
|
3400
|
+
* 而起点一个字不动 —— 三行表在 `DefaultLevelSlot` 上)。
|
|
3319
3401
|
*/
|
|
3320
3402
|
permissionLevel: PermissionLevel;
|
|
3321
3403
|
contextEngine: ContextCompressor | null;
|
|
@@ -3400,6 +3482,17 @@ interface Services {
|
|
|
3400
3482
|
* 在这儿接等于把「MCP 从哪儿来」分成两处答。
|
|
3401
3483
|
*/
|
|
3402
3484
|
plugins: PluginArtifacts;
|
|
3485
|
+
/**
|
|
3486
|
+
* 内容合规闸门(core 的 `compliance/`)。
|
|
3487
|
+
*
|
|
3488
|
+
* **关掉时是 `null` 而不是一个「什么都不拦」的引擎**:`AgentConfig.compliance`
|
|
3489
|
+
* 缺席那条路上,模型输出一个字都不多走一层(判据在 `ComplianceEngine`
|
|
3490
|
+
* 的文件头 —— 不开这个功能的用户不该为它付任何代价)。
|
|
3491
|
+
*
|
|
3492
|
+
* 装配层每一处 `new AgentLoop` 都要把它填进去,子 agent 那条也要。
|
|
3493
|
+
* 会红的门禁在 `runtime/__tests__/compliance-wiring.test.ts`。
|
|
3494
|
+
*/
|
|
3495
|
+
compliance: ComplianceEngine | null;
|
|
3403
3496
|
/** 需要在退出时执行的清理动作,后进先出 */
|
|
3404
3497
|
cleanups: Array<() => void>;
|
|
3405
3498
|
}
|
|
@@ -4297,22 +4390,34 @@ declare function applyMcpConfig(mcp: McpApplySource, sync: ToolSync, opts: {
|
|
|
4297
4390
|
* 一个已有字节都不用碰:逗号长在**新**那一段上。所以新加的排在文件最前面,
|
|
4298
4391
|
* 那是这条判据的结果,不是随手定的顺序。
|
|
4299
4392
|
*
|
|
4300
|
-
* ##
|
|
4301
|
-
*
|
|
4302
|
-
* 字符串(含转义)、`//` 行注释、`/* *\/` 块注释 —— 够定位 `servers` 那个对象的
|
|
4303
|
-
* `{`,也够把它已有的成员名数出来(撞名要用)。
|
|
4393
|
+
* ## 扫描器搬走了,判据没变
|
|
4304
4394
|
*
|
|
4305
|
-
*
|
|
4306
|
-
*
|
|
4307
|
-
*
|
|
4308
|
-
*
|
|
4309
|
-
* 注释顺手抹掉。
|
|
4395
|
+
* 那一段(`skipTrivia` / `readString` / `skipValue` / `walkObject` / `locate` /
|
|
4396
|
+
* `indentOf`)2026-09-02 搬进了 [mcp-json-scan.ts](./mcp-json-scan.ts),因为
|
|
4397
|
+
* 有了第二、第三个调用方(就地**替换**和**删除**,见下)。判据全文跟着一起搬,
|
|
4398
|
+
* 别在这儿读一个摘要就动手。
|
|
4310
4399
|
*
|
|
4311
4400
|
* ## 看不懂就一个字节都不写
|
|
4312
4401
|
*
|
|
4313
|
-
*
|
|
4314
|
-
*
|
|
4315
|
-
*
|
|
4402
|
+
* 猜错一次的代价是把他手写的配置弄坏,而他知道这件事的时刻是「MCP 全没了」的
|
|
4403
|
+
* 那一天。所以扫描器走不到收尾的 `}` 就整段拒绝,并把「哪儿看不懂」说出来。
|
|
4404
|
+
*
|
|
4405
|
+
* ## 📮 2026-09-02:「我们对这个文件只有**一次插入**的权利」这句话翻了
|
|
4406
|
+
*
|
|
4407
|
+
* 上面那一节原来的开头就是这句话,它是本文件所有判据的总纲。**这一轮把它推翻
|
|
4408
|
+
* 了**,而推翻它的理由不是「插入不够用」,是**模型手上那条路没有第二个选项**:
|
|
4409
|
+
*
|
|
4410
|
+
* `mcp_update` / `mcp_remove` 那两个工具([mcp-tools.ts](./mcp-tools.ts))要么
|
|
4411
|
+
* 走就地改写,要么走「读原文 → 整份写回」。而后者意味着**把整份 `mcp.json` 交进
|
|
4412
|
+
* 模型的上下文** —— 里面装着他每一台 server 的 `env` 密钥(判据在
|
|
4413
|
+
* `writeMcpConfigFile` 那句「新建的那一档给 0600,因为这个文件天生装着 token」)。
|
|
4414
|
+
* 一条为了保住排版而存在的判据,不该以「顺带把密钥全交出去」为代价来维持。
|
|
4415
|
+
*
|
|
4416
|
+
* ⚠️ **所以变的是「几次」,不是「什么」。** 就地替换和删除照旧是**定位 + 拼接
|
|
4417
|
+
* 文本**,`parse → serialize` 那一步在这一族里仍然不存在,上面那三节
|
|
4418
|
+
* (round-trip 更毒 / 只做追加不成立 / 另存一处不成立)一个字都没过期。
|
|
4419
|
+
* 删除是这一族里**唯一**会动到用户已经写下的字节的动作(那个逗号),
|
|
4420
|
+
* 它的边界逐条写在 [mcp-config-edit.ts](./mcp-config-edit.ts) 文件头。
|
|
4316
4421
|
*/
|
|
4317
4422
|
|
|
4318
4423
|
/**
|
|
@@ -4363,6 +4468,11 @@ type McpWriteFailure = {
|
|
|
4363
4468
|
reason: 'unwritable';
|
|
4364
4469
|
detail: string;
|
|
4365
4470
|
};
|
|
4471
|
+
/** 插进文本之后的产物,或者为什么没插 */
|
|
4472
|
+
type McpSpliceOutcome = {
|
|
4473
|
+
ok: true;
|
|
4474
|
+
text: string;
|
|
4475
|
+
} | McpWriteFailure;
|
|
4366
4476
|
/** 落盘之后的结局。`configPath` 要发到界面上 —— 用户接下来多半想去改那个文件 */
|
|
4367
4477
|
type McpWriteOutcome = {
|
|
4368
4478
|
ok: true;
|
|
@@ -4396,6 +4506,116 @@ declare function writeMcpServer(input: McpAddInput, opts?: {
|
|
|
4396
4506
|
taken?: Iterable<string>;
|
|
4397
4507
|
}): McpWriteOutcome;
|
|
4398
4508
|
|
|
4509
|
+
/**
|
|
4510
|
+
* `~/.epoch/mcp.json` 的**就地替换与删除**(2026-09-02)—— 隔壁
|
|
4511
|
+
* [mcp-config-write.ts](./mcp-config-write.ts) 那条插入路的另外两个动作。
|
|
4512
|
+
*
|
|
4513
|
+
* ## 它为什么存在:模型手上那条路没有第二个选项
|
|
4514
|
+
*
|
|
4515
|
+
* 判据全文在那个文件头的「📮 2026-09-02」那一节,一句话:`mcp_update` /
|
|
4516
|
+
* `mcp_remove` 要么走就地改写,要么走「把整份原文交进模型的上下文再整份写回」,
|
|
4517
|
+
* 而那份原文里装着用户**每一台** server 的 `env` 密钥。一条为了保住排版而存在
|
|
4518
|
+
* 的判据,不该以「顺带把密钥全交出去」为代价来维持。
|
|
4519
|
+
*
|
|
4520
|
+
* ⚠️ **变的是「几次」,不是「什么」。** 这里照旧是**定位 + 拼接文本**,
|
|
4521
|
+
* `parse → serialize` 那一步在这一族里仍然不存在 —— 注释、空行、字段顺序、
|
|
4522
|
+
* snake_case 键名、未知键,凡是不在被改那一台身上的,一个字节都不动。
|
|
4523
|
+
*
|
|
4524
|
+
* ## ⚠️ 一、删除是这一族里唯一会动到**用户已经写下的字节**的动作
|
|
4525
|
+
*
|
|
4526
|
+
* 插入之所以能做到零改动,是因为逗号长在**新**那一段上(那个文件头第 39 行那一
|
|
4527
|
+
* 节)。删除做不到:被删的那台前面或后面那个逗号是**他写的**,而留着它就是一份
|
|
4528
|
+
* 坏 JSON。所以这一条边界必须明写:
|
|
4529
|
+
*
|
|
4530
|
+
* **我们只动那一个逗号,以及只装着空白的那一行。** 具体说:
|
|
4531
|
+
*
|
|
4532
|
+
* | 现场 | 动了什么 |
|
|
4533
|
+
* | -------------------------------------- | -------------------------------------------- |
|
|
4534
|
+
* | 它后面紧跟一个逗号(中间只有空白) | 连那个逗号一起删 |
|
|
4535
|
+
* | 它是最后一个成员,前面有一个逗号 | 连**前面**那个逗号一起删 |
|
|
4536
|
+
* | 删完那一行只剩空白 | 整行连换行一起删(不留一行空缩进) |
|
|
4537
|
+
* | 删完那一行还剩别的东西(注释、另一台) | **只删它自己那一段**,那一行剩下什么留什么 |
|
|
4538
|
+
*
|
|
4539
|
+
* ⚠️ **「中间只有空白」是硬条件,别放宽成 `skipTrivia`。** 那个函数会跳过注释,
|
|
4540
|
+
* 于是
|
|
4541
|
+
*
|
|
4542
|
+
* ```jsonc
|
|
4543
|
+
* "a": { … }
|
|
4544
|
+
* // 这台先不用
|
|
4545
|
+
* ,
|
|
4546
|
+
* ```
|
|
4547
|
+
*
|
|
4548
|
+
* 里那条注释会落在删除区间里 —— 一次静默的注释丢失,而用户看不出是谁干的。
|
|
4549
|
+
* 判据同插入那条「绝不能因为读口读不了就把那几行注释顺手抹掉」。
|
|
4550
|
+
*
|
|
4551
|
+
* 剩下一档**明说不管**:逗号后面同一行上挂着的行尾注释(`"a": {…}, // 关于 a`)。
|
|
4552
|
+
* 删掉 `a` 之后那条注释会孤零零留在原处。留着它是刻意的 —— 判它「属于 a」要
|
|
4553
|
+
* 猜,而猜错就是删掉用户写的一句话。**一条孤儿注释是噪音,删错一句话是损失。**
|
|
4554
|
+
*
|
|
4555
|
+
* ## ⚠️ 二、替换**不改名字**
|
|
4556
|
+
*
|
|
4557
|
+
* `replaceServerText` 拿 `input.name` 去找那一台,然后把它整段换成新渲染的一段
|
|
4558
|
+
* —— 键名前后一样。改名是「删一台 + 加一台」两个动作,不是这一个:一台 server
|
|
4559
|
+
* 的名字会原样变成工具名的一部分(`mcp__<name>__<工具>`),把改名藏在「更新」
|
|
4560
|
+
* 底下的表现是模型以为自己改了一格配置,而实际上它手上那一批工具全部换了名字。
|
|
4561
|
+
*
|
|
4562
|
+
* ## 三、替换沿用**原文那一台的缩进**,不按 `indent.repeat(2)` 拼
|
|
4563
|
+
*
|
|
4564
|
+
* 判据在 `renderEntry` 的 `pad` 参数上:替换是接在原文已有的缩进后面写的。
|
|
4565
|
+
* 按固定层数拼出来的收尾 `}` 会跑到一个和它上下文对不齐的列上,而那是一次
|
|
4566
|
+
* **看得见的**排版破坏 —— 恰恰是这一族判据要防的那种。
|
|
4567
|
+
*/
|
|
4568
|
+
|
|
4569
|
+
/**
|
|
4570
|
+
* 「这个名字在文件里没有」单独一档。
|
|
4571
|
+
*
|
|
4572
|
+
* 不并进 `McpWriteFailure` 的 `invalid-name`:那一档说的是「这个名字不合法」
|
|
4573
|
+
* (换个名字),这一档说的是「合法,但文件里没有这一台」(去列一遍,或者改用
|
|
4574
|
+
* 添加)。两者的下一步完全不同,而模型只能照回执里那句话选下一步。
|
|
4575
|
+
*/
|
|
4576
|
+
type McpEditFailure = McpWriteFailure | {
|
|
4577
|
+
ok: false;
|
|
4578
|
+
reason: 'not-found';
|
|
4579
|
+
detail: string;
|
|
4580
|
+
};
|
|
4581
|
+
/** 落盘之后的结局。`configPath` 要发到回执上 —— 用户接下来多半想去看那个文件 */
|
|
4582
|
+
type McpEditOutcome = {
|
|
4583
|
+
ok: true;
|
|
4584
|
+
configPath: string;
|
|
4585
|
+
} | McpEditFailure;
|
|
4586
|
+
/**
|
|
4587
|
+
* 把一台**换成**新的一段,其余字节一个不动。
|
|
4588
|
+
*
|
|
4589
|
+
* 纯函数(文本进、文本出),没有 fs —— 「注释和顺序没被抹掉」那条断言才断得住。
|
|
4590
|
+
*/
|
|
4591
|
+
declare function replaceServerText(raw: string, input: McpAddInput): McpSpliceOutcome;
|
|
4592
|
+
/**
|
|
4593
|
+
* 把一台**删掉**,其余字节一个不动(除了那一个逗号,判据在文件头第一节)。
|
|
4594
|
+
*
|
|
4595
|
+
* 纯函数(文本进、文本出)。
|
|
4596
|
+
*/
|
|
4597
|
+
declare function removeServerText(raw: string, name: string): McpSpliceOutcome;
|
|
4598
|
+
/**
|
|
4599
|
+
* 改一台已有 server 的配置。**名字不变**,判据在文件头第二节。
|
|
4600
|
+
*
|
|
4601
|
+
* 成员名单**一个字都不该变**(连顺序)—— 那条断言是「替换切歪了」的唯一报警。
|
|
4602
|
+
*/
|
|
4603
|
+
declare function updateMcpServer(input: McpAddInput, opts?: {
|
|
4604
|
+
homeDir?: string;
|
|
4605
|
+
lang?: Lang;
|
|
4606
|
+
}): McpEditOutcome;
|
|
4607
|
+
/**
|
|
4608
|
+
* 删掉一台 server。
|
|
4609
|
+
*
|
|
4610
|
+
* ⚠️ **删掉最后一台是一个合法动作** —— 一份 `{"servers": {}}` 是完全正常的
|
|
4611
|
+
* `mcp.json`(绝大多数机器上就是这样)。拿「改完还有没有 server」当校验条件
|
|
4612
|
+
* 会把这一档挡掉,所以这里的校验是**名单少掉点名那一个**,不是「名单非空」。
|
|
4613
|
+
*/
|
|
4614
|
+
declare function removeMcpServer(name: string, opts?: {
|
|
4615
|
+
homeDir?: string;
|
|
4616
|
+
lang?: Lang;
|
|
4617
|
+
}): McpEditOutcome;
|
|
4618
|
+
|
|
4399
4619
|
/**
|
|
4400
4620
|
* 工具装配 —— 插件 + 内置工具 + MCP 外部工具,全部进同一个 ToolRegistry。
|
|
4401
4621
|
*
|
|
@@ -4531,6 +4751,28 @@ type McpAddOutcome = {
|
|
|
4531
4751
|
status: McpServerStatus;
|
|
4532
4752
|
configPath: string;
|
|
4533
4753
|
} | McpWriteFailure;
|
|
4754
|
+
/**
|
|
4755
|
+
* 一次「改」的结局(2026-09-02)。形状**和 {@link McpAddOutcome} 一样**,
|
|
4756
|
+
* 因为这两件事在界面 / 回执上要说的话一样(配置落在哪儿 + 这台此刻连上没)。
|
|
4757
|
+
*
|
|
4758
|
+
* 失败那一侧多一档 `not-found`(`McpEditFailure`)—— 判据在那个类型上:
|
|
4759
|
+
* 「这个名字不合法」和「合法但文件里没这一台」的下一步完全不同。
|
|
4760
|
+
*/
|
|
4761
|
+
type McpUpdateOutcome = {
|
|
4762
|
+
ok: true;
|
|
4763
|
+
status: McpServerStatus;
|
|
4764
|
+
configPath: string;
|
|
4765
|
+
} | McpEditFailure;
|
|
4766
|
+
/**
|
|
4767
|
+
* 一次「删」的结局(2026-09-02)。
|
|
4768
|
+
*
|
|
4769
|
+
* ⚠️ **没有 `status`**,那不是漏了:这台 server 在这个进程里已经不存在了,
|
|
4770
|
+
* 交一份状态出去就是交一份必然过期的东西。回执要说的只有「从哪个文件里删掉的」。
|
|
4771
|
+
*/
|
|
4772
|
+
type McpRemoveOutcome = {
|
|
4773
|
+
ok: true;
|
|
4774
|
+
configPath: string;
|
|
4775
|
+
} | McpEditFailure;
|
|
4534
4776
|
/**
|
|
4535
4777
|
* 宿主看得见的那一面 —— **重连,加上「添加」**(后者 2026-08-18 补的)。
|
|
4536
4778
|
*
|
|
@@ -4582,6 +4824,34 @@ interface McpControl {
|
|
|
4582
4824
|
* 不是「能不能添加」。别拿这个方法倒推出一个 `setEnabled`。
|
|
4583
4825
|
*/
|
|
4584
4826
|
add(input: McpAddInput): Promise<McpAddOutcome>;
|
|
4827
|
+
/**
|
|
4828
|
+
* 改一台已有 server(2026-09-02):就地换掉 `mcp.json` 里那一段,再按新配置
|
|
4829
|
+
* 把它重连一次。
|
|
4830
|
+
*
|
|
4831
|
+
* ## ⚠️ 它和 {@link applyConfig} 不是同一个动作,别把它实现成那个的单台版
|
|
4832
|
+
*
|
|
4833
|
+
* `applyConfig` 的宾语是**整份文件**:该断的断、该连的连,包括用户此刻在
|
|
4834
|
+
* 编辑器里刚存下的别的改动。这一条的宾语是**你点名的那一台** —— 别的 server
|
|
4835
|
+
* 一个字节都不碰,哪怕文件里有别人刚写下的待生效改动。
|
|
4836
|
+
*
|
|
4837
|
+
* 那条差别对模型侧那个工具是硬要求:一次 `mcp_update` 顺带把用户还没打算生效
|
|
4838
|
+
* 的三台一起重连了,屏幕上说的却是「改了一台」。
|
|
4839
|
+
*
|
|
4840
|
+
* ## ⚠️ 名字改不了,那是形状不是限制
|
|
4841
|
+
*
|
|
4842
|
+
* `input.name` 是**查询键**,不是新名字。改名等于换掉这台 server 暴露的
|
|
4843
|
+
* 每一个工具名(`mcp__<name>__<工具>`),判据全文在
|
|
4844
|
+
* [mcp-config-edit.ts](./mcp-config-edit.ts) 文件头第二节。
|
|
4845
|
+
*/
|
|
4846
|
+
update(input: McpAddInput): Promise<McpUpdateOutcome>;
|
|
4847
|
+
/**
|
|
4848
|
+
* 删掉一台 server(2026-09-02):从 `mcp.json` 里去掉那一段,在这个进程里
|
|
4849
|
+
* 断开它,并把它那批工具从注册表里摘掉。**不可撤销。**
|
|
4850
|
+
*
|
|
4851
|
+
* ⚠️ 只删得掉 `mcp.json` 里那批(`source === 'user'`)。宿主注入的和插件带来
|
|
4852
|
+
* 的两批天生不在这份文件里 —— 判据同 {@link applyConfig} 那条 ⚠️。
|
|
4853
|
+
*/
|
|
4854
|
+
remove(name: string): Promise<McpRemoveOutcome>;
|
|
4585
4855
|
/** 读一遍 `~/.epoch/mcp.json` 的**原文**(2026-08-18)。什么都不改 */
|
|
4586
4856
|
readConfig(): McpConfigReadOutcome;
|
|
4587
4857
|
/**
|
|
@@ -4939,6 +5209,18 @@ declare function utilityBudgetKey(input: {
|
|
|
4939
5209
|
* 而且**只回文本**:二进制探测在前 8KB 上做(判据同 `file_read` 那一条),
|
|
4940
5210
|
* 不让一个 100MB 的 mp4 为了确认自己是二进制而整个进内存。
|
|
4941
5211
|
*
|
|
5212
|
+
* ## ⚠️ 2026-09-02:这一格**多了第二个消费方**,而它才是它今天最要紧的用途
|
|
5213
|
+
*
|
|
5214
|
+
* Web UI 的「输出里的路径可点 → 站内查看」走的就是这三个方法
|
|
5215
|
+
* (`GET /api/sessions/:id/file-stat` / `/file` / `/file-bytes`)。
|
|
5216
|
+
* `web/src/inspector/preview.tsx` 的文件头当年拒过这件事,理由是「一个 REST
|
|
5217
|
+
* 端点不走工具执行器,用户写的 `deny file_read(.env*)` 拦不到它」——
|
|
5218
|
+
* **那条理由说的是别处,不是这儿**:上面第 3 条正是为它准备的。
|
|
5219
|
+
*
|
|
5220
|
+
* 所以这一格从今天起是一条**用户也走的路**,而不只是 `@` 补全的后台。
|
|
5221
|
+
* 往上面加方法时判据没变:**任何一个新方法都必须过同一道 `file_read` 闸**,
|
|
5222
|
+
* 少一个,浏览器就能看到模型看不到的东西 —— 而那正是当年拒绝那条端点的理由本身。
|
|
5223
|
+
*
|
|
4942
5224
|
* ## 这一份是**镜像**,不是「把 core 的类型导出来」
|
|
4943
5225
|
*
|
|
4944
5226
|
* 三个函数和 core 的 `WorkspaceFiles` / `resolveInWorkspace` / `readOne` 逐个
|
|
@@ -4973,9 +5255,9 @@ interface FileCandidatesResult {
|
|
|
4973
5255
|
/**
|
|
4974
5256
|
* 装配时要递进来的 core 那一份。
|
|
4975
5257
|
*
|
|
4976
|
-
*
|
|
4977
|
-
*
|
|
4978
|
-
*
|
|
5258
|
+
* 写成**三个函数**而不是一个对象:这一格只要这几样,而它们今天住在 core 的
|
|
5259
|
+
* 三个不同文件里 —— 合成一个对象的话,宿主将来想自己供一份(比如内存文件树)
|
|
5260
|
+
* 就得把几个不相关的东西绑在一起。
|
|
4979
5261
|
*/
|
|
4980
5262
|
interface WorkspaceFilesDeps {
|
|
4981
5263
|
listFiles(root: string): {
|
|
@@ -4983,6 +5265,17 @@ interface WorkspaceFilesDeps {
|
|
|
4983
5265
|
truncated: boolean;
|
|
4984
5266
|
};
|
|
4985
5267
|
resolveInWorkspace(root: string, userPath: string): string | null;
|
|
5268
|
+
/**
|
|
5269
|
+
* 三平台「交给系统默认应用」的命令表(core 的 `systemOpenArgv`)。
|
|
5270
|
+
*
|
|
5271
|
+
* 递进来而不是 import:判据同上面两个 —— 这个文件一行都不 import core,
|
|
5272
|
+
* 于是它能拿假依赖直接单测。**用例里那份假的正是这一格最要紧的用处**:
|
|
5273
|
+
* 真的去 `spawn` 一个 `open` 会在跑用例的那台机器上弹出窗口。
|
|
5274
|
+
*/
|
|
5275
|
+
systemOpenArgv(abs: string): {
|
|
5276
|
+
command: string;
|
|
5277
|
+
args: string[];
|
|
5278
|
+
};
|
|
4986
5279
|
}
|
|
4987
5280
|
/**
|
|
4988
5281
|
* 读一次文件的结果。
|
|
@@ -5002,7 +5295,58 @@ type FileReadOutcome = {
|
|
|
5002
5295
|
bytes?: number;
|
|
5003
5296
|
};
|
|
5004
5297
|
/**
|
|
5005
|
-
*
|
|
5298
|
+
* 看一眼一个文件是什么的结果({@link WorkspaceFilesView.statFile})。
|
|
5299
|
+
*
|
|
5300
|
+
* **码比 {@link FileReadOutcome} 多两档**,因为这一格答的问题不同:`readFile`
|
|
5301
|
+
* 只需要回答「拿不拿得到正文」,而这一格是「路径可点」那条路的判据 ——
|
|
5302
|
+
* 「这儿本来就没东西」(`missing`)和「这是个目录」(`not-a-file`)在界面上是
|
|
5303
|
+
* 两句不同的话,糊成一个 `unreadable` 的代价是用户永远不知道自己是不是打错了路径。
|
|
5304
|
+
*
|
|
5305
|
+
* ⚠️ **`denied` 照旧一个码盖住两件事**(跑出工作区 / 权限不放行),
|
|
5306
|
+
* 判据逐字同 `FileReadOutcome`:分开说等于承认「这个路径存在,只是你没权限」。
|
|
5307
|
+
*/
|
|
5308
|
+
type FileStatOutcome = {
|
|
5309
|
+
ok: true;
|
|
5310
|
+
/** 归一化之后的**工作区相对路径**(分隔符统一成 `/`)。调用方拿它去 `readFile` */
|
|
5311
|
+
rel: string;
|
|
5312
|
+
/** 绝对真路径(`realpath` 之后)。给 `createReadStream` 用,见 `statFile` 的 JSDoc */
|
|
5313
|
+
absPath: string;
|
|
5314
|
+
bytes: number;
|
|
5315
|
+
mtimeMs: number;
|
|
5316
|
+
/** 前 8KB 里有没有裸 NUL。判据同 `readFile` 那一处 */
|
|
5317
|
+
kind: 'text' | 'binary';
|
|
5318
|
+
} | {
|
|
5319
|
+
ok: false;
|
|
5320
|
+
reason: 'denied' | 'missing' | 'not-a-file' | 'unreadable';
|
|
5321
|
+
};
|
|
5322
|
+
/**
|
|
5323
|
+
* 这个路径的扩展名在白名单里吗。
|
|
5324
|
+
*
|
|
5325
|
+
* 导出是因为**服务端要拿它算 `WireFileStatOk.openable` 那一格** —— 那一格决定
|
|
5326
|
+
* 界面上画不画那个按钮,而「画一个点了没反应的按钮比不画更糟」(决定 20 ①)。
|
|
5327
|
+
* ⚠️ 它**只答扩展名这一问**:还在不在工作区里、权限放不放行、是不是同一台机器,
|
|
5328
|
+
* 各有各的闸,别拿它一个当结论。
|
|
5329
|
+
*/
|
|
5330
|
+
declare function isSystemOpenable(path: string): boolean;
|
|
5331
|
+
/**
|
|
5332
|
+
* 「用什么命令把这个工作区文件交给系统默认应用」的结果。
|
|
5333
|
+
*
|
|
5334
|
+
* **它不含副作用** —— 真正的 `spawn` 在调用方(服务端)。理由同 core 那一侧
|
|
5335
|
+
* `artifactLaunchArgv` / `openArtifact` 的分法:argv 是能在三个平台上被用例
|
|
5336
|
+
* 逐字钉住的东西,而 `spawn` 不是(真跑一次会在跑用例那台机器上弹出窗口)。
|
|
5337
|
+
*/
|
|
5338
|
+
type FileOpenPlan = {
|
|
5339
|
+
ok: true;
|
|
5340
|
+
command: string;
|
|
5341
|
+
args: readonly string[];
|
|
5342
|
+
absPath: string;
|
|
5343
|
+
} | {
|
|
5344
|
+
ok: false;
|
|
5345
|
+
reason: 'denied' | 'missing' | 'not-a-file' | 'unreadable' | 'extension-not-allowed';
|
|
5346
|
+
};
|
|
5347
|
+
/**
|
|
5348
|
+
* `EpochRuntime.workspaceFiles` 在服务端眼里的样子 —— **三个方法,一个不多**:
|
|
5349
|
+
* 列候选(`@` 面板)、读正文(`@` 提及 + 站内查看)、看一眼是什么(路径可点)。
|
|
5006
5350
|
*
|
|
5007
5351
|
* ⚠️ 每一次调用都收 `root`,**不许在门面上存一个「当前工作区」**:这个门面是
|
|
5008
5352
|
* **进程级**的(像 `permission`),而工作区**一个会话绑一个**(决定 18)。存根的话,
|
|
@@ -5035,11 +5379,47 @@ interface WorkspaceFilesView {
|
|
|
5035
5379
|
* 这一格不替调用方算(它不知道这条消息已经用掉多少预算)。
|
|
5036
5380
|
*/
|
|
5037
5381
|
readFile(path: string, root: string): FileReadOutcome;
|
|
5382
|
+
/**
|
|
5383
|
+
* 「这个路径今天还在不在、是什么、画得出来吗」—— **不读正文**。
|
|
5384
|
+
*
|
|
5385
|
+
* Web UI 那条「路径可点」的路(`GET /api/sessions/:id/file-stat`)一发要问一批
|
|
5386
|
+
* 路径,而它们里面完全可能有一个 100MB 的 mp4:为了确认它存在就把它读进内存,
|
|
5387
|
+
* 是这一格和 {@link readFile} 分成两个方法的**全部理由**。
|
|
5388
|
+
*
|
|
5389
|
+
* 三处和 `readFile` 不一样,逐条都有理由:
|
|
5390
|
+
*
|
|
5391
|
+
* 1. **收得下绝对路径**(先按 `root` 归一化成相对路径再进闸)。`@` 那条路
|
|
5392
|
+
* 刻意拒绝绝对路径(`resolveInWorkspace` 的第一道),因为那是用户手打的;
|
|
5393
|
+
* 而这一格收的是**模型正文里印出来的路径**,那儿绝大多数是绝对路径。
|
|
5394
|
+
* 归一化之后跑不回工作区里的,照样是 `denied`。
|
|
5395
|
+
* 2. **多一道 `realpath` 复查**。`resolveInWorkspace` 只做字符串上的边界判定,
|
|
5396
|
+
* 看不见符号链接 —— 而工作区里完全可以躺着一个指向 `~/.ssh/id_rsa` 的链接
|
|
5397
|
+
* (判据同 `server/src/assets.ts` 那四道闸的第 3 道)。
|
|
5398
|
+
* 3. **二进制探测只 `read` 前 8KB**(`readFile` 是整个读完再看前 8KB)。
|
|
5399
|
+
*
|
|
5400
|
+
* `absPath` 回给调用方是为了**字节那条路**(`file-bytes` 要 `createReadStream`):
|
|
5401
|
+
* 闸在这一格,流在服务端 —— 服务端拿到它就等于拿到了一张通行证,
|
|
5402
|
+
* 所以这个字段只在 `ok` 那一档存在。
|
|
5403
|
+
*/
|
|
5404
|
+
statFile(path: string, root: string): FileStatOutcome;
|
|
5405
|
+
/**
|
|
5406
|
+
* 「这个文件能不能交给系统默认应用,用什么命令」—— **不 spawn**。
|
|
5407
|
+
*
|
|
5408
|
+
* 闸是 {@link statFile} 那一整套(边界 → `file_read` 权限 → `realpath` 复查 →
|
|
5409
|
+
* 必须是普通文件),**外加一道扩展名白名单**({@link isSystemOpenable})。
|
|
5410
|
+
* 也就是说:**模型读不到的文件,用户也点不开** —— 这条路没有给浏览器开小灶。
|
|
5411
|
+
*
|
|
5412
|
+
* ⚠️ 这一格答不了、也不该答的那一问是「浏览器和服务在不在同一台机器上」。
|
|
5413
|
+
* 那是服务端的事实(`--host 0.0.0.0` / SSH),判据在
|
|
5414
|
+
* `server/src/workspace/native-pick.ts`。**调用方必须先过那道闸再来问这一格**,
|
|
5415
|
+
* 否则框会弹在别人屏幕上。
|
|
5416
|
+
*/
|
|
5417
|
+
planSystemOpen(path: string, root: string): FileOpenPlan;
|
|
5038
5418
|
}
|
|
5039
5419
|
/**
|
|
5040
5420
|
* 建那个收窄面。
|
|
5041
5421
|
*
|
|
5042
|
-
* @param deps core
|
|
5422
|
+
* @param deps core 递来的那几样(装配那一行从 `@epoch-agent/core` import)。
|
|
5043
5423
|
* @param permission 权限管理器;**可为 null** —— 那时的语义同 core 那条路的
|
|
5044
5424
|
* 判据:权限模块没起来,模型调 `file_read` 照样读得到,
|
|
5045
5425
|
* `@` 单方面收紧只会让两条路给出不一致的答案。
|
|
@@ -5459,19 +5839,31 @@ interface MemoryControl {
|
|
|
5459
5839
|
}>;
|
|
5460
5840
|
}
|
|
5461
5841
|
/**
|
|
5462
|
-
*
|
|
5842
|
+
* 技能那一栏的**写口**(方案 42 §六,2026-08-18;2026-09-01 加上删除)——
|
|
5843
|
+
* 三个动作:预览、导入、删除。前两个收的是路径,第三个收的是技能名。
|
|
5463
5844
|
*
|
|
5464
5845
|
* ## 为什么不是把 `SkillSystem` 转出去
|
|
5465
5846
|
*
|
|
5466
5847
|
* 同 `MemoryControl` 那条判据,但这一条更硬:`SkillSystem` 上有 `create` /
|
|
5467
5848
|
* `edit` / `patch` / `delete` / `writeFile` / `removeFile` 六个写方法,还有
|
|
5468
|
-
* `view()`(**会 `matchCount
|
|
5469
|
-
*
|
|
5470
|
-
* 够得着「删掉用户的技能」和「污染评分」这两件事 —— 而这一轮要的只是「多一份」。
|
|
5849
|
+
* `view()`(**会 `matchCount++`**,喂着模型看得见哪些技能那本账)。把它整个
|
|
5850
|
+
* 交出去,等于让网线那一侧够得着「改写用户的技能」和「污染评分」这两件事。
|
|
5471
5851
|
*
|
|
5472
|
-
*
|
|
5852
|
+
* 收窄成这三个方法之后,server 那个包**写不出**别的动作:那不是靠 review 盯住
|
|
5473
5853
|
* 的,是编译期的事(同 `McpControl` 只露 `reconnect` 那一条)。
|
|
5474
5854
|
*
|
|
5855
|
+
* ## ⚠️ 2026-09-01 这一格从两个动作变成三个,判据记在这儿
|
|
5856
|
+
*
|
|
5857
|
+
* 上一版这段话里写着「这一轮要的只是『多一份』」,于是删除被刻意挡在外面。
|
|
5858
|
+
* 那句话今天不成立了:能力页上导得进来、**却没有任何一条路把它拿掉** ——
|
|
5859
|
+
* 用户唯一的出路是自己去 `~/.epoch/skills/` 里 `rm -r`,而那一栏底下那句
|
|
5860
|
+
* 「不想要它,把那个目录挪走」正是在教他这么干。**一个只能进不能出的写口
|
|
5861
|
+
* 不是「更安全」,它只是把不可撤销的那一半推给了用户的终端。**
|
|
5862
|
+
*
|
|
5863
|
+
* 放进来的是 {@link SkillControl.remove} 一个,`create` / `edit` / `patch` /
|
|
5864
|
+
* `writeFile` / `removeFile` / `view` 一个都没跟着进来 —— 那几个是「替用户
|
|
5865
|
+
* 写内容」,和「把用户自己装的那份拿掉」不是一件事。
|
|
5866
|
+
*
|
|
5475
5867
|
* ## ⚠️ 这两个动作都**不收字节,只收一条服务端本机路径**
|
|
5476
5868
|
*
|
|
5477
5869
|
* 这是这一条路上的安全性质本体,不是实现细节。判据全文在 core 的
|
|
@@ -5502,7 +5894,43 @@ interface SkillControl {
|
|
|
5502
5894
|
* 一个字都不受它影响
|
|
5503
5895
|
*/
|
|
5504
5896
|
import: (source: string, token: string, lang?: Lang) => Promise<SkillImportResult<SkillImportDone>>;
|
|
5897
|
+
/**
|
|
5898
|
+
* 删掉一个**用户级**技能(2026-09-01)—— 整个目录,递归删,**不可撤销**。
|
|
5899
|
+
*
|
|
5900
|
+
* ⚠️ **它收的是技能名,不是路径**,而那是这个动作上的闸门:宿主说得出
|
|
5901
|
+
* 「删掉叫 alpha 的那个技能」,说不出「删掉 `/etc/…`」。落点由
|
|
5902
|
+
* `SkillSystem` 自己查(它只认自己扫出来的那张表),这一层一个路径都不接。
|
|
5903
|
+
*
|
|
5904
|
+
* ⚠️ **项目 / 插件 / 宿主三档删不掉**,真闸门在 core 的 `requireWritable()`。
|
|
5905
|
+
* 这里额外先查一次 `list()` 只是为了给出 `readonly` 这个**原因码**(好让界面
|
|
5906
|
+
* 说得出下一步),不是第二道闸 —— 别把这里的判断当成安全边界。
|
|
5907
|
+
*
|
|
5908
|
+
* @param lang 回执那句话(含 core 那三句 `readonly_*` 原话)用哪个语言说。
|
|
5909
|
+
* ⚠️ 同 {@link SkillControl.import}:**它只影响回给这一次请求的话**,
|
|
5910
|
+
* 盘上删掉什么一个字都不受它影响
|
|
5911
|
+
*/
|
|
5912
|
+
remove: (name: string, lang?: Lang) => Promise<SkillRemoveResult>;
|
|
5505
5913
|
}
|
|
5914
|
+
/** 删不成的四档。判据(每一档对哪个下一步)在 protocol 的 `WireSkillRemoveFailure` 上 */
|
|
5915
|
+
type SkillRemoveFailure = 'missing' | 'not-found' | 'readonly' | 'failed';
|
|
5916
|
+
/**
|
|
5917
|
+
* 删一次的结局。
|
|
5918
|
+
*
|
|
5919
|
+
* 成了那一支带 {@link SkillRemoveDone.path} —— **不可撤销的动作,回执得说得出
|
|
5920
|
+
* 销毁的是盘上哪一处**(判据在 protocol 的 `WireSkillRemoveResponse.path` 上)。
|
|
5921
|
+
*/
|
|
5922
|
+
interface SkillRemoveDone {
|
|
5923
|
+
name: string;
|
|
5924
|
+
/** 刚刚被递归删掉的那个目录(`<技能目录>/<分类>/<名字>`) */
|
|
5925
|
+
path: string;
|
|
5926
|
+
}
|
|
5927
|
+
type SkillRemoveResult = ({
|
|
5928
|
+
ok: true;
|
|
5929
|
+
} & SkillRemoveDone) | {
|
|
5930
|
+
ok: false;
|
|
5931
|
+
reason: SkillRemoveFailure;
|
|
5932
|
+
detail: string;
|
|
5933
|
+
};
|
|
5506
5934
|
/**
|
|
5507
5935
|
* 一条会话在列表里的样子。
|
|
5508
5936
|
*
|
|
@@ -5739,11 +6167,17 @@ interface PermissionsControl {
|
|
|
5739
6167
|
* `config.permission`,一个纯展示值,改它没有任何意义也没有任何危害 ——
|
|
5740
6168
|
* 而宿主在那一档下本来就不该画出口(web 那侧靠 `editable: false`)。
|
|
5741
6169
|
*
|
|
6170
|
+
* @param opts.remember 这一发**算不算用户显式挑的**(2026-08-31)。必填,
|
|
6171
|
+
* 判据和那张三行表在 `session-guard.ts` 的 `DefaultLevelSlot` 上。
|
|
6172
|
+
* ⚠️ 这个包自己就有一个该传 `false` 的调用点(`schedule/executor.ts` 拿
|
|
6173
|
+
* `def.permission` 起一轮定时任务)—— 那是这个参数存在的直接理由。
|
|
5742
6174
|
* @returns 换成了没有。**不回「换成了哪一档」** —— 那个问 `level()`,
|
|
5743
6175
|
* 一次调用只答一个问题(判据同 `server/src/plan-mode.ts` 那句「状态回读,
|
|
5744
|
-
*
|
|
6176
|
+
* 不用上一步的返回值拼」)。「记住了没有」是另一件事,跟在 `ok:true` 那一支上。
|
|
5745
6177
|
*/
|
|
5746
|
-
setLevel: (level: PermissionLevel
|
|
6178
|
+
setLevel: (level: PermissionLevel, opts: {
|
|
6179
|
+
remember: boolean;
|
|
6180
|
+
}) => LevelChangeResult;
|
|
5747
6181
|
/**
|
|
5748
6182
|
* 工具表 × 判定三轴 —— 「这个工具现在能不能调,是谁在挡它」(方案 43 §七)。
|
|
5749
6183
|
*
|
|
@@ -5817,10 +6251,23 @@ interface EffectiveSetting {
|
|
|
5817
6251
|
* 空数组 = 一层都写不了,宿主据此一个写控件都不画。三种成因和整个判据在 core 的
|
|
5818
6252
|
* `settingWriteTargets()` 上。
|
|
5819
6253
|
*
|
|
5820
|
-
* ⚠️ 它和 `overridable`
|
|
5821
|
-
*
|
|
6254
|
+
* ⚠️ 它和 `overridable` **不是一件事,而且从 2026-08-31 起真的分叉了**
|
|
6255
|
+
* (在那之前它们恰好同真同假):前者说的是「**别人的仓库**能不能覆盖这个键」,
|
|
6256
|
+
* 这一个说的是「宿主能不能替用户改他自己的用户级配置」。
|
|
6257
|
+
* 于是 `budget.*` / `compression.*` 这些行 `overridable: false`、`chain: []`、
|
|
6258
|
+
* 却有一个只含 ② 用户级的 `writes`。判据在 core 的 `config/write.ts` 第二节。
|
|
5822
6259
|
*/
|
|
5823
6260
|
writes: readonly SettingWriteTarget[];
|
|
6261
|
+
/**
|
|
6262
|
+
* 宿主该给这一行画哪种输入控件。**`writes` 非空时必有,空时必无。**
|
|
6263
|
+
*
|
|
6264
|
+
* 真源是 core 的 `settingValueKind()`(从值域 schema 上认)。宿主自己按
|
|
6265
|
+
* 「这一行现在的值是什么类型」去猜是**不行的** —— `budget.*` 四行没有默认值,
|
|
6266
|
+
* 屏幕上是「未设置」、链条上也是空的,猜出来一律是字符串。
|
|
6267
|
+
*/
|
|
6268
|
+
valueKind?: SettingValueKind;
|
|
6269
|
+
/** 这一行只有这几个值可选(枚举)。不是枚举时**整个不带这个键** */
|
|
6270
|
+
choices?: readonly string[];
|
|
5824
6271
|
}
|
|
5825
6272
|
/** {@link SettingsControl.write} 的入参。`key` / `layer` 都由这一层再验一遍 */
|
|
5826
6273
|
interface SettingWriteInput {
|
|
@@ -6398,16 +6845,23 @@ interface EpochRuntime {
|
|
|
6398
6845
|
*/
|
|
6399
6846
|
skillIndexResidency(role: string | null): ReadonlyMap<string, SkillIndexResidency>;
|
|
6400
6847
|
/**
|
|
6401
|
-
*
|
|
6848
|
+
* 往技能目录里写的那一格(方案 42 §六,2026-08-18):从**本机目录**导入,
|
|
6849
|
+
* 以及(2026-09-01 起)删掉一条用户级技能。**不可为 null** ——
|
|
6402
6850
|
* 技能系统没起来时它照样在,只是每次都回 `missing`,而那是一句真话
|
|
6403
6851
|
* (同 `mcp` 那条「一个 server 都没配也照样在」)。
|
|
6404
6852
|
*
|
|
6405
6853
|
* ⚠️ 收的是**路径**不是字节,判据在 {@link SkillControl} 上。
|
|
6854
|
+
*
|
|
6855
|
+
* ⚠️ **2026-09-01 从 `skillImport` 改名成 `skillWrite`**(跟着 `roleWrite`
|
|
6856
|
+
* 那一格的叫法)。改名不是整理:那天这一格多了一个 `remove`,而一个叫
|
|
6857
|
+
* `skillImport` 的字段上挂着「递归删目录」是这一层最不该有的假话 ——
|
|
6858
|
+
* 读到它的人会以为自己在看一个只进不出的口子。叫 `skills` 不行,
|
|
6859
|
+
* 那个名字已经被「列出来」那一格占了({@link skills})。
|
|
6406
6860
|
*/
|
|
6407
|
-
|
|
6861
|
+
skillWrite: SkillControl;
|
|
6408
6862
|
/**
|
|
6409
6863
|
* 建一个用户级身份(2026-08-18)——「新建一个身份」那条路。**不可为 null**:
|
|
6410
|
-
* 底下只有一次文件写,没有起不来的可能(同 `
|
|
6864
|
+
* 底下只有一次文件写,没有起不来的可能(同 `skillWrite` 那条)。
|
|
6411
6865
|
*
|
|
6412
6866
|
* ⚠️ **闸门不在这一格上。** 「一个可能不在本机的浏览器能不能往 system prompt
|
|
6413
6867
|
* 里写一段任意文字」判成**回环绑定才给写**,落点是 `server/src/role-add.ts`
|
|
@@ -6435,7 +6889,7 @@ interface EpochRuntime {
|
|
|
6435
6889
|
* [plugin-control.ts](./plugin-control.ts) 的文件头第二节。
|
|
6436
6890
|
*
|
|
6437
6891
|
* ⚠️ 这条路**只收 `<市场>/<插件>`,收不了一条路径** —— 那是这一轮的安全性质
|
|
6438
|
-
* 本体(比 {@link
|
|
6892
|
+
* 本体(比 {@link skillWrite} 那道闸更紧),判据在那个文件头第四节。
|
|
6439
6893
|
*/
|
|
6440
6894
|
plugins: PluginControl | null;
|
|
6441
6895
|
/**
|
|
@@ -7132,4 +7586,132 @@ declare function probeMcpServer(name: string, opts?: McpAdminOptions): Promise<{
|
|
|
7132
7586
|
needsLogin: boolean;
|
|
7133
7587
|
}>;
|
|
7134
7588
|
|
|
7135
|
-
|
|
7589
|
+
/**
|
|
7590
|
+
* `mcp_list` / `mcp_add` / `mcp_update` / `mcp_remove` —— 模型**管 MCP 自己**
|
|
7591
|
+
* 的那四条路(2026-09-02)。
|
|
7592
|
+
*
|
|
7593
|
+
* ## 在此之前这一格是空的,而不是「没做完」
|
|
7594
|
+
*
|
|
7595
|
+
* MCP 的管理面一直只有人侧那三条:CLI(`epoch mcp`)、HTTP(`POST /api/mcp`、
|
|
7596
|
+
* `GET`/`PUT /api/mcp/config`、`POST /api/mcp/config/apply`)、Web 能力页。
|
|
7597
|
+
* [docs/TOOLS.md](../../../docs/TOOLS.md) 那张总表里 MCP 那一格写的是「MCP
|
|
7598
|
+
* server **暴露出来的**工具」—— 一个管 MCP 自己的工具都没有。
|
|
7599
|
+
*
|
|
7600
|
+
* 模型想加一台的唯一出路是拿 `terminal` 去写 `~/.epoch/mcp.json`,而那条路有
|
|
7601
|
+
* 两处会骗人:写完不生效(落盘和生效是分开的两步),以及它绕过了名字校验和
|
|
7602
|
+
* 撞名检查(判据在 `isWritableName` 和 `addServer` 上)。
|
|
7603
|
+
*
|
|
7604
|
+
* ## ⚠️ 一、为什么这四个归 `command` 而不是 `file_write`
|
|
7605
|
+
*
|
|
7606
|
+
* **写一台 stdio server 进 `mcp.json` 就是配一条以后会被 `spawn` 的命令行。**
|
|
7607
|
+
* 归 `file_write` 的表现很具体:`PermissionManager.ruleMatches` 是按**类别**
|
|
7608
|
+
* 授权的(规则到不了工具名这一级,判据在 `operation-type.ts` 的
|
|
7609
|
+
* `COMMAND_TOOLS` 上),于是一条 `allow: mcp_add` 顺带放行的是任意进程启动。
|
|
7610
|
+
*
|
|
7611
|
+
* ⚠️ 而且这一族比 `terminal` 还多一条性质:**它的引信是延迟的**。写下去的那台
|
|
7612
|
+
* 在下一次 `epoch` 启动时(**任何**宿主,CLI / TUI / Web)被连上并起进程,
|
|
7613
|
+
* 而那时屏幕上不会有任何东西指回这一次工具调用。
|
|
7614
|
+
*
|
|
7615
|
+
* `mcp_list` 是这四个里唯一的例外,归 `file_read`:它读的是**进程内**那张状态
|
|
7616
|
+
* 表,一个字节都不写。归 `file_read` 还有第二个作用 —— `plan` 级别下也列得出
|
|
7617
|
+
* 来(同会话检索那四个的判据:调研阶段恰恰最该先看清手上有什么)。
|
|
7618
|
+
*
|
|
7619
|
+
* ### ⚠️ 写那三个**不进** `NON_SHELL_COMMAND_TOOLS`,那是判断不是漏登记
|
|
7620
|
+
*
|
|
7621
|
+
* 那张表(`core/src/permission/operation-type.ts`)摘掉的是 `checkObfuscation()`
|
|
7622
|
+
* ——一层**只对真 shell 命令成立**的语法启发式。按那个文件上写的判据问一句
|
|
7623
|
+
* 「这个工具的 `detail` 会被交给某个 shell 吗」,这三个的答案是**不会**
|
|
7624
|
+
* (MCP stdio 走的是 `spawn`,不过 shell),所以它们**够格**进那张表。
|
|
7625
|
+
*
|
|
7626
|
+
* 这一轮仍然不进,理由是代价不对称:不进的坏处是一个带 `(` 的 URL 在 Windows 上
|
|
7627
|
+
* 会多弹一次确认框、且理由那句话说得不准(`run_code` / `delegate_task` 当年就是
|
|
7628
|
+
* 因为这个才进表的);而进表的坏处是对一段**即将被 spawn 的命令行**少跑一层
|
|
7629
|
+
* 检查。前者是一次多余的确认,后者是一层真的没了 —— 在没有实测到误报之前,
|
|
7630
|
+
* 往严的那边偏。⚠️ **真碰到误报再进表,别照着「够格」就登记。**
|
|
7631
|
+
*
|
|
7632
|
+
* ## ⚠️ 二、`mcp.json` 的**原文一个字节都不交出去**
|
|
7633
|
+
*
|
|
7634
|
+
* 这一条是这几个工具的形状本体,不是实现细节。`McpControl` 上现成就有
|
|
7635
|
+
* `readConfig()` / `writeConfig()`(浏览器里那个文本编辑器走的就是它们),
|
|
7636
|
+
* 拿它们包两个工具是最省事的做法,**而这里刻意不那么做**:
|
|
7637
|
+
*
|
|
7638
|
+
* 那份文件里装着用户**每一台** server 的 `env` 密钥(判据在
|
|
7639
|
+
* `writeMcpConfigFile` 那句「新建的那一档给 0600,因为这个文件天生装着 token」)。
|
|
7640
|
+
* 一个「读配置」工具等于让模型每次改一台之前先把全部密钥读进上下文 —— 而上下文
|
|
7641
|
+
* 会进日志、进会话库、进下一轮请求体。
|
|
7642
|
+
*
|
|
7643
|
+
* 所以这一族收的全是**结构化输入**(名字 + 那几格),改哪一台就只碰那一台;
|
|
7644
|
+
* 就地替换 / 删除那两条路因此才必须存在(判据在
|
|
7645
|
+
* [mcp-config-edit.ts](./mcp-config-edit.ts) 文件头第一句)。
|
|
7646
|
+
*
|
|
7647
|
+
* ⚠️ **`mcp_list` 也不回 `env`,也不回 `command` / `url`。** 它回的是
|
|
7648
|
+
* `McpServerStatus` 那几格(名字 / 来源 / 连上没 / 几个工具 / 错在哪)——
|
|
7649
|
+
* 那是「下一步该做什么」需要的全部,而 `command` 那一格是密钥最爱藏的地方
|
|
7650
|
+
* 之一(`npx -y foo --token=…`)。
|
|
7651
|
+
*
|
|
7652
|
+
* ## 三、这四个工具住在 runtime 而不是 core
|
|
7653
|
+
*
|
|
7654
|
+
* 它们要的是 `McpControl`,而那是装配层的东西(`registerMcpTools` 的产物)。
|
|
7655
|
+
* core 那边的 `tools/builtin/` 收的是「core 自身服务的薄封装」(判据在那个目录
|
|
7656
|
+
* 的 `index.ts` 文件头),把 `McpControl` 提成 core 的公开 API 只为了让工具住
|
|
7657
|
+
* 进去,是把插件契约撑宽来换一个目录位置。
|
|
7658
|
+
*
|
|
7659
|
+
* ⚠️ 根 `__tests__/tool-registration.test.ts` 扫的是**每个包的 `src`**,
|
|
7660
|
+
* 所以住在这儿照样被那道门禁盯着(`operation` + `operation-type.ts` 的白名单
|
|
7661
|
+
* + `docs/TOOLS.md` 三处同构)。
|
|
7662
|
+
*
|
|
7663
|
+
* ## 四、错误话术为什么是英文
|
|
7664
|
+
*
|
|
7665
|
+
* 判据逐字同 [skill-view.ts](../../core/src/tools/builtin/skill-view.ts) 第七节。
|
|
7666
|
+
* ⚠️ 但**下一层给的 `detail` 原样转发**(`mcp_write.*` 那几句走 `t()`、
|
|
7667
|
+
* `EACCES` 那半截是 Node 的原文)—— 判据同 `McpReconnectOutcome.detail` 那条
|
|
7668
|
+
* 「原样转发底层那句话,这一层不另写一句」。
|
|
7669
|
+
*/
|
|
7670
|
+
|
|
7671
|
+
/**
|
|
7672
|
+
* 四个工具名。**这是同构位置的真源** —— 另外两处认它们:
|
|
7673
|
+
* `core/src/permission/operation-type.ts`(`mcp_list` 进 `PROCESS_TABLE_TOOLS`,
|
|
7674
|
+
* 另外三个进 `COMMAND_TOOLS`)、`docs/TOOLS.md` 顶上那张表。
|
|
7675
|
+
*
|
|
7676
|
+
* ⚠️ **下面 `name:` 那几格写的是字面量,不是这些常量,别「顺手」统一** ——
|
|
7677
|
+
* 判据全文在 core 的 `SKILL_VIEW_TOOL` 的 JSDoc 上。
|
|
7678
|
+
*/
|
|
7679
|
+
declare const MCP_LIST_TOOL = "mcp_list";
|
|
7680
|
+
declare const MCP_ADD_TOOL = "mcp_add";
|
|
7681
|
+
declare const MCP_UPDATE_TOOL = "mcp_update";
|
|
7682
|
+
declare const MCP_REMOVE_TOOL = "mcp_remove";
|
|
7683
|
+
/**
|
|
7684
|
+
* 这四个工具要装配层的哪两样。
|
|
7685
|
+
*
|
|
7686
|
+
* ⚠️ **`readConfig` / `writeConfig` 不在这里,那是刻意的** —— 完整判据在文件头
|
|
7687
|
+
* 第二节。`McpControl` 上有那两个方法,把类型放宽成整个 `McpControl` 之前先把
|
|
7688
|
+
* 那一节读完。
|
|
7689
|
+
*/
|
|
7690
|
+
interface McpToolDeps {
|
|
7691
|
+
control: Pick<McpControl, 'add' | 'update' | 'remove'>;
|
|
7692
|
+
/**
|
|
7693
|
+
* 进程里此刻那张状态表。**函数而不是快照** —— 判据同 `McpWiring.status`:
|
|
7694
|
+
* MCP 会掉线重连,而这个工具存在的唯一理由就是回答「**现在**连上没有」。
|
|
7695
|
+
*
|
|
7696
|
+
* ⚠️ 收的是 `McpServerStatus` 而**不是** `mcp-admin.ts` 的 `McpServerOverview`
|
|
7697
|
+
* —— 后者上面挂着整份 `McpServerConfig`(含 `env` 里的令牌),而这一族的形状
|
|
7698
|
+
* 本体就是「原文一个字节都不交出去」(文件头第二节)。
|
|
7699
|
+
*/
|
|
7700
|
+
servers: () => readonly McpServerStatus[];
|
|
7701
|
+
}
|
|
7702
|
+
/**
|
|
7703
|
+
* 建那四个 MCP 管理工具。
|
|
7704
|
+
*
|
|
7705
|
+
* ⚠️ **注册时机比 `skill_*` 那四个晚一格**:它们要 `McpControl`,而那是
|
|
7706
|
+
* `registerMcpTools()` 的产物,跑在 `registerLocalTools()` **之后**。所以接线
|
|
7707
|
+
* 点在 [build.ts](./build.ts) 里那一行 `registerMcpTools` 的后面,不在
|
|
7708
|
+
* `registerLocalTools` 里 —— 判据同 `wireToolRefresh` 那条「订晚一点」。
|
|
7709
|
+
*
|
|
7710
|
+
* **控制面在就全部注册,不管当下配了几台。** 一台都没配的进程正是 `mcp_add`
|
|
7711
|
+
* 最要紧的现场(判据在 `registerMcpTools` 里 `live()` 上那段 ⚠️:那一支照样
|
|
7712
|
+
* 交出活的控制面)。按「启动那一刻有没有 server」决定注册与否,等于让这条路
|
|
7713
|
+
* 在唯一需要它的那一档下不存在。
|
|
7714
|
+
*/
|
|
7715
|
+
declare function createMcpTools(deps: McpToolDeps): EpochTool[];
|
|
7716
|
+
|
|
7717
|
+
export { type AgentEventBus, type AgentEventSubscriber, AgentRoleError, AgentSession, type AgentSessionOptions, type ApplyHostPresetOptions, ApprovalRelay, type ApprovalRevokeResult, type ApproveFn, type BindWorkspaceFailure, type BindWorkspaceResult, type BuildRuntimeOptions, type BuildToolsInput, type CommandControl, type CommandWiring, type DiscoverOptions, type EffectiveSetting, type EpochRuntime, type ExecutorRuntime, type FileCandidatesResult, type FileOpenPlan, type FileReadOutcome, type FileStatOutcome, type FireResult, type FireScheduleOptions, type GoalControl, HOST_CAPABILITIES_MODULE, HOST_MARKETPLACE_MODULE, HOST_PRESET_MODULE, type HistoryMessage, type HookStatus, type HostAgentRole, type HostCapabilities, type HostCapabilityWiring, type HostMarketplaces, type HostPreset, type HostPresetProvider, type HostPresetWorkspace, type HostSkillDir, type LevelChangeResult, MCP_ADD_TOOL, MCP_LIST_TOOL, MCP_REMOVE_TOOL, MCP_UPDATE_TOOL, type McpAddInput, type McpAddOutcome, type McpAdminOptions, type McpApplyAction, type McpApplyChange, type McpApplyOutcome, type McpApplySource, type McpConfigIssue, type McpConfigReadOutcome, type McpConfigSaveOutcome, type McpConfigSnapshot, type McpControl, type McpEditFailure, type McpEditOutcome, type McpListResult, type McpLoginCliOptions, type McpReconnectOutcome, type McpRemoveOutcome, type McpServerBatch, type McpServerOverview, type McpToolDeps, type McpUpdateOutcome, type McpWriteOutcome, type ModelCatalogControl, type ModelCatalogOptions, type ModelControl, type ModelSuggestions, type ModelTurnScope, type PermissionsControl, type PlanControl, type PluginActionOutcome, type PluginControl, type PluginInstallOutcome, type PluginListEntry, type PluginPreviewOutcome, type PluginRefusal, type PluginRefuseReason, type PluginSearchHit, type PolicyDirStatus, type PolicyStatus, type ProviderKeyWritten, type ProviderOption, QuestionRelay, type RelayClock, type RoleAddInput, type RoleAddOutcome, type RoleControl, type RoleTurnScope, type RoleWriteControl, type RunOptions, SCHEDULE_EXIT_CODES, type ScheduleCapability, type ScheduleControl, type ScheduleControlOptions, type ScheduleSaveResult, type SerializableAgentEvent, type SerializableApprovalRequest, type SerializableQuestionRequest, type SerializedEvent, type ServiceOptions, type Services, type SessionControl, type SessionDeletion, type SessionFileChange, type SessionHit, SessionStore, type SessionSummary, type SessionWorkspace, SessionWorkspaces, type SessionWorkspacesOptions, type SettingWriteDone, type SettingWriteInput, type SettingWriteRefused, type SettingWriteReport, type SettingsControl, type StoredMessage, type TurnDetail, type TurnStep, type UserActionResult, type UserActions, type UtilityControl, type UtilityGenerateInput, type UtilityGenerateResult, type WireWorkspaceInput, type WorkspaceBindingState, type WorkspaceControl, type WorkspaceFilesDeps, type WorkspaceFilesView, 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 };
|