@x-otto/plugin 0.1.0-alpha.6 → 0.1.0-alpha.7

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 CHANGED
@@ -41,6 +41,11 @@ import { definePlugin } from '@x-otto/plugin'
41
41
  export default definePlugin((ctx) => ({
42
42
  hooks: {
43
43
  preToolUse: (ctx) => ctx.toolName === 'write' ? { decision: 'deny' } : { decision: 'allow' },
44
+ // 通知出站交付(turn_complete/error/approval_required/input_required 归一后触发)。
45
+ // 例:把 otto 通知转发到自己的推送渠道。
46
+ onNotification: (ctx) => {
47
+ console.log(`[${ctx.notification_type}] ${ctx.message}`)
48
+ },
44
49
  },
45
50
  }))
46
51
  ```
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { TypedEventEmitter } from "@x-otto/shared";
3
3
  import * as _$_x_otto_interchange0 from "@x-otto/interchange";
4
- import { PluginInputRegistry, SigilEntry, SigilKind, SigilPrefix, SigilProvider, SigilSource, sigilOf } from "@x-otto/interchange";
4
+ import { PluginInputRegistry, PluginInputRegistry as PluginInputRegistry$1, SigilEntry, SigilEntry as SigilEntry$1, SigilKind, SigilPrefix, SigilProvider, SigilProvider as SigilProvider$1, SigilSource, sigilOf } from "@x-otto/interchange";
5
5
  import * as _$_x_otto_provider0 from "@x-otto/provider";
6
6
  import { HookAbortSignal, HookPayloadMap, WaterfallTiming } from "@x-otto/hook-contracts";
7
7
 
@@ -22,7 +22,7 @@ import { HookAbortSignal, HookPayloadMap, WaterfallTiming } from "@x-otto/hook-c
22
22
  * - 非高危 = 纯数据 / 用户显式触发 / 写面物理隔离 / 单向推送不进入模型上下文。
23
23
  */
24
24
  /** 能力声明词表(RFC-105 D8)。详见各能力注释。 */
25
- declare const PLUGIN_CAPABILITIES: readonly ["provider", "tools", "hooks", "tui.renderer", "network", "context", "monitor", "wire-protocol", "a2ui.component", "panel.backend", "mcp.server", "feedback", "input.resolver", "a2ui.renderer", "theme", "agent.dispatch", "service", "storage", "session.read", "llm.complete", "user.notify", "user.ask"];
25
+ declare const PLUGIN_CAPABILITIES: readonly ["provider", "tools", "hooks", "tui.renderer", "network", "context", "monitor", "wire-protocol", "a2ui.component", "panel.backend", "mcp.server", "feedback", "input.resolver", "a2ui.renderer", "theme", "agent.dispatch", "service", "storage", "session.read", "llm.complete", "user.notify", "user.ask", "config", "credentials", "cli.command", "tui.command", "events", "settings.read"];
26
26
  type PluginCapability = (typeof PLUGIN_CAPABILITIES)[number];
27
27
  /**
28
28
  * 高危能力轴(RFC-105 D8):既有布尔信任**不**自动授予这些轴——已信任插件升级后首次
@@ -773,6 +773,14 @@ declare const MAX_I18N_TRANSLATION_BYTES: number;
773
773
  declare const MAX_I18N_LOCALE_FILES_PER_PLUGIN = 20;
774
774
  /** 单个 locale 文件的字节上限(RFC-141 D4,对齐 `context-sources.ts` 的 `MAX_CONTENT_BYTES` 纪律,超限整个文件跳过)。 */
775
775
  declare const MAX_I18N_FILE_BYTES: number;
776
+ /** 单插件可声明的配置字段数上限(RFC-401 D4,防海量字段内存膨胀 DoS)。 */
777
+ declare const MAX_CONFIG_FIELDS_PER_PLUGIN = 32;
778
+ /** 配置 schema 最大嵌套深度(RFC-401 D4,防深嵌套解析 DoS)。 */
779
+ declare const MAX_CONFIG_SCHEMA_DEPTH = 4;
780
+ /** 配置 schema 片段总字节数上限(RFC-401 D4,对齐 i18n 文件上限纪律)。 */
781
+ declare const MAX_CONFIG_SCHEMA_BYTES: number;
782
+ /** 单字段 description 最大字符数(RFC-401 D4,防 UI 渲染膨胀)。 */
783
+ declare const MAX_CONFIG_FIELD_DESCRIPTION_CHARS = 256;
776
784
  /**
777
785
  * 性能采集器贡献(RFC-116 §8:插件可执行的指标采集器,非静态文本)。
778
786
  * `toolRef` 引用同插件 `contributes.tools` 已声明的某个工具名——该工具的返回值即本
@@ -886,6 +894,36 @@ declare const cliContributionSchema: z.ZodObject<{
886
894
  bin: z.ZodCatch<z.ZodOptional<z.ZodString>>;
887
895
  }, z.core.$strip>;
888
896
  type PluginCliContribution = z.infer<typeof cliContributionSchema>;
897
+ /**
898
+ * RFC-410 M2:进程内 CLI 命令贡献(`otto <name> [args...]` 在宿主进程内执行插件
899
+ * definePlugin 导出的 handler,与 RFC-112 的 `contributes.cli`(bin 透传子进程)并列)。
900
+ * `name` 命名约束与 `cliContributionSchema` 完全一致(lowercase kebab-case,同一命名空间——
901
+ * 装载层把 cli 与 cliCommands 的 name 放入同一冲突判定漏斗做 fail-closed)。
902
+ * `summary` 是 `otto --help` 命令段与命令列表的帮助行;`i18nKey` 可选指向本插件
903
+ * contributes.i18n 条目做本地化。带 `.max()` + noControlChars 做终端注入防护。
904
+ * 需 `capabilities:["cli.command"]`(高危,宿主进程内执行代码)。
905
+ */
906
+ declare const cliCommandContributionSchema: z.ZodObject<{
907
+ name: z.ZodString;
908
+ summary: z.ZodString;
909
+ i18nKey: z.ZodCatch<z.ZodOptional<z.ZodString>>;
910
+ }, z.core.$strip>;
911
+ type PluginCliCommandContribution = z.infer<typeof cliCommandContributionSchema>;
912
+ /**
913
+ * RFC-440:TUI 斜杠命令贡献元数据(`contributes.slashCommands[].name`)。
914
+ * `name` 命名约束:lowercase kebab-case(对齐 cliCommands;斜杠命令消费方在命令前加 `/`)。
915
+ * `description` 是 `/help` 与命令列表的帮助行;`group` 可选对齐内置 SLASH_COMMANDS 的
916
+ * 分组('session'/'model'/'agent'/'debug'/'ui'/'other',未知值归 'other');`i18nKey` 可选指向
917
+ * 本插件 contributes.i18n 条目做本地化。带 `.max()` + noControlChars 做终端注入防护。
918
+ * 需 `capabilities:["tui.command"]`(高危,宿主进程内执行代码 + TUI 交互面)。
919
+ */
920
+ declare const slashCommandContributionSchema: z.ZodObject<{
921
+ name: z.ZodString;
922
+ description: z.ZodString;
923
+ group: z.ZodCatch<z.ZodOptional<z.ZodString>>;
924
+ i18nKey: z.ZodCatch<z.ZodOptional<z.ZodString>>;
925
+ }, z.core.$strip>;
926
+ type PluginSlashCommandContribution = z.infer<typeof slashCommandContributionSchema>;
889
927
  /**
890
928
  * 声明式 OAuth 贡献(RFC-122 D1):插件声明一个 OAuth 流程(PKCE 或 device-flow),
891
929
  * 装载器据此注册 `OAuthProvider` 实例到 `OAuthRegistry`,`/login` 命令自动发现。
@@ -926,6 +964,52 @@ declare const oauthContributionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
926
964
  }, z.core.$strip>], "kind">;
927
965
  type OAuthContribution = z.infer<typeof oauthContributionSchema>;
928
966
  /** 插件声明贡献了哪些能力面(信息性 + 未来 gating;缺省全部按目录探测)。 */
967
+ /**
968
+ * config schema 格式 = JSON Schema Draft 07 子集(RFC-401 D6)。
969
+ * 支持的 type: string/number/integer/boolean/array/object。
970
+ * 支持的关键字: type/title/description/default/enum/minimum/maximum/pattern/items/properties。
971
+ * 不支持: $ref/oneOf/anyOf/allOf/$defs(DoS 防护 + YAGNI)。
972
+ * DoS 上限: 字段数 ≤32 / 嵌套深度 ≤4 / 总字节 ≤8192 / description ≤256 字符。
973
+ */
974
+ declare const configSchemaEntrySchema: z.ZodObject<{
975
+ type: z.ZodEnum<{
976
+ string: "string";
977
+ number: "number";
978
+ boolean: "boolean";
979
+ object: "object";
980
+ array: "array";
981
+ integer: "integer";
982
+ }>;
983
+ title: z.ZodCatch<z.ZodOptional<z.ZodString>>;
984
+ description: z.ZodCatch<z.ZodOptional<z.ZodString>>;
985
+ default: z.ZodOptional<z.ZodUnknown>;
986
+ enum: z.ZodCatch<z.ZodOptional<z.ZodArray<z.ZodUnknown>>>;
987
+ minimum: z.ZodCatch<z.ZodOptional<z.ZodNumber>>;
988
+ maximum: z.ZodCatch<z.ZodOptional<z.ZodNumber>>;
989
+ pattern: z.ZodCatch<z.ZodOptional<z.ZodString>>;
990
+ items: z.ZodOptional<z.ZodUnknown>;
991
+ properties: z.ZodCatch<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
992
+ }, z.core.$strip>;
993
+ /** RFC-401: 配置 schema 片段类型(JSON Schema Draft 07 子集,z.infer 单一真源)。 */
994
+ type PluginConfigSchemaEntry = z.infer<typeof configSchemaEntrySchema>;
995
+ /**
996
+ * RFC-427 D1:插件事件贡献条目。插件声明可发布的自定义事件类型 + payload schema。
997
+ *
998
+ * - `type`:事件类型,强制 `<pluginId>.<event>` 命名空间格式(zod refine 校验),
999
+ * 防跨插件撞名。如 `skill-inductor.candidate-detected`。
1000
+ * - `description`:供事件订阅方理解事件语义(文档性 + 审计面)。
1001
+ * - `payloadSchema`:JSON Schema Draft 07 子集(同 `configField` 约束,RFC-401),
1002
+ * 装载时解析为校验器;emit 时 safeParse,失败 → 丢弃 + warn(fail-closed)。
1003
+ *
1004
+ * 不需要 capability(纯数据声明);但 emit/subscribe 运行时需 `events` capability(D3)。
1005
+ * 卸载走 unregisterBySource 漏斗(R4 可逆 effect)。
1006
+ */
1007
+ declare const eventContributionSchema: z.ZodObject<{
1008
+ type: z.ZodString;
1009
+ description: z.ZodCatch<z.ZodOptional<z.ZodString>>;
1010
+ payloadSchema: z.ZodCatch<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
1011
+ }, z.core.$strip>;
1012
+ type PluginEventContribution = z.infer<typeof eventContributionSchema>;
929
1013
  declare const contributesSchema: z.ZodObject<{
930
1014
  skills: z.ZodCatch<z.ZodOptional<z.ZodBoolean>>;
931
1015
  agents: z.ZodCatch<z.ZodOptional<z.ZodBoolean>>;
@@ -935,6 +1019,26 @@ declare const contributesSchema: z.ZodObject<{
935
1019
  name: z.ZodString;
936
1020
  bin: z.ZodCatch<z.ZodOptional<z.ZodString>>;
937
1021
  }, z.core.$strip>>>;
1022
+ cliCommands: z.ZodCatch<z.ZodOptional<z.ZodType<{
1023
+ name: string;
1024
+ summary: string;
1025
+ i18nKey?: string | undefined;
1026
+ }[] | undefined, unknown, z.core.$ZodTypeInternals<{
1027
+ name: string;
1028
+ summary: string;
1029
+ i18nKey?: string | undefined;
1030
+ }[] | undefined, unknown>>>>;
1031
+ slashCommands: z.ZodCatch<z.ZodOptional<z.ZodType<{
1032
+ name: string;
1033
+ description: string;
1034
+ group?: string | undefined;
1035
+ i18nKey?: string | undefined;
1036
+ }[] | undefined, unknown, z.core.$ZodTypeInternals<{
1037
+ name: string;
1038
+ description: string;
1039
+ group?: string | undefined;
1040
+ i18nKey?: string | undefined;
1041
+ }[] | undefined, unknown>>>>;
938
1042
  panels: z.ZodCatch<z.ZodOptional<z.ZodType<{
939
1043
  id: string;
940
1044
  title: string;
@@ -1436,6 +1540,48 @@ declare const contributesSchema: z.ZodObject<{
1436
1540
  entry: string;
1437
1541
  export?: string | undefined;
1438
1542
  }[] | undefined, unknown>>>>;
1543
+ config: z.ZodCatch<z.ZodOptional<z.ZodPipe<z.ZodUnknown, z.ZodTransform<{
1544
+ type: "string" | "number" | "boolean" | "object" | "array" | "integer";
1545
+ title?: string | undefined;
1546
+ description?: string | undefined;
1547
+ default?: unknown;
1548
+ enum?: unknown[] | undefined;
1549
+ minimum?: number | undefined;
1550
+ maximum?: number | undefined;
1551
+ pattern?: string | undefined;
1552
+ items?: unknown;
1553
+ properties?: Record<string, unknown> | undefined;
1554
+ } | undefined, unknown>>>>;
1555
+ credentialBackends: z.ZodCatch<z.ZodOptional<z.ZodType<{
1556
+ id: string;
1557
+ entry: string;
1558
+ label?: string | undefined;
1559
+ capabilities?: {
1560
+ atomic?: boolean | undefined;
1561
+ permSecure?: boolean | undefined;
1562
+ crossProcessLock?: boolean | undefined;
1563
+ watchable?: boolean | undefined;
1564
+ } | undefined;
1565
+ }[] | undefined, unknown, z.core.$ZodTypeInternals<{
1566
+ id: string;
1567
+ entry: string;
1568
+ label?: string | undefined;
1569
+ capabilities?: {
1570
+ atomic?: boolean | undefined;
1571
+ permSecure?: boolean | undefined;
1572
+ crossProcessLock?: boolean | undefined;
1573
+ watchable?: boolean | undefined;
1574
+ } | undefined;
1575
+ }[] | undefined, unknown>>>>;
1576
+ events: z.ZodCatch<z.ZodOptional<z.ZodType<{
1577
+ type: string;
1578
+ description?: string | undefined;
1579
+ payloadSchema?: Record<string, unknown> | undefined;
1580
+ }[] | undefined, unknown, z.core.$ZodTypeInternals<{
1581
+ type: string;
1582
+ description?: string | undefined;
1583
+ payloadSchema?: Record<string, unknown> | undefined;
1584
+ }[] | undefined, unknown>>>>;
1439
1585
  }, z.core.$strip>;
1440
1586
  type PluginContributes = z.infer<typeof contributesSchema>;
1441
1587
  /**
@@ -1468,6 +1614,26 @@ declare const manifestSchema: z.ZodObject<{
1468
1614
  name: z.ZodString;
1469
1615
  bin: z.ZodCatch<z.ZodOptional<z.ZodString>>;
1470
1616
  }, z.core.$strip>>>;
1617
+ cliCommands: z.ZodCatch<z.ZodOptional<z.ZodType<{
1618
+ name: string;
1619
+ summary: string;
1620
+ i18nKey?: string | undefined;
1621
+ }[] | undefined, unknown, z.core.$ZodTypeInternals<{
1622
+ name: string;
1623
+ summary: string;
1624
+ i18nKey?: string | undefined;
1625
+ }[] | undefined, unknown>>>>;
1626
+ slashCommands: z.ZodCatch<z.ZodOptional<z.ZodType<{
1627
+ name: string;
1628
+ description: string;
1629
+ group?: string | undefined;
1630
+ i18nKey?: string | undefined;
1631
+ }[] | undefined, unknown, z.core.$ZodTypeInternals<{
1632
+ name: string;
1633
+ description: string;
1634
+ group?: string | undefined;
1635
+ i18nKey?: string | undefined;
1636
+ }[] | undefined, unknown>>>>;
1471
1637
  panels: z.ZodCatch<z.ZodOptional<z.ZodType<{
1472
1638
  id: string;
1473
1639
  title: string;
@@ -1969,12 +2135,54 @@ declare const manifestSchema: z.ZodObject<{
1969
2135
  entry: string;
1970
2136
  export?: string | undefined;
1971
2137
  }[] | undefined, unknown>>>>;
2138
+ config: z.ZodCatch<z.ZodOptional<z.ZodPipe<z.ZodUnknown, z.ZodTransform<{
2139
+ type: "string" | "number" | "boolean" | "object" | "array" | "integer";
2140
+ title?: string | undefined;
2141
+ description?: string | undefined;
2142
+ default?: unknown;
2143
+ enum?: unknown[] | undefined;
2144
+ minimum?: number | undefined;
2145
+ maximum?: number | undefined;
2146
+ pattern?: string | undefined;
2147
+ items?: unknown;
2148
+ properties?: Record<string, unknown> | undefined;
2149
+ } | undefined, unknown>>>>;
2150
+ credentialBackends: z.ZodCatch<z.ZodOptional<z.ZodType<{
2151
+ id: string;
2152
+ entry: string;
2153
+ label?: string | undefined;
2154
+ capabilities?: {
2155
+ atomic?: boolean | undefined;
2156
+ permSecure?: boolean | undefined;
2157
+ crossProcessLock?: boolean | undefined;
2158
+ watchable?: boolean | undefined;
2159
+ } | undefined;
2160
+ }[] | undefined, unknown, z.core.$ZodTypeInternals<{
2161
+ id: string;
2162
+ entry: string;
2163
+ label?: string | undefined;
2164
+ capabilities?: {
2165
+ atomic?: boolean | undefined;
2166
+ permSecure?: boolean | undefined;
2167
+ crossProcessLock?: boolean | undefined;
2168
+ watchable?: boolean | undefined;
2169
+ } | undefined;
2170
+ }[] | undefined, unknown>>>>;
2171
+ events: z.ZodCatch<z.ZodOptional<z.ZodType<{
2172
+ type: string;
2173
+ description?: string | undefined;
2174
+ payloadSchema?: Record<string, unknown> | undefined;
2175
+ }[] | undefined, unknown, z.core.$ZodTypeInternals<{
2176
+ type: string;
2177
+ description?: string | undefined;
2178
+ payloadSchema?: Record<string, unknown> | undefined;
2179
+ }[] | undefined, unknown>>>>;
1972
2180
  }, z.core.$strip>>>;
1973
2181
  scripts: z.ZodCatch<z.ZodOptional<z.ZodObject<{
1974
2182
  postinstall: z.ZodCatch<z.ZodOptional<z.ZodString>>;
1975
2183
  setup: z.ZodCatch<z.ZodOptional<z.ZodString>>;
1976
2184
  }, z.core.$catchall<z.ZodString>>>>;
1977
- capabilities: z.ZodCatch<z.ZodOptional<z.ZodPipe<z.ZodArray<z.ZodUnknown>, z.ZodTransform<("provider" | "tools" | "hooks" | "tui.renderer" | "network" | "context" | "monitor" | "wire-protocol" | "a2ui.component" | "panel.backend" | "mcp.server" | "feedback" | "input.resolver" | "a2ui.renderer" | "theme" | "agent.dispatch" | "service" | "storage" | "session.read" | "llm.complete" | "user.notify" | "user.ask")[] | undefined, unknown[]>>>>;
2185
+ capabilities: z.ZodCatch<z.ZodOptional<z.ZodPipe<z.ZodArray<z.ZodUnknown>, z.ZodTransform<("config" | "events" | "provider" | "tools" | "hooks" | "tui.renderer" | "network" | "context" | "monitor" | "wire-protocol" | "a2ui.component" | "panel.backend" | "mcp.server" | "feedback" | "input.resolver" | "a2ui.renderer" | "theme" | "agent.dispatch" | "service" | "storage" | "session.read" | "llm.complete" | "user.notify" | "user.ask" | "credentials" | "cli.command" | "tui.command" | "settings.read")[] | undefined, unknown[]>>>>;
1978
2186
  activationEvents: z.ZodCatch<z.ZodOptional<z.ZodPipe<z.ZodArray<z.ZodUnknown>, z.ZodTransform<string[] | undefined, unknown[]>>>>;
1979
2187
  dependsOn: z.ZodCatch<z.ZodOptional<z.ZodPipe<z.ZodArray<z.ZodUnknown>, z.ZodTransform<string[] | undefined, unknown[]>>>>;
1980
2188
  codeProviderIds: z.ZodCatch<z.ZodOptional<z.ZodPipe<z.ZodArray<z.ZodUnknown>, z.ZodTransform<string[] | undefined, unknown[]>>>>;
@@ -2233,6 +2441,13 @@ interface PluginContributions {
2233
2441
  * 贡献——凡是"需要先加载 plugin.ts 代码才能算出"的注入内容,视为独立后续项(YAGNI)。
2234
2442
  */
2235
2443
  contextSources?: ContextSourceContribution[];
2444
+ /**
2445
+ * RFC-427 D1:插件事件贡献面(纯数据声明,逐条 fail-soft)。插件声明可发布的自定义
2446
+ * 事件类型 + payload schema,其他插件经 `subscribeEvent` 订阅。需 `capabilities` 含
2447
+ * `events`(非高危——observer-only,不改写引擎状态、不进 prompt)。事件经宿主 event-dispatcher
2448
+ * 中转(身份/权限/payload/频率单点执法),不持久化、不回放(R7)。卸载走 unregisterBySource。
2449
+ */
2450
+ events?: PluginEventContribution[];
2236
2451
  }
2237
2452
  /**
2238
2453
  * 合并多源贡献为单一 `PluginContributions`(RFC-082 §4.2 装载流,R7;RFC-157 M1 轴表化)。
@@ -2335,24 +2550,32 @@ declare function dispatch(cmd: CommandRef, caps: DispatchCaps): Promise<void>;
2335
2550
  * 安全(RFC-082 R10):`plugin.ts` 在 otto 主进程内、装载时执行(非沙箱,全 Node 权限),
2336
2551
  * 由信任门门控*是否*执行;hooks(尤其可 deny/block 工具的 preToolUse)默认关 + 显式启用。
2337
2552
  */
2338
- /** 工具调用上下文(hooks 观测/拦截用)。 */
2553
+ /**
2554
+ * 工具调用上下文(hooks 观测/拦截用)。
2555
+ *
2556
+ * `paths`/`readonly`/`sessionId` 是只读上下文(从 `ToolExecuteBeforeInput` 透传),
2557
+ * 让插件做路径级权限策略(如 browser-use 的站点级规则)。三字段可选——
2558
+ * 旧宿主不注入时 undefined,插件容错(`?? fallback`)。只读透传不破坏 R10
2559
+ * (插件只能 deny/ask 不能 allow)。
2560
+ */
2339
2561
  interface ToolUseContext {
2340
2562
  toolName: string;
2341
2563
  input: unknown;
2564
+ /** 工具声明的 AgentTool.pathParams 从 args 解析出的文件系统路径(只读)。 */
2565
+ paths?: readonly string[];
2566
+ /** 工具是否为只读(AgentTool.readonly,只读)。 */
2567
+ readonly?: boolean;
2568
+ /** 会话 ID(只读)。 */
2569
+ sessionId?: string;
2342
2570
  }
2343
2571
  /**
2344
2572
  * PreToolUse 决策(拦截器返回)。`deny` 拒绝;`ask` 转人工确认。
2345
2573
  *
2346
- * `allow` 变体是**兼容残留**(RFC-082 时代遗留):运行时恒为 no-op——
2347
- * `module-wiring.ts` preToolUse 映射是 safe-by-construction(allow→no-op
2348
- * 永不授权)。插件作者**不应**返回 allow(没有任何语义效果,deny/ask 之外的
2349
- * 返回值等价于放行到既有 hook 链判定)。其存在只为了避免存量插件类型迁移破坏,
2350
- * 新增代码请勿依赖它——capabilities.ts 的 `hooks: false`(非高危)前提之一
2351
- * 正是「allow 恒 no-op」(架构 review 2026-08-12 S5)。
2574
+ * `void` = 无意见/放行到既有 hook 链判定(等价于 `allow`——不干预,交由既有权限链判定)。
2575
+ * 插件不能授权,只能限制(R10:allow no-op,故不设 allow 变体——
2576
+ * 返回 void 即放行,deny/ask 之外无需第三个值)。
2352
2577
  */
2353
2578
  type PreToolUseDecision = {
2354
- decision: 'allow';
2355
- } | {
2356
2579
  decision: 'deny';
2357
2580
  reason?: string;
2358
2581
  } | {
@@ -2459,6 +2682,18 @@ interface PluginHooks {
2459
2682
  taskId: string;
2460
2683
  agent: string;
2461
2684
  }) => void | Promise<void>;
2685
+ /**
2686
+ * 通知发生时触发(`notification`,终局 review 2026-08-14 插件化方案 A)。
2687
+ * 来自 @x-otto/notification 脊柱的出站交付——会话事件经归一
2688
+ * (turn_complete/error/approval_required/input_required)后由 hook 桥 emit。
2689
+ * `notification_type` 是归一类别而非原始 AgentSessionEvent 形状(跨包边界纪律)。
2690
+ */
2691
+ onNotification?: (ctx: {
2692
+ sessionId: string;
2693
+ message: string;
2694
+ title?: string;
2695
+ notification_type: string;
2696
+ }) => void | Promise<void>;
2462
2697
  /**
2463
2698
  * RFC-327 修订 D1(M1):waterfall 贡献面——插件经受控白名单 timing 改写主循环中间产物。
2464
2699
  *
@@ -2483,6 +2718,14 @@ interface PluginHooks {
2483
2718
  * 本字段是新贡献面(非 observer 改 interceptor),配套 D1a 分权与安全门见 RFC-327 修订 §3。
2484
2719
  */
2485
2720
  waterfall?: PluginWaterfallHooks;
2721
+ /**
2722
+ * RFC-401 D5:配置变更通知(observer-only,不走整插件重载)。
2723
+ * 宿主在 PluginConfigStore 写入后调用此 hook 传入新配置对象——插件可选择实现
2724
+ * (不实现则下次 reload 时自然更新)。与 onActivate/onDeactivate 的区别:
2725
+ * onActivate/onDeactivate 是"装载/卸载"生命周期,onConfigChange 是"运行时配置热更"。
2726
+ * observer 纹理:不改变引擎状态,异常被吞 + 记录日志(同其他 observer hook,R5)。
2727
+ */
2728
+ onConfigChange?: (newConfig: Record<string, unknown>) => void | Promise<void>;
2486
2729
  }
2487
2730
  /**
2488
2731
  * RFC-327 修订 D1(M1):waterfall handler 逐 timing 签名——input/output 与引擎
@@ -2491,6 +2734,20 @@ interface PluginHooks {
2491
2734
  type PluginWaterfallHandler<T extends WaterfallTiming> = (input: HookPayloadMap[T]['input'], output: HookPayloadMap[T]['output']) => void | HookAbortSignal | Promise<void | HookAbortSignal>;
2492
2735
  /** RFC-327 修订 D1(M1):waterfall 贡献面的逐 timing 映射(mapped type 保持 per-key 类型精确)。 */
2493
2736
  type PluginWaterfallHooks = { [T in WaterfallTiming]?: PluginWaterfallHandler<T> };
2737
+ /**
2738
+ * 终局 review(2026-08-14):PluginHooks 按域分组类型别名。
2739
+ * 运行时契约不变——PluginHooks 仍是一层扁平可选字段;这些别名在类型层补域结构,
2740
+ * 供 IDE/文档/插件作者理解 hook 的分类。新增 timing 必须在此登记到对应域,
2741
+ * 且在 module-wiring 的 ScalarPluginHookField 排除/登记(编译期门)。
2742
+ */
2743
+ /** 工具域 hooks(pre/post tool use)。 */
2744
+ type PluginToolHooks = Pick<PluginHooks, 'preToolUse' | 'postToolUse'>;
2745
+ /** 会话域 hooks(session lifecycle + stream + compaction)。 */
2746
+ type PluginSessionHooks = Pick<PluginHooks, 'sessionStart' | 'sessionRestored' | 'sessionDeleted' | 'sessionIdle' | 'sessionError' | 'stop' | 'streamStart' | 'compactionPending' | 'compactionOccurred'>;
2747
+ /** 任务域 hooks(task lifecycle)。 */
2748
+ type PluginTaskHooks = Pick<PluginHooks, 'taskCreated' | 'taskStarted' | 'taskCompleted' | 'taskFailed' | 'taskCancelled'>;
2749
+ /** 消息域 hooks(prompt submit + config change)。 */
2750
+ type PluginMessageHooks = Pick<PluginHooks, 'userPromptSubmit' | 'onConfigChange'>;
2494
2751
  /** 装载时注入工厂的上下文。 */
2495
2752
  interface PluginContext {
2496
2753
  /** 插件 id(= manifest.id = source 标签)。 */
@@ -2560,6 +2817,44 @@ interface PluginContext {
2560
2817
  * 可选(additive)——旧宿主不注入时插件侧应容忍 undefined。
2561
2818
  */
2562
2819
  listActivePlugins?: () => readonly ActivePluginSnapshot[];
2820
+ /**
2821
+ * RFC-401 D2:插件配置值(解析后的对象,additive——旧宿主不注入时为 undefined)。
2822
+ * 宿主在装载时合并 manifest config schema 默认值 + PluginConfigStore 持久化值 → 注入。
2823
+ * 配置变更时经 onConfigChange hook 通知插件(不走整插件重载)。
2824
+ */
2825
+ config?: Record<string, unknown>;
2826
+ /**
2827
+ * RFC-427 D2:发布一个插件事件。事件类型必须在本插件 manifest 的 `contributes.events`
2828
+ * 中声明(未声明 → fail-closed 丢弃 + warn)。payload 经声明式 schema 校验,
2829
+ * 校验失败 → fail-closed 丢弃 + warn。宿主中转给所有已声明订阅的插件。
2830
+ * observer-only:不影响引擎状态、不写 prompt、不进 session log(R2/R7)。
2831
+ *
2832
+ * 跨进程(service worker 插件):经反向 RPC 桥接到宿主 event-dispatcher
2833
+ * (复用 RFC-287 D2 host 能力面桥接模式),不是 postMessage 函数传递。
2834
+ *
2835
+ * 可选(additive)——旧宿主/轻量测试装配不注入时插件侧应容忍 undefined。
2836
+ */
2837
+ emitEvent?: (type: string, payload: Readonly<Record<string, unknown>>) => void;
2838
+ /**
2839
+ * RFC-427 D2:订阅一个插件事件。type 可以是完整事件类型
2840
+ * (`skill-inductor.candidate-detected`)或通配符(`skill-inductor.*`)。
2841
+ * 订阅是 effect:插件卸载时自动反注册(unregisterBySource,R4 可逆 effect)。
2842
+ * handler 收到的 payload 已过 schema 校验 + 深冻结(Object.freeze 递归,R2 observer-only),
2843
+ * handler 不可 mutate。
2844
+ *
2845
+ * 通配符 `*`(订阅全部)在 M2 阶段不实现(R5 闸门)——API 签名保留但运行时 reject + warn,
2846
+ * 防止实现时顺手支持绕过闸门。
2847
+ *
2848
+ * handler 异常 = 吞掉 + warn,不传播、不阻断其他订阅方(R2a,对齐 PluginMonitor.onEvent
2849
+ * 既有纪律 sdk.ts:524-530)。
2850
+ *
2851
+ * 可选(additive)——旧宿主不注入时为 undefined。
2852
+ *
2853
+ * @returns disposer,手动取消订阅(对齐 dsh 可逆 effect 语义)
2854
+ */
2855
+ subscribeEvent?: (type: string, handler: (payload: Readonly<Record<string, unknown>>, source: {
2856
+ pluginId: string;
2857
+ }) => void) => () => void;
2563
2858
  }
2564
2859
  /**
2565
2860
  * provider 工厂(RFC-105 D3,代码式 provider 轴)。
@@ -2654,6 +2949,48 @@ interface PluginTool {
2654
2949
  * host/signal);存量单参工具不传第二参数即忽略,天然兼容。
2655
2950
  */
2656
2951
  execute: (args: unknown, ctx?: PluginToolContext) => Promise<unknown>;
2952
+ /**
2953
+ * RFC-397 D1:工具所属预设层级。缺省 'full'(= 当前硬编码行为)。
2954
+ * 内联联合而非 import ToolPreset——packages/plugin 不依赖 @x-otto/setting,
2955
+ * 保持分层纪律(同 PluginTool 不 import AgentTool)。
2956
+ */
2957
+ preset?: 'minimal' | 'standard' | 'full';
2958
+ /** RFC-397 D4:工具分类(如 'fs'/'search'/'analysis')。缺省 = 不分组。 */
2959
+ category?: string;
2960
+ /**
2961
+ * RFC-397 D4:行为指南文本(注入 system prompt 的 Tool Reference 段,按 category 分组渲染)。
2962
+ * 与内置工具的 ToolNode.guidance 同等待遇。缺省 = 无指南。
2963
+ */
2964
+ guideline?: string;
2965
+ /**
2966
+ * RFC-397 D2:工具对模型的暴露策略。缺省 'direct'(= 当前行为,直接进模型工具表)。
2967
+ * 'deferred' = 经 tool_search 按需发现(MCP 工具默认态);'hidden' = 仅可派发,不进表。
2968
+ */
2969
+ exposure?: 'direct' | 'deferred' | 'hidden';
2970
+ /**
2971
+ * RFC-397 D3:声明哪些参数承载文件系统路径(如 ['path'] / ['filePath'])。
2972
+ * PEP(permission/file guard)据此解析受管路径。
2973
+ * **必须与 resolvePath 成对声明**——无 resolvePath 时 PEP 拿到原始相对串,
2974
+ * file-guard 锚定模式(如 `^/etc/`)对相对穿越(`../../etc/x`)失配 = 路径权限绕过。
2975
+ */
2976
+ pathParams?: readonly string[];
2977
+ /**
2978
+ * RFC-397 D3:把 pathParams 的原始值解析成工具实际操作的绝对路径。
2979
+ * **pathParams 的安全伴侣**——声明了 pathParams 就必须声明 resolvePath。
2980
+ *
2981
+ * 二参函数(与 AgentTool.resolvePath 的单参形态不同):第二参数 projectRoot 由
2982
+ * toAgentTool 在透传时从 PluginToolCtxSource.workspaceDir 注入,适配为单参闭包
2983
+ * `(raw) => tool.resolvePath!(raw, projectRoot)`(RFC-397 D6)。
2984
+ */
2985
+ resolvePath?: (rawPath: string, projectRoot: string) => string;
2986
+ /** RFC-397 D3:标记为只读工具,可与其他只读工具并发执行(work-loop 并发桶)。 */
2987
+ readonly?: boolean;
2988
+ /**
2989
+ * RFC-437 D1:声明本工具输出可再生可剪(无副作用查询类)。memory prune 经装配侧
2990
+ * ToolRegistry 收集(listRegenerableNames)——插件工具声明即生效,长会话可剪。
2991
+ * 缺省 undefined = 不声明 = 不可再生(fail-closed)。写类工具不得声明。
2992
+ */
2993
+ regenerableOutput?: boolean;
2657
2994
  }
2658
2995
  /**
2659
2996
  * Monitor 投影事件(RFC-129 D6):与 `PluginHooks` 平行的独立概念——"被动订阅事件流做旁路统计"
@@ -2795,6 +3132,46 @@ type FeedbackIssueResult = {
2795
3132
  kind: string;
2796
3133
  detail: string;
2797
3134
  };
3135
+ /**
3136
+ * issue 反馈来源平台(RFC-430 §2.1 双平台)。`'github'` = 公网 GitHub;`'coding'` =
3137
+ * 企业内网代码托管平台——内网地址一律经 settings(`feedback_coding_base_url`)/
3138
+ * env(token)运行时注入,公网源码(含注释)零内网域名硬编码。
3139
+ */
3140
+ type FeedbackSource = 'github' | 'coding';
3141
+ /** 面板列表展示的归一化 issue 条目(RFC-430 §2.4,github/coding 双源归一到同一形状)。 */
3142
+ interface FeedbackIssueItem {
3143
+ source: FeedbackSource;
3144
+ /** 平台侧 issue 标识(GitHub number / Coding issue id 字符串化)。 */
3145
+ platformIssueId: string;
3146
+ title: string;
3147
+ state: 'open' | 'closed';
3148
+ url: string;
3149
+ author?: string;
3150
+ createdAt?: string;
3151
+ updatedAt?: string;
3152
+ /** GitHub REST `/issues` 端点会把 PR 一并返回——`kind` 区分 issue 与讨论类条目。 */
3153
+ kind: 'issue' | 'discussion';
3154
+ /** 列表行预览摘要(正文截断片段,可选)。 */
3155
+ excerpt?: string;
3156
+ labels?: string[];
3157
+ }
3158
+ /**
3159
+ * 双源聚合列表结果(RFC-430 §2.4):任一源失败/无凭据/超时时不抛错——该源
3160
+ * `items` 为空且 `unavailable[source]` 携带面向用户的原因文案(**只含 HTTP 状态码/
3161
+ * 失败类别,绝不携带 token 或内网地址以外的敏感信息**)。
3162
+ */
3163
+ interface FeedbackIssueListResult {
3164
+ items: FeedbackIssueItem[];
3165
+ unavailable?: Partial<Record<FeedbackSource, string>>;
3166
+ }
3167
+ /** 一条「本地标记已解决」记录(RFC-430 §2.4,插件侧 JSONL 落地)。 */
3168
+ interface FeedbackResolvedMark {
3169
+ ts: number;
3170
+ source: FeedbackSource;
3171
+ platformIssueId: string;
3172
+ url: string;
3173
+ title: string;
3174
+ }
2798
3175
  /**
2799
3176
  * Feedback 能力契约(RFC-209 §2.1,capability: `feedback`)——业务逻辑/数据契约层,
2800
3177
  * 不含任何 TUI/web-ui 渲染代码。核心包(coding/cli/tui/service)只通过 `FeedbackRegistry`
@@ -2818,6 +3195,32 @@ interface FeedbackProvider {
2818
3195
  * 提供 `feedbackProviders` 的插件必须实现本方法,即使不实现 `submitIssueViaGh`)。
2819
3196
  */
2820
3197
  buildIssueUrl: (input: FeedbackIssueInput) => string;
3198
+ /**
3199
+ * 双源 issue 列表聚合(RFC-430 §2.4,反馈面板数据面)。**必填**(M1 契约扩展,
3200
+ * 与 `buildIssueUrl` 同为面板/路由的最低兜底方法)。必须 fail-soft:任一源失败/
3201
+ * 无凭据/超时时该源 items 为空且 `unavailable[source]` 携带原因,绝不抛出。
3202
+ */
3203
+ listIssues: (opts?: {
3204
+ state?: 'open' | 'closed' | 'all';
3205
+ limitPerSource?: number;
3206
+ }) => Promise<FeedbackIssueListResult>;
3207
+ /** RFC-430 §2.1 平台判定链:settings 显式值优先,`auto` 时按内网可达性探测。 */
3208
+ resolveFeedbackPlatform?: () => Promise<FeedbackSource>;
3209
+ /**
3210
+ * 本地标记「已解决」(RFC-430 §2.4)——仅本地标记(JSONL 落地),真实关闭请在
3211
+ * 平台侧操作。返回是否实际写入(shadow 模式零写盘返回 false)。
3212
+ */
3213
+ markIssueResolved?: (mark: Omit<FeedbackResolvedMark, 'ts'>) => Promise<boolean>;
3214
+ /** 取消本地「已解决」标记(原子重写剔除匹配行);无匹配/影子态返回 false。 */
3215
+ unmarkIssueResolved?: (source: FeedbackSource, platformIssueId: string) => Promise<boolean>;
3216
+ /** 读取全部本地「已解决」标记;读取失败/影子态返回空数组(fail-soft,不抛)。 */
3217
+ listResolvedMarks?: () => Promise<FeedbackResolvedMark[]>;
3218
+ /**
3219
+ * 构造 Coding 平台新建 issue 预填 URL(RFC-430 §2.6)——纯函数式字符串构造,
3220
+ * 不执行任何子进程/网络调用(同 `buildIssueUrl` 的物理分离纪律);未配置
3221
+ * `feedback_coding_base_url`/`feedback_coding_repo` 时返回 null。
3222
+ */
3223
+ buildCodingIssueUrl?: (input: FeedbackIssueInput) => string | null;
2821
3224
  }
2822
3225
  /**
2823
3226
  * 工厂返回的插件模块(装载器拆解的对象)。
@@ -2898,11 +3301,128 @@ interface PluginModule {
2898
3301
  * `onActivate` 里起 setInterval——那会被既有池的超时/回收语义打断。
2899
3302
  */
2900
3303
  services?: PluginService[];
3304
+ /**
3305
+ * RFC-410 M2:进程内 CLI 命令 handler(代码轴,挂 `PluginModule`——不是 `contributes.*`
3306
+ * 声明式轴,元数据在 `contributes.cliCommands` 声明、handler 实现在此)。key = 命令名
3307
+ * (必须与 `contributes.cliCommands[].name` 对应;装载器只注册两侧都齐全的命令)。
3308
+ *
3309
+ * 需 manifest 声明 `capabilities: ["cli.command"]`(高危,fail-closed——宿主进程内自主
3310
+ * 执行代码,同 tui.renderer 风险等级)。handler 收窄 ctx(`PluginCliCommandContext`),
3311
+ * **不含** CliContext——只有 args/options/stdout/stderr + 有限 host 能力。
3312
+ *
3313
+ * 无状态一次性边界(RFC-112 D5 继承):handler 只做查询/配置读写类一次性子命令,
3314
+ * **不得**做生命周期管理/后台常驻/detached 进程/daemonize——宿主以一次性调用 + 超时
3315
+ * 包装执行(对齐透传档 60s 语义)。返回退出码(0=成功)。
3316
+ */
3317
+ cliCommands?: Record<string, PluginCliCommandHandler>;
3318
+ /**
3319
+ * RFC-440:TUI 斜杠命令 handler(代码轴,挂 `PluginModule`——元数据在
3320
+ * `contributes.slashCommands` 声明、handler 实现在此)。key = 命令名(必须与
3321
+ * `contributes.slashCommands[].name` 对应;装载器只注册两侧都齐全的命令)。
3322
+ *
3323
+ * 需 manifest 声明 `capabilities: ["tui.command"]`(高危,fail-closed——宿主进程内
3324
+ * 自主执行代码 + TUI 交互面,同 cli.command/tui.renderer 风险等级)。handler 收窄 ctx
3325
+ * (`PluginSlashCommandContext`),**不含** CliContext——TUI 桥(openInput/confirm/
3326
+ * openPluginPanel/showToast)与 app 窄面(workspaceDir/modelId/settings.get/pluginPanels)
3327
+ * 由宿主在命令执行时注入(装载时不持有 TUI 会话状态)。
3328
+ *
3329
+ * 一次性边界(继承 RFC-410 D5 纪律):handler 只做查询/交互式一次性命令,**不得**做
3330
+ * 生命周期管理/后台常驻/detached 进程——宿主以超时包装执行。
3331
+ */
3332
+ slashCommands?: Record<string, PluginSlashCommandHandler>;
2901
3333
  /** 激活钩子:装载完成、贡献注册后调用。 */
2902
3334
  onActivate?: () => void | Promise<void>;
2903
3335
  /** 卸载/禁用钩子。 */
2904
3336
  onDeactivate?: () => void | Promise<void>;
2905
3337
  }
3338
+ /**
3339
+ * RFC-410 M2:进程内 CLI 命令 handler 的执行上下文(**窄接口**,独立于 `@x-otto/cli`——
3340
+ * plugin 包禁止反向依赖 cli 包,故不复用 `SubCommandContext`/`CLIOptions`,只声明命令
3341
+ * handler 真正需要的字段)。
3342
+ */
3343
+ interface PluginCliCommandContext {
3344
+ /** 命令名之后的位置参数(已 tokenize,不含命令名本身)。 */
3345
+ args: string[];
3346
+ /**
3347
+ * 与命令相关的运行选项子集(宿主从 CLI 解析结果投影而来的**纯数据**快照,只读)。
3348
+ * 有意只暴露命令 handler 常用的少数字段,不透传完整 CLIOptions(避免耦合与越权)。
3349
+ */
3350
+ options: PluginCliCommandOptions;
3351
+ /** 写 stdout(宿主注入,默认 process.stdout.write)。 */
3352
+ stdout: (s: string) => void;
3353
+ /** 写 stderr(宿主注入,默认 process.stderr.write)。 */
3354
+ stderr: (s: string) => void;
3355
+ /** JSON 输出模式(`otto --json <cmd>`)——handler 据此决定输出形状(RFC-410 M3 语义)。 */
3356
+ json: boolean;
3357
+ }
3358
+ /** RFC-410 M2:投影给插件 CLI handler 的只读运行选项(纯数据,不含引擎/会话句柄)。 */
3359
+ interface PluginCliCommandOptions {
3360
+ /** 工作区目录(绝对路径)。 */
3361
+ workspaceDir: string;
3362
+ /** 显式模型 id(`-m/--model`),未指定为 undefined。 */
3363
+ model?: string;
3364
+ /** 配置文件路径(`-c/--config`),未指定为 undefined。 */
3365
+ configPath?: string;
3366
+ }
3367
+ /**
3368
+ * RFC-410 M2:进程内 CLI 命令 handler 签名。返回退出码(0=成功,非 0=失败)。
3369
+ * 宿主以超时包装调用(handler 挂起 → 超时报错,不阻塞 CLI 无限期)。
3370
+ */
3371
+ type PluginCliCommandHandler = (ctx: PluginCliCommandContext) => Promise<number> | number;
3372
+ /**
3373
+ * RFC-440:TUI 斜杠命令 handler 的 TUI 窄桥(宿主执行命令时注入)。
3374
+ * 窄接口自声明(本包不依赖 cli/tui 包,同 PluginCliCommandContext 纪律)——只暴露
3375
+ * 命令 handler 真正需要的 TUI 原语,不透传 CliContext(53 成员教训,RFC-410 评审必须调整项 #3)。
3376
+ */
3377
+ interface PluginSlashCommandTuiBridge {
3378
+ /** 打开输入弹层,返回用户输入(取消/关闭返回 null)。 */
3379
+ openInput(opts: {
3380
+ title: string;
3381
+ description?: string[];
3382
+ placeholder?: string;
3383
+ }): Promise<string | null>;
3384
+ /** 打开确认弹层,返回用户选择。 */
3385
+ confirm(title: string, message: string | string[]): Promise<boolean>;
3386
+ /** 打开插件面板(contributes.panels 注册的 Ink 面板,对齐 /schedule 的 pluginPanel 打开模式)。 */
3387
+ openPluginPanel(info: {
3388
+ title: string;
3389
+ modulePath: string;
3390
+ exportName: string;
3391
+ pluginId: string;
3392
+ }): Promise<void>;
3393
+ /** 状态栏 toast。 */
3394
+ showToast(title: string, message: string): void;
3395
+ }
3396
+ /** RFC-440:投影给插件斜杠命令 handler 的只读 app 窄面(纯数据 + 窄读取,无引擎/会话句柄)。 */
3397
+ interface PluginSlashCommandAppBridge {
3398
+ /** 工作区目录(绝对路径)。 */
3399
+ workspaceDir: string;
3400
+ /** 当前会话模型 id(可能未配置)。 */
3401
+ modelId: string | undefined;
3402
+ /** settings 窄读取(对齐 RFC-430 的 host.getSettings 语义:只读单键值,返回 unknown)。 */
3403
+ settings: {
3404
+ get(key: string): unknown;
3405
+ };
3406
+ /** 已注册的插件面板列表(供 handler 定位自己的面板打开)。 */
3407
+ pluginPanels: Array<{
3408
+ pluginId: string;
3409
+ title: string;
3410
+ modulePath: string;
3411
+ exportName: string;
3412
+ }>;
3413
+ }
3414
+ /** RFC-440:TUI 斜杠命令 handler 上下文(窄接口,宿主执行命令时注入 TUI 桥与 app 窄面)。 */
3415
+ interface PluginSlashCommandContext {
3416
+ /** 命令名之后的参数(不含 '/' 前缀与命令名本身)。 */
3417
+ args: string[];
3418
+ tui: PluginSlashCommandTuiBridge;
3419
+ app: PluginSlashCommandAppBridge;
3420
+ }
3421
+ /**
3422
+ * RFC-440:TUI 斜杠命令 handler 签名。返回 void(TUI 内以 toast/面板呈现结果,
3423
+ * 无进程退出码概念)。宿主以超时包装调用(handler 挂起 → 超时报错,不阻塞 TUI 无限期)。
3424
+ */
3425
+ type PluginSlashCommandHandler = (ctx: PluginSlashCommandContext) => Promise<void> | void;
2906
3426
  /**
2907
3427
  * RFC-287 D2:宿主注入给插件的**受控能力面**(capability-gated host capabilities)。
2908
3428
  *
@@ -3016,6 +3536,18 @@ interface PluginHostCapabilities {
3016
3536
  * `plugin:<pluginId>`,插件不能伪装成 agent/user/其他插件。
3017
3537
  */
3018
3538
  ask?: (input: HostAskInput) => Promise<_$_x_otto_interchange0.GrillAnswer>;
3539
+ /**
3540
+ * RFC-430 M1:读取宿主 settings 的**单个键值**。需 manifest 声明并授权
3541
+ * `capabilities: ["settings.read"]`(fail-closed);未授予或宿主未提供取值源时
3542
+ * 本字段为 `undefined`。
3543
+ *
3544
+ * **契约**:
3545
+ * - 只读单键、返回 `unknown`——插件侧自行运行时收窄(非法值落默认),宿主不做类型保证;
3546
+ * - 不做键白名单:settings 值面与凭据物理隔离(token 走 env / AuthStore,不进 settings);
3547
+ * - 惰性:`settings.load()` 完成前调用返回 `undefined`,插件应 fail-soft 落默认值
3548
+ * (与 RFC-230 D5「延迟冻结」纹理一致)。
3549
+ */
3550
+ getSettings?: (key: string) => unknown;
3019
3551
  }
3020
3552
  /**
3021
3553
  * RFC-318 D2(M1):`readTranscript` 的投影消息——**不是** `@x-otto/ai` 的 `Message`。
@@ -3127,6 +3659,19 @@ interface PluginServiceContext {
3127
3659
  * (走既有退避重启),**不静默吞掉**。未注册 handler 时通知被丢弃。
3128
3660
  */
3129
3661
  onNotify?: (handler: (n: PluginServiceNotification) => void | Promise<void>) => void;
3662
+ /**
3663
+ * RFC-427 D2:插件事件发布函数(可选——同 PluginContext.emitEvent)。
3664
+ * 在 service worker 中经反向 RPC 桥接到宿主 event-dispatcher。
3665
+ * 需 manifest 声明 `capabilities: ["events"]`(fail-closed:未声明不注入)。
3666
+ */
3667
+ emitEvent?: (type: string, payload: Readonly<Record<string, unknown>>) => void;
3668
+ /**
3669
+ * RFC-427 D2:插件事件订阅函数(可选——同 PluginContext.subscribeEvent)。
3670
+ * 返回 disposer(R4 可逆 effect)。
3671
+ */
3672
+ subscribeEvent?: (type: string, handler: (payload: Readonly<Record<string, unknown>>, source: {
3673
+ pluginId: string;
3674
+ }) => void) => () => void;
3130
3675
  }
3131
3676
  /**
3132
3677
  * RFC-303 M5:插件 scoped persistence 的契约 shape(`@x-otto/plugin` 契约层不下沉实现——
@@ -3211,10 +3756,10 @@ type PluginFactory = (ctx: PluginContext) => PluginModule | Promise<PluginModule
3211
3756
  declare function definePlugin(factory: PluginFactory): PluginFactory;
3212
3757
  //#endregion
3213
3758
  //#region src/input/registry.d.ts
3214
- declare function createPluginInputRegistry(): PluginInputRegistry;
3759
+ declare function createPluginInputRegistry(): PluginInputRegistry$1;
3215
3760
  //#endregion
3216
3761
  //#region src/input/builtin.d.ts
3217
- declare function createBuiltinHashtags(): SigilEntry[];
3762
+ declare function createBuiltinHashtags(): SigilEntry$1[];
3218
3763
  //#endregion
3219
3764
  //#region src/input/file-provider.d.ts
3220
3765
  interface FileProviderOptions {
@@ -3232,7 +3777,7 @@ interface FileProviderOptions {
3232
3777
  * 创建 @file 供给器。walk 结果按 TTL 缓存,query 在缓存上过滤——
3233
3778
  * 每次按键 O(n) 过滤而非重 walk 文件系统。
3234
3779
  */
3235
- declare function createFileProvider(opts: FileProviderOptions): SigilProvider;
3780
+ declare function createFileProvider(opts: FileProviderOptions): SigilProvider$1;
3236
3781
  //#endregion
3237
3782
  //#region src/trust-store.d.ts
3238
3783
  /** key(绝对路径)是否在白名单中。 */
@@ -3370,6 +3915,47 @@ interface PluginTrustVerdict {
3370
3915
  */
3371
3916
  declare function evaluatePluginTrust(plugin: DiscoveredPlugin, storePath?: string): PluginTrustVerdict;
3372
3917
  //#endregion
3918
+ //#region src/config-sensitive.d.ts
3919
+ /**
3920
+ * config-sensitive.ts —— RFC-401 重要事项规则 9:配置敏感字段脱敏。
3921
+ *
3922
+ * config 能力本身非高危(纯数据声明),但配置值可能含 apiKey/secret/token 等敏感字段。
3923
+ * 宿主在日志/错误信息中输出配置值时,须自动 redact 敏感字段——不依赖插件自觉。
3924
+ *
3925
+ * 判据:字段名(不区分大小写)匹配 `apiKey`/`secret`/`token`/`password`/`credential`
3926
+ * 任一子串 → 值替换为 `'[REDACTED]'`。非敏感字段原样输出。
3927
+ */
3928
+ /** 判断字段名是否敏感(子串匹配,不区分大小写)。 */
3929
+ declare function isSensitiveField(fieldName: string): boolean;
3930
+ /**
3931
+ * 脱敏配置对象:深遍历,敏感字段的值替换为 `'[REDACTED]'`。
3932
+ * 返回新对象,不修改原对象。
3933
+ */
3934
+ declare function redactSensitiveFields(config: Record<string, unknown>): Record<string, unknown>;
3935
+ //#endregion
3936
+ //#region src/config-merge.d.ts
3937
+ /**
3938
+ * config-merge.ts —— RFC-401 D2:配置合并纯函数。
3939
+ *
3940
+ * 将 manifest config schema 的默认值与 PluginConfigStore 持久化值合并
3941
+ * (store 值 overlay manifest 默认值)。纯函数、零副作用、零依赖。
3942
+ *
3943
+ * 供 @x-otto/coding 的 module-loader 在装载时调用,也供测试验证。
3944
+ */
3945
+ interface ConfigSchemaLike {
3946
+ /**
3947
+ * 属性表:值类型为 unknown(与 manifest 的 zod schema 对齐——z.record(z.string(), z.unknown()),
3948
+ * 递归 JSON schema entry 的值在 zod 推导时为 unknown)。合并实现做运行时窄化
3949
+ * (`typeof def === 'object' && 'default' in def`),只读 default 字段,不关心其余形状。
3950
+ */
3951
+ properties?: Record<string, unknown>;
3952
+ }
3953
+ /**
3954
+ * 合并 manifest config schema 默认值与 PluginConfigStore 持久化值。
3955
+ * store 值 overlay manifest 默认值——store 里不在 schema 里的字段保留(前向兼容)。
3956
+ */
3957
+ declare function mergeConfigWithDefaults(schema: ConfigSchemaLike | undefined, stored: Record<string, unknown>): Record<string, unknown>;
3958
+ //#endregion
3373
3959
  //#region src/i18n-registry.d.ts
3374
3960
  /** 单条 i18n 贡献条目(loader 产出,供 `register()` 消费)。 */
3375
3961
  interface PluginI18nEntry {
@@ -3419,5 +4005,5 @@ declare class PluginI18nRegistry extends TypedEventEmitter<PluginI18nRegistryEve
3419
4005
  t(fullKey: string, locale?: string): string;
3420
4006
  }
3421
4007
  //#endregion
3422
- export { type ActionContribution, type AgentContribution, type CommandContribution, type CommandRef, type ContextKeyProvider, type ContextMap, type ContextSourceContribution, type ContributionPoint, type DiscoverPluginsOptions, type DiscoveredPlugin, type DispatchCaps, EXTENSION_POINTS, type EngineCompatResult, type FeedbackIssueInput, type FeedbackIssueResult, type FeedbackProvider, type FileProviderOptions, HIGH_RISK_CAPABILITIES, type HostAskInput, ID_RE, MAX_I18N_ENTRIES_PER_PLUGIN, MAX_I18N_FILE_BYTES, MAX_I18N_LOCALE_FILES_PER_PLUGIN, MAX_I18N_TRANSLATION_BYTES, MAX_THEME_PRESETS_PER_PLUGIN, type McpContribution, type McpTransport, type MenuContribution, type MonitorEvent, type OAuthContribution, PLUGIN_API_VERSION, PLUGIN_CAPABILITIES, PLUGIN_MANIFEST_FILENAME, POINT, type PerfMetricContribution, type PluginA2uiComponentEntry, type PluginA2uiRendererEntry, type PluginCapability, type PluginCliContribution, type PluginContext, type PluginContributes, type PluginContributions, type PluginExecutableSurface, type PluginFactory, type PluginFileViewerEntry, type PluginHooks, type PluginHostCapabilities, type PluginI18nContribution, type PluginI18nEntry, PluginI18nRegistry, type PluginInputRegistry, type PluginLoadFailureReason, type PluginManifest, type PluginManifestPlane, type PluginModelEntry, type PluginModule, type PluginMonitor, type PluginPanelEntry, type PluginProviderEntry, type PluginProviderFactory, type PluginRendererEntry, type PluginScheduleSubscription, type PluginScopedStorageShape, type PluginScripts, type PluginService, type PluginServiceContext, type PluginServiceNotification, type PluginThemePresetColors, type PluginThemePresetContribution, type PluginTool, type PluginToolContext, type PluginTrustVerdict, type PluginWaterfallHandler, type PluginWaterfallHooks, type PluginWireProtocolRegistration, type PreToolUseDecision, type PulseSurveyEntry, type PulseSurveyRating, type PulseSurveyResolvedConfig, type ResolvedAction, SHELL_POINTS, type ScheduleTickNotification, type SessionIdleNotification, type SigilEntry, type SigilEntryContribution, type SigilKind, type SigilPrefix, type SigilProvider, type SigilSource, type SkillContribution, type StatusItemContribution, type StatusWidgetContribution, type ToolUseContext, type TopoSortResult, type TranscriptMessage, createBuiltinHashtags, createFileProvider, createPluginInputRegistry, definePlugin, detectPluginExecutableSurface, discoverPlugins, dispatch, evaluateContextKey, evaluateContextKeys, evaluatePluginTrust, evaluateWhen, expandPath, filterEngineCompatible, grantCapabilitiesWithVersion, grantPluginCapabilities, grantPluginCapabilitiesWithVersion, grantedCapabilitiesForVersion, grantedPluginCapabilities, grantedPluginCapabilitiesForVersion, isEngineCompatible, isPathTrusted, isPluginTrusted, mergeContributions, parsePluginManifest, pluginTrustStorePath, readHighRiskCapabilities, resolveActions, resolveActivationEvents, resolveManifestPlane, sigilOf, topoSortPlugins, trustPath, trustPlugin, untrustPath, untrustPlugin };
4008
+ export { type ActionContribution, type AgentContribution, type CommandContribution, type CommandRef, type ConfigSchemaLike, type ContextKeyProvider, type ContextMap, type ContextSourceContribution, type ContributionPoint, type DiscoverPluginsOptions, type DiscoveredPlugin, type DispatchCaps, EXTENSION_POINTS, type EngineCompatResult, type FeedbackIssueInput, type FeedbackIssueItem, type FeedbackIssueListResult, type FeedbackIssueResult, type FeedbackProvider, type FeedbackResolvedMark, type FeedbackSource, type FileProviderOptions, HIGH_RISK_CAPABILITIES, type HostAskInput, ID_RE, MAX_CONFIG_FIELDS_PER_PLUGIN, MAX_CONFIG_FIELD_DESCRIPTION_CHARS, MAX_CONFIG_SCHEMA_BYTES, MAX_CONFIG_SCHEMA_DEPTH, MAX_I18N_ENTRIES_PER_PLUGIN, MAX_I18N_FILE_BYTES, MAX_I18N_LOCALE_FILES_PER_PLUGIN, MAX_I18N_TRANSLATION_BYTES, MAX_THEME_PRESETS_PER_PLUGIN, type McpContribution, type McpTransport, type MenuContribution, type MonitorEvent, type OAuthContribution, PLUGIN_API_VERSION, PLUGIN_CAPABILITIES, PLUGIN_MANIFEST_FILENAME, POINT, type PerfMetricContribution, type PluginA2uiComponentEntry, type PluginA2uiRendererEntry, type PluginCapability, type PluginCliCommandContext, type PluginCliCommandContribution, type PluginCliCommandHandler, type PluginCliCommandOptions, type PluginCliContribution, type PluginConfigSchemaEntry, type PluginContext, type PluginContributes, type PluginContributions, type PluginEventContribution, type PluginExecutableSurface, type PluginFactory, type PluginFileViewerEntry, type PluginHooks, type PluginHostCapabilities, type PluginI18nContribution, type PluginI18nEntry, PluginI18nRegistry, type PluginInputRegistry, type PluginLoadFailureReason, type PluginManifest, type PluginManifestPlane, type PluginMessageHooks, type PluginModelEntry, type PluginModule, type PluginMonitor, type PluginPanelEntry, type PluginProviderEntry, type PluginProviderFactory, type PluginRendererEntry, type PluginScheduleSubscription, type PluginScopedStorageShape, type PluginScripts, type PluginService, type PluginServiceContext, type PluginServiceNotification, type PluginSessionHooks, type PluginSlashCommandAppBridge, type PluginSlashCommandContext, type PluginSlashCommandContribution, type PluginSlashCommandHandler, type PluginSlashCommandTuiBridge, type PluginTaskHooks, type PluginThemePresetColors, type PluginThemePresetContribution, type PluginTool, type PluginToolContext, type PluginToolHooks, type PluginTrustVerdict, type PluginWaterfallHandler, type PluginWaterfallHooks, type PluginWireProtocolRegistration, type PreToolUseDecision, type PulseSurveyEntry, type PulseSurveyRating, type PulseSurveyResolvedConfig, type ResolvedAction, SHELL_POINTS, type ScheduleTickNotification, type SessionIdleNotification, type SigilEntry, type SigilEntryContribution, type SigilKind, type SigilPrefix, type SigilProvider, type SigilSource, type SkillContribution, type StatusItemContribution, type StatusWidgetContribution, type ToolUseContext, type TopoSortResult, type TranscriptMessage, createBuiltinHashtags, createFileProvider, createPluginInputRegistry, definePlugin, detectPluginExecutableSurface, discoverPlugins, dispatch, evaluateContextKey, evaluateContextKeys, evaluatePluginTrust, evaluateWhen, expandPath, filterEngineCompatible, grantCapabilitiesWithVersion, grantPluginCapabilities, grantPluginCapabilitiesWithVersion, grantedCapabilitiesForVersion, grantedPluginCapabilities, grantedPluginCapabilitiesForVersion, isEngineCompatible, isPathTrusted, isPluginTrusted, isSensitiveField, mergeConfigWithDefaults, mergeContributions, parsePluginManifest, pluginTrustStorePath, readHighRiskCapabilities, redactSensitiveFields, resolveActions, resolveActivationEvents, resolveManifestPlane, sigilOf, topoSortPlugins, trustPath, trustPlugin, untrustPath, untrustPlugin };
3423
4009
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import{z as e}from"zod";import{TypedEventEmitter as t,acquireFileLockSync as n,createLogger as r,releaseFileLockSync as i,waitForFileLockReleaseSync as a}from"@x-otto/shared";import{closeSync as o,existsSync as s,fsyncSync as c,mkdirSync as l,openSync as u,readFileSync as d,readSync as f,readdirSync as p,renameSync as ee,statSync as te,unlinkSync as ne,writeFileSync as re}from"node:fs";import{basename as ie,dirname as ae,join as m,relative as oe,resolve as h}from"node:path";import{OTTO_HOME as g,findWorkspaceRoot as se,isShadowModeEnabled as ce,resolveConfigLayers as le}from"@x-otto/env";import{satisfies as ue,validRange as de}from"semver";import{homedir as fe}from"node:os";import{sigilOf as _}from"@x-otto/interchange";import{readdir as pe}from"node:fs/promises";import{randomUUID as me}from"node:crypto";const he=[`provider`,`tools`,`hooks`,`tui.renderer`,`network`,`context`,`monitor`,`wire-protocol`,`a2ui.component`,`panel.backend`,`mcp.server`,`feedback`,`input.resolver`,`a2ui.renderer`,`theme`,`agent.dispatch`,`service`,`storage`,`session.read`,`llm.complete`,`user.notify`,`user.ask`],ge={provider:!0,tools:!0,hooks:!1,"tui.renderer":!0,network:!0,context:!1,monitor:!1,"wire-protocol":!0,"a2ui.component":!0,"panel.backend":!0,"mcp.server":!0,feedback:!1,"input.resolver":!0,"a2ui.renderer":!0,theme:!1,"agent.dispatch":!0,service:!0,storage:!1,"session.read":!0,"llm.complete":!0,"user.notify":!1,"user.ask":!0},v=Object.freeze(Object.keys(ge).filter(e=>ge[e])),y=r(`@x-otto/coding:plugin-manifest`),b=`otto-plugin.json`,x=/^[a-z0-9]+(?:-[a-z0-9]+)*$/,S=e=>e.optional().catch(void 0),C=e.string().refine(e=>e.trim().length>0,`must be non-empty`),_e=e.object({id:C,title:C,entry:C.refine(e=>!e.startsWith(`/`)&&!e.split(/[\\/]/).includes(`..`),`unsafe path (absolute or traversal)`),export:S(C)}),w=e.object({role:S(C),toolName:S(C),contentType:S(C)}).refine(e=>e.role!=null||e.toolName!=null||e.contentType!=null,{message:`matcher must declare at least one of role/toolName/contentType`}),ve=e.object({id:C,matcher:w,entry:C.refine(e=>!e.startsWith(`/`)&&!e.split(/[\\/]/).includes(`..`),`unsafe path (absolute or traversal)`),export:S(C)}),ye=e.object({id:C,matcher:w,entry:C.refine(e=>!e.startsWith(`/`)&&!e.split(/[\\/]/).includes(`..`),`unsafe path (absolute or traversal)`),export:S(C)}),be=e.object({extensions:S(e.array(C)),filenames:S(e.array(C)),glob:S(e.array(C))}).refine(e=>(e.extensions?.length??0)+(e.filenames?.length??0)+(e.glob?.length??0)>0,{message:`matcher must declare at least one of extensions/filenames/glob`}),xe=T(e.object({id:C,label:C,matcher:be,entry:C.refine(e=>!e.startsWith(`/`)&&!e.split(/[\\/]/).includes(`..`),`unsafe path (absolute or traversal)`),export:S(C),order:S(e.number())}),`fileViewers`),Se=T(e.object({type:C,entry:C.refine(e=>!e.startsWith(`/`)&&!e.split(/[\\/]/).includes(`..`),`unsafe path (absolute or traversal)`),export:S(C)}),`a2uiComponents`),Ce=T(e.object({id:C,entry:C.refine(e=>!e.startsWith(`/`)&&!e.split(/[\\/]/).includes(`..`),`unsafe path (absolute or traversal)`),export:S(C),order:S(e.number()),tickMs:S(e.number())}),`statusWidgets`),we=T(ve,`renderers`),Te=T(_e,`panels`),Ee=e.discriminatedUnion(`type`,[e.object({type:e.literal(`builtin`),id:C}),e.object({type:e.literal(`mcp`),server:C,tool:C,params:S(e.record(e.string(),e.unknown()))}),e.object({type:e.literal(`open`),target:C}),e.object({type:e.literal(`command`),name:C,args:S(C)}),e.object({type:e.literal(`script`),name:C})]),De=e.object({id:C,title:C,command:Ee,color:S(e.enum([`accent`,`amber`,`red`,`green`,`gray`]))}),Oe=e.object({action:C,point:C,when:S(C),order:S(e.number())}),ke=e.object({key:C,groups:e.array(e.array(C)),map:e.object({none:C,partial:C,all:C})});function T(t,n){return e.array(e.unknown()).transform(e=>{let r=e.map(e=>t.safeParse(e)),i=r.filter(e=>e.success).map(e=>e.data),a=r.length-i.length;if(a>0&&n){let e=r.filter(e=>!e.success).map(e=>e.success?``:e.error.issues.map(e=>e.message).join(`; `));y.warn({label:n,dropped:a,total:r.length,issues:e},`contributes.${n}: ${a}/${r.length} 条 entry 校验失败,已静默丢弃(fail-soft)`)}return i.length>0?i:void 0})}const Ae=e.object({input:e.number().min(0),output:e.number().min(0),cacheRead:e.number().min(0),cacheWrite:e.number().min(0)}),je=e.object({maxImagesPerRequest:e.number().int().positive(),maxDimensionPxIfOverLimit:S(e.number().int().positive())}),Me=e.enum([`planning`,`knowledge`,`coding`,`reasoning`,`vision`,`speed`,`long-context`]),Ne=e.enum([`low`,`medium`,`high`,`xhigh`,`max`]),Pe=e.enum([`enabled`,`adaptive`]),E=e.object({id:C,contextWindow:e.number().int().positive(),maxOutput:e.number().int().positive(),cost:S(Ae),strengths:S(e.array(Me).min(1)),reasoning:S(e.boolean()),input:S(e.array(e.enum([`text`,`image`])).min(1)),thinkingLevels:S(e.array(Ne).min(1)),thinkingMode:S(Pe)}),Fe=e.enum([`openai-completions`,`openai-responses`,`anthropic-messages`]),Ie=e.object({label:C,value:C.refine(e=>!e.startsWith(`!`)&&!e.startsWith(`#`),`value must not start with '!' or '#' (mode-prefix collision)`),kind:e.enum([`resource`,`hashtag`]),description:S(C),resolverId:S(C)}),Le=e.object({id:C,dataSource:e.object({contextKey:C}),point:S(C),when:S(C),order:S(e.number())}),Re=e.union([C,e.object({file:C.refine(e=>!e.startsWith(`/`)&&!e.split(/[\\/]/).includes(`..`),`unsafe path (absolute or traversal)`)})]),ze=e.object({id:C,priority:S(e.number()),content:Re}),Be=e.object({id:C,translations:e.record(e.string(),e.string())}),Ve=/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/,D=e=>!Ve.test(e),He=/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/,O=t=>e.string().max(16).refine(e=>He.test(e),`${t} must be a #RRGGBB or #RGB hex color`),Ue=e.object({brand:O(`brand`),brandShimmer:O(`brandShimmer`),accent:O(`accent`),accentDim:O(`accentDim`),accentBright:O(`accentBright`),text:S(O(`text`)),inactive:O(`inactive`),secondary:O(`secondary`),subtle:O(`subtle`),replyPrefix:O(`replyPrefix`),success:O(`success`),error:O(`error`),warning:O(`warning`),special:O(`special`),suggestion:O(`suggestion`),permission:O(`permission`),codeText:O(`codeText`),diffAdd:O(`diffAdd`),diffRemove:O(`diffRemove`),diffAddBg:O(`diffAddBg`),diffRemoveBg:O(`diffRemoveBg`),panelBorder:O(`panelBorder`),overlayBg:O(`overlayBg`),tagBg:O(`tagBg`),toolText:O(`toolText`),focusBorder:O(`focusBorder`),selection:O(`selection`)}),k=e.enum(`brand.brandShimmer.accent.accentDim.accentBright.text.inactive.secondary.subtle.replyPrefix.success.error.warning.special.suggestion.permission.codeText.diffAdd.diffRemove.diffAddBg.diffRemoveBg.panelBorder.overlayBg.tagBg.toolText.focusBorder.selection`.split(`.`)),We=e.strictObject({heading1:S(k),heading2:S(k),headingWeak:S(k),listMarker:S(k),listMarkerMuted:S(k),quoteBar:S(k),quoteText:S(k),link:S(k),inlineCode:S(k),codeFence:S(k),tableHeader:S(k),tableDivider:S(k),listIndent:S(e.union([e.literal(2),e.literal(3)])),listMarkers:S(e.tuple([e.string().min(1).max(4),e.string().min(1).max(4),e.string().min(1).max(4),e.string().min(1).max(4)])),codeBlockDivider:S(e.enum([`hr`,`none`]))}),Ge=e.object({localId:C.max(64).refine(e=>x.test(e),`localId must be kebab-case`),label:C.max(64).refine(D,`label must not contain control characters`),appearance:e.enum([`dark`,`light`]),colors:Ue,markdown:S(We),meta:S(e.object({author:S(C.max(128).refine(D,`author must not contain control characters`)),description:S(C.max(256).refine(D,`description must not contain control characters`)),version:S(C.max(32).refine(D,`version must not contain control characters`))}))}),Ke=10,qe=500,Je=2*1024,Ye=20,Xe=64*1024,Ze=e.object({id:C,label:C,toolRef:C}),Qe=e.object({header:C.refine(e=>/^[a-zA-Z0-9][a-zA-Z0-9\-_]*$/.test(e),`header name must be alphanumeric with hyphens/underscores only`),source:e.object({kind:e.literal(`jwt-claim`),claim:C,namespace:S(C),tokenSources:S(e.array(e.enum([`id_token`,`access_token`])))})}),A=e.object({imageConstraints:S(je),supportsTools:S(e.boolean())}),j=e.object({url:C,responseFormat:e.literal(`openai-list`),authScheme:S(e.enum([`bearer`,`x-api-key`,`auto`])),betaHeaderName:S(C),modelIdExclude:S(T(e.string(),`modelIdExclude`))}),M=e.object({store:S(e.boolean()),sendMaxOutputTokens:S(e.boolean())}),$e=e.object({id:C,name:S(C),catalogOnly:S(e.boolean()),baseUrl:S(C),wireApi:S(Fe),envKey:S(C),oauthRef:S(C),headers:S(e.record(e.string(),e.string())),models:S(T(E,`models`)),tokenDerivedHeaders:S(T(Qe,`tokenDerivedHeaders`)),modelsEndpoint:S(j),responseBodyPolicy:S(M),allowAuthHeaderOverride:S(e.boolean()),preserveProviderId:S(e.boolean()),requiresIntranet:S(e.boolean())}).extend(A.shape).superRefine((t,n)=>{if(t.catalogOnly===!0){t.wireApi&&n.addIssue({code:e.ZodIssueCode.custom,message:`catalogOnly entries must not declare wireApi; use providerFactories for custom protocols`,path:[`wireApi`]}),t.baseUrl&&n.addIssue({code:e.ZodIssueCode.custom,message:`catalogOnly entries must not declare baseUrl; use providerFactories for custom protocols`,path:[`baseUrl`]});return}t.baseUrl||n.addIssue({code:e.ZodIssueCode.custom,message:`baseUrl is required unless catalogOnly is true`,path:[`baseUrl`]}),t.wireApi||n.addIssue({code:e.ZodIssueCode.custom,message:`wireApi is required unless catalogOnly is true`,path:[`wireApi`]})}),et=e.object({name:C.refine(e=>/^[a-z][a-z0-9-]*$/.test(e),`must be lowercase kebab-case`),bin:S(C)}),tt=e.discriminatedUnion(`kind`,[e.object({kind:e.literal(`pkce`),id:C,name:S(C),clientId:C,authorizeUrl:C,tokenUrl:C,redirectUri:C,scope:C,loopbackPorts:S(e.array(e.number().int().positive())),assumesLoopbackAlways:S(e.boolean()),extraAuthParams:S(e.record(e.string(),e.string())),tokenExchangeEncoding:S(e.enum([`json`,`form`])),subscriptionScoped:S(e.boolean()),oauthBeta:S(C)}),e.object({kind:e.literal(`device-flow`),id:C,name:S(C),clientId:C,deviceCodeUrlTemplate:C,tokenUrlTemplate:C,refreshUrlTemplate:S(C),scope:C,allowCustomDomain:S(e.boolean()),userAgent:S(C)})]),nt=e.object({skills:S(e.boolean()),agents:S(e.boolean()),commands:S(e.boolean()),mcp:S(e.boolean()),cli:S(et),panels:S(Te),actions:S(T(De,`actions`)),menus:S(T(Oe,`menus`)),contextKeys:S(T(ke,`contextKeys`)),providers:S(T($e,`providers`)),oauth:S(T(tt,`oauth`)),statusItems:S(T(Le,`statusItems`)),perfMetrics:S(T(Ze,`perfMetrics`)),renderers:S(we),fileViewers:S(xe),statusWidgets:S(Ce),a2uiComponents:S(Se),contextSources:S(T(ze,`contextSources`)),i18n:S(T(Be,`i18n`)),themePresets:S(T(Ge,`themePresets`)),i18nDir:S(C.refine(e=>!e.startsWith(`/`)&&!e.split(/[\\/]/).includes(`..`),`unsafe path (absolute or traversal)`)),inputEntries:S(C),sigilEntries:S(T(Ie,`sigilEntries`)),a2uiRenderers:S(T(ye,`a2uiRenderers`))}),rt=e.enum(he),it=e.array(e.unknown()).transform(e=>{let t=e.map(e=>rt.safeParse(e)).filter(e=>e.success).map(e=>e.data);return t.length>0?[...new Set(t)]:void 0}),at=/^(onStartup|onCommand:[^\s]+|onView:[^\s]+|onProvider:[^\s]+)$/,ot=e.array(e.unknown()).transform(e=>{let t=e.filter(e=>typeof e==`string`&&at.test(e));return t.length>0?[...new Set(t)]:void 0}),st=e.array(e.unknown()).transform(e=>{let t=e.filter(e=>typeof e==`string`&&x.test(e));return t.length>0?[...new Set(t)]:void 0}),ct=e.array(e.unknown()).transform(e=>{let t=e.filter(e=>typeof e==`string`&&e.trim().length>0);return t.length>0?[...new Set(t)]:void 0}),lt=j.extend({headers:S(e.record(e.string(),e.string()))}),ut=e.record(e.string(),e.unknown()).transform(e=>{let t={};for(let[n,r]of Object.entries(e)){let e=lt.safeParse(r);e.success&&(t[n]=e.data)}return Object.keys(t).length>0?t:void 0}),dt=e.object({baseUrl:C,models:T(E,`codeProviderModels`),responseBodyPolicy:S(M)}).extend(A.shape),ft=e.record(e.string(),e.unknown()).transform(e=>{let t={};for(let[n,r]of Object.entries(e)){let e=dt.safeParse(r);e.success&&e.data.models&&e.data.models.length>0&&(t[n]=e.data)}return Object.keys(t).length>0?t:void 0}),pt=e.object({postinstall:S(C),setup:S(C)}).catchall(C),mt=e.object({otto:S(C)}),ht=e.object({plane:S(e.enum([`host`,`preset`])),id:e.string().regex(x),name:S(e.string()),version:S(e.string()),description:S(e.string()),engines:S(mt),contributes:S(nt),scripts:S(pt),capabilities:S(it),activationEvents:S(ot),dependsOn:S(st),codeProviderIds:S(ct),codeProviderModelsEndpoints:S(ut),codeProviderModels:S(ft)});function gt(e){return e.plane??`preset`}function _t(e){return e.activationEvents?.length?e.activationEvents:[`onStartup`]}function N(e,t){let n;try{n=JSON.parse(e)}catch(e){return y.warn({sourcePath:t,err:String(e)},`plugin manifest invalid JSON, skipped`),null}let r=ht.safeParse(n);return r.success?r.data:(y.warn({sourcePath:t,issues:r.error.issues.map(e=>e.path.join(`.`)||`<root>`)},`plugin manifest missing/invalid "id" (kebab-case required) or not an object, skipped`),null)}const P=r(`@x-otto/coding:plugin-discovery`);function F(e){try{return te(e).isDirectory()}catch{return!1}}const vt=[{id:`otto-plugin-manager`,dir:`<builtin:otto-plugin-manager>`,scope:`builtin`,manifest:{id:`otto-plugin-manager`,name:`Plugin Manager`,description:`otto 插件生命周期管理(create/build/dev/install)—— 随 otto 内置分发,恒定信任、恒定启用。`,version:void 0,contributes:void 0,scripts:void 0,capabilities:void 0,activationEvents:void 0,dependsOn:void 0}},{id:`otto-tui`,dir:`<builtin:otto-tui>`,scope:`builtin`,manifest:{id:`otto-tui`,name:`TUI`,description:`otto 终端交互界面本体(渲染/输入/面板/状态栏)—— 随 otto 内置分发,恒定信任、恒定启用。`,version:void 0,contributes:void 0,scripts:void 0,capabilities:void 0,activationEvents:void 0,dependsOn:void 0}}];function yt(e){let{cwd:t,homedir:n}=e,r=e.disabled?new Set(e.disabled):null,i=le({cwd:t,homedir:n,claudeCompat:!1,workspaceRoot:se(t)}).filter(e=>e.kind===`otto`).map(e=>({dir:m(e.dir,`plugins`),scope:e.scope}));for(let t of e.bundledDirs??[])s(t)&&i.unshift({dir:t,scope:`bundled`});let a=new Map;for(let e of vt)a.set(e.id,e);for(let{dir:e,scope:t}of i){if(!s(e)||!F(e))continue;let n;try{n=p(e).sort()}catch(t){P.warn({root:e,err:String(t)},`failed to read plugins root, skipped`);continue}for(let i of n){let n=m(e,i);if(!F(n))continue;let o=m(n,b);if(!s(o))continue;let c;try{c=d(o,`utf-8`)}catch(e){P.warn({manifestPath:o,err:String(e)},`failed to read plugin manifest, skipped`);continue}let l=N(c,o);l&&(r?.has(l.id)||a.has(l.id)||a.set(l.id,{id:l.id,dir:n,manifest:l,scope:t}))}}return[...a.values()].sort((e,t)=>e.id.localeCompare(t.id))}function I(e){return e.manifest.dependsOn??[]}function bt(e){let t=new Map,n=new Map,r=e.filter(e=>e.scope===`builtin`),i=e.filter(e=>e.scope!==`builtin`),a=new Set(r.map(e=>e.id)),o=new Map;for(let e of i)o.set(e.id,e);let s=e=>o.has(e)||a.has(e),c=!0;for(;c;){c=!1;for(let e of o.values()){let t=I(e).filter(e=>!s(e));t.length>0&&(n.set(e.id,[...new Set(t)]),o.delete(e.id),c=!0)}}let l=new Map,u=new Map;for(let e of o.values()){let t=I(e).filter(e=>o.has(e));l.set(e.id,t.length);for(let n of t){let t=u.get(n);t?t.push(e.id):u.set(n,[e.id])}}let d=[...o.values()].filter(e=>l.get(e.id)===0).map(e=>e.id).sort((e,t)=>e.localeCompare(t)),f=[];for(;d.length>0;){let e=d.shift();f.push(e);let t=[];for(let n of u.get(e)??[]){let e=(l.get(n)??0)-1;l.set(n,e),e===0&&t.push(n)}t.length>0&&(d.push(...t),d.sort((e,t)=>e.localeCompare(t)))}let p=new Set(f);for(let e of o.values())p.has(e.id)||t.set(e.id,xt(e.id,o));return{ordered:[...r,...f.map(e=>o.get(e))],cyclic:t,missingDeps:n}}function xt(e,t){let n=[],r=new Set,i=e;for(;i&&!r.has(i);){n.push(i),r.add(i);let e=t.get(i);if(!e)break;i=I(e).filter(e=>t.has(e)).sort((e,t)=>e.localeCompare(t))[0]}return i===e&&n.push(e),n}const St=1;function Ct(){return`1.0.0`}function L(e){if(e.scope===`builtin`)return!0;let t=e.manifest.engines?.otto;return t?de(t)?ue(Ct(),t):!1:!0}function wt(e){let t=[],n=new Map;for(let r of e)L(r)?t.push(r):n.set(r.id,r.manifest.engines?.otto??`(invalid range)`);return{compatible:t,incompatible:n}}function Tt(e,t,n){if(!e?.length)return t?[...t]:void 0;if(!t?.length)return[...e];if(!n)return[...e,...t];let r=new Map(t.map(e=>[n(e),e])),i=new Set,a=[];for(let t of e){let e=n(t);i.add(e),a.push(r.has(e)?r.get(e):t)}for(let e of t)i.has(n(e))||a.push(e);return a}const R={commands:e=>e.name,agents:e=>e.name,skills:e=>e.name,mcp:e=>e.name,panels:e=>e.id,actions:e=>e.id,menus:null,contextKeys:e=>e.key,statusItems:e=>e.id,renderers:e=>e.id,contextSources:e=>e.id,a2uiComponents:e=>e.type,statusWidgets:e=>e.id,fileViewers:e=>e.id};function Et(...e){let t=e.filter(e=>e!=null);return t.length===0?{}:t.reduce((e,t)=>{let n={};for(let r of Object.keys(R)){let i=R[r],a=Tt(e[r],t[r],i);a&&(n[r]=a)}return n},{})}const z={extensionDetailActions:`extension/detail/actions`},B={statuslineItem:`shell/statusline/item`,menuItem:`shell/menu/item`},Dt={...z,...B};function V(e,t){if(!e)return!0;let n=e.indexOf(`:`);if(n===-1){let n=t[e];return n===!0||typeof n==`string`&&n.length>0}let r=e.slice(0,n),i=e.slice(n+1),a=t[r];return a!=null&&String(a)===i}function Ot(e,t,n,r){let i=new Map((n??[]).map(e=>[e.id,e]));return(t??[]).filter(t=>t.point===e&&V(t.when,r)).map(e=>({menu:e,action:i.get(e.action)})).filter(e=>e.action!=null).sort((e,t)=>(e.menu.order??0)-(t.menu.order??0)).map(({action:e})=>({id:e.id,title:e.title,color:e.color,command:e.command}))}function H(e){let t=e.replace(/\$\{OTTO_HOME\}/g,g);return t===`~`?fe():t.startsWith(`~/`)?fe()+t.slice(1):t}function kt(e,t=s){let n=e.groups.length;if(n===0)return e.map.none;let r=e.groups.filter(e=>e.some(e=>t(H(e)))).length;return r===0?e.map.none:r===n?e.map.all:e.map.partial}function At(e,t){let n={};for(let r of e??[])n[r.key]=kt(r,t);return n}var U=class extends Error{constructor(e){super(`dispatch: 缺少 capability「${e}」——宿主未注入`),this.name=`MissingCapabilityError`}};async function jt(e,t){switch(e.type){case`builtin`:if(!t.builtin)throw new U(`builtin`);await t.builtin(e.id);return;case`mcp`:if(!t.callMcp)throw new U(`mcp`);await t.callMcp(e.server,e.tool,e.params);return;case`open`:if(!t.open)throw new U(`open`);await t.open(e.target);return;case`command`:if(!t.runCommand)throw new U(`command`);await t.runCommand(e.name,e.args);return;case`script`:if(!t.runScript)throw new U(`script`);await t.runScript(e.name);return;default:throw new U(e.type)}}function Mt(e){return e}function W(e){return`${e.kind}\u0000${e.label}`}function Nt(e,t){if(t===``)return 0;let n=e.label.toLowerCase();return n.startsWith(t)?2:e.description&&e.description.toLowerCase().includes(t)?1:n.includes(t)?0:-1}function Pt(e){return e.startsWith(`!`)||e.startsWith(`#`)}function Ft(){let e=new Map,t=new Map;function n(t){for(let n of t){if(Pt(n.value))throw Error(`[plugin-input] sigil value 不得以 '!' 或 '#' 开头(会被误判为 bash/memory 模式):${JSON.stringify(n.value)}`);let t=W(n);e.delete(t),e.set(t,n)}}function r(t){let n=[...e.values()];return t?n.filter(e=>_(e.kind)===t):n}function i(t,n,r=50){let i=t.toLowerCase(),a=[],o=0;for(let t of e.values()){let e=o++;if(n&&_(t.kind)!==n)continue;let r=Nt(t,i);r<0||a.push({entry:t,score:r,order:e})}return a.sort((e,t)=>t.score-e.score||e.order-t.order),a.length<=r?a.map(e=>e.entry):a.slice(0,r).map(e=>e.entry)}function a(e,n){let r=t.get(e);return r||(r=new Set,t.set(e,r)),r.add(n),()=>{r?.delete(n)}}async function o(e,n,r=50){let a=i(e,n,r),o=n?t.get(n):void 0;if(!o||o.size===0)return a;let s=[],c=await Promise.allSettled([...o].map(t=>Promise.resolve().then(()=>t(e,n))));for(let e of c)e.status===`fulfilled`&&s.push(...e.value.filter(e=>!Pt(e.value)));let l=new Set(a.map(e=>W(e))),u=[...a];for(let e of s){let t=W(e);l.has(t)||(l.add(t),u.push(e))}return u.length<=r?u:u.slice(0,r)}function s(t){for(let n of e.values())if(n.label===t)return n}function c(t){let n=!1;for(let[r,i]of e)i.label===t&&(e.delete(r),n=!0);return n}function l(t){for(let[n,r]of e)r.source===t&&e.delete(n)}return{register:n,registerProvider:a,search:i,searchAsync:o,getAll:r,findByLabel:s,unregister:c,unregisterBySource:l}}function It(){return[{label:`Code Review`,description:`请求代码审查`,value:`Please review the following code for correctness, style, security, and performance. Provide specific, actionable feedback.`,kind:`hashtag`,source:`builtin`},{label:`Write Tests`,description:`为代码编写单元测试`,value:`Please write comprehensive unit tests for the following code. Cover normal cases, edge cases, error cases, and ensure high coverage.`,kind:`hashtag`,source:`builtin`},{label:`Explain Code`,description:`解释代码逻辑`,value:`Please explain the following code in detail. Cover the overall architecture, key algorithms, data flow, and any notable design decisions.`,kind:`hashtag`,source:`builtin`}]}const Lt=r(`@x-otto/plugin:file-provider`),Rt=new Set([`node_modules`,`.git`,`dist`,`build`,`out`,`coverage`,`.next`,`.nuxt`,`.turbo`,`.nx`,`.cache`,`.otto`,`.venv`,`__pycache__`]);async function zt(e,t,n){let r=[];async function i(a,o){if(r.length>=t)return;let s;try{s=await pe(a,{withFileTypes:!0})}catch(e){o&&n?.(e);return}for(let n of s){if(r.length>=t)return;if(n.isDirectory()){if(Rt.has(n.name)||n.name.startsWith(`.`))continue;await i(m(a,n.name),!1)}else n.isFile()&&r.push(oe(e,m(a,n.name)))}}return await i(e,!0),r}function Bt(e,t){if(t===``)return 0;let n=e.toLowerCase();return ie(n).startsWith(t)?3:n.startsWith(t)?2:n.includes(t)?1:-1}function Vt(e,t,n,r){let i;try{i=u(m(e,t),`r`);let a=Buffer.allocUnsafe(8192),o=f(i,a,0,a.length,0),s=a.toString(`utf-8`,0,o).split(`
1
+ import{z as e}from"zod";import{TypedEventEmitter as t,acquireFileLockSync as n,createLogger as r,releaseFileLockSync as i,waitForFileLockReleaseSync as a}from"@x-otto/shared";import{closeSync as o,existsSync as s,fsyncSync as c,mkdirSync as l,openSync as u,readFileSync as d,readSync as f,readdirSync as p,renameSync as ee,statSync as te,unlinkSync as ne,writeFileSync as re}from"node:fs";import{basename as m,dirname as ie,join as h,relative as ae,resolve as g}from"node:path";import{OTTO_HOME as oe,findWorkspaceRoot as se,isShadowModeEnabled as ce,resolveConfigLayers as le}from"@x-otto/env";import{satisfies as ue,validRange as de}from"semver";import{homedir as fe}from"node:os";import{sigilOf as pe,sigilOf as _}from"@x-otto/interchange";import{readdir as me}from"node:fs/promises";import{randomUUID as he}from"node:crypto";const v=`provider,tools,hooks,tui.renderer,network,context,monitor,wire-protocol,a2ui.component,panel.backend,mcp.server,feedback,input.resolver,a2ui.renderer,theme,agent.dispatch,service,storage,session.read,llm.complete,user.notify,user.ask,config,credentials,cli.command,tui.command,events,settings.read`.split(`,`),ge={provider:!0,tools:!0,hooks:!1,"tui.renderer":!0,network:!0,context:!1,monitor:!1,"wire-protocol":!0,"a2ui.component":!0,"panel.backend":!0,"mcp.server":!0,feedback:!1,"input.resolver":!0,"a2ui.renderer":!0,theme:!1,"agent.dispatch":!0,service:!0,storage:!1,"session.read":!0,"llm.complete":!0,"user.notify":!1,"user.ask":!0,config:!1,credentials:!0,"cli.command":!0,"tui.command":!0,events:!1,"settings.read":!1},y=Object.freeze(Object.keys(ge).filter(e=>ge[e])),b=r(`@x-otto/coding:plugin-manifest`),x=`otto-plugin.json`,S=/^[a-z0-9]+(?:-[a-z0-9]+)*$/,C=e=>e.optional().catch(void 0),w=e.string().refine(e=>e.trim().length>0,`must be non-empty`),_e=e.object({id:w,title:w,entry:w.refine(e=>!e.startsWith(`/`)&&!e.split(/[\\/]/).includes(`..`),`unsafe path (absolute or traversal)`),export:C(w)}),ve=e.object({role:C(w),toolName:C(w),contentType:C(w)}).refine(e=>e.role!=null||e.toolName!=null||e.contentType!=null,{message:`matcher must declare at least one of role/toolName/contentType`}),ye=e.object({id:w,matcher:ve,entry:w.refine(e=>!e.startsWith(`/`)&&!e.split(/[\\/]/).includes(`..`),`unsafe path (absolute or traversal)`),export:C(w)}),be=e.object({id:w,matcher:ve,entry:w.refine(e=>!e.startsWith(`/`)&&!e.split(/[\\/]/).includes(`..`),`unsafe path (absolute or traversal)`),export:C(w)}),xe=e.object({extensions:C(e.array(w)),filenames:C(e.array(w)),glob:C(e.array(w))}).refine(e=>(e.extensions?.length??0)+(e.filenames?.length??0)+(e.glob?.length??0)>0,{message:`matcher must declare at least one of extensions/filenames/glob`}),Se=T(e.object({id:w,label:w,matcher:xe,entry:w.refine(e=>!e.startsWith(`/`)&&!e.split(/[\\/]/).includes(`..`),`unsafe path (absolute or traversal)`),export:C(w),order:C(e.number())}),`fileViewers`),Ce=T(e.object({type:w,entry:w.refine(e=>!e.startsWith(`/`)&&!e.split(/[\\/]/).includes(`..`),`unsafe path (absolute or traversal)`),export:C(w)}),`a2uiComponents`),we=T(e.object({id:w,entry:w.refine(e=>!e.startsWith(`/`)&&!e.split(/[\\/]/).includes(`..`),`unsafe path (absolute or traversal)`),export:C(w),order:C(e.number()),tickMs:C(e.number())}),`statusWidgets`),Te=T(ye,`renderers`),Ee=T(_e,`panels`),De=e.discriminatedUnion(`type`,[e.object({type:e.literal(`builtin`),id:w}),e.object({type:e.literal(`mcp`),server:w,tool:w,params:C(e.record(e.string(),e.unknown()))}),e.object({type:e.literal(`open`),target:w}),e.object({type:e.literal(`command`),name:w,args:C(w)}),e.object({type:e.literal(`script`),name:w})]),Oe=e.object({id:w,title:w,command:De,color:C(e.enum([`accent`,`amber`,`red`,`green`,`gray`]))}),ke=e.object({action:w,point:w,when:C(w),order:C(e.number())}),Ae=e.object({key:w,groups:e.array(e.array(w)),map:e.object({none:w,partial:w,all:w})});function T(t,n){return e.array(e.unknown()).transform(e=>{let r=e.map(e=>t.safeParse(e)),i=r.filter(e=>e.success).map(e=>e.data),a=r.length-i.length;if(a>0&&n){let e=r.filter(e=>!e.success).map(e=>e.success?``:e.error.issues.map(e=>e.message).join(`; `));b.warn({label:n,dropped:a,total:r.length,issues:e},`contributes.${n}: ${a}/${r.length} 条 entry 校验失败,已静默丢弃(fail-soft)`)}return i.length>0?i:void 0})}const je=e.object({input:e.number().min(0),output:e.number().min(0),cacheRead:e.number().min(0),cacheWrite:e.number().min(0)}),Me=e.object({maxImagesPerRequest:e.number().int().positive(),maxDimensionPxIfOverLimit:C(e.number().int().positive())}),Ne=e.enum([`planning`,`knowledge`,`coding`,`reasoning`,`vision`,`speed`,`long-context`]),Pe=e.enum([`low`,`medium`,`high`,`xhigh`,`max`]),Fe=e.enum([`enabled`,`adaptive`]),Ie=e.object({id:w,contextWindow:e.number().int().positive(),maxOutput:e.number().int().positive(),cost:C(je),strengths:C(e.array(Ne).min(1)),reasoning:C(e.boolean()),input:C(e.array(e.enum([`text`,`image`])).min(1)),thinkingLevels:C(e.array(Pe).min(1)),thinkingMode:C(Fe)}),Le=e.enum([`openai-completions`,`openai-responses`,`anthropic-messages`]),Re=e.object({label:w,value:w.refine(e=>!e.startsWith(`!`)&&!e.startsWith(`#`),`value must not start with '!' or '#' (mode-prefix collision)`),kind:e.enum([`resource`,`hashtag`]),description:C(w),resolverId:C(w)}),ze=e.object({id:w,dataSource:e.object({contextKey:w}),point:C(w),when:C(w),order:C(e.number())}),Be=e.union([w,e.object({file:w.refine(e=>!e.startsWith(`/`)&&!e.split(/[\\/]/).includes(`..`),`unsafe path (absolute or traversal)`)})]),Ve=e.object({id:w,priority:C(e.number()),content:Be}),He=e.object({id:w,translations:e.record(e.string(),e.string())}),Ue=/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/,E=e=>!Ue.test(e),We=/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/,D=t=>e.string().max(16).refine(e=>We.test(e),`${t} must be a #RRGGBB or #RGB hex color`),Ge=e.object({brand:D(`brand`),brandShimmer:D(`brandShimmer`),accent:D(`accent`),accentDim:D(`accentDim`),accentBright:D(`accentBright`),text:C(D(`text`)),inactive:D(`inactive`),secondary:D(`secondary`),subtle:D(`subtle`),replyPrefix:D(`replyPrefix`),success:D(`success`),error:D(`error`),warning:D(`warning`),special:D(`special`),suggestion:D(`suggestion`),permission:D(`permission`),codeText:D(`codeText`),diffAdd:D(`diffAdd`),diffRemove:D(`diffRemove`),diffAddBg:D(`diffAddBg`),diffRemoveBg:D(`diffRemoveBg`),panelBorder:D(`panelBorder`),overlayBg:D(`overlayBg`),tagBg:D(`tagBg`),toolText:D(`toolText`),focusBorder:D(`focusBorder`),selection:D(`selection`)}),O=e.enum(`brand.brandShimmer.accent.accentDim.accentBright.text.inactive.secondary.subtle.replyPrefix.success.error.warning.special.suggestion.permission.codeText.diffAdd.diffRemove.diffAddBg.diffRemoveBg.panelBorder.overlayBg.tagBg.toolText.focusBorder.selection`.split(`.`)),Ke=e.strictObject({heading1:C(O),heading2:C(O),headingWeak:C(O),listMarker:C(O),listMarkerMuted:C(O),quoteBar:C(O),quoteText:C(O),link:C(O),inlineCode:C(O),codeFence:C(O),tableHeader:C(O),tableDivider:C(O),listIndent:C(e.union([e.literal(2),e.literal(3)])),listMarkers:C(e.tuple([e.string().min(1).max(4),e.string().min(1).max(4),e.string().min(1).max(4),e.string().min(1).max(4)])),codeBlockDivider:C(e.enum([`hr`,`none`]))}),qe=e.object({localId:w.max(64).refine(e=>S.test(e),`localId must be kebab-case`),label:w.max(64).refine(E,`label must not contain control characters`),appearance:e.enum([`dark`,`light`]),colors:Ge,markdown:C(Ke),meta:C(e.object({author:C(w.max(128).refine(E,`author must not contain control characters`)),description:C(w.max(256).refine(E,`description must not contain control characters`)),version:C(w.max(32).refine(E,`version must not contain control characters`))}))}),Je=10,Ye=500,Xe=2*1024,Ze=20,Qe=64*1024,$e=32,et=4,tt=8*1024,nt=256,rt=e.object({id:w,label:w,toolRef:w}),it=e.object({header:w.refine(e=>/^[a-zA-Z0-9][a-zA-Z0-9\-_]*$/.test(e),`header name must be alphanumeric with hyphens/underscores only`),source:e.object({kind:e.literal(`jwt-claim`),claim:w,namespace:C(w),tokenSources:C(e.array(e.enum([`id_token`,`access_token`])))})}),k=e.object({imageConstraints:C(Me),supportsTools:C(e.boolean())}),A=e.object({url:w,responseFormat:e.literal(`openai-list`),authScheme:C(e.enum([`bearer`,`x-api-key`,`auto`])),betaHeaderName:C(w),modelIdExclude:C(T(e.string(),`modelIdExclude`))}),j=e.object({store:C(e.boolean()),sendMaxOutputTokens:C(e.boolean())}),at=e.object({id:w,name:C(w),catalogOnly:C(e.boolean()),baseUrl:C(w),wireApi:C(Le),envKey:C(w),oauthRef:C(w),headers:C(e.record(e.string(),e.string())),models:C(T(Ie,`models`)),tokenDerivedHeaders:C(T(it,`tokenDerivedHeaders`)),modelsEndpoint:C(A),responseBodyPolicy:C(j),allowAuthHeaderOverride:C(e.boolean()),preserveProviderId:C(e.boolean()),requiresIntranet:C(e.boolean())}).extend(k.shape).superRefine((t,n)=>{if(t.catalogOnly===!0){t.wireApi&&n.addIssue({code:e.ZodIssueCode.custom,message:`catalogOnly entries must not declare wireApi; use providerFactories for custom protocols`,path:[`wireApi`]}),t.baseUrl&&n.addIssue({code:e.ZodIssueCode.custom,message:`catalogOnly entries must not declare baseUrl; use providerFactories for custom protocols`,path:[`baseUrl`]});return}t.baseUrl||n.addIssue({code:e.ZodIssueCode.custom,message:`baseUrl is required unless catalogOnly is true`,path:[`baseUrl`]}),t.wireApi||n.addIssue({code:e.ZodIssueCode.custom,message:`wireApi is required unless catalogOnly is true`,path:[`wireApi`]})}),ot=e.object({name:w.refine(e=>/^[a-z][a-z0-9-]*$/.test(e),`must be lowercase kebab-case`),bin:C(w)}),st=e.object({name:w.refine(e=>/^[a-z][a-z0-9-]*$/.test(e),`must be lowercase kebab-case`),summary:w.max(200).refine(E,`must not contain control characters`),i18nKey:C(w)}),ct=e.object({name:w.refine(e=>/^[a-z][a-z0-9-]*$/.test(e),`must be lowercase kebab-case`),description:w.max(200).refine(E,`must not contain control characters`),group:C(w),i18nKey:C(w)}),lt=e.discriminatedUnion(`kind`,[e.object({kind:e.literal(`pkce`),id:w,name:C(w),clientId:w,authorizeUrl:w,tokenUrl:w,redirectUri:w,scope:w,loopbackPorts:C(e.array(e.number().int().positive())),assumesLoopbackAlways:C(e.boolean()),extraAuthParams:C(e.record(e.string(),e.string())),tokenExchangeEncoding:C(e.enum([`json`,`form`])),subscriptionScoped:C(e.boolean()),oauthBeta:C(w)}),e.object({kind:e.literal(`device-flow`),id:w,name:C(w),clientId:w,deviceCodeUrlTemplate:w,tokenUrlTemplate:w,refreshUrlTemplate:C(w),scope:w,allowCustomDomain:C(e.boolean()),userAgent:C(w)})]),ut=e.object({type:e.enum([`string`,`number`,`integer`,`boolean`,`array`,`object`]),title:C(w),description:C(w),default:e.unknown().optional(),enum:C(e.array(e.unknown())),minimum:C(e.number()),maximum:C(e.number()),pattern:C(w),items:e.unknown().optional(),properties:C(e.record(e.string(),e.unknown()))});function M(e,t){if(t>4||typeof e!=`object`||!e)return!1;let n=e;if(typeof n.description==`string`&&n.description.length>256)return!1;if(n.properties&&typeof n.properties==`object`){let e=n.properties;if(Object.keys(e).length>32)return!1;for(let n of Object.values(e))if(!M(n,t+1))return!1}return!(n.items&&typeof n.items==`object`&&!M(n.items,t+1))}const dt=e.unknown().transform(e=>{if(typeof e!=`object`||!e)return;let t=JSON.stringify(e).length;if(t>8192){b.warn({bytes:t,limit:tt},`config schema exceeds byte limit, dropped`);return}if(!M(e,0)){b.warn(`config schema exceeds depth/field/description limit, dropped`);return}let n=ut.safeParse(e);if(!n.success){b.warn({err:n.error.issues},`config schema invalid, dropped`);return}return n.data}),ft=e.object({id:w,label:C(w),entry:w.refine(e=>!e.startsWith(`/`)&&!e.split(/[\\/]/).includes(`..`),`unsafe path (absolute or traversal)`),capabilities:C(e.object({atomic:C(e.boolean()),permSecure:C(e.boolean()),crossProcessLock:C(e.boolean()),watchable:C(e.boolean())}))}),pt=e.object({type:w.refine(e=>{let t=e.split(`.`);return t.length>=2&&t.every(e=>e.length>0)},`event type must be <pluginId>.<event> namespaced format`),description:C(w),payloadSchema:C(e.record(e.string(),e.unknown()))}),mt=e.object({skills:C(e.boolean()),agents:C(e.boolean()),commands:C(e.boolean()),mcp:C(e.boolean()),cli:C(ot),cliCommands:C(T(st,`cliCommands`)),slashCommands:C(T(ct,`slashCommands`)),panels:C(Ee),actions:C(T(Oe,`actions`)),menus:C(T(ke,`menus`)),contextKeys:C(T(Ae,`contextKeys`)),providers:C(T(at,`providers`)),oauth:C(T(lt,`oauth`)),statusItems:C(T(ze,`statusItems`)),perfMetrics:C(T(rt,`perfMetrics`)),renderers:C(Te),fileViewers:C(Se),statusWidgets:C(we),a2uiComponents:C(Ce),contextSources:C(T(Ve,`contextSources`)),i18n:C(T(He,`i18n`)),themePresets:C(T(qe,`themePresets`)),i18nDir:C(w.refine(e=>!e.startsWith(`/`)&&!e.split(/[\\/]/).includes(`..`),`unsafe path (absolute or traversal)`)),inputEntries:C(w),sigilEntries:C(T(Re,`sigilEntries`)),a2uiRenderers:C(T(be,`a2uiRenderers`)),config:C(dt),credentialBackends:C(T(ft,`credentialBackends`)),events:C(T(pt,`events`))}),ht=e.enum(v),gt=e.array(e.unknown()).transform(e=>{let t=e.map(e=>ht.safeParse(e)).filter(e=>e.success).map(e=>e.data);return t.length>0?[...new Set(t)]:void 0}),_t=/^(onStartup|onCommand:[^\s]+|onView:[^\s]+|onProvider:[^\s]+)$/,vt=e.array(e.unknown()).transform(e=>{let t=e.filter(e=>typeof e==`string`&&_t.test(e));return t.length>0?[...new Set(t)]:void 0}),yt=e.array(e.unknown()).transform(e=>{let t=e.filter(e=>typeof e==`string`&&S.test(e));return t.length>0?[...new Set(t)]:void 0}),bt=e.array(e.unknown()).transform(e=>{let t=e.filter(e=>typeof e==`string`&&e.trim().length>0);return t.length>0?[...new Set(t)]:void 0}),xt=A.extend({headers:C(e.record(e.string(),e.string()))}),St=e.record(e.string(),e.unknown()).transform(e=>{let t={};for(let[n,r]of Object.entries(e)){let e=xt.safeParse(r);e.success&&(t[n]=e.data)}return Object.keys(t).length>0?t:void 0}),Ct=e.object({baseUrl:w,models:T(Ie,`codeProviderModels`),responseBodyPolicy:C(j)}).extend(k.shape),wt=e.record(e.string(),e.unknown()).transform(e=>{let t={};for(let[n,r]of Object.entries(e)){let e=Ct.safeParse(r);e.success&&e.data.models&&e.data.models.length>0&&(t[n]=e.data)}return Object.keys(t).length>0?t:void 0}),Tt=e.object({postinstall:C(w),setup:C(w)}).catchall(w),Et=e.object({otto:C(w)}),Dt=e.object({plane:C(e.enum([`host`,`preset`])),id:e.string().regex(S),name:C(e.string()),version:C(e.string()),description:C(e.string()),engines:C(Et),contributes:C(mt),scripts:C(Tt),capabilities:C(gt),activationEvents:C(vt),dependsOn:C(yt),codeProviderIds:C(bt),codeProviderModelsEndpoints:C(St),codeProviderModels:C(wt)});function Ot(e){return e.plane??`preset`}function kt(e){return e.activationEvents?.length?e.activationEvents:[`onStartup`]}function N(e,t){let n;try{n=JSON.parse(e)}catch(e){return b.warn({sourcePath:t,err:String(e)},`plugin manifest invalid JSON, skipped`),null}let r=Dt.safeParse(n);return r.success?r.data:(b.warn({sourcePath:t,issues:r.error.issues.map(e=>e.path.join(`.`)||`<root>`)},`plugin manifest missing/invalid "id" (kebab-case required) or not an object, skipped`),null)}const P=r(`@x-otto/coding:plugin-discovery`);function F(e){try{return te(e).isDirectory()}catch{return!1}}const At=[{id:`otto-plugin-manager`,dir:`<builtin:otto-plugin-manager>`,scope:`builtin`,manifest:{id:`otto-plugin-manager`,name:`Plugin Manager`,description:`otto 插件生命周期管理(create/build/dev/install)—— 随 otto 内置分发,恒定信任、恒定启用。`,version:void 0,contributes:void 0,scripts:void 0,capabilities:void 0,activationEvents:void 0,dependsOn:void 0}},{id:`otto-tui`,dir:`<builtin:otto-tui>`,scope:`builtin`,manifest:{id:`otto-tui`,name:`TUI`,description:`otto 终端交互界面本体(渲染/输入/面板/状态栏)—— 随 otto 内置分发,恒定信任、恒定启用。`,version:void 0,contributes:void 0,scripts:void 0,capabilities:void 0,activationEvents:void 0,dependsOn:void 0}}];function jt(e){let{cwd:t,homedir:n}=e,r=e.disabled?new Set(e.disabled):null,i=le({cwd:t,homedir:n,claudeCompat:!1,workspaceRoot:se(t)}).filter(e=>e.kind===`otto`).map(e=>({dir:h(e.dir,`plugins`),scope:e.scope}));for(let t of e.bundledDirs??[])s(t)&&i.unshift({dir:t,scope:`bundled`});let a=new Map;for(let e of At)a.set(e.id,e);for(let{dir:e,scope:t}of i){if(!s(e)||!F(e))continue;let n;try{n=p(e).sort()}catch(t){P.warn({root:e,err:String(t)},`failed to read plugins root, skipped`);continue}for(let i of n){let n=h(e,i);if(!F(n))continue;let o=h(n,x);if(!s(o))continue;let c;try{c=d(o,`utf-8`)}catch(e){P.warn({manifestPath:o,err:String(e)},`failed to read plugin manifest, skipped`);continue}let l=N(c,o);l&&(r?.has(l.id)||a.has(l.id)||a.set(l.id,{id:l.id,dir:n,manifest:l,scope:t}))}}return[...a.values()].sort((e,t)=>e.id.localeCompare(t.id))}function I(e){return e.manifest.dependsOn??[]}function Mt(e){let t=new Map,n=new Map,r=e.filter(e=>e.scope===`builtin`),i=e.filter(e=>e.scope!==`builtin`),a=new Set(r.map(e=>e.id)),o=new Map;for(let e of i)o.set(e.id,e);let s=e=>o.has(e)||a.has(e),c=!0;for(;c;){c=!1;for(let e of o.values()){let t=I(e).filter(e=>!s(e));t.length>0&&(n.set(e.id,[...new Set(t)]),o.delete(e.id),c=!0)}}let l=new Map,u=new Map;for(let e of o.values()){let t=I(e).filter(e=>o.has(e));l.set(e.id,t.length);for(let n of t){let t=u.get(n);t?t.push(e.id):u.set(n,[e.id])}}let d=[...o.values()].filter(e=>l.get(e.id)===0).map(e=>e.id).sort((e,t)=>e.localeCompare(t)),f=[];for(;d.length>0;){let e=d.shift();f.push(e);let t=[];for(let n of u.get(e)??[]){let e=(l.get(n)??0)-1;l.set(n,e),e===0&&t.push(n)}t.length>0&&(d.push(...t),d.sort((e,t)=>e.localeCompare(t)))}let p=new Set(f);for(let e of o.values())p.has(e.id)||t.set(e.id,Nt(e.id,o));return{ordered:[...r,...f.map(e=>o.get(e))],cyclic:t,missingDeps:n}}function Nt(e,t){let n=[],r=new Set,i=e;for(;i&&!r.has(i);){n.push(i),r.add(i);let e=t.get(i);if(!e)break;i=I(e).filter(e=>t.has(e)).sort((e,t)=>e.localeCompare(t))[0]}return i===e&&n.push(e),n}const Pt=1;function Ft(){return`1.0.0`}function L(e){if(e.scope===`builtin`)return!0;let t=e.manifest.engines?.otto;return t?de(t)?ue(Ft(),t):!1:!0}function It(e){let t=[],n=new Map;for(let r of e)L(r)?t.push(r):n.set(r.id,r.manifest.engines?.otto??`(invalid range)`);return{compatible:t,incompatible:n}}function Lt(e,t,n){if(!e?.length)return t?[...t]:void 0;if(!t?.length)return[...e];if(!n)return[...e,...t];let r=new Map(t.map(e=>[n(e),e])),i=new Set,a=[];for(let t of e){let e=n(t);i.add(e),a.push(r.has(e)?r.get(e):t)}for(let e of t)i.has(n(e))||a.push(e);return a}const R={commands:e=>e.name,agents:e=>e.name,skills:e=>e.name,mcp:e=>e.name,panels:e=>e.id,actions:e=>e.id,menus:null,contextKeys:e=>e.key,statusItems:e=>e.id,renderers:e=>e.id,contextSources:e=>e.id,a2uiComponents:e=>e.type,statusWidgets:e=>e.id,fileViewers:e=>e.id,events:e=>e.type};function Rt(...e){let t=e.filter(e=>e!=null);return t.length===0?{}:t.reduce((e,t)=>{let n={};for(let r of Object.keys(R)){let i=R[r],a=Lt(e[r],t[r],i);a&&(n[r]=a)}return n},{})}const z={extensionDetailActions:`extension/detail/actions`},B={statuslineItem:`shell/statusline/item`,menuItem:`shell/menu/item`},zt={...z,...B};function V(e,t){if(!e)return!0;let n=e.indexOf(`:`);if(n===-1){let n=t[e];return n===!0||typeof n==`string`&&n.length>0}let r=e.slice(0,n),i=e.slice(n+1),a=t[r];return a!=null&&String(a)===i}function Bt(e,t,n,r){let i=new Map((n??[]).map(e=>[e.id,e]));return(t??[]).filter(t=>t.point===e&&V(t.when,r)).map(e=>({menu:e,action:i.get(e.action)})).filter(e=>e.action!=null).sort((e,t)=>(e.menu.order??0)-(t.menu.order??0)).map(({action:e})=>({id:e.id,title:e.title,color:e.color,command:e.command}))}function H(e){let t=e.replace(/\$\{OTTO_HOME\}/g,oe);return t===`~`?fe():t.startsWith(`~/`)?fe()+t.slice(1):t}function U(e,t=s){let n=e.groups.length;if(n===0)return e.map.none;let r=e.groups.filter(e=>e.some(e=>t(H(e)))).length;return r===0?e.map.none:r===n?e.map.all:e.map.partial}function Vt(e,t){let n={};for(let r of e??[])n[r.key]=U(r,t);return n}var W=class extends Error{constructor(e){super(`dispatch: 缺少 capability「${e}」——宿主未注入`),this.name=`MissingCapabilityError`}};async function Ht(e,t){switch(e.type){case`builtin`:if(!t.builtin)throw new W(`builtin`);await t.builtin(e.id);return;case`mcp`:if(!t.callMcp)throw new W(`mcp`);await t.callMcp(e.server,e.tool,e.params);return;case`open`:if(!t.open)throw new W(`open`);await t.open(e.target);return;case`command`:if(!t.runCommand)throw new W(`command`);await t.runCommand(e.name,e.args);return;case`script`:if(!t.runScript)throw new W(`script`);await t.runScript(e.name);return;default:throw new W(e.type)}}function Ut(e){return e}function G(e){return`${e.kind}\u0000${e.label}`}function Wt(e,t){if(t===``)return 0;let n=e.label.toLowerCase();return n.startsWith(t)?3:e.description&&e.description.toLowerCase().includes(t)?2:n.includes(t)?1:e.kind.toLowerCase()===t?0:-1}function Gt(e){return e.startsWith(`!`)||e.startsWith(`#`)}function Kt(){let e=new Map,t=new Map;function n(t){for(let n of t){if(Gt(n.value))throw Error(`[plugin-input] sigil value 不得以 '!' 或 '#' 开头(会被误判为 bash/memory 模式):${JSON.stringify(n.value)}`);let t=G(n);e.delete(t),e.set(t,n)}}function r(t){let n=[...e.values()];return t?n.filter(e=>_(e.kind)===t):n}function i(t,n,r=50){let i=t.toLowerCase(),a=[],o=0;for(let t of e.values()){let e=o++;if(n&&_(t.kind)!==n)continue;let r=Wt(t,i);r<0||a.push({entry:t,score:r,order:e})}return a.sort((e,t)=>t.score-e.score||e.order-t.order),a.length<=r?a.map(e=>e.entry):a.slice(0,r).map(e=>e.entry)}function a(e,n){let r=t.get(e);return r||(r=new Set,t.set(e,r)),r.add(n),()=>{r?.delete(n)}}async function o(e,n,r=50){let a=i(e,n,r),o=n?t.get(n):void 0;if(!o||o.size===0)return a;let s=[],c=await Promise.allSettled([...o].map(t=>Promise.resolve().then(()=>t(e,n))));for(let e of c)e.status===`fulfilled`&&s.push(...e.value.filter(e=>!Gt(e.value)));let l=new Set(a.map(e=>G(e))),u=[...a];for(let e of s){let t=G(e);l.has(t)||(l.add(t),u.push(e))}return u.length<=r?u:u.slice(0,r)}function s(t){for(let n of e.values())if(n.label===t)return n}function c(t){let n=!1;for(let[r,i]of e)i.label===t&&(e.delete(r),n=!0);return n}function l(t){for(let[n,r]of e)r.source===t&&e.delete(n)}return{register:n,registerProvider:a,search:i,searchAsync:o,getAll:r,findByLabel:s,unregister:c,unregisterBySource:l}}function qt(){return[{label:`Code Review`,description:`请求代码审查`,value:`Please review the following code for correctness, style, security, and performance. Provide specific, actionable feedback.`,kind:`hashtag`,source:`builtin`},{label:`Write Tests`,description:`为代码编写单元测试`,value:`Please write comprehensive unit tests for the following code. Cover normal cases, edge cases, error cases, and ensure high coverage.`,kind:`hashtag`,source:`builtin`},{label:`Explain Code`,description:`解释代码逻辑`,value:`Please explain the following code in detail. Cover the overall architecture, key algorithms, data flow, and any notable design decisions.`,kind:`hashtag`,source:`builtin`}]}const Jt=r(`@x-otto/plugin:file-provider`),Yt=new Set([`node_modules`,`.git`,`dist`,`build`,`out`,`coverage`,`.next`,`.nuxt`,`.turbo`,`.nx`,`.cache`,`.otto`,`.venv`,`__pycache__`]);async function Xt(e,t,n){let r=[];async function i(a,o){if(r.length>=t)return;let s;try{s=await me(a,{withFileTypes:!0})}catch(e){o&&n?.(e);return}for(let n of s){if(r.length>=t)return;if(n.isDirectory()){if(Yt.has(n.name)||n.name.startsWith(`.`))continue;await i(h(a,n.name),!1)}else n.isFile()&&r.push(ae(e,h(a,n.name)))}}return await i(e,!0),r}function Zt(e,t){if(t===``)return 0;let n=e.toLowerCase();return m(n).startsWith(t)?3:n.startsWith(t)?2:n.includes(t)?1:-1}function Qt(e,t,n,r){let i;try{i=u(h(e,t),`r`);let a=Buffer.allocUnsafe(8192),o=f(i,a,0,a.length,0),s=a.toString(`utf-8`,0,o).split(`
2
2
  `).slice(0,n).join(`
3
- `);return s.length>r?`${s.slice(0,r-1)}…`:s}catch{return``}finally{if(i!==void 0)try{o(i)}catch{}}}function Ht(e){let{cwd:t,max:n=20,walkMax:r=5e3,ttlMs:i=1e4,now:a=Date.now}=e,o=null,s=0,c=null,l=new Map;async function u(){return o&&a()-s<i?o:c||(c=zt(t,r,e=>{Lt.warn({cwd:t,err:String(e)},`@file provider workspace walk failed at root — @ panel will show no files`)}).then(e=>(o=e,s=a(),c=null,l=new Map,e)),c)}function d(e){let n=l.get(e);if(n!==void 0)return n;let r=Vt(t,e,20,500);return l.set(e,r),r}return async e=>{let t=await u(),r=e.toLowerCase(),i=[];for(let e of t){let t=Bt(e,r);if(!(t<0)&&(i.push({path:e,score:t}),r===``&&i.length>=n))break}return i.sort((e,t)=>t.score-e.score||e.path.length-t.path.length),i.slice(0,n).map(({path:e})=>({label:e,value:`[@file:${e.replace(/]/g,`\\]`)}]`,kind:`file`,source:`file`,preview:d(e)}))}}const Ut=r(`@x-otto/coding:trust-store`),Wt={"ui.renderer":`tui.renderer`};function Gt(e){return[...new Set(e.map(e=>Wt[e]??e))]}const G=2e3;function K(e,t,r){let o=ae(e),s=ie(e),c=n(o,s,{timeoutMs:G});if(c||a(o,s,{timeoutMs:G})&&(c=n(o,s,{timeoutMs:G})),!c&&r?.requireLock)throw Error(`Failed to acquire trust store lock within ${G}ms; revocation aborted to avoid silently losing it to a concurrent writer`);try{return t()}finally{c&&i(o,s)}}function q(e){try{let t=JSON.parse(d(e,`utf-8`)),n=t?.trusted,r={},i=t?.capabilities;if(i&&typeof i==`object`)for(let[e,t]of Object.entries(i))Array.isArray(t)&&(r[e]=Gt(t.filter(e=>typeof e==`string`)));let a={},o=t?.versions;if(o&&typeof o==`object`)for(let[e,t]of Object.entries(o))typeof t==`string`&&(a[h(e)]=t);return{trusted:Array.isArray(n)?n.filter(e=>typeof e==`string`):[],capabilities:r,versions:a}}catch{return{trusted:[],capabilities:{},versions:{}}}}function Kt(e,t){let n=`${e}.${me()}.tmp`,r;try{r=u(n,`w`),re(r,t),c(r),o(r),r=void 0,ee(n,e)}catch(e){if(r!==void 0)try{o(r)}catch{}try{ne(n)}catch{}throw e}}function J(e,t,n){if(!ce())try{l(ae(e),{recursive:!0});let n={trusted:[...new Set(t.trusted)]};t.capabilities&&Object.keys(t.capabilities).length>0&&(n.capabilities=t.capabilities),t.versions&&Object.keys(t.versions).length>0&&(n.versions=t.versions),Kt(e,JSON.stringify(n,null,2))}catch(t){throw Ut.warn({storePath:e,err:t},`Failed to persist ${n} store`),t}}function qt(e,t){return q(e).trusted.includes(h(t))}function Jt(e,t,n){return K(e,()=>{let r=h(t),i=q(e);return i.trusted.includes(r)?!1:(i.trusted.push(r),J(e,i,n),!0)})}function Yt(e,t,n){return K(e,()=>{let r=h(t),i=q(e),a=i.trusted.filter(e=>e!==r),o={...i.capabilities},s=r in o;delete o[r];let c={...i.versions},l=r in c;return delete c[r],a.length===i.trusted.length&&!s&&!l?!1:(J(e,{trusted:a,capabilities:o,versions:c},n),!0)},{requireLock:!0})}function Xt(e,t){return q(e).capabilities?.[h(t)]??[]}function Zt(e,t,n,r){return K(e,()=>{let i=h(t),a=q(e),o=new Set(a.capabilities?.[i]??[]),s=o.size;for(let e of n)o.add(e);return o.size===s?!1:(J(e,{trusted:a.trusted,capabilities:{...a.capabilities,[i]:[...o]}},r),!0)})}function Y(e,t,n,r,i){return K(e,()=>{let a=h(t),o=q(e),s=new Set(o.capabilities?.[a]??[]),c=s.size;for(let e of n)s.add(e);let l=o.versions?.[a]!==r;return s.size===c&&!l?!1:(J(e,{trusted:o.trusted,capabilities:{...o.capabilities,[a]:[...s]},versions:{...o.versions,[a]:r}},i),!0)})}function Qt(e,t,n){let r=h(t),i=q(e);return i.versions?.[r]===n?i.capabilities?.[r]??[]:[]}const X=r(`@x-otto/plugin:trust`);function Z(){return process.env.OTTO_PLUGIN_TRUST_PATH||m(g,`plugin-trust.json`)}function $t(e,t=Z()){return qt(t,e)}function en(e,t=Z()){Jt(t,e,`plugin-trust`)&&X.info({dir:h(e)},`Plugin trusted`)}function tn(e,t=Z()){Yt(t,e,`plugin-trust`)&&X.info({dir:h(e)},`Plugin trust revoked`)}function nn(e,t=Z()){return Xt(t,e)}function rn(e){try{let t=m(e,b);if(!s(t))return[];let n=N(d(t,`utf8`),t);return n?.capabilities?n.capabilities.filter(e=>v.includes(e)):[]}catch{return[]}}function an(e,t,n=Z()){Zt(n,e,t,`plugin-trust`)&&X.info({dir:h(e),caps:t},`Plugin capabilities granted`)}function on(e,t,n,r=Z()){Y(r,e,t,n,`plugin-trust`)&&X.info({dir:h(e),caps:t,version:n},`Plugin capabilities granted (version-bound)`)}function sn(e,t,n=Z()){return Qt(n,e,t)}function Q(e){try{return p(e).length>0}catch{return!1}}function cn(e,t){let n=h(e),r=s(m(n,`.mcp.json`)),i=Q(m(n,`commands`)),a=!!(t?.scripts?.postinstall||t?.scripts?.setup),o=!!(t?.contributes?.panels&&t.contributes.panels.length>0),c=[`plugin.ts`,`plugin.tsx`].some(e=>s(m(n,e))),l=Q(m(n,`skills`)),u=Q(m(n,`agents`)),d=!!t?.contributes?.cli,f=!!t?.capabilities?.some(e=>v.includes(e));return{mcpJson:r,commands:i,scripts:a,panels:o,pluginEntry:c,skills:l,agents:u,cli:d,highRiskCapabilities:f,any:r||i||a||o||c||l||u||d||f}}function ln(e,t=Z()){let n=cn(e.dir,e.manifest),r=$t(e.dir,t),i=(e.scope===`project`||e.scope===`repository`)&&n.any&&!r,a=new Set(e.manifest?.capabilities??[]),o=e.scope===`bundled`||e.scope===`builtin`?a:new Set(Xt(t,e.dir));return{surface:n,trusted:r,gated:i,pendingCapabilities:v.filter(e=>a.has(e)&&!o.has(e)),grantedCapabilities:v.filter(e=>a.has(e)&&o.has(e))}}function $(e){return e.split(/[-_]/)[0]??e}var un=class extends t{entries=new Map;currentLocale=`zh`;register(e){this.entries.clear();for(let t of e)this.entries.set(`${t.pluginId}:${t.id}`,t.translations)}get size(){return this.entries.size}getLocale(){return this.currentLocale}setLocale(e){e!==this.currentLocale&&(this.currentLocale=e,this.emit(`change`,e))}t(e,t=this.currentLocale){let n=this.entries.get(e);if(!n)return e;let r=n[t];if(r!==void 0)return r;let i=$(t);for(let[e,t]of Object.entries(n))if($(e)===i)return t;return n.en===void 0?Object.values(n)[0]??e:n.en}};export{z as EXTENSION_POINTS,v as HIGH_RISK_CAPABILITIES,x as ID_RE,qe as MAX_I18N_ENTRIES_PER_PLUGIN,Xe as MAX_I18N_FILE_BYTES,Ye as MAX_I18N_LOCALE_FILES_PER_PLUGIN,Je as MAX_I18N_TRANSLATION_BYTES,Ke as MAX_THEME_PRESETS_PER_PLUGIN,St as PLUGIN_API_VERSION,he as PLUGIN_CAPABILITIES,b as PLUGIN_MANIFEST_FILENAME,Dt as POINT,un as PluginI18nRegistry,B as SHELL_POINTS,It as createBuiltinHashtags,Ht as createFileProvider,Ft as createPluginInputRegistry,Mt as definePlugin,cn as detectPluginExecutableSurface,yt as discoverPlugins,jt as dispatch,kt as evaluateContextKey,At as evaluateContextKeys,ln as evaluatePluginTrust,V as evaluateWhen,H as expandPath,wt as filterEngineCompatible,Y as grantCapabilitiesWithVersion,an as grantPluginCapabilities,on as grantPluginCapabilitiesWithVersion,Qt as grantedCapabilitiesForVersion,nn as grantedPluginCapabilities,sn as grantedPluginCapabilitiesForVersion,L as isEngineCompatible,qt as isPathTrusted,$t as isPluginTrusted,Et as mergeContributions,N as parsePluginManifest,Z as pluginTrustStorePath,rn as readHighRiskCapabilities,Ot as resolveActions,_t as resolveActivationEvents,gt as resolveManifestPlane,_ as sigilOf,bt as topoSortPlugins,Jt as trustPath,en as trustPlugin,Yt as untrustPath,tn as untrustPlugin};
3
+ `);return s.length>r?`${s.slice(0,r-1)}…`:s}catch{return``}finally{if(i!==void 0)try{o(i)}catch{}}}function $t(e){let{cwd:t,max:n=20,walkMax:r=5e3,ttlMs:i=1e4,now:a=Date.now}=e,o=null,s=0,c=null,l=new Map;async function u(){return o&&a()-s<i?o:c||(c=Xt(t,r,e=>{Jt.warn({cwd:t,err:String(e)},`@file provider workspace walk failed at root — @ panel will show no files`)}).then(e=>(o=e,s=a(),c=null,l=new Map,e)),c)}function d(e){let n=l.get(e);if(n!==void 0)return n;let r=Qt(t,e,20,500);return l.set(e,r),r}return async e=>{let t=await u(),r=e.toLowerCase(),i=[];for(let e of t){let t=Zt(e,r);if(!(t<0)&&(i.push({path:e,score:t}),r===``&&i.length>=n))break}return i.sort((e,t)=>t.score-e.score||e.path.length-t.path.length),i.slice(0,n).map(({path:e})=>({label:e,value:`[@file:${e.replace(/]/g,`\\]`)}]`,kind:`file`,source:`file`,preview:d(e)}))}}const en=r(`@x-otto/plugin:trust-store`),tn={"ui.renderer":`tui.renderer`};function nn(e){return[...new Set(e.map(e=>tn[e]??e))]}const K=2e3;function q(e,t,r){let o=ie(e),s=m(e),c=n(o,s,{timeoutMs:K});if(c||a(o,s,{timeoutMs:K})&&(c=n(o,s,{timeoutMs:K})),!c&&r?.requireLock)throw Error(`Failed to acquire trust store lock within ${K}ms; revocation aborted to avoid silently losing it to a concurrent writer`);try{return t()}finally{c&&i(o,s)}}function J(e){try{let t=JSON.parse(d(e,`utf-8`)),n=t?.trusted,r={},i=t?.capabilities;if(i&&typeof i==`object`)for(let[e,t]of Object.entries(i))Array.isArray(t)&&(r[e]=nn(t.filter(e=>typeof e==`string`)));let a={},o=t?.versions;if(o&&typeof o==`object`)for(let[e,t]of Object.entries(o))typeof t==`string`&&(a[g(e)]=t);return{trusted:Array.isArray(n)?n.filter(e=>typeof e==`string`):[],capabilities:r,versions:a}}catch{return{trusted:[],capabilities:{},versions:{}}}}function rn(e,t){let n=`${e}.${he()}.tmp`,r;try{r=u(n,`w`),re(r,t),c(r),o(r),r=void 0,ee(n,e)}catch(e){if(r!==void 0)try{o(r)}catch{}try{ne(n)}catch{}throw e}}function Y(e,t,n){if(!ce())try{l(ie(e),{recursive:!0});let n={trusted:[...new Set(t.trusted)]};t.capabilities&&Object.keys(t.capabilities).length>0&&(n.capabilities=t.capabilities),t.versions&&Object.keys(t.versions).length>0&&(n.versions=t.versions),rn(e,JSON.stringify(n,null,2))}catch(t){throw en.warn({storePath:e,err:t},`Failed to persist ${n} store`),t}}function an(e,t){return J(e).trusted.includes(g(t))}function on(e,t,n){return q(e,()=>{let r=g(t),i=J(e);return i.trusted.includes(r)?!1:(i.trusted.push(r),Y(e,i,n),!0)})}function X(e,t,n){return q(e,()=>{let r=g(t),i=J(e),a=i.trusted.filter(e=>e!==r),o={...i.capabilities},s=r in o;delete o[r];let c={...i.versions},l=r in c;return delete c[r],a.length===i.trusted.length&&!s&&!l?!1:(Y(e,{trusted:a,capabilities:o,versions:c},n),!0)},{requireLock:!0})}function sn(e,t){return J(e).capabilities?.[g(t)]??[]}function cn(e,t,n,r){return q(e,()=>{let i=g(t),a=J(e),o=new Set(a.capabilities?.[i]??[]),s=o.size;for(let e of n)o.add(e);return o.size===s?!1:(Y(e,{trusted:a.trusted,capabilities:{...a.capabilities,[i]:[...o]}},r),!0)})}function ln(e,t,n,r,i){return q(e,()=>{let a=g(t),o=J(e),s=new Set(o.capabilities?.[a]??[]),c=s.size;for(let e of n)s.add(e);let l=o.versions?.[a]!==r;return s.size===c&&!l?!1:(Y(e,{trusted:o.trusted,capabilities:{...o.capabilities,[a]:[...s]},versions:{...o.versions,[a]:r}},i),!0)})}function un(e,t,n){let r=g(t),i=J(e);return i.versions?.[r]===n?i.capabilities?.[r]??[]:[]}const Z=r(`@x-otto/plugin:trust`);function Q(){return process.env.OTTO_PLUGIN_TRUST_PATH||h(oe,`plugin-trust.json`)}function dn(e,t=Q()){return an(t,e)}function fn(e,t=Q()){on(t,e,`plugin-trust`)&&Z.info({dir:g(e)},`Plugin trusted`)}function pn(e,t=Q()){X(t,e,`plugin-trust`)&&Z.info({dir:g(e)},`Plugin trust revoked`)}function mn(e,t=Q()){return sn(t,e)}function hn(e){try{let t=h(e,x);if(!s(t))return[];let n=N(d(t,`utf8`),t);return n?.capabilities?n.capabilities.filter(e=>y.includes(e)):[]}catch{return[]}}function gn(e,t,n=Q()){cn(n,e,t,`plugin-trust`)&&Z.info({dir:g(e),caps:t},`Plugin capabilities granted`)}function _n(e,t,n,r=Q()){ln(r,e,t,n,`plugin-trust`)&&Z.info({dir:g(e),caps:t,version:n},`Plugin capabilities granted (version-bound)`)}function vn(e,t,n=Q()){return un(n,e,t)}function $(e){try{return p(e).length>0}catch{return!1}}function yn(e,t){let n=g(e),r=s(h(n,`.mcp.json`)),i=$(h(n,`commands`)),a=!!(t?.scripts?.postinstall||t?.scripts?.setup),o=!!(t?.contributes?.panels&&t.contributes.panels.length>0),c=[`plugin.ts`,`plugin.tsx`].some(e=>s(h(n,e))),l=$(h(n,`skills`)),u=$(h(n,`agents`)),d=!!t?.contributes?.cli,f=!!t?.capabilities?.some(e=>y.includes(e));return{mcpJson:r,commands:i,scripts:a,panels:o,pluginEntry:c,skills:l,agents:u,cli:d,highRiskCapabilities:f,any:r||i||a||o||c||l||u||d||f}}function bn(e,t=Q()){let n=yn(e.dir,e.manifest),r=dn(e.dir,t),i=(e.scope===`project`||e.scope===`repository`)&&n.any&&!r,a=new Set(e.manifest?.capabilities??[]),o=e.scope===`bundled`||e.scope===`builtin`?a:new Set(sn(t,e.dir));return{surface:n,trusted:r,gated:i,pendingCapabilities:y.filter(e=>a.has(e)&&!o.has(e)),grantedCapabilities:y.filter(e=>a.has(e)&&o.has(e))}}const xn=[`apikey`,`secret`,`token`,`password`,`credential`,`privatekey`,`accesskey`];function Sn(e){let t=e.toLowerCase();return xn.some(e=>t.includes(e))}function Cn(e){let t={};for(let[n,r]of Object.entries(e))Sn(n)?t[n]=`[REDACTED]`:r&&typeof r==`object`&&!Array.isArray(r)?t[n]=Cn(r):t[n]=r;return t}function wn(e,t){let n={...t},r=e?.properties;if(r&&typeof r==`object`)for(let[e,t]of Object.entries(r))!(e in n)&&t&&typeof t==`object`&&`default`in t&&(n[e]=t.default);return n}function Tn(e){return e.split(/[-_]/)[0]??e}var En=class extends t{entries=new Map;currentLocale=`zh`;register(e){this.entries.clear();for(let t of e)this.entries.set(`${t.pluginId}:${t.id}`,t.translations)}get size(){return this.entries.size}getLocale(){return this.currentLocale}setLocale(e){e!==this.currentLocale&&(this.currentLocale=e,this.emit(`change`,e))}t(e,t=this.currentLocale){let n=this.entries.get(e);if(!n)return e;let r=n[t];if(r!==void 0)return r;let i=Tn(t);for(let[e,t]of Object.entries(n))if(Tn(e)===i)return t;return n.en===void 0?Object.values(n)[0]??e:n.en}};export{z as EXTENSION_POINTS,y as HIGH_RISK_CAPABILITIES,S as ID_RE,$e as MAX_CONFIG_FIELDS_PER_PLUGIN,nt as MAX_CONFIG_FIELD_DESCRIPTION_CHARS,tt as MAX_CONFIG_SCHEMA_BYTES,et as MAX_CONFIG_SCHEMA_DEPTH,Ye as MAX_I18N_ENTRIES_PER_PLUGIN,Qe as MAX_I18N_FILE_BYTES,Ze as MAX_I18N_LOCALE_FILES_PER_PLUGIN,Xe as MAX_I18N_TRANSLATION_BYTES,Je as MAX_THEME_PRESETS_PER_PLUGIN,Pt as PLUGIN_API_VERSION,v as PLUGIN_CAPABILITIES,x as PLUGIN_MANIFEST_FILENAME,zt as POINT,En as PluginI18nRegistry,B as SHELL_POINTS,qt as createBuiltinHashtags,$t as createFileProvider,Kt as createPluginInputRegistry,Ut as definePlugin,yn as detectPluginExecutableSurface,jt as discoverPlugins,Ht as dispatch,U as evaluateContextKey,Vt as evaluateContextKeys,bn as evaluatePluginTrust,V as evaluateWhen,H as expandPath,It as filterEngineCompatible,ln as grantCapabilitiesWithVersion,gn as grantPluginCapabilities,_n as grantPluginCapabilitiesWithVersion,un as grantedCapabilitiesForVersion,mn as grantedPluginCapabilities,vn as grantedPluginCapabilitiesForVersion,L as isEngineCompatible,an as isPathTrusted,dn as isPluginTrusted,Sn as isSensitiveField,wn as mergeConfigWithDefaults,Rt as mergeContributions,N as parsePluginManifest,Q as pluginTrustStorePath,hn as readHighRiskCapabilities,Cn as redactSensitiveFields,Bt as resolveActions,kt as resolveActivationEvents,Ot as resolveManifestPlane,pe as sigilOf,Mt as topoSortPlugins,on as trustPath,fn as trustPlugin,X as untrustPath,pn as untrustPlugin};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@x-otto/plugin",
3
- "version": "0.1.0-alpha.6",
3
+ "version": "0.1.0-alpha.7",
4
4
  "files": [
5
5
  "dist",
6
6
  "README.md"
@@ -22,11 +22,11 @@
22
22
  "dependencies": {
23
23
  "semver": "7.7.4",
24
24
  "zod": "4.3.6",
25
- "@x-otto/env": "0.1.0-alpha.6",
26
- "@x-otto/provider": "0.1.0-alpha.6",
27
- "@x-otto/interchange": "0.1.0-alpha.5",
28
- "@x-otto/shared": "0.1.0-alpha.6",
29
- "@x-otto/hook-contracts": "0.0.1-alpha.3"
25
+ "@x-otto/hook-contracts": "0.0.1-alpha.3",
26
+ "@x-otto/provider": "0.1.0-alpha.7",
27
+ "@x-otto/env": "0.1.0-alpha.7",
28
+ "@x-otto/interchange": "0.1.0-alpha.7",
29
+ "@x-otto/shared": "0.1.0-alpha.7"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/semver": "7.7.1"