@x-otto/plugin 0.1.0-alpha.5 → 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 +15 -5
- package/dist/index.d.ts +685 -19
- package/dist/index.js +2 -2
- package/package.json +6 -6
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
|
```
|
|
@@ -53,6 +58,8 @@ src/
|
|
|
53
58
|
discovery.ts # discoverPlugins three-tier directory scanning
|
|
54
59
|
contributions.ts # PluginContributions unified contribution vocabulary type
|
|
55
60
|
contribution-points.ts # contribution point registry (POINT constants)
|
|
61
|
+
points-extension.ts # per-domain contribution point constants
|
|
62
|
+
points-shell.ts # per-domain contribution point constants
|
|
56
63
|
contribution-resolver.ts # contribution point resolver
|
|
57
64
|
context-keys.ts # context key evaluation
|
|
58
65
|
contribution-dispatch.ts # contribution point dispatcher
|
|
@@ -60,7 +67,8 @@ src/
|
|
|
60
67
|
dependency-graph.ts # topoSortPlugins topological sort
|
|
61
68
|
engine-compat.ts # engine version compatibility gate
|
|
62
69
|
api-version.ts # PLUGIN_API_VERSION constant
|
|
63
|
-
|
|
70
|
+
capabilities.ts # PLUGIN_CAPABILITIES + HIGH_RISK_CAPABILITIES risk classification (single source)
|
|
71
|
+
plugin-trust.ts # trust model (executable surface scan + per-capability grant)
|
|
64
72
|
trust-store.ts # trust allowlist persistence
|
|
65
73
|
i18n-registry.ts # internationalization entry registry
|
|
66
74
|
input/ # sigil input system (builtin/file/registry)
|
|
@@ -78,13 +86,15 @@ tests/ # test files
|
|
|
78
86
|
|
|
79
87
|
## Trust Model
|
|
80
88
|
|
|
81
|
-
High-risk capability list (`HIGH_RISK_CAPABILITIES
|
|
82
|
-
|
|
83
|
-
|
|
89
|
+
High-risk capability list (`HIGH_RISK_CAPABILITIES`, derived in `capabilities.ts` from the per-capability risk
|
|
90
|
+
classification): provider / tools / tui.renderer / network / wire-protocol / a2ui.component / panel.backend /
|
|
91
|
+
mcp.server / input.resolver / a2ui.renderer / agent.dispatch / service / session.read / llm.complete / user.ask.
|
|
92
|
+
When an already-trusted plugin is upgraded and declares one of these capabilities for the first time, it must be
|
|
93
|
+
re-confirmed.
|
|
84
94
|
|
|
85
95
|
## Dependencies
|
|
86
96
|
|
|
87
|
-
- Internal: `@x-otto/env`, `@x-otto/interchange`, `@x-otto/provider`, `@x-otto/shared`
|
|
97
|
+
- Internal: `@x-otto/env`, `@x-otto/hook-contracts`, `@x-otto/interchange`, `@x-otto/provider`, `@x-otto/shared`
|
|
88
98
|
- External: `zod`, `semver`
|
|
89
99
|
|
|
90
100
|
## Related
|
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
|
/**
|
|
@@ -1448,6 +1594,10 @@ declare const scriptsSchema: z.ZodObject<{
|
|
|
1448
1594
|
}, z.core.$catchall<z.ZodString>>;
|
|
1449
1595
|
type PluginScripts = z.infer<typeof scriptsSchema>;
|
|
1450
1596
|
declare const manifestSchema: z.ZodObject<{
|
|
1597
|
+
plane: z.ZodCatch<z.ZodOptional<z.ZodEnum<{
|
|
1598
|
+
host: "host";
|
|
1599
|
+
preset: "preset";
|
|
1600
|
+
}>>>;
|
|
1451
1601
|
id: z.ZodString;
|
|
1452
1602
|
name: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
1453
1603
|
version: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
@@ -1464,6 +1614,26 @@ declare const manifestSchema: z.ZodObject<{
|
|
|
1464
1614
|
name: z.ZodString;
|
|
1465
1615
|
bin: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
1466
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>>>>;
|
|
1467
1637
|
panels: z.ZodCatch<z.ZodOptional<z.ZodType<{
|
|
1468
1638
|
id: string;
|
|
1469
1639
|
title: string;
|
|
@@ -1965,12 +2135,54 @@ declare const manifestSchema: z.ZodObject<{
|
|
|
1965
2135
|
entry: string;
|
|
1966
2136
|
export?: string | undefined;
|
|
1967
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>>>>;
|
|
1968
2180
|
}, z.core.$strip>>>;
|
|
1969
2181
|
scripts: z.ZodCatch<z.ZodOptional<z.ZodObject<{
|
|
1970
2182
|
postinstall: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
1971
2183
|
setup: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
1972
2184
|
}, z.core.$catchall<z.ZodString>>>>;
|
|
1973
|
-
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[]>>>>;
|
|
1974
2186
|
activationEvents: z.ZodCatch<z.ZodOptional<z.ZodPipe<z.ZodArray<z.ZodUnknown>, z.ZodTransform<string[] | undefined, unknown[]>>>>;
|
|
1975
2187
|
dependsOn: z.ZodCatch<z.ZodOptional<z.ZodPipe<z.ZodArray<z.ZodUnknown>, z.ZodTransform<string[] | undefined, unknown[]>>>>;
|
|
1976
2188
|
codeProviderIds: z.ZodCatch<z.ZodOptional<z.ZodPipe<z.ZodArray<z.ZodUnknown>, z.ZodTransform<string[] | undefined, unknown[]>>>>;
|
|
@@ -2012,6 +2224,13 @@ declare const manifestSchema: z.ZodObject<{
|
|
|
2012
2224
|
}> | undefined, Record<string, unknown>>>>>;
|
|
2013
2225
|
}, z.core.$strip>;
|
|
2014
2226
|
type PluginManifest = z.infer<typeof manifestSchema>;
|
|
2227
|
+
/** RFC-327 修订 D2(M2):manifest 显式 plane 声明('host' | 'preset')。 */
|
|
2228
|
+
type PluginManifestPlane = 'host' | 'preset';
|
|
2229
|
+
/**
|
|
2230
|
+
* RFC-327 修订 D2(M2):解析 manifest 的 plane 声明。缺省 = `'preset'`
|
|
2231
|
+
* (存量插件零行为变化——绝大多数插件贡献是会话/workspace 级,host 是刻意声明)。
|
|
2232
|
+
*/
|
|
2233
|
+
declare function resolveManifestPlane(manifest: PluginManifest): PluginManifestPlane;
|
|
2015
2234
|
/** 有效激活事件:字段缺失或空 → `['onStartup']`(存量插件零行为变化,RFC-105 D7)。 */
|
|
2016
2235
|
declare function resolveActivationEvents(manifest: PluginManifest): string[];
|
|
2017
2236
|
/**
|
|
@@ -2222,6 +2441,13 @@ interface PluginContributions {
|
|
|
2222
2441
|
* 贡献——凡是"需要先加载 plugin.ts 代码才能算出"的注入内容,视为独立后续项(YAGNI)。
|
|
2223
2442
|
*/
|
|
2224
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[];
|
|
2225
2451
|
}
|
|
2226
2452
|
/**
|
|
2227
2453
|
* 合并多源贡献为单一 `PluginContributions`(RFC-082 §4.2 装载流,R7;RFC-157 M1 轴表化)。
|
|
@@ -2324,24 +2550,32 @@ declare function dispatch(cmd: CommandRef, caps: DispatchCaps): Promise<void>;
|
|
|
2324
2550
|
* 安全(RFC-082 R10):`plugin.ts` 在 otto 主进程内、装载时执行(非沙箱,全 Node 权限),
|
|
2325
2551
|
* 由信任门门控*是否*执行;hooks(尤其可 deny/block 工具的 preToolUse)默认关 + 显式启用。
|
|
2326
2552
|
*/
|
|
2327
|
-
/**
|
|
2553
|
+
/**
|
|
2554
|
+
* 工具调用上下文(hooks 观测/拦截用)。
|
|
2555
|
+
*
|
|
2556
|
+
* `paths`/`readonly`/`sessionId` 是只读上下文(从 `ToolExecuteBeforeInput` 透传),
|
|
2557
|
+
* 让插件做路径级权限策略(如 browser-use 的站点级规则)。三字段可选——
|
|
2558
|
+
* 旧宿主不注入时 undefined,插件容错(`?? fallback`)。只读透传不破坏 R10
|
|
2559
|
+
* (插件只能 deny/ask 不能 allow)。
|
|
2560
|
+
*/
|
|
2328
2561
|
interface ToolUseContext {
|
|
2329
2562
|
toolName: string;
|
|
2330
2563
|
input: unknown;
|
|
2564
|
+
/** 工具声明的 AgentTool.pathParams 从 args 解析出的文件系统路径(只读)。 */
|
|
2565
|
+
paths?: readonly string[];
|
|
2566
|
+
/** 工具是否为只读(AgentTool.readonly,只读)。 */
|
|
2567
|
+
readonly?: boolean;
|
|
2568
|
+
/** 会话 ID(只读)。 */
|
|
2569
|
+
sessionId?: string;
|
|
2331
2570
|
}
|
|
2332
2571
|
/**
|
|
2333
2572
|
* PreToolUse 决策(拦截器返回)。`deny` 拒绝;`ask` 转人工确认。
|
|
2334
2573
|
*
|
|
2335
|
-
*
|
|
2336
|
-
*
|
|
2337
|
-
*
|
|
2338
|
-
* 返回值等价于放行到既有 hook 链判定)。其存在只为了避免存量插件类型迁移破坏,
|
|
2339
|
-
* 新增代码请勿依赖它——capabilities.ts 的 `hooks: false`(非高危)前提之一
|
|
2340
|
-
* 正是「allow 恒 no-op」(架构 review 2026-08-12 S5)。
|
|
2574
|
+
* `void` = 无意见/放行到既有 hook 链判定(等价于 `allow`——不干预,交由既有权限链判定)。
|
|
2575
|
+
* 插件不能授权,只能限制(R10:allow 恒 no-op,故不设 allow 变体——
|
|
2576
|
+
* 返回 void 即放行,deny/ask 之外无需第三个值)。
|
|
2341
2577
|
*/
|
|
2342
2578
|
type PreToolUseDecision = {
|
|
2343
|
-
decision: 'allow';
|
|
2344
|
-
} | {
|
|
2345
2579
|
decision: 'deny';
|
|
2346
2580
|
reason?: string;
|
|
2347
2581
|
} | {
|
|
@@ -2429,6 +2663,13 @@ interface PluginHooks {
|
|
|
2429
2663
|
taskId: string;
|
|
2430
2664
|
error: string;
|
|
2431
2665
|
}) => void | Promise<void>;
|
|
2666
|
+
/**
|
|
2667
|
+
* 任务被取消时触发(`task.cancelled`,08-13 终局 review 观察项:纯 observer,
|
|
2668
|
+
* 对自迭代感知"子任务被取消"有真实价值——被取消的任务不该重复派发)。
|
|
2669
|
+
*/
|
|
2670
|
+
taskCancelled?: (ctx: {
|
|
2671
|
+
taskId: string;
|
|
2672
|
+
}) => void | Promise<void>;
|
|
2432
2673
|
/**
|
|
2433
2674
|
* 任务(子 agent task)建档时触发(`task.created`),先于 `task.started` 触发。
|
|
2434
2675
|
* 此时 agent 归属尚未确定(`TaskHookInfo` 无 subagent 字段),仅 taskId 可用。
|
|
@@ -2441,6 +2682,18 @@ interface PluginHooks {
|
|
|
2441
2682
|
taskId: string;
|
|
2442
2683
|
agent: string;
|
|
2443
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>;
|
|
2444
2697
|
/**
|
|
2445
2698
|
* RFC-327 修订 D1(M1):waterfall 贡献面——插件经受控白名单 timing 改写主循环中间产物。
|
|
2446
2699
|
*
|
|
@@ -2450,7 +2703,9 @@ interface PluginHooks {
|
|
|
2450
2703
|
*
|
|
2451
2704
|
* 安全边界(R1'/R10):
|
|
2452
2705
|
* - 只作用于白名单 timing 的**受控中间产物**(`system.prompt.transform` /
|
|
2453
|
-
* `chat.params` / `messages.transform
|
|
2706
|
+
* `chat.params` / `messages.transform`)——不开放原始会话存储与工具执行参数
|
|
2707
|
+
* (messages.transform 会看到待发送消息列表,含历史派生内容——白名单设计内的
|
|
2708
|
+
* 能力,全程审计);
|
|
2454
2709
|
* - 白名单编译期强制(`WaterfallTiming` 从 `HookPayloadMap` Pick),装载器运行时校验兜底,
|
|
2455
2710
|
* 白名单外 timing 一律拒绝注册(fail-closed);
|
|
2456
2711
|
* - `system.prompt.transform` 默认只开放 `systemTail`(volatile,cache 断点后,逐轮可变)
|
|
@@ -2463,6 +2718,14 @@ interface PluginHooks {
|
|
|
2463
2718
|
* 本字段是新贡献面(非 observer 改 interceptor),配套 D1a 分权与安全门见 RFC-327 修订 §3。
|
|
2464
2719
|
*/
|
|
2465
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>;
|
|
2466
2729
|
}
|
|
2467
2730
|
/**
|
|
2468
2731
|
* RFC-327 修订 D1(M1):waterfall handler 逐 timing 签名——input/output 与引擎
|
|
@@ -2471,6 +2734,20 @@ interface PluginHooks {
|
|
|
2471
2734
|
type PluginWaterfallHandler<T extends WaterfallTiming> = (input: HookPayloadMap[T]['input'], output: HookPayloadMap[T]['output']) => void | HookAbortSignal | Promise<void | HookAbortSignal>;
|
|
2472
2735
|
/** RFC-327 修订 D1(M1):waterfall 贡献面的逐 timing 映射(mapped type 保持 per-key 类型精确)。 */
|
|
2473
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'>;
|
|
2474
2751
|
/** 装载时注入工厂的上下文。 */
|
|
2475
2752
|
interface PluginContext {
|
|
2476
2753
|
/** 插件 id(= manifest.id = source 标签)。 */
|
|
@@ -2509,9 +2786,18 @@ interface PluginContext {
|
|
|
2509
2786
|
* 未注入 `wireProtocolRegistry` 时(如轻量测试装配)恒返回 `undefined`。
|
|
2510
2787
|
*/
|
|
2511
2788
|
getWireProtocol: (wireApi: string) => _$_x_otto_provider0.WireProtocolRegistration | undefined;
|
|
2789
|
+
/**
|
|
2790
|
+
* 解析该插件自身 provider 的裸凭据 key(RFC-148 M3,`resolveProviderAuth` 的姊妹方法):
|
|
2791
|
+
* 多数代码式 provider 工厂(如 GitHub Copilot 自定义头场景)只需要裸 key,不需要
|
|
2792
|
+
* `mode`/`beta` 等完整凭据上下文。跨插件隔离:仅放行该插件 manifest 声明的
|
|
2793
|
+
* `codeProviderIds` 白名单或自身 `<pluginId>:` 前缀命名空间,其余一律返回 `null`
|
|
2794
|
+
* (不触达 AuthStore,见 `plugin-module-manager.ts` 装载器实现)。
|
|
2795
|
+
* 未注入 `providerRegistry` 或 api 不属于本插件时恒返回 `null`。
|
|
2796
|
+
*/
|
|
2797
|
+
resolveProviderCredential: (api: string) => Promise<string | null>;
|
|
2512
2798
|
/**
|
|
2513
2799
|
* 解析该插件自身 provider 的完整凭据(RFC-148 M3,`resolveProviderCredential` 的姊妹方法):
|
|
2514
|
-
*
|
|
2800
|
+
* 前者只给裸 key(多数代码式 provider 够用,如 GitHub Copilot),本方法额外给 `mode`/`beta`
|
|
2515
2801
|
* ——`anthropic-messages` 协议实现需要区分 api_key 与 oauth 模式(决定是否启用 Claude Code
|
|
2516
2802
|
* 客户端伪装)。跨插件隔离规则与 `resolveProviderCredential` 一致(同一 owned-namespace
|
|
2517
2803
|
* 白名单校验,装载器实现层面共享同一份判定逻辑,见 `plugin-module-manager.ts`)。
|
|
@@ -2531,6 +2817,44 @@ interface PluginContext {
|
|
|
2531
2817
|
* 可选(additive)——旧宿主不注入时插件侧应容忍 undefined。
|
|
2532
2818
|
*/
|
|
2533
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;
|
|
2534
2858
|
}
|
|
2535
2859
|
/**
|
|
2536
2860
|
* provider 工厂(RFC-105 D3,代码式 provider 轴)。
|
|
@@ -2625,6 +2949,48 @@ interface PluginTool {
|
|
|
2625
2949
|
* host/signal);存量单参工具不传第二参数即忽略,天然兼容。
|
|
2626
2950
|
*/
|
|
2627
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;
|
|
2628
2994
|
}
|
|
2629
2995
|
/**
|
|
2630
2996
|
* Monitor 投影事件(RFC-129 D6):与 `PluginHooks` 平行的独立概念——"被动订阅事件流做旁路统计"
|
|
@@ -2645,7 +3011,7 @@ interface PluginTool {
|
|
|
2645
3011
|
type MonitorEvent = {
|
|
2646
3012
|
type: 'turn.completed';
|
|
2647
3013
|
sessionId: string;
|
|
2648
|
-
|
|
3014
|
+
tokenUsage: {
|
|
2649
3015
|
input: number;
|
|
2650
3016
|
output: number;
|
|
2651
3017
|
};
|
|
@@ -2691,7 +3057,32 @@ type MonitorEvent = {
|
|
|
2691
3057
|
type: 'tool.unregistered';
|
|
2692
3058
|
toolName: string;
|
|
2693
3059
|
source?: string;
|
|
3060
|
+
}
|
|
3061
|
+
/**
|
|
3062
|
+
* 插件装载失败(自迭代闭环负反馈面,08-13 终局 review B1):模型生成/用户安装的
|
|
3063
|
+
* 插件在装载链路任一环节失败时派发。与 `pluginHealthTracker`(人看面板)不同,
|
|
3064
|
+
* 本事件进 monitor 事件面——运行中的插件(如 skill-inductor)与模型可感知
|
|
3065
|
+
* "我生成的插件坏了",是自迭代"失败可学习"的结构化通道。observer-only。
|
|
3066
|
+
*/
|
|
3067
|
+
| {
|
|
3068
|
+
type: 'plugin.load-failed';
|
|
3069
|
+
pluginId: string;
|
|
3070
|
+
reason: PluginLoadFailureReason;
|
|
3071
|
+
message: string;
|
|
3072
|
+
}
|
|
3073
|
+
/**
|
|
3074
|
+
* 能力缺口上报(自迭代闭环认知输入面,08-13 终局 review B2):capability_gap 工具
|
|
3075
|
+
* 每次上报经宿主桥接派发。缺口记录本身是跨会话聚合 store(CapabilityGapReportStore),
|
|
3076
|
+
* 本事件是同一事实的事件面投影——运行中的 service 插件无需轮询即可感知"本工作区
|
|
3077
|
+
* 出现了新能力缺口"并主动响应。observer-only。
|
|
3078
|
+
*/
|
|
3079
|
+
| {
|
|
3080
|
+
type: 'capability_gap.reported';
|
|
3081
|
+
dedupKey: string;
|
|
3082
|
+
searchTerms: string[];
|
|
2694
3083
|
};
|
|
3084
|
+
/** 插件装载失败原因分类(`plugin.load-failed` 事件 reason 字段)。 */
|
|
3085
|
+
type PluginLoadFailureReason = /** 依赖缺失/环(topology 层剔除,RFC-132 D5)。 */'dependency-missing' /** 依赖环(topology 层剔除,RFC-132 D5)。 */ | 'dependency-cycle' /** 引擎版本门不兼容(RFC-148 M0)。 */ | 'version-incompatible' /** plugin.ts 编译/加载/工厂执行失败(module-loader 层)。 */ | 'module-error' /** project/repository 插件未通过布尔信任门(RFC-124 C1,08-13 终局复核 B1 扩展)。 */ | 'not-trusted' /** manifest 声明高危能力但未显式授予(pendingCapabilities 门,RFC-140 D3,08-13 终局复核 B1 扩展)。 */ | 'capability-not-granted' /** 声明式 provider/oauth 轴装载拒绝(能力未声明/id 冲突/保留 id 等,08-13 终局复核 B1 扩展)。 */ | 'provider-rejected';
|
|
2695
3086
|
/**
|
|
2696
3087
|
* 插件后台观察者(RFC-129 D6,capability: `monitor`)。`onEvent` 抛错被装载器 100% 隔离
|
|
2697
3088
|
* (try/catch 吞掉 + 记录日志,绝不 rethrow、绝不阻塞引擎主流程或影响其他插件/hook,R5)——
|
|
@@ -2741,6 +3132,46 @@ type FeedbackIssueResult = {
|
|
|
2741
3132
|
kind: string;
|
|
2742
3133
|
detail: string;
|
|
2743
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
|
+
}
|
|
2744
3175
|
/**
|
|
2745
3176
|
* Feedback 能力契约(RFC-209 §2.1,capability: `feedback`)——业务逻辑/数据契约层,
|
|
2746
3177
|
* 不含任何 TUI/web-ui 渲染代码。核心包(coding/cli/tui/service)只通过 `FeedbackRegistry`
|
|
@@ -2764,6 +3195,32 @@ interface FeedbackProvider {
|
|
|
2764
3195
|
* 提供 `feedbackProviders` 的插件必须实现本方法,即使不实现 `submitIssueViaGh`)。
|
|
2765
3196
|
*/
|
|
2766
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;
|
|
2767
3224
|
}
|
|
2768
3225
|
/**
|
|
2769
3226
|
* 工厂返回的插件模块(装载器拆解的对象)。
|
|
@@ -2844,11 +3301,128 @@ interface PluginModule {
|
|
|
2844
3301
|
* `onActivate` 里起 setInterval——那会被既有池的超时/回收语义打断。
|
|
2845
3302
|
*/
|
|
2846
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>;
|
|
2847
3333
|
/** 激活钩子:装载完成、贡献注册后调用。 */
|
|
2848
3334
|
onActivate?: () => void | Promise<void>;
|
|
2849
3335
|
/** 卸载/禁用钩子。 */
|
|
2850
3336
|
onDeactivate?: () => void | Promise<void>;
|
|
2851
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;
|
|
2852
3426
|
/**
|
|
2853
3427
|
* RFC-287 D2:宿主注入给插件的**受控能力面**(capability-gated host capabilities)。
|
|
2854
3428
|
*
|
|
@@ -2962,6 +3536,18 @@ interface PluginHostCapabilities {
|
|
|
2962
3536
|
* `plugin:<pluginId>`,插件不能伪装成 agent/user/其他插件。
|
|
2963
3537
|
*/
|
|
2964
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;
|
|
2965
3551
|
}
|
|
2966
3552
|
/**
|
|
2967
3553
|
* RFC-318 D2(M1):`readTranscript` 的投影消息——**不是** `@x-otto/ai` 的 `Message`。
|
|
@@ -3073,6 +3659,19 @@ interface PluginServiceContext {
|
|
|
3073
3659
|
* (走既有退避重启),**不静默吞掉**。未注册 handler 时通知被丢弃。
|
|
3074
3660
|
*/
|
|
3075
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;
|
|
3076
3675
|
}
|
|
3077
3676
|
/**
|
|
3078
3677
|
* RFC-303 M5:插件 scoped persistence 的契约 shape(`@x-otto/plugin` 契约层不下沉实现——
|
|
@@ -3157,10 +3756,10 @@ type PluginFactory = (ctx: PluginContext) => PluginModule | Promise<PluginModule
|
|
|
3157
3756
|
declare function definePlugin(factory: PluginFactory): PluginFactory;
|
|
3158
3757
|
//#endregion
|
|
3159
3758
|
//#region src/input/registry.d.ts
|
|
3160
|
-
declare function createPluginInputRegistry(): PluginInputRegistry;
|
|
3759
|
+
declare function createPluginInputRegistry(): PluginInputRegistry$1;
|
|
3161
3760
|
//#endregion
|
|
3162
3761
|
//#region src/input/builtin.d.ts
|
|
3163
|
-
declare function createBuiltinHashtags(): SigilEntry[];
|
|
3762
|
+
declare function createBuiltinHashtags(): SigilEntry$1[];
|
|
3164
3763
|
//#endregion
|
|
3165
3764
|
//#region src/input/file-provider.d.ts
|
|
3166
3765
|
interface FileProviderOptions {
|
|
@@ -3178,7 +3777,7 @@ interface FileProviderOptions {
|
|
|
3178
3777
|
* 创建 @file 供给器。walk 结果按 TTL 缓存,query 在缓存上过滤——
|
|
3179
3778
|
* 每次按键 O(n) 过滤而非重 walk 文件系统。
|
|
3180
3779
|
*/
|
|
3181
|
-
declare function createFileProvider(opts: FileProviderOptions): SigilProvider;
|
|
3780
|
+
declare function createFileProvider(opts: FileProviderOptions): SigilProvider$1;
|
|
3182
3781
|
//#endregion
|
|
3183
3782
|
//#region src/trust-store.d.ts
|
|
3184
3783
|
/** key(绝对路径)是否在白名单中。 */
|
|
@@ -3195,6 +3794,20 @@ declare function trustPath(storePath: string, key: string, label: string): boole
|
|
|
3195
3794
|
* 调用方(`extension-plugin.ts` / `misc.ts` 的撤销入口)应捕获并如实告知用户。
|
|
3196
3795
|
*/
|
|
3197
3796
|
declare function untrustPath(storePath: string, key: string, label: string): boolean;
|
|
3797
|
+
/**
|
|
3798
|
+
* RFC-327 修订 R6'(M2):版本感知的授予——授予高危能力的同时记录授权时刻的插件
|
|
3799
|
+
* manifest 版本快照。语义同 `grantCapabilities`,额外写 `versions[key] = version`。
|
|
3800
|
+
*
|
|
3801
|
+
* 幂等语义:同 key 同版本重复调用 → 能力已含 + 版本已一致 → 返回 false(无变更)。
|
|
3802
|
+
* 版本变更后再次授予 → 覆盖版本快照并合并能力(重新评估的落点)。
|
|
3803
|
+
*/
|
|
3804
|
+
declare function grantCapabilitiesWithVersion(storePath: string, key: string, caps: readonly string[], version: string, label: string): boolean;
|
|
3805
|
+
/**
|
|
3806
|
+
* RFC-327 修订 R6'(M2):版本匹配的能力读取——仅当记录的授权版本 === 当前版本时
|
|
3807
|
+
* 返回已授予能力;版本不符(或旧文件无版本快照)返回 `[]`(fail-closed)——
|
|
3808
|
+
* 插件更新后需重新评估白名单 timing,trust 不跨版本自动存续。
|
|
3809
|
+
*/
|
|
3810
|
+
declare function grantedCapabilitiesForVersion(storePath: string, key: string, currentVersion: string): string[];
|
|
3198
3811
|
//#endregion
|
|
3199
3812
|
//#region src/plugin-trust.d.ts
|
|
3200
3813
|
/** 信任白名单落盘路径(用户级)。`OTTO_PLUGIN_TRUST_PATH` 可覆盖(测试隔离)。调用时求值。 */
|
|
@@ -3219,6 +3832,18 @@ declare function grantedPluginCapabilities(pluginDir: string, storePath?: string
|
|
|
3219
3832
|
declare function readHighRiskCapabilities(sourceDir: string): string[];
|
|
3220
3833
|
/** 显式授予插件一组高危能力(幂等,持久化;用户逐项/整体确认后调用)。 */
|
|
3221
3834
|
declare function grantPluginCapabilities(pluginDir: string, caps: readonly string[], storePath?: string): void;
|
|
3835
|
+
/**
|
|
3836
|
+
* RFC-327 修订 R6'(M2):版本感知的授予——同 grantPluginCapabilities,额外记录
|
|
3837
|
+
* 授权时刻的 manifest 版本快照。插件更新后调用
|
|
3838
|
+
* `grantedPluginCapabilitiesForVersion` 读取时版本不匹配 → 空集(fail-closed,
|
|
3839
|
+
* trust 不跨版本自动存续,需重新评估白名单)。
|
|
3840
|
+
*/
|
|
3841
|
+
declare function grantPluginCapabilitiesWithVersion(pluginDir: string, caps: readonly string[], version: string, storePath?: string): void;
|
|
3842
|
+
/**
|
|
3843
|
+
* RFC-327 修订 R6'(M2):版本匹配的能力读取——仅当授权版本 === 当前 manifest 版本时
|
|
3844
|
+
* 返回已授予能力;版本变更(或旧文件无版本快照)→ `[]`(需重新评估)。
|
|
3845
|
+
*/
|
|
3846
|
+
declare function grantedPluginCapabilitiesForVersion(pluginDir: string, version: string, storePath?: string): string[];
|
|
3222
3847
|
/** 插件目录受信任门面探测结果(可执行面 ① + 内容面 ② + 高危能力声明 ③)。 */
|
|
3223
3848
|
interface PluginExecutableSurface {
|
|
3224
3849
|
mcpJson: boolean;
|
|
@@ -3290,6 +3915,47 @@ interface PluginTrustVerdict {
|
|
|
3290
3915
|
*/
|
|
3291
3916
|
declare function evaluatePluginTrust(plugin: DiscoveredPlugin, storePath?: string): PluginTrustVerdict;
|
|
3292
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
|
|
3293
3959
|
//#region src/i18n-registry.d.ts
|
|
3294
3960
|
/** 单条 i18n 贡献条目(loader 产出,供 `register()` 消费)。 */
|
|
3295
3961
|
interface PluginI18nEntry {
|
|
@@ -3339,5 +4005,5 @@ declare class PluginI18nRegistry extends TypedEventEmitter<PluginI18nRegistryEve
|
|
|
3339
4005
|
t(fullKey: string, locale?: string): string;
|
|
3340
4006
|
}
|
|
3341
4007
|
//#endregion
|
|
3342
|
-
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 PluginManifest, 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, grantPluginCapabilities, grantedPluginCapabilities, isEngineCompatible, isPathTrusted, isPluginTrusted, mergeContributions, parsePluginManifest, pluginTrustStorePath, readHighRiskCapabilities, resolveActions, resolveActivationEvents, 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 };
|
|
3343
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 m,dirname as ie,join as h,relative as ae,resolve as g}from"node:path";import{OTTO_HOME as _,findWorkspaceRoot as oe,isShadowModeEnabled as se,resolveConfigLayers as ce}from"@x-otto/env";import{satisfies as le,validRange as ue}from"semver";import{homedir as v}from"node:os";import{sigilOf as y}from"@x-otto/interchange";import{readdir as de}from"node:fs/promises";import{randomUUID as fe}from"node:crypto";const pe=[`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`],b={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},x=Object.freeze(Object.keys(b).filter(e=>b[e])),S=r(`@x-otto/coding:plugin-manifest`),C=`otto-plugin.json`,w=/^[a-z0-9]+(?:-[a-z0-9]+)*$/,T=e=>e.optional().catch(void 0),E=e.string().refine(e=>e.trim().length>0,`must be non-empty`),me=e.object({id:E,title:E,entry:E.refine(e=>!e.startsWith(`/`)&&!e.split(/[\\/]/).includes(`..`),`unsafe path (absolute or traversal)`),export:T(E)}),he=e.object({role:T(E),toolName:T(E),contentType:T(E)}).refine(e=>e.role!=null||e.toolName!=null||e.contentType!=null,{message:`matcher must declare at least one of role/toolName/contentType`}),ge=e.object({id:E,matcher:he,entry:E.refine(e=>!e.startsWith(`/`)&&!e.split(/[\\/]/).includes(`..`),`unsafe path (absolute or traversal)`),export:T(E)}),_e=e.object({id:E,matcher:he,entry:E.refine(e=>!e.startsWith(`/`)&&!e.split(/[\\/]/).includes(`..`),`unsafe path (absolute or traversal)`),export:T(E)}),ve=e.object({extensions:T(e.array(E)),filenames:T(e.array(E)),glob:T(e.array(E))}).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`}),ye=D(e.object({id:E,label:E,matcher:ve,entry:E.refine(e=>!e.startsWith(`/`)&&!e.split(/[\\/]/).includes(`..`),`unsafe path (absolute or traversal)`),export:T(E),order:T(e.number())}),`fileViewers`),be=D(e.object({type:E,entry:E.refine(e=>!e.startsWith(`/`)&&!e.split(/[\\/]/).includes(`..`),`unsafe path (absolute or traversal)`),export:T(E)}),`a2uiComponents`),xe=D(e.object({id:E,entry:E.refine(e=>!e.startsWith(`/`)&&!e.split(/[\\/]/).includes(`..`),`unsafe path (absolute or traversal)`),export:T(E),order:T(e.number()),tickMs:T(e.number())}),`statusWidgets`),Se=D(ge,`renderers`),Ce=D(me,`panels`),we=e.discriminatedUnion(`type`,[e.object({type:e.literal(`builtin`),id:E}),e.object({type:e.literal(`mcp`),server:E,tool:E,params:T(e.record(e.string(),e.unknown()))}),e.object({type:e.literal(`open`),target:E}),e.object({type:e.literal(`command`),name:E,args:T(E)}),e.object({type:e.literal(`script`),name:E})]),Te=e.object({id:E,title:E,command:we,color:T(e.enum([`accent`,`amber`,`red`,`green`,`gray`]))}),Ee=e.object({action:E,point:E,when:T(E),order:T(e.number())}),De=e.object({key:E,groups:e.array(e.array(E)),map:e.object({none:E,partial:E,all:E})});function D(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(`; `));S.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 Oe=e.object({input:e.number().min(0),output:e.number().min(0),cacheRead:e.number().min(0),cacheWrite:e.number().min(0)}),ke=e.object({maxImagesPerRequest:e.number().int().positive(),maxDimensionPxIfOverLimit:T(e.number().int().positive())}),Ae=e.enum([`planning`,`knowledge`,`coding`,`reasoning`,`vision`,`speed`,`long-context`]),je=e.enum([`low`,`medium`,`high`,`xhigh`,`max`]),Me=e.enum([`enabled`,`adaptive`]),Ne=e.object({id:E,contextWindow:e.number().int().positive(),maxOutput:e.number().int().positive(),cost:T(Oe),strengths:T(e.array(Ae).min(1)),reasoning:T(e.boolean()),input:T(e.array(e.enum([`text`,`image`])).min(1)),thinkingLevels:T(e.array(je).min(1)),thinkingMode:T(Me)}),Pe=e.enum([`openai-completions`,`openai-responses`,`anthropic-messages`]),Fe=e.object({label:E,value:E.refine(e=>!e.startsWith(`!`)&&!e.startsWith(`#`),`value must not start with '!' or '#' (mode-prefix collision)`),kind:e.enum([`resource`,`hashtag`]),description:T(E),resolverId:T(E)}),Ie=e.object({id:E,dataSource:e.object({contextKey:E}),point:T(E),when:T(E),order:T(e.number())}),Le=e.union([E,e.object({file:E.refine(e=>!e.startsWith(`/`)&&!e.split(/[\\/]/).includes(`..`),`unsafe path (absolute or traversal)`)})]),Re=e.object({id:E,priority:T(e.number()),content:Le}),ze=e.object({id:E,translations:e.record(e.string(),e.string())}),Be=/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/,O=e=>!Be.test(e),Ve=/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/,k=t=>e.string().max(16).refine(e=>Ve.test(e),`${t} must be a #RRGGBB or #RGB hex color`),He=e.object({brand:k(`brand`),brandShimmer:k(`brandShimmer`),accent:k(`accent`),accentDim:k(`accentDim`),accentBright:k(`accentBright`),text:T(k(`text`)),inactive:k(`inactive`),secondary:k(`secondary`),subtle:k(`subtle`),replyPrefix:k(`replyPrefix`),success:k(`success`),error:k(`error`),warning:k(`warning`),special:k(`special`),suggestion:k(`suggestion`),permission:k(`permission`),codeText:k(`codeText`),diffAdd:k(`diffAdd`),diffRemove:k(`diffRemove`),diffAddBg:k(`diffAddBg`),diffRemoveBg:k(`diffRemoveBg`),panelBorder:k(`panelBorder`),overlayBg:k(`overlayBg`),tagBg:k(`tagBg`),toolText:k(`toolText`),focusBorder:k(`focusBorder`),selection:k(`selection`)}),A=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(`.`)),Ue=e.strictObject({heading1:T(A),heading2:T(A),headingWeak:T(A),listMarker:T(A),listMarkerMuted:T(A),quoteBar:T(A),quoteText:T(A),link:T(A),inlineCode:T(A),codeFence:T(A),tableHeader:T(A),tableDivider:T(A),listIndent:T(e.union([e.literal(2),e.literal(3)])),listMarkers:T(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:T(e.enum([`hr`,`none`]))}),We=e.object({localId:E.max(64).refine(e=>w.test(e),`localId must be kebab-case`),label:E.max(64).refine(O,`label must not contain control characters`),appearance:e.enum([`dark`,`light`]),colors:He,markdown:T(Ue),meta:T(e.object({author:T(E.max(128).refine(O,`author must not contain control characters`)),description:T(E.max(256).refine(O,`description must not contain control characters`)),version:T(E.max(32).refine(O,`version must not contain control characters`))}))}),Ge=10,Ke=500,qe=2*1024,Je=20,Ye=64*1024,Xe=e.object({id:E,label:E,toolRef:E}),Ze=e.object({header:E.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:E,namespace:T(E),tokenSources:T(e.array(e.enum([`id_token`,`access_token`])))})}),j=e.object({imageConstraints:T(ke),supportsTools:T(e.boolean())}),M=e.object({url:E,responseFormat:e.literal(`openai-list`),authScheme:T(e.enum([`bearer`,`x-api-key`,`auto`])),betaHeaderName:T(E),modelIdExclude:T(D(e.string(),`modelIdExclude`))}),N=e.object({store:T(e.boolean()),sendMaxOutputTokens:T(e.boolean())}),Qe=e.object({id:E,name:T(E),catalogOnly:T(e.boolean()),baseUrl:T(E),wireApi:T(Pe),envKey:T(E),oauthRef:T(E),headers:T(e.record(e.string(),e.string())),models:T(D(Ne,`models`)),tokenDerivedHeaders:T(D(Ze,`tokenDerivedHeaders`)),modelsEndpoint:T(M),responseBodyPolicy:T(N),allowAuthHeaderOverride:T(e.boolean()),preserveProviderId:T(e.boolean()),requiresIntranet:T(e.boolean())}).extend(j.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`]})}),$e=e.object({name:E.refine(e=>/^[a-z][a-z0-9-]*$/.test(e),`must be lowercase kebab-case`),bin:T(E)}),et=e.discriminatedUnion(`kind`,[e.object({kind:e.literal(`pkce`),id:E,name:T(E),clientId:E,authorizeUrl:E,tokenUrl:E,redirectUri:E,scope:E,loopbackPorts:T(e.array(e.number().int().positive())),assumesLoopbackAlways:T(e.boolean()),extraAuthParams:T(e.record(e.string(),e.string())),tokenExchangeEncoding:T(e.enum([`json`,`form`])),subscriptionScoped:T(e.boolean()),oauthBeta:T(E)}),e.object({kind:e.literal(`device-flow`),id:E,name:T(E),clientId:E,deviceCodeUrlTemplate:E,tokenUrlTemplate:E,refreshUrlTemplate:T(E),scope:E,allowCustomDomain:T(e.boolean()),userAgent:T(E)})]),tt=e.object({skills:T(e.boolean()),agents:T(e.boolean()),commands:T(e.boolean()),mcp:T(e.boolean()),cli:T($e),panels:T(Ce),actions:T(D(Te,`actions`)),menus:T(D(Ee,`menus`)),contextKeys:T(D(De,`contextKeys`)),providers:T(D(Qe,`providers`)),oauth:T(D(et,`oauth`)),statusItems:T(D(Ie,`statusItems`)),perfMetrics:T(D(Xe,`perfMetrics`)),renderers:T(Se),fileViewers:T(ye),statusWidgets:T(xe),a2uiComponents:T(be),contextSources:T(D(Re,`contextSources`)),i18n:T(D(ze,`i18n`)),themePresets:T(D(We,`themePresets`)),i18nDir:T(E.refine(e=>!e.startsWith(`/`)&&!e.split(/[\\/]/).includes(`..`),`unsafe path (absolute or traversal)`)),inputEntries:T(E),sigilEntries:T(D(Fe,`sigilEntries`)),a2uiRenderers:T(D(_e,`a2uiRenderers`))}),nt=e.enum(pe),rt=e.array(e.unknown()).transform(e=>{let t=e.map(e=>nt.safeParse(e)).filter(e=>e.success).map(e=>e.data);return t.length>0?[...new Set(t)]:void 0}),it=/^(onStartup|onCommand:[^\s]+|onView:[^\s]+|onProvider:[^\s]+)$/,at=e.array(e.unknown()).transform(e=>{let t=e.filter(e=>typeof e==`string`&&it.test(e));return t.length>0?[...new Set(t)]:void 0}),ot=e.array(e.unknown()).transform(e=>{let t=e.filter(e=>typeof e==`string`&&w.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`&&e.trim().length>0);return t.length>0?[...new Set(t)]:void 0}),ct=M.extend({headers:T(e.record(e.string(),e.string()))}),lt=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&&(t[n]=e.data)}return Object.keys(t).length>0?t:void 0}),ut=e.object({baseUrl:E,models:D(Ne,`codeProviderModels`),responseBodyPolicy:T(N)}).extend(j.shape),dt=e.record(e.string(),e.unknown()).transform(e=>{let t={};for(let[n,r]of Object.entries(e)){let e=ut.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}),ft=e.object({postinstall:T(E),setup:T(E)}).catchall(E),pt=e.object({otto:T(E)}),mt=e.object({id:e.string().regex(w),name:T(e.string()),version:T(e.string()),description:T(e.string()),engines:T(pt),contributes:T(tt),scripts:T(ft),capabilities:T(rt),activationEvents:T(at),dependsOn:T(ot),codeProviderIds:T(st),codeProviderModelsEndpoints:T(lt),codeProviderModels:T(dt)});function ht(e){return e.activationEvents?.length?e.activationEvents:[`onStartup`]}function P(e,t){let n;try{n=JSON.parse(e)}catch(e){return S.warn({sourcePath:t,err:String(e)},`plugin manifest invalid JSON, skipped`),null}let r=mt.safeParse(n);return r.success?r.data:(S.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 F=r(`@x-otto/coding:plugin-discovery`);function I(e){try{return te(e).isDirectory()}catch{return!1}}const gt=[{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 _t(e){let{cwd:t,homedir:n}=e,r=e.disabled?new Set(e.disabled):null,i=ce({cwd:t,homedir:n,claudeCompat:!1,workspaceRoot:oe(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 gt)a.set(e.id,e);for(let{dir:e,scope:t}of i){if(!s(e)||!I(e))continue;let n;try{n=p(e).sort()}catch(t){F.warn({root:e,err:String(t)},`failed to read plugins root, skipped`);continue}for(let i of n){let n=h(e,i);if(!I(n))continue;let o=h(n,C);if(!s(o))continue;let c;try{c=d(o,`utf-8`)}catch(e){F.warn({manifestPath:o,err:String(e)},`failed to read plugin manifest, skipped`);continue}let l=P(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 L(e){return e.manifest.dependsOn??[]}function vt(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=L(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=L(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,yt(e.id,o));return{ordered:[...r,...f.map(e=>o.get(e))],cyclic:t,missingDeps:n}}function yt(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=L(e).filter(e=>t.has(e)).sort((e,t)=>e.localeCompare(t))[0]}return i===e&&n.push(e),n}const bt=1;function xt(){return`1.0.0`}function R(e){if(e.scope===`builtin`)return!0;let t=e.manifest.engines?.otto;return t?ue(t)?le(xt(),t):!1:!0}function St(e){let t=[],n=new Map;for(let r of e)R(r)?t.push(r):n.set(r.id,r.manifest.engines?.otto??`(invalid range)`);return{compatible:t,incompatible:n}}function Ct(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 z={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 wt(...e){let t=e.filter(e=>e!=null);return t.length===0?{}:t.reduce((e,t)=>{let n={};for(let r of Object.keys(z)){let i=z[r],a=Ct(e[r],t[r],i);a&&(n[r]=a)}return n},{})}const B={extensionDetailActions:`extension/detail/actions`},V={statuslineItem:`shell/statusline/item`,menuItem:`shell/menu/item`},Tt={...B,...V};function H(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 Et(e,t,n,r){let i=new Map((n??[]).map(e=>[e.id,e]));return(t??[]).filter(t=>t.point===e&&H(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 U(e){let t=e.replace(/\$\{OTTO_HOME\}/g,_);return t===`~`?v():t.startsWith(`~/`)?v()+t.slice(1):t}function W(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(U(e)))).length;return r===0?e.map.none:r===n?e.map.all:e.map.partial}function Dt(e,t){let n={};for(let r of e??[])n[r.key]=W(r,t);return n}var G=class extends Error{constructor(e){super(`dispatch: 缺少 capability「${e}」——宿主未注入`),this.name=`MissingCapabilityError`}};async function Ot(e,t){switch(e.type){case`builtin`:if(!t.builtin)throw new G(`builtin`);await t.builtin(e.id);return;case`mcp`:if(!t.callMcp)throw new G(`mcp`);await t.callMcp(e.server,e.tool,e.params);return;case`open`:if(!t.open)throw new G(`open`);await t.open(e.target);return;case`command`:if(!t.runCommand)throw new G(`command`);await t.runCommand(e.name,e.args);return;case`script`:if(!t.runScript)throw new G(`script`);await t.runScript(e.name);return;default:throw new G(e.type)}}function kt(e){return e}function K(e){return`${e.kind}\u0000${e.label}`}function At(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 jt(e){return e.startsWith(`!`)||e.startsWith(`#`)}function Mt(){let e=new Map,t=new Map;function n(t){for(let n of t){if(jt(n.value))throw Error(`[plugin-input] sigil value 不得以 '!' 或 '#' 开头(会被误判为 bash/memory 模式):${JSON.stringify(n.value)}`);let t=K(n);e.delete(t),e.set(t,n)}}function r(t){let n=[...e.values()];return t?n.filter(e=>y(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&&y(t.kind)!==n)continue;let r=At(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=>!jt(e.value)));let l=new Set(a.map(e=>K(e))),u=[...a];for(let e of s){let t=K(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 Nt(){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 Pt=r(`@x-otto/plugin:file-provider`),Ft=new Set([`node_modules`,`.git`,`dist`,`build`,`out`,`coverage`,`.next`,`.nuxt`,`.turbo`,`.nx`,`.cache`,`.otto`,`.venv`,`__pycache__`]);async function It(e,t,n){let r=[];async function i(a,o){if(r.length>=t)return;let s;try{s=await de(a,{withFileTypes:!0})}catch(e){o&&n?.(e);return}for(let n of s){if(r.length>=t)return;if(n.isDirectory()){if(Ft.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 Lt(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 Rt(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(`
|
|
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
|
|
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.
|
|
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/hook-contracts": "0.0.1-alpha.
|
|
26
|
-
"@x-otto/provider": "0.1.0-alpha.
|
|
27
|
-
"@x-otto/
|
|
28
|
-
"@x-otto/interchange": "0.1.0-alpha.
|
|
29
|
-
"@x-otto/
|
|
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"
|