@epoch-agent/protocol 0.2.0 → 0.3.2

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.d.ts CHANGED
@@ -1198,6 +1198,142 @@ interface ActiveAgentRole {
1198
1198
  source?: AgentRoleSource;
1199
1199
  }
1200
1200
 
1201
+ /**
1202
+ * 内容合规的**契约值** —— 类别与动作。
1203
+ *
1204
+ * 引擎在 core 的 `compliance/`,这里只有那两个字符串联合。放 protocol 是因为
1205
+ * 它们跨了包,而且跨的方式是「同一个字面量在四个地方出现」:
1206
+ *
1207
+ * 1. 用户配置的键名(`compliance.actions.sexual`,schema 在 core/config);
1208
+ * 2. 词库文件名(`~/.epoch/compliance/sexual.txt`);
1209
+ * 3. 遥测属性值(`epoch.compliance.category`);
1210
+ * 4. 界面上那句提示要翻成人话(catalog key `compliance.category.sexual`)。
1211
+ *
1212
+ * 也就是说改一个字面量会同时改掉用户的配置文件、他的词库文件名和看板上的
1213
+ * 一条曲线。这种东西只能有一份,而 protocol 是唯一所有人都能依赖的地方。
1214
+ */
1215
+ /**
1216
+ * 类别。
1217
+ *
1218
+ * 分类的粒度决定了「能不能分档处置」:色情和歧视必须能配不同的动作,
1219
+ * 否则要么一起断线(歧视词误伤代价太大),要么一起放行(色情不该只是告警)。
1220
+ *
1221
+ * 每一类的收词判据和默认档位写在 core 的 `compliance/lexicon/<类别>.ts` 文件头 ——
1222
+ * 那里是唯一该讲「为什么是这一档」的地方。
1223
+ */
1224
+ declare const COMPLIANCE_CATEGORIES: readonly ["sexual", "political", "hate", "violence", "illegal", "self_harm", "privacy"];
1225
+ type ComplianceCategory = (typeof COMPLIANCE_CATEGORIES)[number];
1226
+ /**
1227
+ * 命中之后干什么。
1228
+ *
1229
+ * `off` 是**类别级**的开关,不是一个动作 —— 配成 `off` 的类别连扫都不扫,
1230
+ * 于是它既不出现在判定里,也不进遥测。这和「扫了但不处置」(`warn`)
1231
+ * 是两件事:后者看板上有曲线,前者没有。
1232
+ */
1233
+ declare const COMPLIANCE_ACTIONS: readonly ["block", "mask", "warn", "off"];
1234
+ type ComplianceAction = (typeof COMPLIANCE_ACTIONS)[number];
1235
+
1236
+ /**
1237
+ * 宿主自定义的那几行侧栏菜单(2026-09-02)。
1238
+ *
1239
+ * 形状和口径逐字照 `EpochConfig.brand`(决定 19):**宿主装配期预置的一个配置项,
1240
+ * 界面上没有写端点**,`GET /api/config` 原样下发、没配就不带这个键。
1241
+ * 为什么是配置项而不是 `createWebServer()` 的参数,全文在那个字段上
1242
+ * (一句话:走 `epoch web --json` 起子进程的宿主没有构造函数可传)。
1243
+ *
1244
+ * ## 点下去发生什么:往父窗口发一条 `postMessage`,**不跳页**
1245
+ *
1246
+ * 这是这一格和 `brand` 唯一的形状差别 —— 品牌是一个字符串,菜单是一个**动作**,
1247
+ * 而动作要有个落点。三条路里选了这一条:
1248
+ *
1249
+ * | 落点 | 为什么不 |
1250
+ * | -------------------------- | ------------------------------------------------------------------------ |
1251
+ * | 每项配一个 `href`,画成 `<a>` | 同标签页跳走会**丢掉输入框里没发出去的那句话**(草稿只在内存里,判据在 web 的 `composer/draft.ts`);开新标签页在 webview 里又常常被拦掉,那就是「点了没反应」(决定 20 ①) |
1252
+ * | 我们这边执行一段宿主给的脚本 | 配置文件里的字符串当代码跑,等于把 `~/.epoch/config.yaml` 变成一条执行入口 |
1253
+ * | **发一条消息,宿主自己决定** | 宿主本来就握着这张页所在的窗口 —— 它想跳页、想开抽屉、想弹自己的原生框都行 |
1254
+ *
1255
+ * 于是这一格里**没有 URL**:一项只有 `id`(宿主自己认的那个码)和 `label`
1256
+ * (屏幕上那几个字)。少一个字段的代价是宿主必须自己接一句监听,
1257
+ * 换来的是「点下去会怎样」这件事完全在宿主手里,我们一处都猜不着。
1258
+ *
1259
+ * ## 宿主那一侧怎么接
1260
+ *
1261
+ * 两种嵌法都是同一条 `message` 事件,因为我们发的时候用的是 `window.parent`
1262
+ * ——它在**没有父窗口**时就等于 `window` 自己(DOM 规范),于是:
1263
+ *
1264
+ * ```js
1265
+ * // ① iframe / webview 里嵌:监听装在外层那份文档上
1266
+ * // ② Electron 这类「这张页就是顶层文档」的嵌法:监听由 preload 装在同一个 window 上
1267
+ * window.addEventListener('message', (e) => {
1268
+ * if (e.data?.type !== 'epoch:sidebar-menu') return;
1269
+ * openMyOwnPage(e.data.id);
1270
+ * });
1271
+ * ```
1272
+ *
1273
+ * ⚠️ **判 `type` 那一句不能省**:同一个 window 上跑着的别人(devtools 扩展、
1274
+ * 打包器的热更新通道)也在发 `message`。那个字面量的真源是
1275
+ * {@link SIDEBAR_MENU_MESSAGE_TYPE},别在宿主那边手抄第二遍。
1276
+ *
1277
+ * ⚠️ **`targetOrigin` 我们发的是 `'*'`,而这不是漏了一道校验。** 这条消息的全部
1278
+ * 内容是**宿主自己写进自己配置文件的那个 `id`** —— 里面没有 token、没有会话 id、
1279
+ * 没有任何一个字是从别处读来的。收窄成一个可配的 origin 只会多一个键,
1280
+ * 而它唯一的效果是让宿主更容易把自己的监听配错(同 core 那条
1281
+ * 「没有消费方的配置项就是死键」)。反过来的方向照旧守着:**我们不监听
1282
+ * `message`**,所以宿主发什么进来都改不动这张页的任何状态。
1283
+ */
1284
+ /**
1285
+ * 侧栏菜单里的一项。
1286
+ *
1287
+ * ⚠️ 三个字段都是**宿主的话**,一个都不过 i18n catalog —— 同侧栏底下那几条启动
1288
+ * 诊断的 `detail`:它们的内容随宿主而变,没有有限的码可以枚举。用户把界面切成
1289
+ * 英文之后这几行**仍然是宿主给的那份文案**,那是预期形态不是漏翻。
1290
+ */
1291
+ interface SidebarMenuItem {
1292
+ /**
1293
+ * 宿主自己认的那个码 —— 点下去原样回给它({@link SidebarMenuMessage.id})。
1294
+ *
1295
+ * ⚠️ **一份菜单里不许重复**(core 的 schema 拦这一条):重了的话宿主收到消息
1296
+ * 也分不出用户按的是哪一行,而屏幕上那两行长得完全不一样。
1297
+ *
1298
+ * 它**不是**给人看的,所以不必好读;给人看的是 {@link label}。
1299
+ */
1300
+ id: string;
1301
+ /** 屏幕上那几个字。空串是配错了(不要这一项就别写它),core 的 schema 会拦 */
1302
+ label: string;
1303
+ /**
1304
+ * 行首那枚图标的名字。**不给不等于没有图标** —— 那一行会落一枚默认的
1305
+ * (一个朝外的箭头,「点下去离开这一屏」)。
1306
+ *
1307
+ * ⚠️ **这一句 2026-09-02 改过口径**:原来是「不给就不画图标」,而那一版
1308
+ * 拍照拍出来是「有 / 没有 / 没有 / 设置有」的**参差一列** —— 中间那两行的字
1309
+ * 悬在空处,读成「掉了图标」。判据在 web 的 `HOST_ICON_FALLBACK` 上:
1310
+ * 一列图标要么整列有、要么整列没有。
1311
+ *
1312
+ * ⚠️ 类型是 `string` 而不是一个联合:图标名是**第一方 Web UI 自己那张精灵图**的
1313
+ * 私有词表(`web/src/icons.tsx`),把它写进契约等于对宿主承诺那三十几个名字
1314
+ * 一个都不改。所以认哪几个名字由 web 那一层说了算(一张**白名单**,判据在
1315
+ * `shell/host-menu.tsx` 上),**认不出来的当没给** —— 落那枚默认的,
1316
+ * 不画一个空框,也不整份配置退默认值。
1317
+ */
1318
+ icon?: string;
1319
+ }
1320
+ /**
1321
+ * 那条 `postMessage` 的 `type`。**宿主和我们共用这一个字面量**,两边都别手抄。
1322
+ *
1323
+ * 带 `epoch:` 前缀是为了在一条本来就热闹的通道上认出自己的消息(见文件头那条 ⚠️)。
1324
+ */
1325
+ declare const SIDEBAR_MENU_MESSAGE_TYPE = "epoch:sidebar-menu";
1326
+ /**
1327
+ * 点了某一行之后发出去的那条消息。
1328
+ *
1329
+ * ⚠️ **只有 `id`,不带 `label`**:标签是宿主自己配的,它手上已经有;而多带一份
1330
+ * 就是多一份会漂的副本 —— 宿主改了配置、这条消息里那句话还是上一版的。
1331
+ */
1332
+ interface SidebarMenuMessage {
1333
+ type: typeof SIDEBAR_MENU_MESSAGE_TYPE;
1334
+ id: string;
1335
+ }
1336
+
1201
1337
  /**
1202
1338
  * 配置契约。
1203
1339
  *
@@ -1539,6 +1675,57 @@ interface EpochConfig {
1539
1675
  /** 一次返回几条,缺省 5。上限 20 —— 再多也超出模型能有效读的范围 */
1540
1676
  maxResults?: number;
1541
1677
  };
1678
+ /**
1679
+ * 内容合规过滤(core 的 `compliance/`)。
1680
+ *
1681
+ * ## 整段缺省 = **开着,但只拦用户输入**
1682
+ *
1683
+ * 这个仓库里绝大多数能力是「没配就没有」(`search` / `telemetry` / `statusLine`)。
1684
+ * 合规反过来:默认关等于「装了但没生效」,而那种状态和压根没做这个功能在
1685
+ * 外部看起来一模一样 —— 一个部署方会以为自己有一道闸门。
1686
+ *
1687
+ * ⚠️ **但缺省只拦输入侧**(`scope.output` 缺省 `false`,2026-09-01 的口径)。
1688
+ * 模型输出那一侧代码全在、一行配置就能开,缺省关掉的两条理由都是量出来的:
1689
+ * 它是误伤的大头(`block` 档会**直接终止这一轮回答**,而模型输出里一半是代码
1690
+ * 和技术叙述),以及它要收流式延迟(句子级 holdback,每个用户每轮都在付)。
1691
+ *
1692
+ * 输入侧的代价小得多:拦错了是用户自己刚敲的那一句,他当场看得见、改一下重发。
1693
+ *
1694
+ * 剩下的误伤靠两条压住:内置词表刻意窄(只收「几乎没有正当技术用途」的词),
1695
+ * 以及**默认跳过代码**(围栏块与行内 code)。判据分别在
1696
+ * core 的 `compliance/lexicon/sexual.ts` 和 `compliance/types.ts`。
1697
+ *
1698
+ * ## 它明确做不到什么
1699
+ *
1700
+ * 判不了语义(换个说法就绕过去了)、管不到模型生成的工具入参
1701
+ * (`scope.toolArgs`,默认关)、管不到工具 / 联网读回来的内容。
1702
+ * 这三条是知情的缺口,全文在 `compliance/types.ts` 的文件头。
1703
+ *
1704
+ * 每一格缺省时**逐键**退回 core 的 `DEFAULT_COMPLIANCE_SETTINGS`:
1705
+ * 只写一行 `actions.hate` 不会把其余六个类别一起清空。
1706
+ */
1707
+ compliance?: {
1708
+ /** 缺省 `true` */
1709
+ enabled?: boolean;
1710
+ /** 拦哪几条通道。缺省:**输入开、输出关**、工具入参关(见上面那段 ⚠️) */
1711
+ scope?: {
1712
+ output?: boolean;
1713
+ input?: boolean;
1714
+ toolArgs?: boolean;
1715
+ };
1716
+ /** 围栏块和行内 code 里的内容不判。缺省 `true` */
1717
+ skipCodeBlocks?: boolean;
1718
+ /** 缓冲区上限,缺省 400。超过就强制判一次,不为了等一个句号憋住整段 */
1719
+ maxHoldChars?: number;
1720
+ /**
1721
+ * 类别 → 动作。缺省:色情 / 涉政 `block`,歧视 `mask`,
1722
+ * 暴恐 / 违法 / 自残 `warn`,个人敏感信息 `off`。
1723
+ *
1724
+ * `off` 是**连扫都不扫**(不进判定、不进遥测),和「扫了但不处置」
1725
+ * (`warn`)是两件事。
1726
+ */
1727
+ actions?: Partial<Record<ComplianceCategory, ComplianceAction>>;
1728
+ };
1542
1729
  /**
1543
1730
  * 会话检索(方案 48)。整段缺省 = 两个工具照常注册,**只是不跨工作区**。
1544
1731
  *
@@ -1623,6 +1810,23 @@ interface EpochConfig {
1623
1810
  * 因为侧栏那一行还要装搜索框。
1624
1811
  */
1625
1812
  brand?: string;
1813
+ /**
1814
+ * 宿主自定义的那几行侧栏菜单(2026-09-02)。**缺省 = 侧栏上一行都不多**。
1815
+ *
1816
+ * 形状、口径、以及「点下去往父窗口发一条 `postMessage`」那条判据,全文在
1817
+ * [sidebar-menu.ts](./sidebar-menu.ts) 的文件头 —— 这儿只记它和 {@link brand}
1818
+ * 的**关系**:两个键是同一类东西(宿主装配期按下的事实、只读、`GET /api/config`
1819
+ * 原样下发、没配就不带那个键),所以它们并排放在顶层,而不是塞进 `display`
1820
+ * (那一段收的是**用户**的显示偏好)。
1821
+ *
1822
+ * ⚠️ **空数组是配错了,不是「不要菜单」** —— 不要就别写这个键,同 `brand`
1823
+ * 那条「空串是配错了」。core 的 schema 会为它产出一条带字段路径的 warn 诊断。
1824
+ *
1825
+ * ⚠️ **不设条数上限**,判据同 `brand` 的「不设长度上限」:为「菜单有点长」判一次
1826
+ * 校验失败,代价是宿主**整份配置**(含模型和 key)退默认值。装不下由界面自己
1827
+ * 兜(web 那一格滚动),不由校验兜。
1828
+ */
1829
+ sidebarMenu?: readonly SidebarMenuItem[];
1626
1830
  /**
1627
1831
  * `~/.epoch/artifacts/` 的保留策略(方案 47 PR-4)。**缺省 = 内置默认**
1628
1832
  * (7 天 / 512MB)—— 整段不配和显式写这两个默认值,行为逐字节相同。
@@ -2301,6 +2505,15 @@ declare const METRIC: {
2301
2505
  /** 同一 provider 内模型级降级的次数(方案 26 #10/#11),属性带 from/to */
2302
2506
  readonly MODEL_FALLBACK: "epoch.model.fallback.count";
2303
2507
  readonly CONTEXT_COMPACTION: "epoch.context.compaction.count";
2508
+ /**
2509
+ * 内容合规闸门命中的次数。属性 `category`(类别)/ `action`(block / mask / warn)
2510
+ * / `channel`(`output` 还是 `input`)。
2511
+ *
2512
+ * ⚠️ **命中的原文和词条本身永不进遥测** —— 那等于我们自己把违规内容抄一遍
2513
+ * 送出门(判据在 core 的 telemetry/sanitize.ts 开头)。看板上要的是
2514
+ * 「哪一类在涨」,不是「涨的是哪句话」。
2515
+ */
2516
+ readonly COMPLIANCE_HIT: "epoch.compliance.hit.count";
2304
2517
  readonly STARTUP_DURATION: "epoch.startup.duration";
2305
2518
  };
2306
2519
  /**
@@ -2509,8 +2722,20 @@ type FinishReason =
2509
2722
  | 'stalled'
2510
2723
  /** 被宿主中止 */
2511
2724
  | 'aborted'
2512
- /** 模型安全策略拒答 */
2725
+ /** 模型安全策略拒答 —— 是 **provider** 拒答,不是我们拦的 */
2513
2726
  | 'content-filter'
2727
+ /**
2728
+ * 我们自己的内容合规闸门拦下了(core 的 `compliance/`)。
2729
+ *
2730
+ * ⚠️ **刻意不复用 `content-filter`。** 那个值的含义是「provider 拒答了」,
2731
+ * 处置和说法都不一样:那一档用户能做的是换个说法再问,这一档是内容触了
2732
+ * 部署方配的合规红线(词表 + 类别档位),换说法不一定有用,而且该看的是
2733
+ * 「哪个类别」。混成一个值之后,界面上只能给出一句同时对两种情况都不准的话。
2734
+ *
2735
+ * 输入侧被拦下时也是这个值 —— 那一次**请求根本没发出去**(`usage` 全零),
2736
+ * 与输出侧被拦下的区别在文本里说清,不在这个枚举里分叉。
2737
+ */
2738
+ | 'compliance'
2514
2739
  /** 触达成本或 token 预算上限 */
2515
2740
  | 'budget-exceeded'
2516
2741
  /** 异常终止(详情见前一个 error 事件) */
@@ -2781,7 +3006,43 @@ type NoticeCode =
2781
3006
  * (`maybeLearn()` 紧接着就是 `yield finish('stop'); return;`)。发在这里还有
2782
3007
  * 第二层意思:那一轮正是这条技能**真的进了 system prompt** 的那一轮。
2783
3008
  */
2784
- 'skill-learned';
3009
+ 'skill-learned'
3010
+ /**
3011
+ * provider **把用户发的图片丢了**,而请求已经发出去、已经计费(2026-09-01)。
3012
+ *
3013
+ * 和 `agent_run.images_unsupported` 那条错误分得很清,两者别混:
3014
+ *
3015
+ * | | 谁判的 | 什么时候 | 钱 |
3016
+ * | --- | --- | --- | --- |
3017
+ * | `images_unsupported`(`error` 帧) | **我们**(闸门 1 的输入侧) | 发请求**之前** | 没花 |
3018
+ * | 这一条(`notice` 帧) | **provider 自己报的 warning** | 请求回来之后 | **已经花了** |
3019
+ *
3020
+ * 也就是说走到这一帧意味着闸门 1 判「这个模型认图」,而 provider 那侧不认。
3021
+ * 2026-09-01 那次事故就是这一格:`@ai-sdk/deepseek@3.0.20` 静默丢弃图片,
3022
+ * 模型对着一张不存在的图回答「我只看到一个占位符」,而屏幕上一个字都没有。
3023
+ *
3024
+ * ⚠️ **一次 run 只发一帧**,不是每轮一帧:带图的那条用户消息在这一次 run 的
3025
+ * 每一轮都会重发,逐轮报的话一次多工具调用的对话会刷出十几行同样的话。
3026
+ */
3027
+ | 'images-dropped'
3028
+ /**
3029
+ * 内容合规的输入闸门**改写了用户刚发的那句话**(`mask` 档,2026-09-01)。
3030
+ *
3031
+ * ## 没有这一帧的话,这件事是**静默**的
3032
+ *
3033
+ * `mask` 档不拒绝、也不报错:它把命中处换成 `*` 之后照常发出去。而屏幕上那条
3034
+ * 用户气泡显示的是**用户自己敲的原文**(宿主回显的是输入框里那份),
3035
+ * 模型收到的是打过码的那份 —— 两边不一样,且没有任何东西说过这件事。
3036
+ *
3037
+ * 实测过:`帮我翻译一下黑鬼这个词` 发出去变成 `帮我翻译一下**这个词`。
3038
+ * 用户看着自己那句完整的话,收到一个答非所问的回复,**没有任何线索**。
3039
+ * 这一帧就是那个线索。
3040
+ *
3041
+ * ⚠️ 和 `block` 那一档分得很清:那一档请求根本没发,走的是
3042
+ * `finish: 'compliance'` + 一句正文,用户当场知道发生了什么。
3043
+ * 这一档请求照发,所以只欠一句旁白。
3044
+ */
3045
+ | 'compliance-masked';
2785
3046
  type AgentEventType = AgentEvent['type'];
2786
3047
  /** 取出某个具体事件的类型,例如 AgentEventOf<'tool-call'> */
2787
3048
  type AgentEventOf<T extends AgentEventType> = Extract<AgentEvent, {
@@ -3721,13 +3982,36 @@ declare function sessionPartSummary(part: EpochSessionPart): string;
3721
3982
  * | 谁要 | 用哪一份 | 为什么 |
3722
3983
  * | ---- | -------- | ------ |
3723
3984
  * | 模型(`sdk-adapter.ts` / `agent/loop.ts` / `runtime/agent-session.ts`) | **这一个** | 模型输入契约 + eval 指纹,跟着 locale 变就漂(§33.2) |
3985
+ * | ↳ 其中 `agent/loop.ts` 拼**给模型的文本部件**那一次 | 这一个 + `{ imagesCarriedAsParts: true }` | 图片另有真部件带着,占位符会变成一句假话(判据见 {@link ContentToTextOptions}) |
3724
3986
  * | `messages.content` 那一列(存盘 / 压缩 / 审计 / FTS) | **这一个** | 一条落库的记录跟着 `EPOCH_LANGUAGE` 变语言,等于把同一段会话的历史劈成两半 |
3725
3987
  * | 界面(tui 的历史项、web 的气泡) | `contentToDisplayText(content, t)` | 那一份走 catalog 的 `message_part.*`,住在两个宿主里 |
3726
3988
  *
3727
3989
  * 界面那一份**落在宿主层**而不是这儿,判据同 §34.6 给 `view` 那 1 处的做法
3728
3990
  * (兜底文案挪给渲染端):这个包的依赖白名单是空的,`t()` 只能由有它的那一层调。
3729
3991
  */
3730
- declare function contentToText(content: EpochUserContent): string;
3992
+ interface ContentToTextOptions {
3993
+ /**
3994
+ * 这一条消息的图片**是不是另有真部件带着**(`EpochMessage.parts` 里那份
3995
+ * base64,最终变成 provider 的原生图片部件)。
3996
+ *
3997
+ * 默认 `false` —— 也就是改造前的行为,落库那一列要的正是它:那一行
3998
+ * `[图片 image/png]` 是**这一轮带过一张图**的唯一记录(`parts` 列另存,
3999
+ * 但 FTS / 预览 / 标题只看 content)。
4000
+ *
4001
+ * `true` 时图片那一支整个**不折占位符**。判据是那句占位符的含义 ——
4002
+ * 「这里本来有张图,但你看不到」。图真的在旁边时它就成了一句假话,而且是
4003
+ * 会被照着信的那种:2026-09-01 实测,`@ai-sdk/deepseek@3.0.20` 把图片
4004
+ * 静默丢了之后,模型读到的就是这一行,于是它逐字回答「我只看到
4005
+ * 「[图片 image/jpeg]」这个占位符,图片没有传到我这边」。**升级 SDK 让图片
4006
+ * 真的送达之后,这一行反而成了唯一还在撒谎的地方。**
4007
+ *
4008
+ * ⚠️ 只关图片那一支。`file` / `session` 的占位符**照旧**:它们说的是
4009
+ * 「内容没附上」(`omitted` / `not-restored`),那句话在任何情况下都是真的,
4010
+ * 而且是模型唯一能知道「有个东西没给我」的途径。
4011
+ */
4012
+ imagesCarriedAsParts?: boolean;
4013
+ }
4014
+ declare function contentToText(content: EpochUserContent, opts?: ContentToTextOptions): string;
3731
4015
  /**
3732
4016
  * 「上一轮实际动过手」在**给模型的**历史里长什么样(2026-08-28)。
3733
4017
  *
@@ -5540,6 +5824,21 @@ interface WireConfigResponse {
5540
5824
  * 界面上改不了它 —— 那是宿主的事,不是用户的事。
5541
5825
  */
5542
5826
  brand?: string;
5827
+ /**
5828
+ * 宿主自定义的那几行侧栏菜单 —— **宿主没配就没有这个键**(2026-09-02)。
5829
+ *
5830
+ * 和 {@link brand} 同一个形状、同一个理由,连搭在这条端点上的理由都一样:
5831
+ * 侧栏首屏就要画它,为一份可选的静态清单另开一个端点等于让每个客户端多一次往返。
5832
+ * 它**只读** —— 那是宿主装配期的事,界面上没有、也不会有改它的入口。
5833
+ *
5834
+ * 来源是 `EpochConfig.sidebarMenu`;每一项的字段含义、以及点下去发出去的那条
5835
+ * `postMessage` 的契约,全文在 [sidebar-menu.ts](./sidebar-menu.ts) 的文件头。
5836
+ *
5837
+ * ⚠️ **原样转发,服务端一个字都不改**:`label` 不翻译(它是宿主的话,
5838
+ * 同诊断的 `detail`),`icon` 认不认得出来也不在这一层判 —— 那张白名单是
5839
+ * 第一方 Web UI 自己的事,而这条端点也服务着自己画前端的宿主(EMBEDDING §10)。
5840
+ */
5841
+ sidebarMenu?: readonly SidebarMenuItem[];
5543
5842
  /**
5544
5843
  * 自动压缩那条线(决定 8 的第三个时刻,2026-08-16)。形状与三条判据见
5545
5844
  * {@link WireCompressionLine}。
@@ -5585,6 +5884,33 @@ interface WireConfigResponse {
5585
5884
  * 端点要么复用一个名字不对的字段,要么再加一格,而两格迟早对不上。
5586
5885
  */
5587
5886
  lanExposed: boolean;
5887
+ /**
5888
+ * 这个服务进程**能不能替浏览器弹一个系统目录选择框**(2026-09-01)。
5889
+ *
5890
+ * 真源是 `server/src/workspace/native-pick.ts` 的 `resolveNativeDirPicker()`,
5891
+ * 装配时算一次(那是一个纯函数,判据和它继承的那个盲区都写在它上面)。
5892
+ *
5893
+ * ## 为什么浏览器要知道它
5894
+ *
5895
+ * 因为「打开本地文件夹」那一项**有两种实现**,而选哪一种只有服务端答得出:
5896
+ * 为真时那一下走 `POST /api/workspaces/pick`(框弹在服务进程那台机器的屏幕上,
5897
+ * 而那台机器此刻就是用户面前这一台);为假时走自绘那一屏
5898
+ * (`GET /api/workspaces/dirs` + `session/dir-picker.tsx`)。
5899
+ *
5900
+ * ⚠️ **界面这一格是给人的那道闸,服务端那一道才是给网线的**:`POST
5901
+ * /api/workspaces/pick` 在这一格为假时一律 **403 `native-picker-unavailable`**,
5902
+ * 一个子进程都不起。判据逐字同 {@link lanExposed} —— 它是一个客户端自己改得动
5903
+ * 的布尔,别把服务端那道摘掉让这一格单独扛。
5904
+ *
5905
+ * ⚠️ **必填,而且不知道时该当 `false`**(客户端那一侧的 `?? false`):
5906
+ * 往保守那一侧落 —— 猜错成 `true` 的表现是用户点下去吃一个 403,
5907
+ * 猜错成 `false` 的表现只是他看到自绘那一屏,而那一屏在任何机器上都能用。
5908
+ *
5909
+ * ⚠️ 它**不是**「这台机器有没有图形界面」那种通用事实,别拿它去关别的东西:
5910
+ * 它是四件事的合取(回环绑定 + 非 SSH + 平台在支持列表里 + 这条路真的实现了),
5911
+ * 而其中第一条是一句**关于浏览器在哪儿**的推断。
5912
+ */
5913
+ nativeDirPicker: boolean;
5588
5914
  }
5589
5915
  declare const WIRE_CONFIG_FIELDS: WireFields<WireConfigResponse>;
5590
5916
  /**
@@ -6216,6 +6542,33 @@ interface WireReplayMessage extends EpochMessage {
6216
6542
  }
6217
6543
  interface WireMessageListResponse {
6218
6544
  messages: readonly WireReplayMessage[];
6545
+ /**
6546
+ * 这段会话到此刻**累计**花了多少(2026-09-04)。
6547
+ *
6548
+ * ## 它和上面那些 `turnFacts` 是**两笔账**,不许相加
6549
+ *
6550
+ * `turnFacts.usage` 是**每一轮**的(`finish.runUsage`),这一格是**整段会话**的
6551
+ * (`usage` 事件里那个 `cumulative`,真源是 `BudgetStore` 落盘的那本账)。
6552
+ * 判据逐字在 `web/src/settings/spend.tsx` 文件头:后者含 `BudgetStore` 从上一个
6553
+ * 进程恢复的历史,还含一轮里最后一次模型调用之后的压缩计费。
6554
+ *
6555
+ * ## 为什么要有它:那是网线上**补不回来的一帧**
6556
+ *
6557
+ * `usage` 是事件,新连上来的浏览器收不到过去那几帧。2026-08-18 补 `turnFacts`
6558
+ * 治的是「刷新之后每一轮的账没了」,而**总账**当时没跟着补 —— 症状在
6559
+ * 2026-09-02 把那个数搬到输入框旁边之后才显形:打开一段旧会话,逐轮脚注
6560
+ * 都有数、总账那一格空着。
6561
+ *
6562
+ * ## 缺席的三种情况,一种都不是故障(所以是可选字段,**不写 0**)
6563
+ *
6564
+ * 1. `GET /config` 的 `usageScope === 'run'` —— 那一档下没有 `BudgetStore`,
6565
+ * 「会话累计」这个东西本来就不存在;
6566
+ * 2. 这个会话一轮都还没跑过;
6567
+ * 3. 那条记录过了保留期(`BudgetStore` 的 `RETENTION_DAYS`,7 天)。
6568
+ *
6569
+ * 三种都是「不知道」,界面上一律整格不画 —— 画 0 是一个假事实。
6570
+ */
6571
+ usage?: TokenUsage;
6219
6572
  }
6220
6573
  declare const WIRE_MESSAGE_LIST_FIELDS: WireFields<WireMessageListResponse>;
6221
6574
  /**
@@ -6351,6 +6704,34 @@ declare const WIRE_SESSION_REFERENCE_FIELDS: WireFields<WireSessionReference>;
6351
6704
  */
6352
6705
  type WireFileReference = Omit<EpochFilePart, 'type' | 'text'>;
6353
6706
  declare const WIRE_FILE_REFERENCE_FIELDS: WireFields<WireFileReference>;
6707
+ /**
6708
+ * 用户附的一张图**在网线上的样子**(2026-09-01)。
6709
+ *
6710
+ * ## 为什么它和上面两份不一样:**这一格不摘,它就是引用本身**
6711
+ *
6712
+ * `WireSessionReference` / `WireFileReference` 摘的是 `text`(内容),因为内容对
6713
+ * 浏览器没用。图片反过来 —— 界面要的**就是**那张图。所以这里只摘 `type`
6714
+ * (在这儿是恒等式),`image` 原样留着。
6715
+ *
6716
+ * ⚠️ **但 `image` 在这条线上永远是一个「引用」,不是 base64**,三种形态:
6717
+ *
6718
+ * | 从哪来 | `image` 装的是 | 谁换算成 `<img src>` |
6719
+ * | --- | --- | --- |
6720
+ * | 回放(`GET /messages`) | **服务端绝对路径** | 前端,`artifactSrc()` 那几行 |
6721
+ * | 远端图(用户贴的 http 图) | 原样 `http(s)://…` | 直接用 |
6722
+ * | 刚发出去那一条(本地乐观回显) | `data:image/…;base64,…` | 直接用 |
6723
+ *
6724
+ * 前两种由 `restoreParts({ inlineImages: false })` 保证(判据逐字在
6725
+ * `core/src/session/payload.ts` 的 `RestorePartsOptions` 上:那一支「**不能**直接
6726
+ * 喂给 provider」,反过来说它正是给界面的那一支)。第三种压根没上过网线 ——
6727
+ * 浏览器自己刚读的那个文件(`composer/attach.tsx` 里 `readAsDataUrl`)。
6728
+ *
6729
+ * **服务端绝不在这条线上发 base64。** 几百 KB 逐字符走 SSE 会把流塞住,
6730
+ * 这条硬规矩和 `ToolArtifact.data` 那一条是同一条(全文在
6731
+ * `web/src/api/artifacts.ts` 的文件头)。
6732
+ */
6733
+ type WireImageReference = Omit<EpochImagePart, 'type'>;
6734
+ declare const WIRE_IMAGE_REFERENCE_FIELDS: WireFields<WireImageReference>;
6354
6735
  declare const WIRE_SEND_MESSAGE_RESPONSE_FIELDS: WireFields<WireSendMessageResponse>;
6355
6736
  /** `aborted: false` 表示这一轮本来就没在跑,不是失败 */
6356
6737
  interface WireAbortResponse {
@@ -7142,8 +7523,8 @@ interface WireWorkspaceDirEntry {
7142
7523
  }
7143
7524
  declare const WIRE_WORKSPACE_DIR_ENTRY_FIELDS: WireFields<WireWorkspaceDirEntry>;
7144
7525
  /**
7145
- * `GET /api/workspaces/dirs[?path=<abs>][&hidden=1]` —— 一层子目录,或者不带 `path`
7146
- * 时的**起点锚**。
7526
+ * `GET /api/workspaces/dirs[?path=<abs>][&at=home][&hidden=1]` —— 一层子目录,
7527
+ * 或者不带 `path` 时的**起点锚**。
7147
7528
  *
7148
7529
  * ## 一个形状盖两发,而这不是偷懒
7149
7530
  *
@@ -7157,6 +7538,21 @@ declare const WIRE_WORKSPACE_DIR_ENTRY_FIELDS: WireFields<WireWorkspaceDirEntry>
7157
7538
  *
7158
7539
  * 那是这类 picker 唯一一个必然会被骂的地方。所以不带 `path` 时给的是三组
7159
7540
  * 「你多半想去的地方」,而不是文件系统根。
7541
+ *
7542
+ * ## `?at=home`:**「home 那一层」这件事只有服务端知道**(2026-09-01)
7543
+ *
7544
+ * 这一格是加法,**不带参数那一发的语义一个字没改**(照旧是锚屏)。它存在的理由
7545
+ * 很具体:界面这一轮要「打开就落在一个真目录上」(锚屏没有父级,于是打开那一屏
7546
+ * 上「往上一层」根本画不出来,用户得先点进一层才翻得上去 —— 用户报的就是这件事)。
7547
+ * 而浏览器**算不出**服务端那台机器的 home:`about.homeDir` 是 `~/.epoch` 不是它,
7548
+ * 锚屏里那一行倒是它,但那要多一次往返、而且得靠 `group === 'home'` 认。
7549
+ *
7550
+ * 于是这一发 = 「按 `path` 列 home 那一层」,回的东西和带 `path` 那一发**逐字同型**
7551
+ * (`path` 是那条绝对路径、`parent` 是 `/Users` 之类)—— 它不是第三种响应。
7552
+ *
7553
+ * ⚠️ **`path` 和 `at` 同时给时以 `path` 为准**:`at` 是「我不知道那条路径,你替我
7554
+ * 算一个」,而 `path` 是「我知道」。反过来(`at` 压 `path`)会让「记住上次逛到
7555
+ * 哪一层」那条路悄悄失效 —— 那一格恰好两个参数都会带上。
7160
7556
  */
7161
7557
  interface WireWorkspaceDirsResponse {
7162
7558
  /**
@@ -7256,6 +7652,35 @@ interface WireCreateWorkspaceResponse {
7256
7652
  entry: WireWorkspaceDirEntry;
7257
7653
  }
7258
7654
  declare const WIRE_CREATE_WORKSPACE_RESPONSE_FIELDS: WireFields<WireCreateWorkspaceResponse>;
7655
+ /**
7656
+ * `POST /api/workspaces/pick` —— 用户在那个系统框里选了什么。
7657
+ *
7658
+ * ## 取消是**真的一档**,不是空路径
7659
+ *
7660
+ * 判别联合而不是 `{ path: string | null }`:这两档下一步动作完全不同 ——
7661
+ * 选了就去绑,取消了**什么都不做**(连一句错都不该说)。写成可空字符串的话,
7662
+ * 迟早有一处把空串当成「选了根目录」,而那正是这条路上最贵的一次误判。
7663
+ *
7664
+ * ⚠️ **`path` 一律是服务端那台机器上的绝对路径**,浏览器只转手不解析 ——
7665
+ * 它接着会被原样发回 `POST /api/sessions/:id/workspace`(那儿才有校验和信任闸门)。
7666
+ *
7667
+ * ## 三种失败,三个不同的码
7668
+ *
7669
+ * | 码 | 什么时候 | 界面下一步 |
7670
+ * | --------------------------------- | ------------------------------------------ | --------------------- |
7671
+ * | 403 `native-picker-unavailable` | {@link WireConfigResponse.nativeDirPicker} 为假 | 回落自绘那一屏 |
7672
+ * | 409 `native-picker-busy` | 已经有一个框开着(同一进程只许一个) | 让用户去处理那个框 |
7673
+ * | 500 `native-picker-failed` | 起不来 / 命令出错,原话转发 | 回落自绘那一屏 |
7674
+ *
7675
+ * ⚠️ 409 那一档存在的理由不是洁癖:两个框叠在一台机器的屏幕上,用户答的那一个
7676
+ * 未必是浏览器还等着的那一个 —— 而先回来的那一发会把另一发的答案顶掉。
7677
+ */
7678
+ type WirePickDirectoryResponse = {
7679
+ path: string;
7680
+ canceled?: false;
7681
+ } | {
7682
+ canceled: true;
7683
+ };
7259
7684
  /**
7260
7685
  * `GET /api/sessions/:id/context` —— **这一段会话**此刻的预算构成。
7261
7686
  *
@@ -7929,6 +8354,58 @@ interface WireSkillImportResponse {
7929
8354
  reason: WireSkillImportFailure | null;
7930
8355
  }
7931
8356
  declare const WIRE_SKILL_IMPORT_FIELDS: WireFields<WireSkillImportResponse>;
8357
+ /**
8358
+ * 删不成的原因。**四档,各对一个不同的下一步**,判据同
8359
+ * {@link WireSkillSummary.residency} 那三档:合成一个 `ok:false` 之后界面说得出
8360
+ * 「没删掉」,说不出去哪儿改。
8361
+ *
8362
+ * | 值 | 出了什么事 | 用户的下一步 |
8363
+ * | ----------- | ----------------------------------------- | ---------------------------------- |
8364
+ * | `missing` | 这一程技能系统压根没起来 | 去看启动诊断里 Skill 那几条 |
8365
+ * | `not-found` | 服务端这张表里没有这个名字 | 刷新 —— 手上这一屏和盘上走散了 |
8366
+ * | `readonly` | 它是项目 / 插件 / 宿主级的,不归我们删 | 按回执里那句去仓库 / 插件那边处理 |
8367
+ * | `failed` | 真去删了,文件系统那一下没成(权限、占用)| 看回执里的原话 |
8368
+ *
8369
+ * ⚠️ **`readonly` 不是「你没权限」**,是「删错地方了」:项目级技能跟着用户的
8370
+ * 仓库走、插件级下一次 `epoch plugin update` 会重新装回来、宿主级住在应用包里。
8371
+ * 那三句话由 core 现渲染(`skill_load.readonly_*`),一字不改地放进 {@link
8372
+ * WireSkillRemoveResponse.detail} —— 每一句里都写着「那该去哪儿改」。
8373
+ */
8374
+ type WireSkillRemoveFailure = 'missing' | 'not-found' | 'readonly' | 'failed';
8375
+ /**
8376
+ * `POST /api/skills/remove` —— 删掉一个**用户级**技能。
8377
+ *
8378
+ * ## ⚠️ 这一条没有 preview → confirm 两段,而那不是不一致
8379
+ *
8380
+ * 导入那两段防的是「你确认的和真装上的不是同一份」(装进来的那段文字会无条件
8381
+ * 进往后每一轮的 system prompt)。删除没有「装上什么」这个问题 —— 它的风险在
8382
+ * 另一头:**不可撤销**。所以闸门也在另一头:界面上那一次确认(防**人**点错)
8383
+ * 加这一份回执里的 {@link path}(说清刚刚销毁的是盘上哪一处)。判据逐字同
8384
+ * `POST /api/plugins/uninstall`。
8385
+ *
8386
+ * ## ⚠️ 它只删得掉用户级那一档
8387
+ *
8388
+ * 真闸门在 core 的 `requireWritable()`(项目 / 插件 / 宿主三档各自抛一句带出路
8389
+ * 的话),不在界面上那个「这一行画不画删除按钮」的判断上 —— 后者是**观感**,
8390
+ * 前者才是拦得住 `curl` 的那一道。
8391
+ */
8392
+ interface WireSkillRemoveResponse {
8393
+ ok: boolean;
8394
+ /** 真被删掉的那个技能名。没删成时是空串 */
8395
+ name: string;
8396
+ /**
8397
+ * 刚刚被递归删掉的那个目录(`<技能目录>/<分类>/<名字>`)。没删成时 `null`。
8398
+ *
8399
+ * ⚠️ **这一格是这条路上的回执,不是调试信息**:删除不可撤销,而「我到底
8400
+ * 销毁了什么」只有服务端答得出(同名的目录在这台机器上可能不止一处)。
8401
+ * 界面上要把它印出来 —— 用户拿它去备份里找回来,或者照着原路再导一次。
8402
+ */
8403
+ path: string | null;
8404
+ /** 没成时那句为什么(含 `readonly` 那三句 core 的原话);成了是 `null` */
8405
+ detail: string | null;
8406
+ reason: WireSkillRemoveFailure | null;
8407
+ }
8408
+ declare const WIRE_SKILL_REMOVE_FIELDS: WireFields<WireSkillRemoveResponse>;
7932
8409
 
7933
8410
  /**
7934
8411
  * `POST /api/roles` 的请求体 —— 稿子上有、身份那一栏一直没有的那张「新建」。
@@ -8071,15 +8548,20 @@ interface WireRoleAddResponse {
8071
8548
  declare const WIRE_ROLE_ADD_RESPONSE_FIELDS: WireFields<WireRoleAddResponse>;
8072
8549
 
8073
8550
  /**
8074
- * 插件那一页的契约 —— 六条端点(方案 59 §六 E2,2026-08-21)。
8551
+ * 插件那一页的契约 —— 十条端点(方案 59 §六 E2,2026-08-21;本地源与市场管理
8552
+ * 2026-09-04 补,见下面第一节的**翻案**)。
8075
8553
  *
8076
8554
  * ```
8077
- * GET /api/plugins 装着的 + 市场里有什么 + pendingRestart
8078
- * POST /api/plugins/install/preview {ref} → 将安装什么,一个字节都不写
8079
- * POST /api/plugins/install {ref, token} → 真装
8080
- * POST /api/plugins/update/preview {name} 更新会带来什么
8081
- * POST /api/plugins/update {name, token} 真更新
8082
- * POST /api/plugins/uninstall {name} 卸载
8555
+ * GET /api/plugins 装着的 + 市场里有什么 + 已加的市场 + pendingRestart
8556
+ * POST /api/plugins/install/preview {ref} → 将安装什么,一个字节都不写
8557
+ * POST /api/plugins/install {ref, token} → 真装
8558
+ * POST /api/plugins/install/local/preview {path} 同上,来源是本机一条目录
8559
+ * POST /api/plugins/install/local {path, token} 真装
8560
+ * POST /api/plugins/update/preview {name} 更新会带来什么
8561
+ * POST /api/plugins/update {name, token} → 真更新
8562
+ * POST /api/plugins/uninstall {name} → 卸载
8563
+ * POST /api/plugins/marketplaces {source} → 加一个市场
8564
+ * POST /api/plugins/marketplaces/remove {name} → 移除一个市场
8083
8565
  * ```
8084
8566
  *
8085
8567
  * 底座是 `EpochRuntime.plugins`(方案 59 PR-4 落的那一片,判据全文在
@@ -8097,29 +8579,64 @@ declare const WIRE_ROLE_ADD_RESPONSE_FIELDS: WireFields<WireRoleAddResponse>;
8097
8579
  *
8098
8580
  * ---
8099
8581
  *
8100
- * # 一、⚠️ **只收 `<市场>/<插件>`,收不了路径** —— 这是这条路的安全性质本体
8101
- *
8102
- * {@link WirePluginInstallRequest} 上**没有 `source` 这个东西**,只有一个
8103
- * {@link WirePluginInstallRequest.ref}。于是「指一条任意路径装一个插件」这件事
8104
- * 在这个形状下**说不出口** —— 不是靠 review 盯住的,是编译期的事
8105
- * (同 `HostAgentRole` 少一个 `source`、同 runtime 那一层 `preview(ref)` 的入参)。
8582
+ * # 一、⚠️ 装进来的路**有两条,各自收一种东西**,第三种说不出口
8583
+ *
8584
+ * | 端点 | 收什么 | 真装的时候会发生什么 |
8585
+ * | ----------------------- | --------------------- | ----------------------------- |
8586
+ * | `install` | `<市场>/<插件>` | 看那条目录项,可能 clone / 拉包 |
8587
+ * | `install/local` | 本机一条**绝对路径** | **只 `symlinkSync` 一下** |
8588
+ * | 没有第三条 | — | — |
8589
+ *
8590
+ * {@link WirePluginInstallRequest} 上**没有 `source` 这个东西**,
8591
+ * {@link WirePluginLocalRequest} 上**只有一条 `path`**。于是「给我一个
8592
+ * `github:acme/x` 我就 clone」这件事在这两个形状下**都说不出口** ——
8593
+ * 不是靠 review 盯住的,是编译期的事(同 `HostAgentRole` 少一个 `source`)。
8594
+ *
8595
+ * ## ⚠️ 这一节 2026-09-04 **翻过案**,原话和翻案的理由都记在这儿
8596
+ *
8597
+ * 原话是「**只收 `<市场>/<插件>`,收不了路径**」,`plugins.tsx` 的文件头上还有
8598
+ * 配套的一句「**别在这儿加一个输入框**」。翻案的是**本地路径那一档**,
8599
+ * 三条判据:
8600
+ *
8601
+ * 1. **它和远程不是一个量级的风险,而原来那条判据把两者合在一起判了。**
8602
+ * `runtime/src/plugin-control.ts` 文件头第三节自己就把四种来源分成了两档:
8603
+ * 本地目录 = `symlinkSync`,其余三种 = 起子进程或拉网。`allowRemote` 默认关
8604
+ * 正是为了那三种。把「本地目录」和它们一起挡在网线外,挡掉的是那一档里**风险
8605
+ * 最低**的一种;
8606
+ * 2. **原来那条路在真实部署上是断的。** `epoch web --plugins` 传的是
8607
+ * `{ sources: [] }`(判据在 CLI 那个 `pluginsOption` 上)—— 一台自己机器上
8608
+ * 起的界面**没有任何预置市场**,而加市场当时只能在命令行上。于是「可视化装
8609
+ * 一个插件」这句话在 CLI 宿主上从来没有成立过:那一页只能看,不能装。
8610
+ * ⚠️ 这一条才是翻案的真正理由 —— 一道**只在没人走得到的路上生效**的闸门
8611
+ * 不是安全性,是一个空转的判断;
8612
+ * 3. **那道真闸门原地不动,而且它挡的正是这一条。** {@link WirePluginLocalRequest}
8613
+ * 照样吃第二节那道 403(LAN 那一档下一律拒),于是「浏览器在别人机器上」那个
8614
+ * 形态下这条路根本不存在。**本地路径这一档的全部前提是「网线另一头就是这台
8615
+ * 机器」**,而那正是 `lanExposed` 那一格答的问题。
8106
8616
  *
8107
8617
  * 逐条对着今天那条最像的路(`POST /api/skills/import`):
8108
8618
  *
8109
- * | 判据 | `skills/import` 今天 | 这一条 |
8110
- * | ---------------- | ------------------------- | -------------------------- |
8111
- * | 能不能传字节 | 不能(只收路径) | 不能 |
8112
- * | 能不能指任意路径 | **能**(收一个任意 path) | **不能**(只能从清单里挑) |
8113
- * | 清单是谁放的 | | 宿主主进程 / 用户自己敲的 |
8619
+ * | 判据 | `skills/import` | `install` | `install/local` |
8620
+ * | ---------------- | ------------------------- | -------------------- | -------------------- |
8621
+ * | 能不能传字节 | 不能(只收路径) | 不能 | 不能 |
8622
+ * | 能不能指任意路径 | **能** | **不能** | **能,但只限本地** |
8623
+ * | 装之前有没有预览 | | 有 | 有(**同一屏**) |
8624
+ * | LAN 那一档 | 不挡 | **403** | **403** |
8625
+ *
8626
+ * ⚠️ **`install/local` 只收本地,且必须是绝对路径。** 一条
8627
+ * `github:acme/x` 走到那条端点上会被判成 `not-local` 而不是「顺手当远程装了」——
8628
+ * 判据在 {@link WirePluginLocalRequest} 上。相对路径同样拒:它会按**服务进程的
8629
+ * cwd** 解析,而坐在浏览器前面的人根本不知道那是哪儿。
8114
8630
  *
8115
8631
  * ⚠️ 「清单」= `~/.epoch/marketplaces.json` 里那些,也就是**宿主预置的**加上
8116
- * **用户自己 `epoch plugin marketplace add` 加的**。要装清单外的东西,路仍然在,
8117
- * 只是**在命令行上** —— 那一问的闸门只该有一份。
8632
+ * **用户自己加的**。后者现在有两个入口(命令行那条 `epoch plugin marketplace add`
8633
+ * {@link WirePluginMarketplaceRequest}),而它们**是同一件事的两个入口,不是
8634
+ * 两道闸门** —— 网线那条多吃一道 403,判据同上第 3 条。
8118
8635
  *
8119
- * # 二、⚠️ 闸门:**绑在回环之外时那五条 POST 一律 403**
8636
+ * # 二、⚠️ 闸门:**绑在回环之外时那九条 POST 一律 403**
8120
8637
  *
8121
8638
  * 判据同 [wire-agent-role.ts](./wire-agent-role.ts) 那道,但**这条路比它还远一格**,
8122
- * 三条:
8639
+ * 四条:
8123
8640
  *
8124
8641
  * 1. **装一个插件会带进 hook 和 `mcp.json`,那两样起子进程。** 一份 `hooks.json`
8125
8642
  * 里的 `PreToolUse` 就是一条 shell 命令,`mcp.json` 里的一台就是一个可执行
@@ -8128,8 +8645,15 @@ declare const WIRE_ROLE_ADD_RESPONSE_FIELDS: WireFields<WireRoleAddResponse>;
8128
8645
  * 2. **`install/preview` 那条也吃这道闸,虽然它一个字节都不写。**
8129
8646
  * `allowRemote: true` 的宿主上,预览一条远程条目会 spawn `git clone --depth 1`
8130
8647
  * 到 staging(判据在 core 的 `install.ts` 那张表)—— 那是一次**由网线发起的
8131
- * 子进程**,不是一次只读。五条一起挡,比按「写不写盘」切一刀诚实;
8132
- * 3. **不能拿「反正他能让 agent 跑命令」开绿灯**,判据逐字同 `wire-agent-role.ts`
8648
+ * 子进程**,不是一次只读。九条一起挡,比按「写不写盘」切一刀诚实。
8649
+ * ⚠️ **加市场那条同理**:`addMarketplace` 会去取一份目录,远程市场那一档
8650
+ * 是一次网络请求 / 一次 clone;
8651
+ * 3. **本地那两条也一起挡**,虽然它们既不拉网也不起子进程 ——
8652
+ * 判据在第一节翻案那三条的第 3 条:本地路径这一档的**全部前提**就是
8653
+ * 「网线另一头就是这台机器」,而 `lanExposed` 为真时那句前提是假的。
8654
+ * ⚠️ 真按「不拉网就放行」切一刀的话,一个 LAN 上的人能指着**这台机器上任意
8655
+ * 一个目录**说「装它」—— 而那个目录里的 `hooks.json` 是谁写的,他知道我们不知道;
8656
+ * 4. **不能拿「反正他能让 agent 跑命令」开绿灯**,判据逐字同 `wire-agent-role.ts`
8133
8657
  * 第三节:`plan` 档和只读会话下 agent 一个字节都落不了盘,而这条端点落得了。
8134
8658
  *
8135
8659
  * ⚠️ **界面那一侧还有一道,但它是给人的**:`GET /api/config` 上带着 `lanExposed`,
@@ -8257,6 +8781,30 @@ interface WirePluginHit {
8257
8781
  /** 已经装过同名的了。界面据此把「安装」换成「已装上」 */
8258
8782
  installed: boolean;
8259
8783
  }
8784
+ /**
8785
+ * 已加的一个市场 —— 「插件从哪儿来」那张清单上的一行。
8786
+ *
8787
+ * ## 为什么要发这一张表,`hits` 已经在了
8788
+ *
8789
+ * `hits` 答的是「有什么插件」,这一张答的是「**清单是谁放的、还能不能加一条**」——
8790
+ * 而后者正是那一页在 CLI 宿主上从来答不出的问题(`epoch web --plugins` 传的是
8791
+ * `sources: []`,一个市场都没有,那时 `hits` 是空的,而空 `hits` 说不出
8792
+ * 「没加过市场」还是「加了但市场是空的」这两种完全不同的处境)。
8793
+ */
8794
+ interface WirePluginMarketplace {
8795
+ name: string;
8796
+ /** 用户敲的那串来源,原样 */
8797
+ source: string;
8798
+ /** 目录里有几条。0 是合法的 —— 一个空市场和没加过市场是两回事 */
8799
+ entries: number;
8800
+ addedAt: number;
8801
+ /**
8802
+ * 它是本地的吗。界面据此说清「刷新它要不要上网」——
8803
+ * 口径同 {@link WirePluginHit.installable}:这一格是**服务端算好的结论**,
8804
+ * 不是把 `allowRemote` 那个全局开关发上来让界面自己推。
8805
+ */
8806
+ local: boolean;
8807
+ }
8260
8808
  /**
8261
8809
  * `GET /api/plugins` —— 那一页的首屏,**一次取齐**。
8262
8810
  *
@@ -8273,6 +8821,11 @@ interface WirePluginsResponse {
8273
8821
  installed: readonly WirePluginEntry[];
8274
8822
  /** 已加的市场里全部条目。空关键词的答案是「全部」而不是「零条」 */
8275
8823
  hits: readonly WirePluginHit[];
8824
+ /**
8825
+ * 已加了哪几个市场。**空数组和 `hits` 为空不是同一件事**,
8826
+ * 判据在 {@link WirePluginMarketplace} 上。
8827
+ */
8828
+ marketplaces: readonly WirePluginMarketplace[];
8276
8829
  /**
8277
8830
  * **这一程动过插件、而那些改动还没生效。** 判据全文见文件头第三节。
8278
8831
  *
@@ -8306,6 +8859,62 @@ interface WirePluginInstallRequest extends WirePluginPreviewRequest {
8306
8859
  token: string;
8307
8860
  }
8308
8861
  declare const WIRE_PLUGIN_INSTALL_FIELDS: WireFields<WirePluginInstallRequest>;
8862
+ /**
8863
+ * `POST /api/plugins/install/local/preview` 的请求体 —— **本机上一条目录**。
8864
+ *
8865
+ * ## ⚠️ 只有 `path` 一格,而且它只能是**本地绝对路径**
8866
+ *
8867
+ * 这不是 {@link WirePluginPreviewRequest} 的一个「source 重载」——
8868
+ * 它是**另一条端点**,形状上只表达得出一件事。于是:
8869
+ *
8870
+ * - 往这儿传 `github:acme/x` → `not-local`,**不会**被当成远程装(`resolveSource`
8871
+ * 认得出那个前缀,这一层判它不是 `local` 就拒);
8872
+ * - 往这儿传一条相对路径 → 同样 `not-local`。它会按**服务进程的 cwd** 解析,
8873
+ * 而坐在浏览器前面的人不知道那是哪儿 —— 一次「装错了一个目录」的安装
8874
+ * 在这条路上是不可逆的(装完就有 `hooks.json` 挂着了)。
8875
+ *
8876
+ * ⚠️ **这条路上的 `path` 是服务端那台机器上的路径,不是浏览器那台的。**
8877
+ * 界面上必须把这句话说出来,判据逐字同 `skill-import.tsx` 的 `hint_local`:
8878
+ * 否则用户会对着一个看起来像上传框的东西反复贴自己电脑上的路径。
8879
+ *
8880
+ * ## 为什么它不吃 `allowRemote`
8881
+ *
8882
+ * 本地来源装的时候**只是 `symlinkSync` 一下**(判据在
8883
+ * `runtime/src/plugin-control.ts` 文件头第三节那张四行的表)。`allowRemote`
8884
+ * 默认关挡的是「起子进程 / 拉网」那三档,拿它顺手把本地也挡掉,
8885
+ * 等于让这条路在**默认配置下**永远走不通 —— 而那正是本地这一档存在的理由。
8886
+ */
8887
+ interface WirePluginLocalRequest {
8888
+ /** 本机上一条**绝对**路径,指向一个插件目录(里面得有 `epoch-plugin.json`) */
8889
+ path: string;
8890
+ }
8891
+ declare const WIRE_PLUGIN_LOCAL_FIELDS: WireFields<WirePluginLocalRequest>;
8892
+ /**
8893
+ * `POST /api/plugins/install/local` 的请求体。`token` 的判据同
8894
+ * {@link WirePluginInstallRequest} —— 本地源**一样要**:预览和安装之间那个目录
8895
+ * 完全可能被改过(它是一条软链的源头,改起来比远程包容易得多)。
8896
+ */
8897
+ interface WirePluginLocalInstallRequest extends WirePluginLocalRequest {
8898
+ token: string;
8899
+ }
8900
+ declare const WIRE_PLUGIN_LOCAL_INSTALL_FIELDS: WireFields<WirePluginLocalInstallRequest>;
8901
+ /**
8902
+ * `POST /api/plugins/marketplaces` 的请求体 —— 加一个市场。
8903
+ *
8904
+ * ⚠️ 这一条**收得下远程地址**(`github:acme/market` / 一条 https 的 JSON),
8905
+ * 而那和上面 `install/local` 只收本地**不矛盾**:加市场取的是一份**目录**
8906
+ * (一份 JSON),它本身不会往 `~/.epoch/plugins/` 里落任何会跑的东西。
8907
+ * 真要装里面某一条时,那一条照样要过 `installable` 那道 `allowRemote` 闸。
8908
+ *
8909
+ * 但取目录本身是一次网络动作,所以**远程市场那一档同样吃 `allowRemote`** ——
8910
+ * 关着的时候回 `remote-disabled`,判据同 `wireHostMarketplaces` 里那个循环
8911
+ * (两处是同一个风险的两处出口,那个文件头明写着)。
8912
+ */
8913
+ interface WirePluginMarketplaceRequest {
8914
+ /** 市场地址:一条本地路径 / 目录,或 `github:` / `https://` */
8915
+ source: string;
8916
+ }
8917
+ declare const WIRE_PLUGIN_MARKETPLACE_FIELDS: WireFields<WirePluginMarketplaceRequest>;
8309
8918
  /**
8310
8919
  * `POST /api/plugins/update/preview` 的请求体 —— 按**装着的那个名字**,不是 ref。
8311
8920
  *
@@ -8335,14 +8944,20 @@ declare const WIRE_PLUGIN_UPDATE_FIELDS: WireFields<WirePluginUpdateRequest>;
8335
8944
  * | 档 | 说的是 | 用户的下一步 |
8336
8945
  * | ----------------- | ------------------------------------------------ | ---------------- |
8337
8946
  * | `unknown-ref` | 不是 `<市场>/<插件>`,或那个市场 / 插件不存在 | 换一条 |
8947
+ * | `not-local` | 本地那条端点收到的不是一条**本地绝对路径** | 换一条绝对路径 |
8338
8948
  * | `remote-disabled` | 它是远程 source,而远程那一档关着 | 去开那个开关 |
8339
8949
  * | `conflict` | 已经装过同名的 | 先卸载 |
8340
8950
  * | `stale-token` | 你确认的和现在要装的对不上,**一个字节都没写** | 重看一次预览 |
8341
8951
  * | `not-installed` | update / uninstall 收到一个没装过的名字 | 先装上 |
8342
8952
  * | `nothing-to-do` | **不是出错**:软链装的插件一直是最新的 | 什么都不用做 |
8343
8953
  * | `failed` | 其余(拉不到 / 清单坏了 / 写记录时被抢先) | 看 `detail` |
8954
+ *
8955
+ * ⚠️ `not-local` **不能并进 `unknown-ref`**,尽管两者都是「你给的那串不对」:
8956
+ * 前者的下一步是「把相对路径改成绝对路径」或「这东西得从市场装」,
8957
+ * 后者是「换一条市场引用」。合成一档的表现是界面拿同一句话去答两个问题 ——
8958
+ * 而这条路上最常见的一次输入错误恰恰就是把 `github:` 贴进了本地那个框。
8344
8959
  */
8345
- type WirePluginRefuseReason = 'unknown-ref' | 'remote-disabled' | 'conflict' | 'stale-token' | 'not-installed' | 'nothing-to-do' | 'failed';
8960
+ type WirePluginRefuseReason = 'unknown-ref' | 'not-local' | 'remote-disabled' | 'conflict' | 'stale-token' | 'not-installed' | 'nothing-to-do' | 'failed';
8346
8961
  /** 一次 hook 盘点:这个事件上有几条 */
8347
8962
  interface WirePluginHookTally {
8348
8963
  type: string;
@@ -8458,6 +9073,26 @@ interface WirePluginActionResponse {
8458
9073
  reason: WirePluginRefuseReason | null;
8459
9074
  }
8460
9075
  declare const WIRE_PLUGIN_ACTION_FIELDS: WireFields<WirePluginActionResponse>;
9076
+ /**
9077
+ * `POST /api/plugins/marketplaces` / `.../marketplaces/remove` 的回执。
9078
+ *
9079
+ * ⚠️ **这一份上没有 `pendingRestart`,而那不是漏了**:加 / 移除一个市场
9080
+ * 一个扩展物都不动 —— 它改的是「有什么可以装」,不是「装了什么」。
9081
+ * 顺手发一格恒为旧值的 `pendingRestart` 会让界面以为刚才那一下动过插件。
9082
+ *
9083
+ * ⚠️ 失败也是 200,判据同 {@link WirePluginPreviewResponse}。
9084
+ * `remote-disabled` 那一档在这条路上说的是「取这份目录要上网,而远程那一档关着」。
9085
+ */
9086
+ interface WirePluginMarketplaceResponse {
9087
+ ok: boolean;
9088
+ /** 加成功那一档:刚进来的那一条。移除那一档是 `null`(它已经不在表里了) */
9089
+ marketplace: WirePluginMarketplace | null;
9090
+ /** 没成才有 */
9091
+ reason: WirePluginRefuseReason | null;
9092
+ /** 没成才有。**服务端渲染好的一句人话**,界面原样上屏 */
9093
+ detail: string | null;
9094
+ }
9095
+ declare const WIRE_PLUGIN_MARKETPLACE_RESPONSE_FIELDS: WireFields<WirePluginMarketplaceResponse>;
8461
9096
 
8462
9097
  /**
8463
9098
  * 一条诊断。**原样来自 `parseMcpConfig` 的 `issues`**,服务端不改写也不筛。
@@ -9365,11 +10000,50 @@ declare const WIRE_PERMISSION_SET_REQUEST_FIELDS: WireFields<WirePermissionSetRe
9365
10000
  * 同 `WirePlanModeResponse`:用户要的结果已经成立了。回 409 的话,
9366
10001
  * 两个标签页里靠后按的那一个会收到一条描述「一件已经如愿的事」的错误。
9367
10002
  */
10003
+ /**
10004
+ * 「记住这一档」为什么没记成(2026-08-31)。
10005
+ *
10006
+ * 换档成功之后,这一档会被记成**新会话的起点**:进程里当场生效,同时写进
10007
+ * `~/.epoch/config.yaml` 的 ② 用户级。盘上那一半可能写不成,而**换档那一半
10008
+ * 已经成了** —— 两件事的结局必须分开报,合成一个布尔的话,界面只能在
10009
+ * 「这一发全成了」和「这一发全砸了」之间二选一,而真实情况两者都不是。
10010
+ *
10011
+ * 是**码不是句子**,判据同 {@link WireBlockedLevel.reason} / 文件头那张
10012
+ * 「中文不下网线」的表:句子在两份 catalog 里,这条路只答一个词。
10013
+ *
10014
+ * | 码 | 现场 | 用户的下一步 |
10015
+ * | ---------------- | ------------------------------------------------- | ------------------------ |
10016
+ * | `no-user-config` | 这个进程说不出用户级配置在哪个文件 | 基本只出现在嵌入宿主里 |
10017
+ * | `unwritable` | 文件在,但那份 yaml 的写法这条路认不出来,不敢覆盖 | 自己去编辑那一行(带路径)|
10018
+ * | `io` | 写盘本身失败(只读盘 / 权限) | 同上 |
10019
+ * | `env-pinned` | `EPOCH_PERMISSION` 压着,写进去下次启动也不生效 | 去掉那个环境变量 |
10020
+ *
10021
+ * ⚠️ **没有「企业托管挡下」这一档,那不是遗漏**:托管那道闸挡的是**换档本身**
10022
+ * (`disableBypassPermissionsMode`,`SessionPermissions.setLevel` 第 1 步),
10023
+ * 走到「记不记」这一步时它已经放行了。给它编一个码等于留一条没有用户的分支。
10024
+ *
10025
+ * ⚠️ `env-pinned` 那一档也**不是**「写失败了」—— 文件真的写进去了。它说的是
10026
+ * 「写了但这台机器上不会生效」,而用户的下一步完全不同,所以不能和 `io` 合并。
10027
+ */
10028
+ type WirePermissionRememberFailure = 'no-user-config' | 'unwritable' | 'io' | 'env-pinned';
9368
10029
  interface WirePermissionSetResponse {
9369
10030
  /** 换成了吗。`false` = 被挡下,级别**一个字没动**(见 {@link refused}) */
9370
10031
  ok: boolean;
9371
10032
  /** 挡下的理由;`ok: true` 时整个不带这个键 */
9372
10033
  refused?: WireBlockedLevel['reason'];
10034
+ /**
10035
+ * **没记住**的时候才有这个键(2026-08-31)。记住了 = 整个不带,
10036
+ * 判据同 {@link refused}:这条路只在有话要说时说话。
10037
+ *
10038
+ * ⚠️ 它**不影响 {@link ok}**:换档成了就是 `ok:true`,哪怕这一格在。
10039
+ * 反过来 `ok:false` 时这一格永远不在 —— 级别一个字没动,也就没有
10040
+ * 「要记住的那一档」。
10041
+ */
10042
+ notRemembered?: {
10043
+ reason: WirePermissionRememberFailure;
10044
+ /** 本来要写的那个文件。`no-user-config` 那一档说不出路径,于是整个不带 */
10045
+ path?: string;
10046
+ };
9373
10047
  /**
9374
10048
  * 这一发真的改变了级别吗。`false` = 本来就是这一档,什么都没做。
9375
10049
  *
@@ -9384,6 +10058,8 @@ interface WirePermissionSetResponse {
9384
10058
  * ⚠️ **`refused` 不在这份清单里,那是对的**:{@link wireFields} 只收必填键
9385
10059
  * (它的 `RequiredKeys` 判据是「`Pick<T,K>` 能不能被空对象满足」)。
9386
10060
  * `ok: true` 那一支整个不带这个键 —— 写进清单等于要求成功的响应也带上它。
10061
+ *
10062
+ * ⚠️ `notRemembered`(2026-08-31)同理不在这里:记住了就整个不带。
9387
10063
  */
9388
10064
  declare const WIRE_PERMISSION_SET_RESPONSE_FIELDS: WireFields<WirePermissionSetResponse>;
9389
10065
 
@@ -9412,7 +10088,9 @@ declare const WIRE_PERMISSION_SET_RESPONSE_FIELDS: WireFields<WirePermissionSetR
9412
10088
  * 不是一条随敲随传的流
9413
10089
  * 2. **写 ② 用户级或 ④ 项目本地**({@link WireSettingWriteLayer}),而且
9414
10090
  * **「写进去会不会生效」在读响应里就带着**({@link WireSettingWrite.futile})——
9415
- * 这是整份契约里最要紧的一个字段,理由见它自己的 JSDoc
10091
+ * 这是整份契约里最要紧的一个字段,理由见它自己的 JSDoc
10092
+ * ⚠️ 2026-08-31 起**哪几档由键决定**:只有 `SettingsFileSchema` 白名单里那两个
10093
+ * 标量给得出 ④,其余可写键的 `writes` 里只有 ② 一档(`settingWriteLayers()`)
9416
10094
  * 3. **写完要重启进程**({@link WireSettingApply})
9417
10095
  *
9418
10096
  * ⚠️ 读响应里那些 `value` **写完不会变**,而那不是 bug:这个进程装配时读过一次
@@ -9475,21 +10153,31 @@ type WireSettingLayer = 'builtin' | 'user' | 'env' | 'project' | 'projectLocal'
9475
10153
  * **服务端不 join 成字符串**:分隔符是排版不是文字,两份 catalog 里未必一样,
9476
10154
  * 而拼一次就等于把一个界面决定钉死在网线上。
9477
10155
  *
9478
- * ## `boolean` 从 2026-08-16 起真的会出现,在那之前它是个**没有用户的成员**
10156
+ * ## `boolean` 从 2026-08-16 起真的会出现,2026-08-31 起还**写得进去**
9479
10157
  *
9480
10158
  * 第一个布尔行是 `compression.enabled`(设计稿「设置 › 记忆与上下文」那个
9481
- * 「快满时自动压缩」)。记下来是因为它改变了界面那一侧的一条既有假设:
9482
- * `config.tsx` 的 `valueKindOf()` 只分 `'number' | 'string'`,而它是**给写控件
9483
- * 挑输入框类型**用的 —— 布尔行今天走不到那儿(`compression.*` 不在
9484
- * `SettingsFileSchema` 白名单里,`writes` 是空数组,一个写控件都不画),
9485
- * 所以那个函数**不用**跟着长第三档。
10159
+ * 「快满时自动压缩」)。它刚出现时只读,于是这里记着一句「界面那个
10160
+ * `valueKindOf()` 不用长第三档」;两张名单拆开之后(core `config/write.ts`
10161
+ * 文件头第二节)它有铅笔了,那一档跟着长了出来。
9486
10162
  *
9487
- * ⚠️ 哪天真有一个布尔键进了白名单,要动的是 `valueKindOf()`
9488
- * `parseDraft()` 两处,而不是在这儿再加一个 `kind` 字段 —— 判据在
9489
- * `valueKindOf()` 自己的 JSDoc 上(「再发一个 `kind` 过来,就是给同一个问题
9490
- * 第二个答案」)。
10163
+ * ⚠️ 那条「别再发一个 `kind` 字段过来」的老规矩**同一轮作废了**,而作废的
10164
+ * 理由不是嫌麻烦:它的前提是「值的类型能从值本身看出来」,而
10165
+ * `budget.*` 四个键**一个默认值都没有** —— 屏幕上是「未设置」、链条上是空的,
10166
+ * 那时按值猜类型只会猜成字符串,然后把 `"10"` 发给一个 `z.number()`。
10167
+ * 现在类型由 {@link WireSettingRow.valueKind} 明说,真源在 core 的
10168
+ * `settingValueKind()`(从 schema 上认,不另立一张表)。
9491
10169
  */
9492
10170
  type WireSettingValue = string | number | boolean | readonly string[];
10171
+ /**
10172
+ * 写控件的形态。**只有三档,因为这条路只画得出「改一个标量」** ——
10173
+ * 数组不在里面(`fallback.providers` 是一条有序的链,不是一个输入框,
10174
+ * 判据在 core 的 `SETTING_WRITE_KEYS` 上)。
10175
+ *
10176
+ * 枚举也归 `string`,候选另走 {@link WireSettingRow.choices}:多一档
10177
+ * `'enum'` 的话,「是不是枚举」在载荷上就有了两个答案(那一档和那个数组),
10178
+ * 而它们分叉的那天,界面会照着一个空候选画一个点不出东西的分段器。
10179
+ */
10180
+ type WireSettingValueKind = 'string' | 'number' | 'boolean';
9493
10181
  /**
9494
10182
  * 这条路**写得进去**的层,低 → 高。
9495
10183
  *
@@ -9593,6 +10281,9 @@ interface WireSettingRow {
9593
10281
  * 只放行 `permissions` / `model` / `maxTurns` 三个键,别的键 ③④⑤∞ 碰不到 ——
9594
10282
  * 给它们画一条六层链条,等于告诉用户「这一行也可能被项目级压回去」,
9595
10283
  * 而他会照着这个暗示去 `.epoch/settings.json` 里写一个永远不生效的键。
10284
+ *
10285
+ * ⚠️⚠️ **空链条 ≠ 这一行改不了**,2026-08-31 起这两件事分开了:链条空说的是
10286
+ * 「没有项目级这回事」,能不能改看下面的 {@link writes}。
9596
10287
  */
9597
10288
  chain: readonly WireSettingStep[];
9598
10289
  /** 这个键能不能被 ③④⑤∞ 覆盖 */
@@ -9601,17 +10292,38 @@ interface WireSettingRow {
9601
10292
  * 这一行**这个界面**能写进哪几层,以及写进去会不会真的生效。
9602
10293
  * 一层都写不了时是空数组,界面据此一个写控件都不画(决定 20 ①)。
9603
10294
  *
9604
- * ⚠️ 它和上面那个 `overridable` **今天恰好同真同假,但不是一件事**:
9605
- * `overridable` 说的是「③④⑤∞ 能不能覆盖这个键」(`SettingsFileSchema` 的
9606
- * 白名单),这一个说的是「这张界面能不能替你写它」。两者分叉的第一天已经
9607
- * 看得见 —— `permissions` 在白名单里、却永远不会有这个界面上的写控件
9608
- * (它走并集不走覆盖,入口在安全中心)。合成一个字段的话,那一天
9609
- * 「这一行为什么没有链条」和「这一行为什么改不了」会共用一个答案。
10295
+ * ⚠️ 它和上面那个 `overridable` **不是一件事,而且从 2026-08-31 起真的分叉了**
10296
+ * (在那之前它们恰好同真同假,那句「恰好」是这条注释原来的原话):
10297
+ *
10298
+ * - `overridable` 说的是「**别人的仓库**能不能覆盖这个键」(`SettingsFileSchema`)
10299
+ * - 这一个说的是「**这张界面**能不能替你改你自己的用户级配置」
10300
+ * (core 的 `SETTING_WRITE_KEYS`)
9610
10301
  *
9611
- * 空数组的三种成因见 core 的 `settingWriteTargets()`:不在白名单、
10302
+ * 于是今天两个方向的分叉都看得见:`permissions` 在白名单里、永远没有写控件
10303
+ * (走并集不走覆盖,入口在安全中心);`budget.*` / `compression.*` 不在白名单里、
10304
+ * 却有铅笔 —— 只是它们的 `writes` 里**只有 ② 用户级一档**。判据全文在 core 的
10305
+ * `config/write.ts` 文件头第二节。
10306
+ *
10307
+ * 空数组的三种成因见 core 的 `settingWriteTargets()`:不在可写名单、
9612
10308
  * 赢在企业托管层、或者来源分不出(`unknown`,说不出会不会被压回去)。
9613
10309
  */
9614
10310
  writes: readonly WireSettingWrite[];
10311
+ /**
10312
+ * 界面该给这一行画哪种输入控件。**`writes` 非空时必有,空时必无。**
10313
+ *
10314
+ * 判据(含「为什么这不违反上面那条别发 `kind` 的老规矩」)在 core 的
10315
+ * `settingValueKind()` 上,一句话版:`budget.*` 那四行没有默认值,
10316
+ * 屏幕上是「未设置」、链条上是空的,按值猜类型只会猜成字符串。
10317
+ */
10318
+ valueKind?: WireSettingValueKind;
10319
+ /**
10320
+ * 这一行只有这几个值可选(`budget.onUnknownPricing` 的 `block` / `warn`)。
10321
+ * **不是枚举时整个不带这个键**,界面画输入框。
10322
+ *
10323
+ * 候选由 schema 说了算(core 的 `settingValueChoices()`)。让界面自己写死一份
10324
+ * 的话,schema 哪天多一档,那一档在文档里存在、在界面上点不出来。
10325
+ */
10326
+ choices?: readonly string[];
9615
10327
  }
9616
10328
  declare const WIRE_SETTING_ROW_FIELDS: WireFields<WireSettingRow>;
9617
10329
  interface WireSettingsResponse {
@@ -9678,7 +10390,8 @@ interface WireSettingsWriteRequest {
9678
10390
  *
9679
10391
  * **服务端会按这个键自己的值域再校验一遍**(`maxTurns` 是正整数这类),
9680
10392
  * 界面那一侧的检查只是为了早说一句,不是判定。数组和对象一律拒 ——
9681
- * 今天两个可写键都是标量。
10393
+ * 可写的键**全是标量**,判据在 core 的 `SETTING_WRITE_KEYS` 上
10394
+ * (`fallback.providers` 那条链因此没有铅笔)。
9682
10395
  */
9683
10396
  value: WireSettingValue;
9684
10397
  }
@@ -9898,6 +10611,20 @@ interface WireProviderOption {
9898
10611
  * 而「没配 key」这句话对它是假的。
9899
10612
  */
9900
10613
  hasKey: boolean;
10614
+ /**
10615
+ * 这台机器上那把 key 长什么样 —— **已脱敏**(`maskApiKey`),没配 / 不需要时是 `null`。
10616
+ *
10617
+ * ⚠️ **这一格是「已配置」的可核对形态,不是凭据。** 只有 `hasKey: true` 的话,
10618
+ * 用户在设置页看到的是一个空的密码框加一句「已配置」—— 而他此刻真正想知道的是
10619
+ * **哪一把**(换了机器、翻过 profile、上周贴过两次),而那个问题
10620
+ * 一个布尔答不了。走的是 `WireProviderSummary.apiKeyHint`(TUI 的 `/model` 印的那份)
10621
+ * 同一个函数,先例在那儿。
10622
+ *
10623
+ * ⚠️ 而它**不许放宽成裸 key**:[§6.4 的第一条「绝不」](../../../docs/verify/VERIFY_RECORD-20-web.md)
10624
+ * 是「API key 下发到浏览器」—— devtools 直接就能读。脱敏在**服务端**做,
10625
+ * 不是发全的让浏览器自己截(那样 key 已经过了网线)。
10626
+ */
10627
+ keyHint: string | null;
9901
10628
  }
9902
10629
  declare const WIRE_PROVIDER_OPTION_FIELDS: WireFields<WireProviderOption>;
9903
10630
  /** `GET /api/providers` 的响应 */
@@ -9999,6 +10726,82 @@ interface WireProviderModelsResponse {
9999
10726
  suggestions: WireModelSuggestions;
10000
10727
  }
10001
10728
  declare const WIRE_PROVIDER_MODELS_FIELDS: WireFields<WireProviderModelsResponse>;
10729
+ /**
10730
+ * `POST /api/providers/:type/key` 的请求体 —— **在这台机器上配一把 key**(2026-08-31)。
10731
+ *
10732
+ * ## 为什么 Web UI 上需要它
10733
+ *
10734
+ * 在这条端点之前,浏览器上「没配 key」这一档能说的只有一句
10735
+ * 「设一个 `MOONSHOT_API_KEY`,或者跑 `epoch model`」—— 也就是**把用户赶回终端**。
10736
+ * 而 `epoch web` 的整个前提是那台机器就在用户手上(回环 + 一次性 token,
10737
+ * 见 `auth.ts` 的文件头),所以「替他把 key 写进他自己的钥匙串」不是越权,
10738
+ * 是这一屏本来就欠的那一步。
10739
+ *
10740
+ * ## ⚠️ 这一发是**单向**的:key 上得去,下不来
10741
+ *
10742
+ * 请求体里有裸 key,响应体里**没有**(只有 {@link WireProviderKeyResponse.keyHint}
10743
+ * 那份脱敏串)。§6.4 第一条「绝不」管的是**下行**,这一条一个字都没松。
10744
+ *
10745
+ * ## 落点由服务端决定,不由请求说
10746
+ *
10747
+ * 请求体里**没有**「写进钥匙串还是写进 .env」这样的字段:那是
10748
+ * `SecretStore` 的降级链(钥匙串 → DPAPI → libsecret → 明文 0600)自己的事,
10749
+ * 而它的判据(「降级必须响亮」,方案 3.3)在 infra。让浏览器点一个后端,
10750
+ * 等于把那条链抄成了第二份,还允许用户主动挑一个更不安全的。
10751
+ * 实际落到哪儿由响应回声,见 {@link WireProviderKeyResponse.backend}。
10752
+ */
10753
+ interface WireProviderKeyRequest {
10754
+ /**
10755
+ * 那把 key 的原文。**必填、且不许是空串**。
10756
+ *
10757
+ * ⚠️ 空串**不当成「删掉这把 key」**,回 400。这一屏上「清空输入框再按保存」和
10758
+ * 「我要撤销这把 key」长得一模一样,而它们的后果差很远 —— 真要删的那天单开一个
10759
+ * 说得出自己在删的动作(`DELETE`),别让一个保存按钮兼职。
10760
+ */
10761
+ apiKey: string;
10762
+ }
10763
+ /**
10764
+ * `POST /api/providers/:type/key` 的响应 —— **写在哪儿、加没加密**。
10765
+ *
10766
+ * ⚠️ **这里一个中文字都不发**,判据逐字同这一组另外两条(`providers.ts` 文件头第 4 节):
10767
+ * `SecretStore.detail` 是一句现成的中文,照抄过来能省浏览器一份 catalog ——
10768
+ * 但那句话是按**服务进程的 locale** 渲染的,而一台 `epoch web` 后面可能同时坐着
10769
+ * 两种语言的人(方案 58)。所以下发的是 {@link backend} 这个**码**,措辞在浏览器那侧。
10770
+ */
10771
+ interface WireProviderKeyResponse {
10772
+ /** 配的是哪一家。回声一遍,同 `WireModelSuggestions.provider` */
10773
+ provider: ProviderType;
10774
+ /**
10775
+ * 写完之后这家配好了没有。
10776
+ *
10777
+ * **恒 `true`**,而它照旧下发:这一格是浏览器那张 provider 单子里同名字段的
10778
+ * 新值(那张单子取一次就不再取,见 `web/src/model/catalog.ts` 的 `asked`),
10779
+ * 让调用方去推「写成功 = 现在有了」,就是把服务端的结论在下游重算一遍。
10780
+ */
10781
+ hasKey: boolean;
10782
+ /** 脱敏后的那把 key(`maskApiKey`)。同 {@link WireProviderOption.keyHint},**不是原文** */
10783
+ keyHint: string;
10784
+ /**
10785
+ * 真的落到哪个后端了 —— `keychain` / `dpapi` / `libsecret` / `plaintext`。
10786
+ *
10787
+ * ⚠️ **必须回显**:方案 3.3「降级必须响亮」在写入侧的对应物就是这一格
10788
+ * (`cli/src/files/env-file.ts` 的 `writeProviderKey` 文件头逐字写着
10789
+ * 「我的 key 进了钥匙串还是明文文件,是用户有权当场知道的事」)。
10790
+ * 值域刻意是 `string` 而不是一个联合:后端清单是 infra 那一侧的事,
10791
+ * 在契约里再钉一张表就是第二份名单。
10792
+ */
10793
+ backend: string;
10794
+ /** 这个后端加密不加密。`plaintext` 那一档是 false,界面据此把那句话说重一点 */
10795
+ encrypted: boolean;
10796
+ /**
10797
+ * 明文降级时那个文件在哪儿;没降级就是 `null`。
10798
+ *
10799
+ * 只有这一档给路径,是因为只有这一档用户**有下一步可做**(去 `chmod`、
10800
+ * 去把它加进 `.gitignore`、或者干脆去修钥匙串)。钥匙串那一档给路径没有意义。
10801
+ */
10802
+ envPath: string | null;
10803
+ }
10804
+ declare const WIRE_PROVIDER_KEY_FIELDS: WireFields<WireProviderKeyResponse>;
10002
10805
 
10003
10806
  /**
10004
10807
  * 「不看参数」这一问的答案。**四档,`depends` 是其中一个真答案**(见文件头)。
@@ -10191,6 +10994,184 @@ interface WireArtifactsResponse {
10191
10994
  }
10192
10995
  declare const WIRE_ARTIFACTS_FIELDS: WireFields<WireArtifactsResponse>;
10193
10996
 
10997
+ /**
10998
+ * 「站内看一眼工作区里的这个文件」的载荷契约 —— 四条端点共用:
10999
+ *
11000
+ * ```
11001
+ * GET /api/sessions/:id/file-stat?path=…&path=… 这几条路径存在吗、是什么、画得出来吗
11002
+ * GET /api/sessions/:id/file?path=… 文本正文
11003
+ * GET /api/sessions/:id/file-bytes?path=… 字节(图片 / PDF / 媒体)
11004
+ * POST /api/sessions/:id/file-open 交给系统默认应用(同机才有)
11005
+ * ```
11006
+ *
11007
+ * ## 这一份存在的理由:把「路径」变成能点的东西
11008
+ *
11009
+ * 在它之前,模型正文和工具卡里的路径全是纯文本 —— 用户看见「我写到了
11010
+ * `src/foo.ts`」,只能自己复制走。而**画一个点了没反应的东西比不画更糟**
11011
+ * ([决定 20](../../../design/web-ui/README.md) ①),所以「可点」这件事必须先有
11012
+ * 一个能回答「这条路径今天还在不在」的端点,那就是 `file-stat`。
11013
+ *
11014
+ * ## ⚠️ 这不是一个新开的「读任意文件」的洞
11015
+ *
11016
+ * `web/src/inspector/preview.tsx` 的文件头曾经写着「**不新开一个读文件的端点**」,
11017
+ * 理由是「权限层拦不到它 —— 用户写的 `deny file_read(.env*)` 是给工具用的规则,
11018
+ * 一个 REST 端点不走工具执行器」。**那条理由今天不成立了**:
11019
+ * `runtime/src/workspace-files.ts` 的 `readFile()` 做的正是「边界 + 权限 +
11020
+ * 二进制,一趟走完」,而且权限判定用的就是 `toolName: 'file_read'` ——
11021
+ * 同一本审批缓存账本。`@文件` 提及那条路已经在用它把工作区文件递给浏览器。
11022
+ *
11023
+ * 也就是说这四条端点**没有比模型自己那条路更宽**:模型读不到的,浏览器也读不到。
11024
+ * 这一条是这份契约的全部安全性质,改这里之前先回去读那个函数。
11025
+ *
11026
+ * ## 三条口径
11027
+ *
11028
+ * 1. **路径原样回来**({@link WireFileStat.path})。浏览器一发问一批路径,
11029
+ * 回来要对号入座;服务端把绝对路径归一化成工作区相对路径之后,
11030
+ * 回一个和问的时候不一样的字符串,浏览器就对不上了。
11031
+ * 2. **拒绝有码,而不是一个笼统的 404**({@link WireFileRefusal})。
11032
+ * 「被权限拦了」和「这个文件没了」和「它在工作区外面」是三句不同的话,
11033
+ * 而界面上要说的正是那句不同的话。
11034
+ * 3. **超出一发上限的那几条照数回来**({@link WireFileStatResponse.overLimit}),
11035
+ * 不悄悄丢。沉默截断是这个仓库反复在骂的病。
11036
+ */
11037
+ /**
11038
+ * 一发 `file-stat` 最多问多少条路径。
11039
+ *
11040
+ * 服务端和浏览器**都**要用它:前者据此截断并回 `overLimit`,后者据此分批。
11041
+ * 32 的取法是「一条模型正文里合理会出现几条路径」——再多的多半是误判
11042
+ * (散文里的斜杠),而那种情况下我们本来就不该把整篇都画成链接。
11043
+ */
11044
+ declare const WIRE_FILE_STAT_MAX = 32;
11045
+ /**
11046
+ * 浏览器拿这一档决定「这个文件画成什么」。
11047
+ *
11048
+ * **不是 MIME**,是「用哪个 HTML 元素」:`text` 进 `<pre>`、`image` 进 `<img>`、
11049
+ * `pdf` 进 `<iframe>`、`audio` / `video` 各进自己那个标签、`opaque` 画一张元信息卡。
11050
+ * 直接发 MIME 的话,每个消费方都要自己再分一次档 —— 而分档的判据
11051
+ * (哪些类型给 inline)是**安全判断**,不该在浏览器里重做一遍。
11052
+ */
11053
+ type WireFileKind = 'text' | 'image' | 'pdf' | 'audio' | 'video' | 'opaque';
11054
+ /**
11055
+ * 看不了的理由。
11056
+ *
11057
+ * `denied` 一个码盖住两件事(跑出工作区 / 权限不放行),**这是故意的**:
11058
+ * 分开说等于告诉浏览器「这个路径存在,只是你没权限」,那是一条信息泄露 ——
11059
+ * 口径逐字同 runtime 那一侧的 `FileReadOutcome`(那儿也是同一个码)。
11060
+ */
11061
+ type WireFileRefusal = 'denied'
11062
+ /** 路径解析得了,但那儿今天没有东西 */
11063
+ | 'missing'
11064
+ /** 是个目录 / 设备 / 管道 —— 存在,但不是「一个文件」 */
11065
+ | 'not-a-file'
11066
+ /** 大到不值得从这条路看(阈值在服务端,见 `server/src/workspace/file-view.ts`) */
11067
+ | 'too-large'
11068
+ /** 读得到,但读的时候出错了(权限位、正在被写、盘掉了) */
11069
+ | 'unreadable';
11070
+ /** `file-stat` 里能看的那一档 */
11071
+ interface WireFileStatOk {
11072
+ /** **原样**回请求里那个字符串(判据见文件头第 1 条) */
11073
+ path: string;
11074
+ ok: true;
11075
+ kind: WireFileKind;
11076
+ /** 文件的原始字节数。元信息那行印它,浏览器也拿它决定要不要直接放弃拉正文 */
11077
+ bytes: number;
11078
+ /** 最后修改时间(epoch 毫秒)。元信息那行那句「它是不是我以为的那一版」 */
11079
+ mtimeMs: number;
11080
+ /**
11081
+ * 能不能交给系统默认应用打开。
11082
+ *
11083
+ * ⚠️ **这是一个服务端算出来的结论,浏览器不许自己按扩展名再算一遍**:
11084
+ * 它同时含着「扩展名在白名单里」和「浏览器和服务在同一台机器上」两件事,
11085
+ * 后者浏览器根本无从判断(`--host 0.0.0.0` 时那个按钮弹在别人屏幕上)。
11086
+ */
11087
+ openable: boolean;
11088
+ }
11089
+ declare const WIRE_FILE_STAT_OK_FIELDS: WireFields<WireFileStatOk>;
11090
+ /** `file-stat` 里看不了的那一档 */
11091
+ interface WireFileStatRefused {
11092
+ path: string;
11093
+ ok: false;
11094
+ refusal: WireFileRefusal;
11095
+ }
11096
+ declare const WIRE_FILE_STAT_REFUSED_FIELDS: WireFields<WireFileStatRefused>;
11097
+ /**
11098
+ * 一条路径的答案。
11099
+ *
11100
+ * 联合而不是「一个对象带一堆可选字段」:可选字段那种写法下,
11101
+ * 「`ok` 是 true 但 `kind` 没给」是个编译得过的状态,而它在界面上的表现是
11102
+ * 一张什么都画不出来的卡。
11103
+ */
11104
+ type WireFileStat = WireFileStatOk | WireFileStatRefused;
11105
+ /** `GET /api/sessions/:id/file-stat` 的响应体 */
11106
+ interface WireFileStatResponse {
11107
+ /** 逐条一个答案,**顺序同请求**(浏览器可以按下标对,也可以按 `path` 对) */
11108
+ files: readonly WireFileStat[];
11109
+ /**
11110
+ * 请求里超出 {@link WIRE_FILE_STAT_MAX} 那几条的条数(判据见文件头第 3 条)。
11111
+ * 一条都没超时是 0,**不是省略** —— 省略的话浏览器分不出「没超」和「旧版服务端」。
11112
+ */
11113
+ overLimit: number;
11114
+ /**
11115
+ * 相对路径是相对哪个根算的。这个会话没绑工作区时 `null`。
11116
+ *
11117
+ * 理由同 `WireArtifactsResponse.root`:预览那一屏要显示完整路径,
11118
+ * 而让浏览器去别的端点凑这个值,会在「两次请求之间换了工作区」时
11119
+ * 拼出一条从来不存在的路径。
11120
+ */
11121
+ root: string | null;
11122
+ }
11123
+ declare const WIRE_FILE_STAT_RESPONSE_FIELDS: WireFields<WireFileStatResponse>;
11124
+ /**
11125
+ * `GET /api/sessions/:id/file` 的响应体 —— 文本正文。
11126
+ *
11127
+ * **只回文本那一档**:图片 / PDF / 媒体走 `file-bytes`(那条路回的是字节流,
11128
+ * 不是 JSON),`opaque` 那一档压根不该来问这条。
11129
+ */
11130
+ interface WireFileTextResponse {
11131
+ /** 原样回请求里那个字符串(判据同 {@link WireFileStat.path}) */
11132
+ path: string;
11133
+ /** 正文。**服务端不截**:截断是显示层的事(`web/src/inspector/content.ts`) */
11134
+ text: string;
11135
+ /** 文件的原始字节数。和 `text` 的长度**不一定相等**(UTF-8、以及末尾换行) */
11136
+ bytes: number;
11137
+ }
11138
+ declare const WIRE_FILE_TEXT_RESPONSE_FIELDS: WireFields<WireFileTextResponse>;
11139
+ /**
11140
+ * 交给系统默认应用打开,为什么没打开。
11141
+ *
11142
+ * 前五档和 {@link WireFileRefusal} 同名同义(同一道闸),后两档是这条路独有的。
11143
+ */
11144
+ type WireFileOpenRefusal = WireFileRefusal
11145
+ /**
11146
+ * 扩展名不在白名单里。
11147
+ *
11148
+ * ⚠️ 白名单**只收「站内渲染不了、而系统应用打得开」的那些**(Office / PDF /
11149
+ * 媒体)。代码和文本一律走站内查看 —— 顺带绕开 Windows 上 `.js` / `.vbs` /
11150
+ * `.wsf` 被 Windows Script Host **执行**那颗雷(`open` / `xdg-open` /
11151
+ * `FileProtocolHandler` 本质都是 ShellExecute,判据全文在
11152
+ * `core/src/context/artifact-open.ts` 的文件头)。
11153
+ */
11154
+ | 'extension-not-allowed'
11155
+ /**
11156
+ * 浏览器和服务不在同一台机器上。
11157
+ *
11158
+ * 那一档下框会弹在**服务进程那台机器**的屏幕上 —— 要么没人看见,要么弹在
11159
+ * 别人屏幕上。判据复用 `server/src/workspace/native-pick.ts` 的那一条。
11160
+ */
11161
+ | 'not-same-machine'
11162
+ /** 闸门全过了,但那个 `spawn` 没起来 */
11163
+ | 'launch-failed';
11164
+ /** `POST /api/sessions/:id/file-open` 的响应体 */
11165
+ interface WireFileOpenResponse {
11166
+ /** 起起来了没有。**它只说「命令发出去了」**,不说用户屏幕上真的出现了窗口 */
11167
+ ok: boolean;
11168
+ /** 没起来的理由。`ok` 为 true 时不出现 */
11169
+ refusal?: WireFileOpenRefusal;
11170
+ /** 那句给用户看的话(服务端按 `?lang=` 渲染好)。`ok` 为 true 时不出现 */
11171
+ message?: string;
11172
+ }
11173
+ declare const WIRE_FILE_OPEN_RESPONSE_FIELDS: WireFields<WireFileOpenResponse>;
11174
+
10194
11175
  /**
10195
11176
  * 一条校验不通过的原因码。**真源在 core,这是网线上那一份**,见文件头。
10196
11177
  *
@@ -11147,4 +12128,4 @@ declare const SECRET_SERVICE = "epoch-agent";
11147
12128
  */
11148
12129
  declare const MCP_DATA_KEY_NAME = "mcp-auth-data-key";
11149
12130
 
11150
- export { ACTION_CONTEXTS, AGENT_ROLE_NAME_PATTERN, API_KEY_ENV_VARS, APPROVAL_OUTCOMES, type ActiveAgentRole, type AgentEvent, type AgentEventOf, type AgentEventType, type AgentRole, type AgentRoleScope, type AgentRoleSource, type ApprovalAnswer, type ApprovalOutcome, type ApprovalReply, type ApprovalRequest, type ArtifactKind, type AttributeValue, type Attributes, type BackgroundTaskInfo, type BackgroundTaskKind, type BackgroundTaskStatus, type BoundKey, type BudgetBreach, COMMAND_NAME_PATTERN, type Catalogs, type ContextBreakdown, type ContextSegment, type CustomCommandDef, type CustomCommandSource, DEFAULT_AGENT_ROLE, DEFAULT_LANG, type DecidedTrustLevel, type Diagnostic, DiagnosticSink, type DiagnosticStatus, type EpochConfig, type EpochContentPart, type EpochFilePart, type EpochHook, type EpochImagePart, type EpochMessage, type EpochMessageRole, type EpochPlugin, type EpochSessionPart, type EpochSkill, type EpochTextPart, type EpochTool, type EpochToolCall, type EpochToolResult, type EpochTurnFacts, type EpochUserContent, type ExpandedCommand, type FileOmitReason, type FinishReason, type FixableApproval, type FlatCatalog, GEN_AI, HEADLESS_INPUT_EVENT_TYPES, HEADLESS_INPUT_FORMATS, HEADLESS_OUTPUT_FORMATS, HEADLESS_PROTOCOL_VERSION, type HeadlessEnvelope, type HeadlessEvent, type HeadlessInit, type HeadlessInputEvent, type HeadlessInputEventType, type HeadlessInputFormat, type HeadlessLifecycleEvent, type HeadlessOutputFormat, type HeadlessResult, type HeadlessResultReason, type HookContext, type HookEvent, type HookHandler, type HookManager, type HookRegistration, type HookType, type JsonSchema, KEY_ACTIONS, type KeyAction, type KeyChord, type KeyContext, type KeySequence, type KeybindingReport, type KeybindingSource, type KeybindingTable, LANGS, type Lang, type LocalizedDetail, type LogFn, MAX_ATTACH_BYTES, MAX_ATTACH_FILES, MAX_ATTACH_TOTAL_BYTES, MAX_HEADER_CHARS, MAX_OPTIONS, MAX_QUESTIONS, MAX_SESSION_ATTACH_BYTES, MAX_SESSION_REFS, MCP_DATA_KEY_NAME, METRIC, MIN_OPTIONS, type MentionSet, type ModelRef, type ModelSelection, type ModelSelectionOrigin, NOOP_TELEMETRY, type NoticeCode, OPERATION_TYPES, type Operation, type OperationType, PERMISSION_LEVELS, PLAN_OUTCOMES, PROVIDER_INFOS, PROVIDER_TYPES, type PermissionDecision, type PermissionLevel, type PermissionManager, type PermissionRuleLists, type PlanApprovalOutcome, type PlanProposal, type PluginContext, type ProviderInfo, type ProviderType, type QuestionAnswer, type QuestionItem, type QuestionOption, type QuestionRequest, RESERVED_CHORDS, RESERVED_COMMAND_NAMES, type RejectReason, type RejectedKey, type RuleValue, SCHEDULE_INTERVAL_MINUTES, SCHEDULE_RUN_STATUSES, SCHEDULE_TEMPLATES, SEARCH_API_KEY_ENV, SEARCH_PROVIDER_TYPES, SECRET_SERVICE, SEQUENCE_CONTEXTS, SESSION_MENTION_PREFIX, SESSION_REFERENCE_ERROR_CODES, SHELL_KINDS, SIMULTANEOUS_CONTEXTS, SYSTEM_NOTE_PREFIX, type ScheduleAllowlist, type ScheduleBackendKind, type ScheduleCycle, type ScheduleDefinition, type SchedulePendingApproval, type SchedulePendingItem, type ScheduleRun, type ScheduleRunStatus, type ScheduleRunnerSpec, type ScheduleTemplate, type ScheduleTemplateId, type ScheduleTrigger, type ScheduleTriggerKind, type ScheduleWeekday, type SearchProviderType, type SecretBackendId, type SecretStore, type SelectionContext, type SelectionRejection, type SelectionWarning, type SessionAttachKind, type SessionAttachResult, type SessionOmitReason, type SessionSurfaceMessage, type SessionSurfaceView, type SetSelectionResult, type ShellKind, type SpanLike, type SubToolCall, type SubToolCallResult, type Telemetry, type TokenUsage, type ToolAnnotations, type ToolArtifact, type ToolArtifactInput, type ToolContext, type ToolDefinition, ToolExposure, type ToolProvider, type ToolResult, type Translate, type TranslateVars, type TrustLevel, type TrustManager, type TrustRecord, type TrustScope, type VerifyResult, WIRE_ABORT_FIELDS, WIRE_ABOUT_FIELDS, WIRE_AGENT_ROLE_FIELDS, WIRE_APPROVAL_CACHE_REVOKE_REQUEST_FIELDS, WIRE_APPROVAL_CACHE_REVOKE_RESPONSE_FIELDS, WIRE_APPROVAL_LIST_FIELDS, WIRE_APPROVAL_ROW_FIELDS, WIRE_ARTIFACTS_FIELDS, WIRE_AUDIT_LOG_FIELDS, WIRE_AUDIT_ROW_FIELDS, WIRE_AUTH_COOKIE, WIRE_AUTH_COOKIE_ATTRS, WIRE_AUTH_TOKEN_PARAM, WIRE_BACKGROUND_TASK_FIELDS, WIRE_BLOCKED_GOALS_FIELDS, WIRE_BLOCKED_GOAL_FIELDS, WIRE_BLOCKED_LEVEL_FIELDS, WIRE_BUILTIN_CONNECTOR_FIELDS, WIRE_CACHED_APPROVAL_FIELDS, WIRE_CAPABILITIES_FIELDS, WIRE_CAPABILITY_PROJECT_FIELDS, WIRE_CHECKPOINT_LIST_FIELDS, WIRE_CHECKPOINT_SUMMARY_FIELDS, WIRE_COMMANDS_FIELDS, WIRE_COMMAND_EXPANSION_FIELDS, WIRE_COMPACT_FIELDS, WIRE_COMPRESSION_LINE_FIELDS, WIRE_CONFIG_FIELDS, WIRE_CONTEXT_FIELDS, WIRE_CREATE_SESSION_FIELDS, WIRE_CREATE_WORKSPACE_FIELDS, WIRE_CREATE_WORKSPACE_RESPONSE_FIELDS, WIRE_CUSTOM_COMMAND_FIELDS, WIRE_DELETE_SESSION_FIELDS, WIRE_DIFF_FILE_FIELDS, WIRE_ERROR_FIELDS, WIRE_FILE_CANDIDATES_FIELDS, WIRE_FILE_CANDIDATE_FIELDS, WIRE_FILE_CHANGE_FIELDS, WIRE_FILE_REFERENCE_FIELDS, WIRE_GOAL_ACTIONS, WIRE_GOAL_BLOCK_FIELDS, WIRE_GOAL_CLEAR_FIELDS, WIRE_GOAL_CREATE_FIELDS, WIRE_GOAL_FIELDS, WIRE_GOAL_LIMITS_FIELDS, WIRE_GOAL_PHASES, WIRE_GOAL_REFUSALS, WIRE_GOAL_RESPONSE_FIELDS, WIRE_GOAL_WRITE_FIELDS, WIRE_HEALTH_FIELDS, WIRE_KNOWN_WORKSPACE_FIELDS, WIRE_LANG_PARAM, WIRE_MANAGED_LOCK_FIELDS, WIRE_MCP_ADD_FIELDS, WIRE_MCP_ADD_RESPONSE_FIELDS, WIRE_MCP_APPLY_ENTRY_FIELDS, WIRE_MCP_APPLY_RESPONSE_FIELDS, WIRE_MCP_CONFIG_APPLY_FIELDS, WIRE_MCP_CONFIG_FIELDS, WIRE_MCP_CONFIG_ISSUE_FIELDS, WIRE_MCP_CONFIG_SAVE_FIELDS, WIRE_MCP_CONFIG_SAVE_RESPONSE_FIELDS, WIRE_MCP_CONNECTOR_FIELDS, WIRE_MCP_RECONNECT_FIELDS, WIRE_MESSAGE_LIST_FIELDS, WIRE_MODEL_FIELDS, WIRE_MODEL_SELECTION_FIELDS, WIRE_MODEL_SET_FIELDS, WIRE_MODEL_SUGGESTIONS_FIELDS, WIRE_PATCH_SESSION_FIELDS, WIRE_PATCH_SESSION_RESPONSE_FIELDS, WIRE_PERMISSION_RULE_FIELDS, WIRE_PERMISSION_SET_REQUEST_FIELDS, WIRE_PERMISSION_SET_RESPONSE_FIELDS, WIRE_PERMISSION_SHADOW_FIELDS, WIRE_PERMISSION_STATE_FIELDS, WIRE_PERMISSION_STATUS_FIELDS, WIRE_PLAN_ACTIONS, WIRE_PLAN_FIELDS, WIRE_PLAN_MODE_REQUEST_FIELDS, WIRE_PLAN_MODE_RESPONSE_FIELDS, WIRE_PLAN_MODE_STATE_FIELDS, WIRE_PLUGINS_FIELDS, WIRE_PLUGIN_ACTION_FIELDS, WIRE_PLUGIN_INSTALL_FIELDS, WIRE_PLUGIN_NAME_FIELDS, WIRE_PLUGIN_PREVIEW_FIELDS, WIRE_PLUGIN_PREVIEW_RESPONSE_FIELDS, WIRE_PLUGIN_UPDATE_FIELDS, WIRE_POLICY_DIR_FIELDS, WIRE_POLICY_STATUS_FIELDS, WIRE_PROVIDERS_FIELDS, WIRE_PROVIDER_MODELS_FIELDS, WIRE_PROVIDER_OPTION_FIELDS, WIRE_QUESTION_LIST_FIELDS, WIRE_QUESTION_ROW_FIELDS, WIRE_RESPOND_APPROVAL_FIELDS, WIRE_RESPOND_APPROVAL_RESPONSE_FIELDS, WIRE_RESPOND_QUESTION_FIELDS, WIRE_RESPOND_QUESTION_RESPONSE_FIELDS, WIRE_REWIND_FILE_PLAN_FIELDS, WIRE_REWIND_OUTCOME_FIELDS, WIRE_REWIND_PREVIEW_FIELDS, WIRE_REWIND_REQUEST_FIELDS, WIRE_REWIND_RESPONSE_FIELDS, WIRE_REWIND_SCOPES, WIRE_ROLE_ADD_FIELDS, WIRE_ROLE_ADD_RESPONSE_FIELDS, WIRE_SANDBOX_STATUS_FIELDS, WIRE_SCHEDULE_CAPABILITY_FIELDS, WIRE_SCHEDULE_CREATE_FIELDS, WIRE_SCHEDULE_DEFAULTS_FIELDS, WIRE_SCHEDULE_DELETE_FIELDS, WIRE_SCHEDULE_FIELDS, WIRE_SCHEDULE_FIX_FIELDS, WIRE_SCHEDULE_FIX_REQUEST_FIELDS, WIRE_SCHEDULE_ISSUE_CODES, WIRE_SCHEDULE_LIST_FIELDS, WIRE_SCHEDULE_PENDING_FIELDS, WIRE_SCHEDULE_RECORDING_FIELDS, WIRE_SCHEDULE_ROW_FIELDS, WIRE_SCHEDULE_RUNS_FIELDS, WIRE_SCHEDULE_RUN_FIELDS, WIRE_SCHEDULE_SAVE_FIELDS, WIRE_SECURITY_FIELDS, WIRE_SECURITY_WORKSPACE_FIELDS, WIRE_SEND_MESSAGE_FIELDS, WIRE_SEND_MESSAGE_RESPONSE_FIELDS, WIRE_SESSION_CANDIDATES_FIELDS, WIRE_SESSION_CANDIDATE_FIELDS, WIRE_SESSION_FINISH_FIELDS, WIRE_SESSION_LIST_FIELDS, WIRE_SESSION_REFERENCE_FIELDS, WIRE_SESSION_RESPONSE_FIELDS, WIRE_SESSION_SUMMARY_FIELDS, WIRE_SESSION_WORKSPACE_FIELDS, WIRE_SESSION_WORKSPACE_RESPONSE_FIELDS, WIRE_SETTINGS_FIELDS, WIRE_SETTINGS_WRITE_FIELDS, WIRE_SETTINGS_WRITE_REQUEST_FIELDS, WIRE_SETTING_ROW_FIELDS, WIRE_SETTING_STEP_FIELDS, WIRE_SETTING_WRITE_FIELDS, WIRE_SETTING_WRITE_LAYERS, WIRE_SKILL_BODY_FIELDS, WIRE_SKILL_FIELDS, WIRE_SKILL_IMPORT_FIELDS, WIRE_SKILL_IMPORT_PREVIEW_FIELDS, WIRE_TASKS_FIELDS, WIRE_TOOLS_FIELDS, WIRE_TOOL_GATE_FIELDS, WIRE_TOOL_ROW_FIELDS, WIRE_WORKSPACE_DIFF_FIELDS, WIRE_WORKSPACE_DIRS_FIELDS, WIRE_WORKSPACE_DIR_ENTRY_FIELDS, WIRE_WORKSPACE_REF_FIELDS, type WireAbortResponse, type WireAbout, type WireAgentEnvelope, type WireAgentEvent, type WireAgentRoleSummary, type WireApprovalCacheRevokeRequest, type WireApprovalCacheRevokeResponse, type WireApprovalListResponse, type WireApprovalRequest, type WireArtifactsResponse, type WireAuditCode, type WireAuditLog, type WireAuditOutcome, type WireAuditRow, type WireAuthDenyReason, type WireAuthDenyStatus, type WireAuthGuard, type WireAuthRequestView, type WireAuthVerdict, type WireBackgroundTask, type WireBindWorkspaceRequest, type WireBlockedGoal, type WireBlockedGoalsResponse, type WireBlockedLevel, type WireBuiltinConnector, type WireCachedApproval, type WireCachedDecision, type WireCapabilitiesResponse, type WireCapabilityProject, type WireCheckpointListResponse, type WireCheckpointSummary, type WireCommandExpansion, type WireCommandsResponse, type WireCompactRequest, type WireCompactResponse, type WireCompressionLine, type WireConfigResponse, type WireContextResponse, type WireCreateSessionRequest, type WireCreateSessionResponse, type WireCreateWorkspaceRequest, type WireCreateWorkspaceResponse, type WireCustomCommand, type WireDeleteSessionResponse, type WireDiffFile, type WireDiffNote, type WireDiffOmitReason, type WireDiffSource, type WireDiffStatus, type WireEnvelope, type WireErrorResponse, type WireEvent, type WireFields, type WireFileCandidate, type WireFileCandidatesResponse, type WireFileChange, type WireFileReference, type WireFixture, type WireFixtureCase, type WireGoal, type WireGoalBlock, type WireGoalClearResponse, type WireGoalCreateRequest, type WireGoalLimits, type WireGoalPhase, type WireGoalRefusal, type WireGoalResponse, type WireGoalUpdateRequest, type WireGoalWriteResponse, type WireHealthResponse, type WireHubEnvelope, type WireHubEvent, type WireKnownWorkspace, type WireManagedLock, type WireMcpAddRequest, type WireMcpAddResponse, type WireMcpApplyAction, type WireMcpApplyEntry, type WireMcpApplyResponse, type WireMcpConfigApplyRequest, type WireMcpConfigIssue, type WireMcpConfigResponse, type WireMcpConfigSaveRequest, type WireMcpConfigSaveResponse, type WireMcpConnector, type WireMcpReconnectResponse, type WireMessageListResponse, type WireModelCaveat, type WireModelProbeStatus, type WireModelRejection, type WireModelResponse, type WireModelSelection, type WireModelSetRequest, type WireModelSetResponse, type WireModelSource, type WireModelSuggestions, type WirePatchSessionRequest, type WirePatchSessionResponse, type WirePermissionRuleRow, type WirePermissionSetRequest, type WirePermissionSetResponse, type WirePermissionShadow, type WirePermissionState, type WirePermissionStatus, type WirePlanAction, type WirePlanModeRequest, type WirePlanModeResponse, type WirePlanModeState, type WirePlanResponse, type WirePluginActionResponse, type WirePluginCounts, type WirePluginEntry, type WirePluginHit, type WirePluginHookTally, type WirePluginInstallRequest, type WirePluginNameRequest, type WirePluginPreview, type WirePluginPreviewRequest, type WirePluginPreviewResponse, type WirePluginRefuseReason, type WirePluginSourceType, type WirePluginUpdateRequest, type WirePluginsResponse, type WirePolicyDir, type WirePolicyStatus, type WireProviderModelsRequest, type WireProviderModelsResponse, type WireProviderOption, type WireProviderSummary, type WireProvidersResponse, type WireQuestionListResponse, type WireQuestionRequest, type WireReplayMessage, type WireResetReason, type WireRespondApprovalRequest, type WireRespondApprovalResponse, type WireRespondQuestionRequest, type WireRespondQuestionResponse, type WireRewindAction, type WireRewindDrift, type WireRewindFilePlan, type WireRewindOutcome, type WireRewindPreview, type WireRewindRequest, type WireRewindResponse, type WireRewindScope, type WireRoleAddRequest, type WireRoleAddResponse, type WireSandboxReason, type WireSandboxStatus, type WireScheduleCapability, type WireScheduleCreateRequest, type WireScheduleDefaults, type WireScheduleDeleteResponse, type WireScheduleFixRequest, type WireScheduleFixResponse, type WireScheduleIssue, type WireScheduleIssueCode, type WireScheduleListResponse, type WireSchedulePendingResponse, type WireScheduleRecordingResponse, type WireScheduleRegisterRefusal, type WireScheduleRegisterWarning, type WireScheduleRegistration, type WireScheduleResponse, type WireScheduleRow, type WireScheduleRunResponse, type WireScheduleRunsResponse, type WireScheduleSaveResponse, type WireScheduleUpdateRequest, type WireSecurityResponse, type WireSecurityWorkspace, type WireSendMessageRequest, type WireSendMessageResponse, type WireSessionCandidate, type WireSessionCandidatesResponse, type WireSessionFinish, type WireSessionListResponse, type WireSessionReference, type WireSessionResponse, type WireSessionSummary, type WireSessionWorkspace, type WireSessionWorkspaceResponse, type WireSettingApply, type WireSettingFutile, type WireSettingLayer, type WireSettingRow, type WireSettingStep, type WireSettingValue, type WireSettingWrite, type WireSettingWriteLayer, type WireSettingsResponse, type WireSettingsWriteRequest, type WireSettingsWriteResponse, type WireSkillBodyResponse, type WireSkillImportConflict, type WireSkillImportEntry, type WireSkillImportFailure, type WireSkillImportForm, type WireSkillImportIssue, type WireSkillImportPreviewResponse, type WireSkillImportResponse, type WireSkillSummary, type WireSummaryWorkspace, type WireTaggedEvent, type WireTasksResponse, type WireToolGate, type WireToolGateAxis, type WireToolRow, type WireToolSummary, type WireToolVerdict, type WireToolsResponse, type WireTurnState, type WireWorkspaceBinding, type WireWorkspaceDiffResponse, type WireWorkspaceDirEntry, type WireWorkspaceDirsResponse, type WireWorkspaceRef, apiKeyEnvVar, assertNeverPart, assistantTurnText, attachSessionSurface, chordId, collectPendingApprovals, compressionThresholdTokens, conformsToWire, contentToText, diagnosticToLine, extractAllMentions, extractMentions, extractSessionMentions, filePartSummary, flattenCatalog, getProviderInfo, hasFailure, isApprovalOutcome, isContextNearlyFull, isHeadlessInputFormat, isHeadlessLifecycleEvent, isHeadlessOutputFormat, isKeyAction, isLang, isMentionableFilePath, isOperationType, isPermissionLevel, isPlanOutcome, isProviderType, isQuestionAnswerMap, isSearchProviderType, isValidAgentRoleName, isValidCommandName, isWireGoalPhase, isWireHubEvent, isWirePlanAction, isWireRewindScope, isWireSettingWriteLayer, makeTranslate, normalizeApproval, packSessionSurface, parseChord, parseModelRef, parseSequence, perAction, placeholdersOf, promptTokens, sequenceId, sessionPartSummary, stripSystemNote, systemNote, tableOf, totalUsageTokens, translatorFor, turnDigest, unfixedRules, utf8ByteLength, wireFields };
12131
+ export { ACTION_CONTEXTS, AGENT_ROLE_NAME_PATTERN, API_KEY_ENV_VARS, APPROVAL_OUTCOMES, type ActiveAgentRole, type AgentEvent, type AgentEventOf, type AgentEventType, type AgentRole, type AgentRoleScope, type AgentRoleSource, type ApprovalAnswer, type ApprovalOutcome, type ApprovalReply, type ApprovalRequest, type ArtifactKind, type AttributeValue, type Attributes, type BackgroundTaskInfo, type BackgroundTaskKind, type BackgroundTaskStatus, type BoundKey, type BudgetBreach, COMMAND_NAME_PATTERN, COMPLIANCE_ACTIONS, COMPLIANCE_CATEGORIES, type Catalogs, type ComplianceAction, type ComplianceCategory, type ContextBreakdown, type ContextSegment, type CustomCommandDef, type CustomCommandSource, DEFAULT_AGENT_ROLE, DEFAULT_LANG, type DecidedTrustLevel, type Diagnostic, DiagnosticSink, type DiagnosticStatus, type EpochConfig, type EpochContentPart, type EpochFilePart, type EpochHook, type EpochImagePart, type EpochMessage, type EpochMessageRole, type EpochPlugin, type EpochSessionPart, type EpochSkill, type EpochTextPart, type EpochTool, type EpochToolCall, type EpochToolResult, type EpochTurnFacts, type EpochUserContent, type ExpandedCommand, type FileOmitReason, type FinishReason, type FixableApproval, type FlatCatalog, GEN_AI, HEADLESS_INPUT_EVENT_TYPES, HEADLESS_INPUT_FORMATS, HEADLESS_OUTPUT_FORMATS, HEADLESS_PROTOCOL_VERSION, type HeadlessEnvelope, type HeadlessEvent, type HeadlessInit, type HeadlessInputEvent, type HeadlessInputEventType, type HeadlessInputFormat, type HeadlessLifecycleEvent, type HeadlessOutputFormat, type HeadlessResult, type HeadlessResultReason, type HookContext, type HookEvent, type HookHandler, type HookManager, type HookRegistration, type HookType, type JsonSchema, KEY_ACTIONS, type KeyAction, type KeyChord, type KeyContext, type KeySequence, type KeybindingReport, type KeybindingSource, type KeybindingTable, LANGS, type Lang, type LocalizedDetail, type LogFn, MAX_ATTACH_BYTES, MAX_ATTACH_FILES, MAX_ATTACH_TOTAL_BYTES, MAX_HEADER_CHARS, MAX_OPTIONS, MAX_QUESTIONS, MAX_SESSION_ATTACH_BYTES, MAX_SESSION_REFS, MCP_DATA_KEY_NAME, METRIC, MIN_OPTIONS, type MentionSet, type ModelRef, type ModelSelection, type ModelSelectionOrigin, NOOP_TELEMETRY, type NoticeCode, OPERATION_TYPES, type Operation, type OperationType, PERMISSION_LEVELS, PLAN_OUTCOMES, PROVIDER_INFOS, PROVIDER_TYPES, type PermissionDecision, type PermissionLevel, type PermissionManager, type PermissionRuleLists, type PlanApprovalOutcome, type PlanProposal, type PluginContext, type ProviderInfo, type ProviderType, type QuestionAnswer, type QuestionItem, type QuestionOption, type QuestionRequest, RESERVED_CHORDS, RESERVED_COMMAND_NAMES, type RejectReason, type RejectedKey, type RuleValue, SCHEDULE_INTERVAL_MINUTES, SCHEDULE_RUN_STATUSES, SCHEDULE_TEMPLATES, SEARCH_API_KEY_ENV, SEARCH_PROVIDER_TYPES, SECRET_SERVICE, SEQUENCE_CONTEXTS, SESSION_MENTION_PREFIX, SESSION_REFERENCE_ERROR_CODES, SHELL_KINDS, SIDEBAR_MENU_MESSAGE_TYPE, SIMULTANEOUS_CONTEXTS, SYSTEM_NOTE_PREFIX, type ScheduleAllowlist, type ScheduleBackendKind, type ScheduleCycle, type ScheduleDefinition, type SchedulePendingApproval, type SchedulePendingItem, type ScheduleRun, type ScheduleRunStatus, type ScheduleRunnerSpec, type ScheduleTemplate, type ScheduleTemplateId, type ScheduleTrigger, type ScheduleTriggerKind, type ScheduleWeekday, type SearchProviderType, type SecretBackendId, type SecretStore, type SelectionContext, type SelectionRejection, type SelectionWarning, type SessionAttachKind, type SessionAttachResult, type SessionOmitReason, type SessionSurfaceMessage, type SessionSurfaceView, type SetSelectionResult, type ShellKind, type SidebarMenuItem, type SidebarMenuMessage, type SpanLike, type SubToolCall, type SubToolCallResult, type Telemetry, type TokenUsage, type ToolAnnotations, type ToolArtifact, type ToolArtifactInput, type ToolContext, type ToolDefinition, ToolExposure, type ToolProvider, type ToolResult, type Translate, type TranslateVars, type TrustLevel, type TrustManager, type TrustRecord, type TrustScope, type VerifyResult, WIRE_ABORT_FIELDS, WIRE_ABOUT_FIELDS, WIRE_AGENT_ROLE_FIELDS, WIRE_APPROVAL_CACHE_REVOKE_REQUEST_FIELDS, WIRE_APPROVAL_CACHE_REVOKE_RESPONSE_FIELDS, WIRE_APPROVAL_LIST_FIELDS, WIRE_APPROVAL_ROW_FIELDS, WIRE_ARTIFACTS_FIELDS, WIRE_AUDIT_LOG_FIELDS, WIRE_AUDIT_ROW_FIELDS, WIRE_AUTH_COOKIE, WIRE_AUTH_COOKIE_ATTRS, WIRE_AUTH_TOKEN_PARAM, WIRE_BACKGROUND_TASK_FIELDS, WIRE_BLOCKED_GOALS_FIELDS, WIRE_BLOCKED_GOAL_FIELDS, WIRE_BLOCKED_LEVEL_FIELDS, WIRE_BUILTIN_CONNECTOR_FIELDS, WIRE_CACHED_APPROVAL_FIELDS, WIRE_CAPABILITIES_FIELDS, WIRE_CAPABILITY_PROJECT_FIELDS, WIRE_CHECKPOINT_LIST_FIELDS, WIRE_CHECKPOINT_SUMMARY_FIELDS, WIRE_COMMANDS_FIELDS, WIRE_COMMAND_EXPANSION_FIELDS, WIRE_COMPACT_FIELDS, WIRE_COMPRESSION_LINE_FIELDS, WIRE_CONFIG_FIELDS, WIRE_CONTEXT_FIELDS, WIRE_CREATE_SESSION_FIELDS, WIRE_CREATE_WORKSPACE_FIELDS, WIRE_CREATE_WORKSPACE_RESPONSE_FIELDS, WIRE_CUSTOM_COMMAND_FIELDS, WIRE_DELETE_SESSION_FIELDS, WIRE_DIFF_FILE_FIELDS, WIRE_ERROR_FIELDS, WIRE_FILE_CANDIDATES_FIELDS, WIRE_FILE_CANDIDATE_FIELDS, WIRE_FILE_CHANGE_FIELDS, WIRE_FILE_OPEN_RESPONSE_FIELDS, WIRE_FILE_REFERENCE_FIELDS, WIRE_FILE_STAT_MAX, WIRE_FILE_STAT_OK_FIELDS, WIRE_FILE_STAT_REFUSED_FIELDS, WIRE_FILE_STAT_RESPONSE_FIELDS, WIRE_FILE_TEXT_RESPONSE_FIELDS, WIRE_GOAL_ACTIONS, WIRE_GOAL_BLOCK_FIELDS, WIRE_GOAL_CLEAR_FIELDS, WIRE_GOAL_CREATE_FIELDS, WIRE_GOAL_FIELDS, WIRE_GOAL_LIMITS_FIELDS, WIRE_GOAL_PHASES, WIRE_GOAL_REFUSALS, WIRE_GOAL_RESPONSE_FIELDS, WIRE_GOAL_WRITE_FIELDS, WIRE_HEALTH_FIELDS, WIRE_IMAGE_REFERENCE_FIELDS, WIRE_KNOWN_WORKSPACE_FIELDS, WIRE_LANG_PARAM, WIRE_MANAGED_LOCK_FIELDS, WIRE_MCP_ADD_FIELDS, WIRE_MCP_ADD_RESPONSE_FIELDS, WIRE_MCP_APPLY_ENTRY_FIELDS, WIRE_MCP_APPLY_RESPONSE_FIELDS, WIRE_MCP_CONFIG_APPLY_FIELDS, WIRE_MCP_CONFIG_FIELDS, WIRE_MCP_CONFIG_ISSUE_FIELDS, WIRE_MCP_CONFIG_SAVE_FIELDS, WIRE_MCP_CONFIG_SAVE_RESPONSE_FIELDS, WIRE_MCP_CONNECTOR_FIELDS, WIRE_MCP_RECONNECT_FIELDS, WIRE_MESSAGE_LIST_FIELDS, WIRE_MODEL_FIELDS, WIRE_MODEL_SELECTION_FIELDS, WIRE_MODEL_SET_FIELDS, WIRE_MODEL_SUGGESTIONS_FIELDS, WIRE_PATCH_SESSION_FIELDS, WIRE_PATCH_SESSION_RESPONSE_FIELDS, WIRE_PERMISSION_RULE_FIELDS, WIRE_PERMISSION_SET_REQUEST_FIELDS, WIRE_PERMISSION_SET_RESPONSE_FIELDS, WIRE_PERMISSION_SHADOW_FIELDS, WIRE_PERMISSION_STATE_FIELDS, WIRE_PERMISSION_STATUS_FIELDS, WIRE_PLAN_ACTIONS, WIRE_PLAN_FIELDS, WIRE_PLAN_MODE_REQUEST_FIELDS, WIRE_PLAN_MODE_RESPONSE_FIELDS, WIRE_PLAN_MODE_STATE_FIELDS, WIRE_PLUGINS_FIELDS, WIRE_PLUGIN_ACTION_FIELDS, WIRE_PLUGIN_INSTALL_FIELDS, WIRE_PLUGIN_LOCAL_FIELDS, WIRE_PLUGIN_LOCAL_INSTALL_FIELDS, WIRE_PLUGIN_MARKETPLACE_FIELDS, WIRE_PLUGIN_MARKETPLACE_RESPONSE_FIELDS, WIRE_PLUGIN_NAME_FIELDS, WIRE_PLUGIN_PREVIEW_FIELDS, WIRE_PLUGIN_PREVIEW_RESPONSE_FIELDS, WIRE_PLUGIN_UPDATE_FIELDS, WIRE_POLICY_DIR_FIELDS, WIRE_POLICY_STATUS_FIELDS, WIRE_PROVIDERS_FIELDS, WIRE_PROVIDER_KEY_FIELDS, WIRE_PROVIDER_MODELS_FIELDS, WIRE_PROVIDER_OPTION_FIELDS, WIRE_QUESTION_LIST_FIELDS, WIRE_QUESTION_ROW_FIELDS, WIRE_RESPOND_APPROVAL_FIELDS, WIRE_RESPOND_APPROVAL_RESPONSE_FIELDS, WIRE_RESPOND_QUESTION_FIELDS, WIRE_RESPOND_QUESTION_RESPONSE_FIELDS, WIRE_REWIND_FILE_PLAN_FIELDS, WIRE_REWIND_OUTCOME_FIELDS, WIRE_REWIND_PREVIEW_FIELDS, WIRE_REWIND_REQUEST_FIELDS, WIRE_REWIND_RESPONSE_FIELDS, WIRE_REWIND_SCOPES, WIRE_ROLE_ADD_FIELDS, WIRE_ROLE_ADD_RESPONSE_FIELDS, WIRE_SANDBOX_STATUS_FIELDS, WIRE_SCHEDULE_CAPABILITY_FIELDS, WIRE_SCHEDULE_CREATE_FIELDS, WIRE_SCHEDULE_DEFAULTS_FIELDS, WIRE_SCHEDULE_DELETE_FIELDS, WIRE_SCHEDULE_FIELDS, WIRE_SCHEDULE_FIX_FIELDS, WIRE_SCHEDULE_FIX_REQUEST_FIELDS, WIRE_SCHEDULE_ISSUE_CODES, WIRE_SCHEDULE_LIST_FIELDS, WIRE_SCHEDULE_PENDING_FIELDS, WIRE_SCHEDULE_RECORDING_FIELDS, WIRE_SCHEDULE_ROW_FIELDS, WIRE_SCHEDULE_RUNS_FIELDS, WIRE_SCHEDULE_RUN_FIELDS, WIRE_SCHEDULE_SAVE_FIELDS, WIRE_SECURITY_FIELDS, WIRE_SECURITY_WORKSPACE_FIELDS, WIRE_SEND_MESSAGE_FIELDS, WIRE_SEND_MESSAGE_RESPONSE_FIELDS, WIRE_SESSION_CANDIDATES_FIELDS, WIRE_SESSION_CANDIDATE_FIELDS, WIRE_SESSION_FINISH_FIELDS, WIRE_SESSION_LIST_FIELDS, WIRE_SESSION_REFERENCE_FIELDS, WIRE_SESSION_RESPONSE_FIELDS, WIRE_SESSION_SUMMARY_FIELDS, WIRE_SESSION_WORKSPACE_FIELDS, WIRE_SESSION_WORKSPACE_RESPONSE_FIELDS, WIRE_SETTINGS_FIELDS, WIRE_SETTINGS_WRITE_FIELDS, WIRE_SETTINGS_WRITE_REQUEST_FIELDS, WIRE_SETTING_ROW_FIELDS, WIRE_SETTING_STEP_FIELDS, WIRE_SETTING_WRITE_FIELDS, WIRE_SETTING_WRITE_LAYERS, WIRE_SKILL_BODY_FIELDS, WIRE_SKILL_FIELDS, WIRE_SKILL_IMPORT_FIELDS, WIRE_SKILL_IMPORT_PREVIEW_FIELDS, WIRE_SKILL_REMOVE_FIELDS, WIRE_TASKS_FIELDS, WIRE_TOOLS_FIELDS, WIRE_TOOL_GATE_FIELDS, WIRE_TOOL_ROW_FIELDS, WIRE_WORKSPACE_DIFF_FIELDS, WIRE_WORKSPACE_DIRS_FIELDS, WIRE_WORKSPACE_DIR_ENTRY_FIELDS, WIRE_WORKSPACE_REF_FIELDS, type WireAbortResponse, type WireAbout, type WireAgentEnvelope, type WireAgentEvent, type WireAgentRoleSummary, type WireApprovalCacheRevokeRequest, type WireApprovalCacheRevokeResponse, type WireApprovalListResponse, type WireApprovalRequest, type WireArtifactsResponse, type WireAuditCode, type WireAuditLog, type WireAuditOutcome, type WireAuditRow, type WireAuthDenyReason, type WireAuthDenyStatus, type WireAuthGuard, type WireAuthRequestView, type WireAuthVerdict, type WireBackgroundTask, type WireBindWorkspaceRequest, type WireBlockedGoal, type WireBlockedGoalsResponse, type WireBlockedLevel, type WireBuiltinConnector, type WireCachedApproval, type WireCachedDecision, type WireCapabilitiesResponse, type WireCapabilityProject, type WireCheckpointListResponse, type WireCheckpointSummary, type WireCommandExpansion, type WireCommandsResponse, type WireCompactRequest, type WireCompactResponse, type WireCompressionLine, type WireConfigResponse, type WireContextResponse, type WireCreateSessionRequest, type WireCreateSessionResponse, type WireCreateWorkspaceRequest, type WireCreateWorkspaceResponse, type WireCustomCommand, type WireDeleteSessionResponse, type WireDiffFile, type WireDiffNote, type WireDiffOmitReason, type WireDiffSource, type WireDiffStatus, type WireEnvelope, type WireErrorResponse, type WireEvent, type WireFields, type WireFileCandidate, type WireFileCandidatesResponse, type WireFileChange, type WireFileKind, type WireFileOpenRefusal, type WireFileOpenResponse, type WireFileReference, type WireFileRefusal, type WireFileStat, type WireFileStatOk, type WireFileStatRefused, type WireFileStatResponse, type WireFileTextResponse, type WireFixture, type WireFixtureCase, type WireGoal, type WireGoalBlock, type WireGoalClearResponse, type WireGoalCreateRequest, type WireGoalLimits, type WireGoalPhase, type WireGoalRefusal, type WireGoalResponse, type WireGoalUpdateRequest, type WireGoalWriteResponse, type WireHealthResponse, type WireHubEnvelope, type WireHubEvent, type WireImageReference, type WireKnownWorkspace, type WireManagedLock, type WireMcpAddRequest, type WireMcpAddResponse, type WireMcpApplyAction, type WireMcpApplyEntry, type WireMcpApplyResponse, type WireMcpConfigApplyRequest, type WireMcpConfigIssue, type WireMcpConfigResponse, type WireMcpConfigSaveRequest, type WireMcpConfigSaveResponse, type WireMcpConnector, type WireMcpReconnectResponse, type WireMessageListResponse, type WireModelCaveat, type WireModelProbeStatus, type WireModelRejection, type WireModelResponse, type WireModelSelection, type WireModelSetRequest, type WireModelSetResponse, type WireModelSource, type WireModelSuggestions, type WirePatchSessionRequest, type WirePatchSessionResponse, type WirePermissionRememberFailure, type WirePermissionRuleRow, type WirePermissionSetRequest, type WirePermissionSetResponse, type WirePermissionShadow, type WirePermissionState, type WirePermissionStatus, type WirePickDirectoryResponse, type WirePlanAction, type WirePlanModeRequest, type WirePlanModeResponse, type WirePlanModeState, type WirePlanResponse, type WirePluginActionResponse, type WirePluginCounts, type WirePluginEntry, type WirePluginHit, type WirePluginHookTally, type WirePluginInstallRequest, type WirePluginLocalInstallRequest, type WirePluginLocalRequest, type WirePluginMarketplace, type WirePluginMarketplaceRequest, type WirePluginMarketplaceResponse, type WirePluginNameRequest, type WirePluginPreview, type WirePluginPreviewRequest, type WirePluginPreviewResponse, type WirePluginRefuseReason, type WirePluginSourceType, type WirePluginUpdateRequest, type WirePluginsResponse, type WirePolicyDir, type WirePolicyStatus, type WireProviderKeyRequest, type WireProviderKeyResponse, type WireProviderModelsRequest, type WireProviderModelsResponse, type WireProviderOption, type WireProviderSummary, type WireProvidersResponse, type WireQuestionListResponse, type WireQuestionRequest, type WireReplayMessage, type WireResetReason, type WireRespondApprovalRequest, type WireRespondApprovalResponse, type WireRespondQuestionRequest, type WireRespondQuestionResponse, type WireRewindAction, type WireRewindDrift, type WireRewindFilePlan, type WireRewindOutcome, type WireRewindPreview, type WireRewindRequest, type WireRewindResponse, type WireRewindScope, type WireRoleAddRequest, type WireRoleAddResponse, type WireSandboxReason, type WireSandboxStatus, type WireScheduleCapability, type WireScheduleCreateRequest, type WireScheduleDefaults, type WireScheduleDeleteResponse, type WireScheduleFixRequest, type WireScheduleFixResponse, type WireScheduleIssue, type WireScheduleIssueCode, type WireScheduleListResponse, type WireSchedulePendingResponse, type WireScheduleRecordingResponse, type WireScheduleRegisterRefusal, type WireScheduleRegisterWarning, type WireScheduleRegistration, type WireScheduleResponse, type WireScheduleRow, type WireScheduleRunResponse, type WireScheduleRunsResponse, type WireScheduleSaveResponse, type WireScheduleUpdateRequest, type WireSecurityResponse, type WireSecurityWorkspace, type WireSendMessageRequest, type WireSendMessageResponse, type WireSessionCandidate, type WireSessionCandidatesResponse, type WireSessionFinish, type WireSessionListResponse, type WireSessionReference, type WireSessionResponse, type WireSessionSummary, type WireSessionWorkspace, type WireSessionWorkspaceResponse, type WireSettingApply, type WireSettingFutile, type WireSettingLayer, type WireSettingRow, type WireSettingStep, type WireSettingValue, type WireSettingValueKind, type WireSettingWrite, type WireSettingWriteLayer, type WireSettingsResponse, type WireSettingsWriteRequest, type WireSettingsWriteResponse, type WireSkillBodyResponse, type WireSkillImportConflict, type WireSkillImportEntry, type WireSkillImportFailure, type WireSkillImportForm, type WireSkillImportIssue, type WireSkillImportPreviewResponse, type WireSkillImportResponse, type WireSkillRemoveFailure, type WireSkillRemoveResponse, type WireSkillSummary, type WireSummaryWorkspace, type WireTaggedEvent, type WireTasksResponse, type WireToolGate, type WireToolGateAxis, type WireToolRow, type WireToolSummary, type WireToolVerdict, type WireToolsResponse, type WireTurnState, type WireWorkspaceBinding, type WireWorkspaceDiffResponse, type WireWorkspaceDirEntry, type WireWorkspaceDirsResponse, type WireWorkspaceRef, apiKeyEnvVar, assertNeverPart, assistantTurnText, attachSessionSurface, chordId, collectPendingApprovals, compressionThresholdTokens, conformsToWire, contentToText, diagnosticToLine, extractAllMentions, extractMentions, extractSessionMentions, filePartSummary, flattenCatalog, getProviderInfo, hasFailure, isApprovalOutcome, isContextNearlyFull, isHeadlessInputFormat, isHeadlessLifecycleEvent, isHeadlessOutputFormat, isKeyAction, isLang, isMentionableFilePath, isOperationType, isPermissionLevel, isPlanOutcome, isProviderType, isQuestionAnswerMap, isSearchProviderType, isValidAgentRoleName, isValidCommandName, isWireGoalPhase, isWireHubEvent, isWirePlanAction, isWireRewindScope, isWireSettingWriteLayer, makeTranslate, normalizeApproval, packSessionSurface, parseChord, parseModelRef, parseSequence, perAction, placeholdersOf, promptTokens, sequenceId, sessionPartSummary, stripSystemNote, systemNote, tableOf, totalUsageTokens, translatorFor, turnDigest, unfixedRules, utf8ByteLength, wireFields };