@chengchenccc/oh-my-agent 0.1.1-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. package/README.md +79 -0
  2. package/dist/cli/args.d.ts +24 -0
  3. package/dist/cli/args.d.ts.map +1 -0
  4. package/dist/cli/args.js +120 -0
  5. package/dist/cli/initial-input.d.ts +32 -0
  6. package/dist/cli/initial-input.d.ts.map +1 -0
  7. package/dist/cli/initial-input.js +131 -0
  8. package/dist/cli.d.ts +3 -0
  9. package/dist/cli.d.ts.map +1 -0
  10. package/dist/cli.js +3 -0
  11. package/dist/core/__fixtures__/echo-model.d.ts +6 -0
  12. package/dist/core/__fixtures__/echo-model.d.ts.map +1 -0
  13. package/dist/core/__fixtures__/echo-model.js +10 -0
  14. package/dist/core/__fixtures__/mcp-echo-server.d.ts +2 -0
  15. package/dist/core/__fixtures__/mcp-echo-server.d.ts.map +1 -0
  16. package/dist/core/__fixtures__/mcp-echo-server.js +54 -0
  17. package/dist/core/agent-runtime.d.ts +21 -0
  18. package/dist/core/agent-runtime.d.ts.map +1 -0
  19. package/dist/core/agent-runtime.js +14 -0
  20. package/dist/core/memory/autonomous-memory.d.ts +21 -0
  21. package/dist/core/memory/autonomous-memory.d.ts.map +1 -0
  22. package/dist/core/memory/autonomous-memory.js +202 -0
  23. package/dist/core/persistence/in-memory-session-store.d.ts +3 -0
  24. package/dist/core/persistence/in-memory-session-store.d.ts.map +1 -0
  25. package/dist/core/persistence/in-memory-session-store.js +109 -0
  26. package/dist/core/persistence/session-store.contract.d.ts +5 -0
  27. package/dist/core/persistence/session-store.contract.d.ts.map +1 -0
  28. package/dist/core/persistence/session-store.contract.js +183 -0
  29. package/dist/core/persistence/session-store.d.ts +33 -0
  30. package/dist/core/persistence/session-store.d.ts.map +1 -0
  31. package/dist/core/persistence/session-store.js +22 -0
  32. package/dist/core/persistence/session-tree.d.ts +49 -0
  33. package/dist/core/persistence/session-tree.d.ts.map +1 -0
  34. package/dist/core/persistence/session-tree.js +1 -0
  35. package/dist/core/plugins/plugin-code.d.ts +13 -0
  36. package/dist/core/plugins/plugin-code.d.ts.map +1 -0
  37. package/dist/core/plugins/plugin-code.js +62 -0
  38. package/dist/core/plugins/plugin-marketplace.d.ts +72 -0
  39. package/dist/core/plugins/plugin-marketplace.d.ts.map +1 -0
  40. package/dist/core/plugins/plugin-marketplace.js +340 -0
  41. package/dist/core/plugins/plugin-resolve.d.ts +32 -0
  42. package/dist/core/plugins/plugin-resolve.d.ts.map +1 -0
  43. package/dist/core/plugins/plugin-resolve.js +101 -0
  44. package/dist/core/plugins/plugin-trust.d.ts +24 -0
  45. package/dist/core/plugins/plugin-trust.d.ts.map +1 -0
  46. package/dist/core/plugins/plugin-trust.js +89 -0
  47. package/dist/core/runtime/agent-event.d.ts +98 -0
  48. package/dist/core/runtime/agent-event.d.ts.map +1 -0
  49. package/dist/core/runtime/agent-event.js +1 -0
  50. package/dist/core/runtime/agent-loop.d.ts +142 -0
  51. package/dist/core/runtime/agent-loop.d.ts.map +1 -0
  52. package/dist/core/runtime/agent-loop.js +1008 -0
  53. package/dist/core/runtime/approval.d.ts +23 -0
  54. package/dist/core/runtime/approval.d.ts.map +1 -0
  55. package/dist/core/runtime/approval.js +25 -0
  56. package/dist/core/runtime/compaction.d.ts +22 -0
  57. package/dist/core/runtime/compaction.d.ts.map +1 -0
  58. package/dist/core/runtime/compaction.js +90 -0
  59. package/dist/core/runtime/context-estimate.d.ts +42 -0
  60. package/dist/core/runtime/context-estimate.d.ts.map +1 -0
  61. package/dist/core/runtime/context-estimate.js +34 -0
  62. package/dist/core/runtime/create-runtime.d.ts +81 -0
  63. package/dist/core/runtime/create-runtime.d.ts.map +1 -0
  64. package/dist/core/runtime/create-runtime.js +250 -0
  65. package/dist/core/runtime/fake-provider.d.ts +3 -0
  66. package/dist/core/runtime/fake-provider.d.ts.map +1 -0
  67. package/dist/core/runtime/fake-provider.js +87 -0
  68. package/dist/core/runtime/logger.d.ts +13 -0
  69. package/dist/core/runtime/logger.d.ts.map +1 -0
  70. package/dist/core/runtime/logger.js +38 -0
  71. package/dist/core/runtime/loop-input.d.ts +39 -0
  72. package/dist/core/runtime/loop-input.d.ts.map +1 -0
  73. package/dist/core/runtime/loop-input.js +38 -0
  74. package/dist/core/runtime/message-cache.d.ts +16 -0
  75. package/dist/core/runtime/message-cache.d.ts.map +1 -0
  76. package/dist/core/runtime/message-cache.js +23 -0
  77. package/dist/core/runtime/model-catalog.d.ts +10 -0
  78. package/dist/core/runtime/model-catalog.d.ts.map +1 -0
  79. package/dist/core/runtime/model-catalog.js +14 -0
  80. package/dist/core/runtime/plugin-runtime.d.ts +41 -0
  81. package/dist/core/runtime/plugin-runtime.d.ts.map +1 -0
  82. package/dist/core/runtime/plugin-runtime.js +1 -0
  83. package/dist/core/runtime/plugin.d.ts +86 -0
  84. package/dist/core/runtime/plugin.d.ts.map +1 -0
  85. package/dist/core/runtime/plugin.js +23 -0
  86. package/dist/core/runtime/process-tree.d.ts +12 -0
  87. package/dist/core/runtime/process-tree.d.ts.map +1 -0
  88. package/dist/core/runtime/process-tree.js +40 -0
  89. package/dist/core/runtime/prompt.d.ts +23 -0
  90. package/dist/core/runtime/prompt.d.ts.map +1 -0
  91. package/dist/core/runtime/prompt.js +29 -0
  92. package/dist/core/runtime/prompts.d.ts +16 -0
  93. package/dist/core/runtime/prompts.d.ts.map +1 -0
  94. package/dist/core/runtime/prompts.js +36 -0
  95. package/dist/core/runtime/retry.d.ts +20 -0
  96. package/dist/core/runtime/retry.d.ts.map +1 -0
  97. package/dist/core/runtime/retry.js +60 -0
  98. package/dist/core/runtime/run-runtime.d.ts +93 -0
  99. package/dist/core/runtime/run-runtime.d.ts.map +1 -0
  100. package/dist/core/runtime/run-runtime.js +664 -0
  101. package/dist/core/runtime/runtime-catalog.d.ts +12 -0
  102. package/dist/core/runtime/runtime-catalog.d.ts.map +1 -0
  103. package/dist/core/runtime/runtime-catalog.js +89 -0
  104. package/dist/core/runtime/stream-rules.d.ts +9 -0
  105. package/dist/core/runtime/stream-rules.d.ts.map +1 -0
  106. package/dist/core/runtime/stream-rules.js +44 -0
  107. package/dist/core/runtime/title.d.ts +7 -0
  108. package/dist/core/runtime/title.d.ts.map +1 -0
  109. package/dist/core/runtime/title.js +114 -0
  110. package/dist/core/runtime/todo.d.ts +16 -0
  111. package/dist/core/runtime/todo.d.ts.map +1 -0
  112. package/dist/core/runtime/todo.js +40 -0
  113. package/dist/core/runtime/tool-filter.d.ts +12 -0
  114. package/dist/core/runtime/tool-filter.d.ts.map +1 -0
  115. package/dist/core/runtime/tool-filter.js +23 -0
  116. package/dist/core/runtime/tool-pruning.d.ts +29 -0
  117. package/dist/core/runtime/tool-pruning.d.ts.map +1 -0
  118. package/dist/core/runtime/tool-pruning.js +105 -0
  119. package/dist/core/session/input-history.d.ts +8 -0
  120. package/dist/core/session/input-history.d.ts.map +1 -0
  121. package/dist/core/session/input-history.js +41 -0
  122. package/dist/core/session/session-file.d.ts +84 -0
  123. package/dist/core/session/session-file.d.ts.map +1 -0
  124. package/dist/core/session/session-file.js +392 -0
  125. package/dist/core/session/session-loop.d.ts +33 -0
  126. package/dist/core/session/session-loop.d.ts.map +1 -0
  127. package/dist/core/session/session-loop.js +30 -0
  128. package/dist/core/settings/project-settings.d.ts +46 -0
  129. package/dist/core/settings/project-settings.d.ts.map +1 -0
  130. package/dist/core/settings/project-settings.js +85 -0
  131. package/dist/core/settings/workspace-context.d.ts +10 -0
  132. package/dist/core/settings/workspace-context.d.ts.map +1 -0
  133. package/dist/core/settings/workspace-context.js +48 -0
  134. package/dist/core/tools/agent-fs-like.d.ts +15 -0
  135. package/dist/core/tools/agent-fs-like.d.ts.map +1 -0
  136. package/dist/core/tools/agent-fs-like.js +4 -0
  137. package/dist/core/tools/ask-question.d.ts +5 -0
  138. package/dist/core/tools/ask-question.d.ts.map +1 -0
  139. package/dist/core/tools/ask-question.js +51 -0
  140. package/dist/core/tools/bash.d.ts +5 -0
  141. package/dist/core/tools/bash.d.ts.map +1 -0
  142. package/dist/core/tools/bash.js +109 -0
  143. package/dist/core/tools/eval.d.ts +10 -0
  144. package/dist/core/tools/eval.d.ts.map +1 -0
  145. package/dist/core/tools/eval.js +83 -0
  146. package/dist/core/tools/file-tools.d.ts +18 -0
  147. package/dist/core/tools/file-tools.d.ts.map +1 -0
  148. package/dist/core/tools/file-tools.js +249 -0
  149. package/dist/core/tools/glob.d.ts +5 -0
  150. package/dist/core/tools/glob.d.ts.map +1 -0
  151. package/dist/core/tools/glob.js +54 -0
  152. package/dist/core/tools/grep.d.ts +5 -0
  153. package/dist/core/tools/grep.d.ts.map +1 -0
  154. package/dist/core/tools/grep.js +61 -0
  155. package/dist/core/tools/index.d.ts +14 -0
  156. package/dist/core/tools/index.d.ts.map +1 -0
  157. package/dist/core/tools/index.js +14 -0
  158. package/dist/core/tools/ls-tree.d.ts +10 -0
  159. package/dist/core/tools/ls-tree.d.ts.map +1 -0
  160. package/dist/core/tools/ls-tree.js +177 -0
  161. package/dist/core/tools/mcp-mount.d.ts +63 -0
  162. package/dist/core/tools/mcp-mount.d.ts.map +1 -0
  163. package/dist/core/tools/mcp-mount.js +287 -0
  164. package/dist/core/tools/read-image.d.ts +6 -0
  165. package/dist/core/tools/read-image.d.ts.map +1 -0
  166. package/dist/core/tools/read-image.js +98 -0
  167. package/dist/core/tools/skill.d.ts +11 -0
  168. package/dist/core/tools/skill.d.ts.map +1 -0
  169. package/dist/core/tools/skill.js +65 -0
  170. package/dist/core/tools/skills.d.ts +10 -0
  171. package/dist/core/tools/skills.d.ts.map +1 -0
  172. package/dist/core/tools/skills.js +116 -0
  173. package/dist/core/tools/todo-store.d.ts +8 -0
  174. package/dist/core/tools/todo-store.d.ts.map +1 -0
  175. package/dist/core/tools/todo-store.js +100 -0
  176. package/dist/core/tools/todo.d.ts +8 -0
  177. package/dist/core/tools/todo.d.ts.map +1 -0
  178. package/dist/core/tools/todo.js +91 -0
  179. package/dist/core/tools/url-guard.d.ts +12 -0
  180. package/dist/core/tools/url-guard.d.ts.map +1 -0
  181. package/dist/core/tools/url-guard.js +58 -0
  182. package/dist/core/tools/web-ports-std.d.ts +6 -0
  183. package/dist/core/tools/web-ports-std.d.ts.map +1 -0
  184. package/dist/core/tools/web-ports-std.js +145 -0
  185. package/dist/core/tools/web-ports.d.ts +56 -0
  186. package/dist/core/tools/web-ports.d.ts.map +1 -0
  187. package/dist/core/tools/web-ports.js +62 -0
  188. package/dist/core/tools/workspace-sandbox.d.ts +20 -0
  189. package/dist/core/tools/workspace-sandbox.d.ts.map +1 -0
  190. package/dist/core/tools/workspace-sandbox.js +84 -0
  191. package/dist/core/workflow/subagent-registry.d.ts +40 -0
  192. package/dist/core/workflow/subagent-registry.d.ts.map +1 -0
  193. package/dist/core/workflow/subagent-registry.js +118 -0
  194. package/dist/core/workflow/workflow-evaluator.d.ts +23 -0
  195. package/dist/core/workflow/workflow-evaluator.d.ts.map +1 -0
  196. package/dist/core/workflow/workflow-evaluator.js +21 -0
  197. package/dist/core/workflow/workflow-executor.d.ts +108 -0
  198. package/dist/core/workflow/workflow-executor.d.ts.map +1 -0
  199. package/dist/core/workflow/workflow-executor.js +549 -0
  200. package/dist/core/workflow/workflow-tools.d.ts +50 -0
  201. package/dist/core/workflow/workflow-tools.d.ts.map +1 -0
  202. package/dist/core/workflow/workflow-tools.js +217 -0
  203. package/dist/index.d.ts +14 -0
  204. package/dist/index.d.ts.map +1 -0
  205. package/dist/index.js +13 -0
  206. package/dist/main.d.ts +9 -0
  207. package/dist/main.d.ts.map +1 -0
  208. package/dist/main.js +97 -0
  209. package/dist/modes/json-mode.d.ts +5 -0
  210. package/dist/modes/json-mode.d.ts.map +1 -0
  211. package/dist/modes/json-mode.js +46 -0
  212. package/dist/modes/print-mode.d.ts +20 -0
  213. package/dist/modes/print-mode.d.ts.map +1 -0
  214. package/dist/modes/print-mode.js +82 -0
  215. package/dist/modes/rpc/jsonl.d.ts +23 -0
  216. package/dist/modes/rpc/jsonl.d.ts.map +1 -0
  217. package/dist/modes/rpc/jsonl.js +88 -0
  218. package/dist/modes/rpc/rpc-mode.d.ts +18 -0
  219. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -0
  220. package/dist/modes/rpc/rpc-mode.js +326 -0
  221. package/dist/modes/tui/settings-overlay.d.ts +23 -0
  222. package/dist/modes/tui/settings-overlay.d.ts.map +1 -0
  223. package/dist/modes/tui/settings-overlay.js +136 -0
  224. package/dist/modes/tui/tui-commands-session.d.ts +3 -0
  225. package/dist/modes/tui/tui-commands-session.d.ts.map +1 -0
  226. package/dist/modes/tui/tui-commands-session.js +191 -0
  227. package/dist/modes/tui/tui-commands.d.ts +52 -0
  228. package/dist/modes/tui/tui-commands.d.ts.map +1 -0
  229. package/dist/modes/tui/tui-commands.js +373 -0
  230. package/dist/modes/tui/tui-components.d.ts +81 -0
  231. package/dist/modes/tui/tui-components.d.ts.map +1 -0
  232. package/dist/modes/tui/tui-components.js +248 -0
  233. package/dist/modes/tui/tui-format.d.ts +65 -0
  234. package/dist/modes/tui/tui-format.d.ts.map +1 -0
  235. package/dist/modes/tui/tui-format.js +246 -0
  236. package/dist/modes/tui/tui-frame-provider.d.ts +16 -0
  237. package/dist/modes/tui/tui-frame-provider.d.ts.map +1 -0
  238. package/dist/modes/tui/tui-frame-provider.js +31 -0
  239. package/dist/modes/tui/tui-interactive.d.ts +16 -0
  240. package/dist/modes/tui/tui-interactive.d.ts.map +1 -0
  241. package/dist/modes/tui/tui-interactive.js +123 -0
  242. package/dist/modes/tui/tui-io.d.ts +12 -0
  243. package/dist/modes/tui/tui-io.d.ts.map +1 -0
  244. package/dist/modes/tui/tui-io.js +620 -0
  245. package/dist/modes/tui/tui-mode.d.ts +120 -0
  246. package/dist/modes/tui/tui-mode.d.ts.map +1 -0
  247. package/dist/modes/tui/tui-mode.js +391 -0
  248. package/dist/modes/tui/tui-render.d.ts +54 -0
  249. package/dist/modes/tui/tui-render.d.ts.map +1 -0
  250. package/dist/modes/tui/tui-render.js +399 -0
  251. package/dist/modes/tui/tui-slash.d.ts +12 -0
  252. package/dist/modes/tui/tui-slash.d.ts.map +1 -0
  253. package/dist/modes/tui/tui-slash.js +64 -0
  254. package/dist/modes/tui/tui-tool-render.d.ts +6 -0
  255. package/dist/modes/tui/tui-tool-render.d.ts.map +1 -0
  256. package/dist/modes/tui/tui-tool-render.js +79 -0
  257. package/dist/modes/tui/tui-transcript-reconciler.d.ts +25 -0
  258. package/dist/modes/tui/tui-transcript-reconciler.d.ts.map +1 -0
  259. package/dist/modes/tui/tui-transcript-reconciler.js +97 -0
  260. package/dist/modes/tui/view-state.d.ts +67 -0
  261. package/dist/modes/tui/view-state.d.ts.map +1 -0
  262. package/dist/modes/tui/view-state.js +353 -0
  263. package/dist/prompts/agents/subagent.md +26 -0
  264. package/dist/prompts/system/base.md +20 -0
  265. package/dist/prompts/system/memory.md +23 -0
  266. package/dist/prompts/system/safety.md +12 -0
  267. package/dist/protocol/index.d.ts +5 -0
  268. package/dist/protocol/index.d.ts.map +1 -0
  269. package/dist/protocol/index.js +2 -0
  270. package/dist/protocol/mapping.d.ts +25 -0
  271. package/dist/protocol/mapping.d.ts.map +1 -0
  272. package/dist/protocol/mapping.js +115 -0
  273. package/dist/protocol/transport.d.ts +1396 -0
  274. package/dist/protocol/transport.d.ts.map +1 -0
  275. package/dist/protocol/transport.js +170 -0
  276. package/package.json +49 -0
@@ -0,0 +1,62 @@
1
+ import { join } from "node:path";
2
+ import { pathToFileURL } from "node:url";
3
+ const KNOWN_HOOK_KEYS = new Set([
4
+ "beforeRun",
5
+ "afterRun",
6
+ "beforeModel",
7
+ "afterModel",
8
+ "beforeTool",
9
+ "afterTool",
10
+ "transformToolArgs",
11
+ "beforeStop",
12
+ "afterStop",
13
+ ]);
14
+ function isPluginToolLike(v) {
15
+ if (typeof v !== "object" || v === null)
16
+ return false;
17
+ const t = v;
18
+ return (typeof t.name === "string" &&
19
+ typeof t.description === "string" &&
20
+ typeof t.execute === "function");
21
+ }
22
+ /** Load ONE code entry (tools or hooks) from an installed plugin root.
23
+ * Never throws: any failure degrades to {ok:false, error} (spec failure
24
+ * semantics — a broken plugin must never fail the Run). */
25
+ export async function loadPluginCode(root, entry) {
26
+ const file = join(root, entry);
27
+ let mod;
28
+ try {
29
+ // Bun-native dynamic import: TS transpiles natively, no jiti (spec).
30
+ // The import itself executes module top-level code — plugin bugs surface
31
+ // here as caught errors, not oma crashes.
32
+ mod = (await import(pathToFileURL(file).href));
33
+ }
34
+ catch (err) {
35
+ return {
36
+ ok: false,
37
+ warnings: [],
38
+ error: `failed to import ${entry}: ${err instanceof Error ? err.message : String(err)}`,
39
+ };
40
+ }
41
+ const warnings = [];
42
+ const toolsExport = mod.tools ?? mod.default;
43
+ if (toolsExport !== undefined) {
44
+ if (!Array.isArray(toolsExport) || !toolsExport.every(isPluginToolLike)) {
45
+ return { ok: false, warnings, error: `${entry}: tools export must be PluginTool[]` };
46
+ }
47
+ return { ok: true, tools: toolsExport, warnings };
48
+ }
49
+ const hooksExport = mod.hooks ?? mod.default;
50
+ if (typeof hooksExport === "object" && hooksExport !== null) {
51
+ const src = hooksExport;
52
+ const hooks = {};
53
+ for (const [k, v] of Object.entries(src)) {
54
+ if (KNOWN_HOOK_KEYS.has(k))
55
+ hooks[k] = v;
56
+ else
57
+ warnings.push(`${entry}: unknown hook key "${k}" ignored`);
58
+ }
59
+ return { ok: true, hooks: hooks, warnings };
60
+ }
61
+ return { ok: false, warnings, error: `${entry}: no tools/hooks export found` };
62
+ }
@@ -0,0 +1,72 @@
1
+ export interface PluginManifest {
2
+ readonly name: string;
3
+ readonly version?: string;
4
+ readonly description?: string;
5
+ /** Relative directory containing SKILL.md bundles (default "skills"). */
6
+ readonly skills?: string;
7
+ /** Relative directory containing markdown slash commands (reserved). */
8
+ readonly commands?: string;
9
+ /** oma code entry (spec): module exporting PluginTool[] (default export ok). */
10
+ readonly toolsEntry?: string;
11
+ /** oma code entry (spec): module exporting PluginHooks (default export ok). */
12
+ readonly hooksEntry?: string;
13
+ /** Non-fatal conflict/compat notes surfaced to the user (spec conflict matrix). */
14
+ readonly warnings: readonly string[];
15
+ }
16
+ export interface MarketplacePluginEntry {
17
+ readonly name: string;
18
+ readonly path: string;
19
+ readonly version?: string;
20
+ }
21
+ export interface MarketplaceManifest {
22
+ readonly name: string;
23
+ readonly plugins: readonly MarketplacePluginEntry[];
24
+ }
25
+ export interface MarketplaceRecord {
26
+ readonly name: string;
27
+ readonly source: string;
28
+ readonly root: string;
29
+ /** Version fingerprint (git HEAD, zip hash) from the base source-fetch.
30
+ * Absent for local directories (no immutable source). */
31
+ readonly version?: string;
32
+ }
33
+ export interface InstalledPlugin {
34
+ readonly name: string;
35
+ readonly version?: string;
36
+ readonly description?: string;
37
+ readonly sourceMarketplace: string;
38
+ readonly scope: "user" | "project";
39
+ readonly enabled: boolean;
40
+ readonly root: string;
41
+ readonly skillsDir?: string;
42
+ /** oma code entry (spec): module exporting PluginTool[]. */
43
+ readonly toolsEntry?: string;
44
+ /** oma code entry (spec): module exporting PluginHooks. */
45
+ readonly hooksEntry?: string;
46
+ /** Plugin bundles a .mcp.json (mounted via mcp-mount when trusted). */
47
+ readonly hasMcpJson?: boolean;
48
+ /** Non-fatal manifest conflict notes (spec conflict matrix); always set by
49
+ * listInstalledPlugins, absent in persisted registry records. */
50
+ readonly manifestWarnings?: readonly string[];
51
+ }
52
+ export declare function loadMarketplaceManifest(root: string): MarketplaceManifest | null;
53
+ /** Multi-source manifest read (spec: oma plugin.json → .claude-plugin/plugin.json
54
+ * → package.json omp/pi field) with the conflict matrix. */
55
+ export declare function loadPluginManifest(pluginRoot: string): PluginManifest | null;
56
+ export declare function listMarketplaces(workspaceRoot: string): MarketplaceRecord[];
57
+ export declare function addMarketplace(workspaceRoot: string, source: string, scope?: "user" | "project"): {
58
+ ok: boolean;
59
+ error?: string;
60
+ name?: string;
61
+ };
62
+ export declare function removeMarketplace(workspaceRoot: string, name: string): boolean;
63
+ export declare function listInstalledPlugins(workspaceRoot: string): InstalledPlugin[];
64
+ export declare function installPlugin(workspaceRoot: string, ref: string, scope?: "user" | "project"): {
65
+ ok: boolean;
66
+ error?: string;
67
+ };
68
+ export declare function uninstallPlugin(workspaceRoot: string, name: string): boolean;
69
+ export declare function setPluginEnabled(workspaceRoot: string, name: string, enabled: boolean): boolean;
70
+ /** Skill roots contributed by enabled installed plugins. */
71
+ export declare function enabledPluginSkillRoots(workspaceRoot: string): string[];
72
+ //# sourceMappingURL=plugin-marketplace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-marketplace.d.ts","sourceRoot":"","sources":["../../../src/core/plugins/plugin-marketplace.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,340 @@
1
+ import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { fetchGitSourceSync } from "@chengchenccc/source-fetch";
4
+ import { agentDir } from "../session/session-file.js";
5
+ function userRegistryPath() {
6
+ return join(agentDir(), "plugins.json");
7
+ }
8
+ function projectRegistryPath(workspaceRoot) {
9
+ return join(workspaceRoot, ".oma", "plugins.json");
10
+ }
11
+ function readRegistry(path) {
12
+ if (!existsSync(path))
13
+ return { marketplaces: [], plugins: [] };
14
+ try {
15
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
16
+ if (typeof parsed === "object" && parsed !== null) {
17
+ const obj = parsed;
18
+ const marketplaces = Array.isArray(obj.marketplaces) ? obj.marketplaces : [];
19
+ const plugins = Array.isArray(obj.plugins) ? obj.plugins : [];
20
+ return {
21
+ marketplaces: marketplaces.filter(isMarketplaceRecord),
22
+ plugins: plugins.filter(isInstalledPluginRecord),
23
+ };
24
+ }
25
+ }
26
+ catch {
27
+ /* corrupt registry: start empty */
28
+ }
29
+ return { marketplaces: [], plugins: [] };
30
+ }
31
+ function isMarketplaceRecord(v) {
32
+ if (typeof v !== "object" || v === null)
33
+ return false;
34
+ const o = v;
35
+ return typeof o.name === "string" && typeof o.source === "string" && typeof o.root === "string";
36
+ }
37
+ function isInstalledPluginRecord(v) {
38
+ if (typeof v !== "object" || v === null)
39
+ return false;
40
+ const o = v;
41
+ return (typeof o.name === "string" &&
42
+ typeof o.sourceMarketplace === "string" &&
43
+ (o.scope === "user" || o.scope === "project") &&
44
+ typeof o.enabled === "boolean" &&
45
+ typeof o.root === "string");
46
+ }
47
+ function writeRegistry(path, registry) {
48
+ mkdirSync(join(path, ".."), { recursive: true });
49
+ writeFileSync(path, `${JSON.stringify(registry, null, 2)}\n`);
50
+ }
51
+ function parseMarketplaceCatalog(o) {
52
+ const name = typeof o.name === "string" ? o.name : null;
53
+ const rawPlugins = Array.isArray(o.plugins) ? o.plugins : [];
54
+ const plugins = [];
55
+ for (const entry of rawPlugins) {
56
+ if (typeof entry !== "object" || entry === null)
57
+ continue;
58
+ const e = entry;
59
+ const entryName = typeof e.name === "string" ? e.name : null;
60
+ // oma catalogs use `path`; Claude catalogs use `source` ("./rel" form).
61
+ const pathLike = typeof e.path === "string"
62
+ ? e.path
63
+ : typeof e.source === "string" && e.source.startsWith("./")
64
+ ? e.source.slice(1)
65
+ : null;
66
+ if (!entryName || !pathLike)
67
+ continue;
68
+ plugins.push({ name: entryName, path: pathLike });
69
+ }
70
+ if (!name || plugins.length === 0)
71
+ return null;
72
+ return { name, plugins };
73
+ }
74
+ export function loadMarketplaceManifest(root) {
75
+ // oma catalog first; Claude catalog (`.claude-plugin/marketplace.json`) as
76
+ // fallback so existing Claude marketplaces keep loading (omp fetcher parity).
77
+ for (const rel of ["marketplace.json", join(".claude-plugin", "marketplace.json")]) {
78
+ const parsed = readJson(join(root, rel));
79
+ if (!parsed)
80
+ continue;
81
+ const manifest = parseMarketplaceCatalog(parsed);
82
+ if (manifest)
83
+ return manifest;
84
+ }
85
+ return null;
86
+ }
87
+ function readJson(path) {
88
+ if (!existsSync(path))
89
+ return null;
90
+ try {
91
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
92
+ return typeof parsed === "object" && parsed !== null
93
+ ? parsed
94
+ : null;
95
+ }
96
+ catch {
97
+ return null;
98
+ }
99
+ }
100
+ function str(o, key) {
101
+ const v = o[key];
102
+ return typeof v === "string" ? v : undefined;
103
+ }
104
+ /** Multi-source manifest read (spec: oma plugin.json → .claude-plugin/plugin.json
105
+ * → package.json omp/pi field) with the conflict matrix. */
106
+ export function loadPluginManifest(pluginRoot) {
107
+ const warnings = [];
108
+ const oma = readJson(join(pluginRoot, "plugin.json"));
109
+ const claude = readJson(join(pluginRoot, ".claude-plugin", "plugin.json"));
110
+ const pkg = readJson(join(pluginRoot, "package.json"));
111
+ const ompField = pkg ? (pkg.omp ?? pkg.pi) : undefined;
112
+ const omp = typeof ompField === "object" && ompField !== null
113
+ ? ompField
114
+ : null;
115
+ const primary = oma ?? claude ?? (pkg && str(pkg, "name") ? { name: str(pkg, "name") } : null);
116
+ if (!primary || typeof primary.name !== "string")
117
+ return null;
118
+ if (oma && claude) {
119
+ warnings.push("dual manifest: oma plugin.json wins; .claude-plugin/plugin.json fills missing metadata only");
120
+ }
121
+ if (omp) {
122
+ if (typeof omp.tools === "string" || typeof omp.hooks === "string") {
123
+ warnings.push("omp/pi manifest code entries (tools/hooks) are not executed by oma; provide oma plugin.json tools/hooks entries instead");
124
+ }
125
+ }
126
+ if (claude?.hooks !== undefined) {
127
+ warnings.push("claude hooks config detected and ignored: hooks run through the oma hooks entry only");
128
+ }
129
+ if (claude?.commands !== undefined)
130
+ warnings.push("claude commands/ component ignored");
131
+ if (claude?.agents !== undefined)
132
+ warnings.push("claude agents/ component ignored");
133
+ const result = { name: primary.name, warnings };
134
+ const fill = (src) => {
135
+ if (!src)
136
+ return;
137
+ result.version ??= str(src, "version");
138
+ result.description ??= str(src, "description");
139
+ result.skills ??= str(src, "skills");
140
+ };
141
+ fill(oma);
142
+ fill(claude);
143
+ fill(pkg);
144
+ // oma-only code entries — never sourced from omp/claude fields (spec).
145
+ if (oma) {
146
+ const t = str(oma, "tools");
147
+ if (t)
148
+ result.toolsEntry = t;
149
+ const h = str(oma, "hooks");
150
+ if (h)
151
+ result.hooksEntry = h;
152
+ }
153
+ return result;
154
+ }
155
+ function marketplaceSourceToRoot(source) {
156
+ if (existsSync(source)) {
157
+ return { root: source, ok: true };
158
+ }
159
+ const cacheDir = join(agentDir(), "marketplace-cache");
160
+ const slug = source.replace(/[^a-zA-Z0-9_.-]/g, "-").slice(0, 80);
161
+ try {
162
+ // Shared base (spec: marketplace cache reuses the public source-fetch
163
+ // primitive; no backend coupling). rev is the git HEAD — retained for
164
+ // future cache keying even though the local registry doesn't store it.
165
+ const { root, rev } = fetchGitSourceSync({ url: source, dataDir: cacheDir, slug });
166
+ return { root, ok: true, rev };
167
+ }
168
+ catch (err) {
169
+ return {
170
+ root: join(cacheDir, slug),
171
+ ok: false,
172
+ error: err instanceof Error ? err.message : `failed to clone ${source}`,
173
+ };
174
+ }
175
+ }
176
+ export function listMarketplaces(workspaceRoot) {
177
+ const user = readRegistry(userRegistryPath());
178
+ const project = readRegistry(projectRegistryPath(workspaceRoot));
179
+ const byName = new Map();
180
+ for (const m of user.marketplaces)
181
+ byName.set(m.name, m);
182
+ for (const m of project.marketplaces)
183
+ byName.set(m.name, m);
184
+ return Array.from(byName.values());
185
+ }
186
+ export function addMarketplace(workspaceRoot, source, scope = "user") {
187
+ // Local dirs have no immutable fingerprint; git/url sources get the
188
+ // base-fetch rev cached into the record for version display/update.
189
+ const { root, ok, error, rev: sourceRev } = marketplaceSourceToRoot(source);
190
+ if (!ok)
191
+ return { ok: false, error };
192
+ const manifest = loadMarketplaceManifest(root);
193
+ if (!manifest)
194
+ return { ok: false, error: `no marketplace.json in ${root}` };
195
+ const path = scope === "user" ? userRegistryPath() : projectRegistryPath(workspaceRoot);
196
+ const registry = readRegistry(path);
197
+ const existing = registry.marketplaces.find((m) => m.name === manifest.name);
198
+ if (existing) {
199
+ registry.marketplaces = registry.marketplaces.map((m) => m.name === manifest.name
200
+ ? { name: m.name, source, root, ...(sourceRev ? { version: sourceRev } : {}) }
201
+ : m);
202
+ }
203
+ else {
204
+ registry.marketplaces.push({
205
+ name: manifest.name,
206
+ source,
207
+ root,
208
+ ...(sourceRev ? { version: sourceRev } : {}),
209
+ });
210
+ }
211
+ writeRegistry(path, registry);
212
+ return { ok: true, name: manifest.name };
213
+ }
214
+ export function removeMarketplace(workspaceRoot, name) {
215
+ let removed = false;
216
+ for (const [, path] of [
217
+ ["user", userRegistryPath()],
218
+ ["project", projectRegistryPath(workspaceRoot)],
219
+ ]) {
220
+ const registry = readRegistry(path);
221
+ const before = registry.marketplaces.length;
222
+ registry.marketplaces = registry.marketplaces.filter((m) => m.name !== name);
223
+ if (registry.marketplaces.length !== before) {
224
+ writeRegistry(path, registry);
225
+ removed = true;
226
+ }
227
+ }
228
+ return removed;
229
+ }
230
+ export function listInstalledPlugins(workspaceRoot) {
231
+ const user = readRegistry(userRegistryPath());
232
+ const project = readRegistry(projectRegistryPath(workspaceRoot));
233
+ const byName = new Map();
234
+ for (const p of [...user.plugins, ...project.plugins]) {
235
+ if (p.scope === "user")
236
+ byName.set(p.name, p);
237
+ }
238
+ for (const p of project.plugins) {
239
+ if (p.scope === "project")
240
+ byName.set(p.name, p);
241
+ }
242
+ return Array.from(byName.values()).map((p) => {
243
+ const manifest = loadPluginManifest(p.root);
244
+ return {
245
+ ...p,
246
+ description: manifest?.description,
247
+ version: manifest?.version ?? p.version,
248
+ skillsDir: manifest?.skills ? join(p.root, manifest.skills) : join(p.root, "skills"),
249
+ toolsEntry: manifest?.toolsEntry,
250
+ hooksEntry: manifest?.hooksEntry,
251
+ hasMcpJson: existsSync(join(p.root, ".mcp.json")),
252
+ manifestWarnings: manifest?.warnings ?? [],
253
+ };
254
+ });
255
+ }
256
+ export function installPlugin(workspaceRoot, ref, scope = "user") {
257
+ const slash = ref.indexOf("/");
258
+ if (slash <= 0)
259
+ return { ok: false, error: "usage: /plugin install <marketplace>/<plugin>" };
260
+ const marketplaceName = ref.slice(0, slash);
261
+ const pluginName = ref.slice(slash + 1);
262
+ const marketplace = listMarketplaces(workspaceRoot).find((m) => m.name === marketplaceName);
263
+ if (!marketplace)
264
+ return { ok: false, error: `marketplace not found: ${marketplaceName}` };
265
+ const manifest = loadMarketplaceManifest(marketplace.root);
266
+ const entry = manifest?.plugins.find((p) => p.name === pluginName);
267
+ if (!entry)
268
+ return { ok: false, error: `plugin not found: ${pluginName}` };
269
+ const sourceRoot = join(marketplace.root, entry.path);
270
+ const pluginManifest = loadPluginManifest(sourceRoot);
271
+ if (!pluginManifest)
272
+ return { ok: false, error: `no plugin.json in ${sourceRoot}` };
273
+ const installRoot = scope === "user"
274
+ ? join(agentDir(), "plugins", pluginName)
275
+ : join(workspaceRoot, ".oma", "plugins", pluginName);
276
+ if (existsSync(installRoot))
277
+ rmSync(installRoot, { recursive: true, force: true });
278
+ mkdirSync(join(installRoot, ".."), { recursive: true });
279
+ cpSync(sourceRoot, installRoot, { recursive: true });
280
+ const path = scope === "user" ? userRegistryPath() : projectRegistryPath(workspaceRoot);
281
+ const registry = readRegistry(path);
282
+ registry.plugins = registry.plugins.filter((p) => !(p.name === pluginName && p.scope === scope));
283
+ registry.plugins.push({
284
+ name: pluginName,
285
+ version: pluginManifest.version,
286
+ sourceMarketplace: marketplaceName,
287
+ scope,
288
+ enabled: true,
289
+ root: installRoot,
290
+ });
291
+ writeRegistry(path, registry);
292
+ return { ok: true };
293
+ }
294
+ export function uninstallPlugin(workspaceRoot, name) {
295
+ let removed = false;
296
+ for (const [scope, path] of [
297
+ ["user", userRegistryPath()],
298
+ ["project", projectRegistryPath(workspaceRoot)],
299
+ ]) {
300
+ const registry = readRegistry(path);
301
+ const before = registry.plugins.length;
302
+ const removedPlugin = registry.plugins.find((p) => p.name === name && p.scope === scope);
303
+ registry.plugins = registry.plugins.filter((p) => !(p.name === name && p.scope === scope));
304
+ if (registry.plugins.length !== before) {
305
+ writeRegistry(path, registry);
306
+ if (removedPlugin)
307
+ rmSync(removedPlugin.root, { recursive: true, force: true });
308
+ removed = true;
309
+ }
310
+ }
311
+ return removed;
312
+ }
313
+ export function setPluginEnabled(workspaceRoot, name, enabled) {
314
+ let changed = false;
315
+ for (const [scope, path] of [
316
+ ["user", userRegistryPath()],
317
+ ["project", projectRegistryPath(workspaceRoot)],
318
+ ]) {
319
+ const registry = readRegistry(path);
320
+ const rec = registry.plugins.find((p) => p.name === name && p.scope === scope);
321
+ if (!rec || rec.enabled === enabled)
322
+ continue;
323
+ registry.plugins = registry.plugins.map((p) => p.name === name && p.scope === scope ? { ...p, enabled } : p);
324
+ writeRegistry(path, registry);
325
+ changed = true;
326
+ }
327
+ return changed;
328
+ }
329
+ /** Skill roots contributed by enabled installed plugins. */
330
+ export function enabledPluginSkillRoots(workspaceRoot) {
331
+ const plugins = listInstalledPlugins(workspaceRoot);
332
+ const roots = [];
333
+ for (const p of plugins) {
334
+ if (!p.enabled || !p.skillsDir)
335
+ continue;
336
+ if (existsSync(p.skillsDir))
337
+ roots.push(p.skillsDir);
338
+ }
339
+ return roots;
340
+ }
@@ -0,0 +1,32 @@
1
+ import type { Plugin } from "../runtime/plugin.js";
2
+ export type OmaMode = "tui" | "print" | "json" | "rpc";
3
+ export interface CodeEntry {
4
+ readonly name: string;
5
+ readonly root: string;
6
+ readonly toolsEntry?: string;
7
+ readonly hooksEntry?: string;
8
+ }
9
+ /** A plugin's .mcp.json servers, gated identically to code entries. */
10
+ export interface PluginMcpConfig {
11
+ readonly pluginName: string;
12
+ readonly pluginRoot: string;
13
+ readonly scope: "user" | "project";
14
+ readonly servers: Record<string, unknown>;
15
+ }
16
+ export interface ResolvedComponents {
17
+ readonly codeEntries: readonly CodeEntry[];
18
+ readonly mcpServers: readonly PluginMcpConfig[];
19
+ readonly warnings: readonly string[];
20
+ }
21
+ /** Policy layer (spec): scope x mode trust matrix. Pure — no imports, no
22
+ * code loading. The runtime never sees this; it only mounts what wins. */
23
+ export declare function resolvePluginComponents(workspaceRoot: string, mode: OmaMode): ResolvedComponents;
24
+ export interface AssembledPluginRuntime {
25
+ readonly plugins: readonly Plugin[];
26
+ readonly mcpServers: readonly PluginMcpConfig[];
27
+ readonly warnings: readonly string[];
28
+ }
29
+ /** Mode-layer entry: resolve policy + load approved code. The ONLY function
30
+ * modes call (spec: policy in mode layer, runtime mounts only). */
31
+ export declare function assemblePluginRuntime(workspaceRoot: string, mode: OmaMode): Promise<AssembledPluginRuntime>;
32
+ //# sourceMappingURL=plugin-resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-resolve.d.ts","sourceRoot":"","sources":["../../../src/core/plugins/plugin-resolve.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAA2B,MAAM,sBAAsB,CAAC;AAI5E,MAAM,MAAM,OAAO,GAAG,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;AAEvD,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,uEAAuE;AACvE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,WAAW,EAAE,SAAS,SAAS,EAAE,CAAC;IAC3C,QAAQ,CAAC,UAAU,EAAE,SAAS,eAAe,EAAE,CAAC;IAChD,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAeD;2EAC2E;AAC3E,wBAAgB,uBAAuB,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,kBAAkB,CAsChG;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,UAAU,EAAE,SAAS,eAAe,EAAE,CAAC;IAChD,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAED;oEACoE;AACpE,wBAAsB,qBAAqB,CACzC,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC,sBAAsB,CAAC,CAwCjC"}
@@ -0,0 +1,101 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { loadPluginCode } from "./plugin-code.js";
4
+ import { listInstalledPlugins } from "./plugin-marketplace.js";
5
+ import { isFileTrusted, isPluginTrusted, readTrustedPlugins } from "./plugin-trust.js";
6
+ function readPluginMcp(root) {
7
+ const path = join(root, ".mcp.json");
8
+ if (!existsSync(path))
9
+ return null;
10
+ try {
11
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
12
+ return parsed.mcpServers ?? null;
13
+ }
14
+ catch {
15
+ return null;
16
+ }
17
+ }
18
+ /** Policy layer (spec): scope x mode trust matrix. Pure — no imports, no
19
+ * code loading. The runtime never sees this; it only mounts what wins. */
20
+ export function resolvePluginComponents(workspaceRoot, mode) {
21
+ const warnings = [];
22
+ const trusted = readTrustedPlugins();
23
+ const codeEntries = [];
24
+ const mcpServers = [];
25
+ for (const p of listInstalledPlugins(workspaceRoot)) {
26
+ if (!p.enabled)
27
+ continue;
28
+ warnings.push(...(p.manifestWarnings ?? []).map((w) => `${p.name}: ${w}`));
29
+ const hasCode = Boolean(p.toolsEntry || p.hooksEntry);
30
+ const mcp = p.hasMcpJson ? readPluginMcp(p.root) : null;
31
+ if (!hasCode && !mcp)
32
+ continue;
33
+ if (p.scope === "project") {
34
+ if (mode === "rpc") {
35
+ warnings.push(`${p.name}: project-scope code components never load in rpc mode`);
36
+ continue;
37
+ }
38
+ if (!isPluginTrusted(p.root, trusted)) {
39
+ warnings.push(`${p.name}: project-scope code components untrusted (hash mismatch or no record); run /plugin trust ${p.name} to approve`);
40
+ continue;
41
+ }
42
+ }
43
+ if (hasCode) {
44
+ codeEntries.push({
45
+ name: p.name,
46
+ root: p.root,
47
+ ...(p.toolsEntry ? { toolsEntry: p.toolsEntry } : {}),
48
+ ...(p.hooksEntry ? { hooksEntry: p.hooksEntry } : {}),
49
+ });
50
+ }
51
+ if (mcp) {
52
+ mcpServers.push({ pluginName: p.name, pluginRoot: p.root, scope: p.scope, servers: mcp });
53
+ }
54
+ }
55
+ return { codeEntries, mcpServers, warnings };
56
+ }
57
+ /** Mode-layer entry: resolve policy + load approved code. The ONLY function
58
+ * modes call (spec: policy in mode layer, runtime mounts only). */
59
+ export async function assemblePluginRuntime(workspaceRoot, mode) {
60
+ const resolved = resolvePluginComponents(workspaceRoot, mode);
61
+ const warnings = [...resolved.warnings];
62
+ // Standalone trust gate (spec follow-up): the workspace's own .mcp.json is
63
+ // repo-controlled; untrusted content mounts nothing (run-runtime enforces)
64
+ // and the warning tells the user how to approve it. RPC skips this — the
65
+ // product writes that file.
66
+ if (mode !== "rpc") {
67
+ const mcpJsonPath = join(workspaceRoot, ".mcp.json");
68
+ if (existsSync(mcpJsonPath) && !isFileTrusted(mcpJsonPath, readTrustedPlugins())) {
69
+ warnings.push("workspace .mcp.json untrusted: servers not mounted; run /mcp trust to approve this file");
70
+ }
71
+ }
72
+ const plugins = [];
73
+ for (const entry of resolved.codeEntries) {
74
+ const tools = [];
75
+ let hooks;
76
+ if (entry.toolsEntry) {
77
+ const r = await loadPluginCode(entry.root, entry.toolsEntry);
78
+ if (r.ok && r.tools)
79
+ tools.push(...r.tools);
80
+ else
81
+ warnings.push(`${entry.name}: ${r.error ?? "tools entry failed"}`);
82
+ warnings.push(...r.warnings.map((w) => `${entry.name}: ${w}`));
83
+ }
84
+ if (entry.hooksEntry) {
85
+ const r = await loadPluginCode(entry.root, entry.hooksEntry);
86
+ if (r.ok && r.hooks)
87
+ hooks = r.hooks;
88
+ else
89
+ warnings.push(`${entry.name}: ${r.error ?? "hooks entry failed"}`);
90
+ warnings.push(...r.warnings.map((w) => `${entry.name}: ${w}`));
91
+ }
92
+ if (tools.length > 0 || hooks) {
93
+ plugins.push({
94
+ name: `plugin:${entry.name}`,
95
+ ...(tools.length > 0 ? { tools } : {}),
96
+ ...(hooks ? { hooks } : {}),
97
+ });
98
+ }
99
+ }
100
+ return { plugins, mcpServers: resolved.mcpServers, warnings };
101
+ }
@@ -0,0 +1,24 @@
1
+ export interface TrustRecord {
2
+ readonly hash: string;
3
+ readonly trustedAt: string;
4
+ }
5
+ export type TrustedPlugins = Map<string, TrustRecord>;
6
+ /** Directory hash (spec): recursive sha256 over sorted (relpath, fileHash)
7
+ * pairs, excluding node_modules. */
8
+ export declare function computePluginHash(pluginRoot: string): string;
9
+ /** Corrupt file = empty map (all untrusted) — never throws (spec failure
10
+ * semantics). */
11
+ export declare function readTrustedPlugins(): TrustedPlugins;
12
+ export declare function writeTrustedPlugins(map: TrustedPlugins): void;
13
+ /** Record explicit user trust for a plugin root at its current hash. */
14
+ export declare function trustPlugin(pluginRoot: string): void;
15
+ /** Trust decision: approved only when the recorded hash matches the
16
+ * current content hash (any file change re-untrusts). */
17
+ export declare function isPluginTrusted(pluginRoot: string, trusted: TrustedPlugins): boolean;
18
+ /** Content hash of a single file (workspace .mcp.json gating). */
19
+ export declare function computeFileHash(path: string): string;
20
+ /** Record explicit user trust for a single file at its current hash. */
21
+ export declare function trustFile(path: string): void;
22
+ /** File trust decision: recorded hash must match current content. */
23
+ export declare function isFileTrusted(path: string, trusted: TrustedPlugins): boolean;
24
+ //# sourceMappingURL=plugin-trust.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-trust.d.ts","sourceRoot":"","sources":["../../../src/core/plugins/plugin-trust.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,MAAM,cAAc,GAAG,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAMtD;qCACqC;AACrC,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAoB5D;AAED;kBACkB;AAClB,wBAAgB,kBAAkB,IAAI,cAAc,CAkBnD;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,cAAc,GAAG,IAAI,CAI7D;AAED,wEAAwE;AACxE,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAOpD;AAED;0DAC0D;AAC1D,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAGpF;AAED,kEAAkE;AAClE,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wEAAwE;AACxE,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAI5C;AAED,qEAAqE;AACrE,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAG5E"}