@moxxy/core 0.3.0

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 (332) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +125 -0
  3. package/dist/events/factory.d.ts +6 -0
  4. package/dist/events/factory.d.ts.map +1 -0
  5. package/dist/events/factory.js +14 -0
  6. package/dist/events/factory.js.map +1 -0
  7. package/dist/events/log.d.ts +92 -0
  8. package/dist/events/log.d.ts.map +1 -0
  9. package/dist/events/log.js +244 -0
  10. package/dist/events/log.js.map +1 -0
  11. package/dist/index.d.ts +40 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +41 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/logger.d.ts +16 -0
  16. package/dist/logger.d.ts.map +1 -0
  17. package/dist/logger.js +27 -0
  18. package/dist/logger.js.map +1 -0
  19. package/dist/permissions/engine.d.ts +94 -0
  20. package/dist/permissions/engine.d.ts.map +1 -0
  21. package/dist/permissions/engine.js +203 -0
  22. package/dist/permissions/engine.js.map +1 -0
  23. package/dist/permissions/resolvers.d.ts +8 -0
  24. package/dist/permissions/resolvers.d.ts.map +1 -0
  25. package/dist/permissions/resolvers.js +8 -0
  26. package/dist/permissions/resolvers.js.map +1 -0
  27. package/dist/plugins/discovery.d.ts +9 -0
  28. package/dist/plugins/discovery.d.ts.map +1 -0
  29. package/dist/plugins/discovery.js +112 -0
  30. package/dist/plugins/discovery.js.map +1 -0
  31. package/dist/plugins/host-options.d.ts +72 -0
  32. package/dist/plugins/host-options.d.ts.map +1 -0
  33. package/dist/plugins/host-options.js +2 -0
  34. package/dist/plugins/host-options.js.map +1 -0
  35. package/dist/plugins/host.d.ts +53 -0
  36. package/dist/plugins/host.d.ts.map +1 -0
  37. package/dist/plugins/host.js +269 -0
  38. package/dist/plugins/host.js.map +1 -0
  39. package/dist/plugins/lifecycle.d.ts +31 -0
  40. package/dist/plugins/lifecycle.d.ts.map +1 -0
  41. package/dist/plugins/lifecycle.js +100 -0
  42. package/dist/plugins/lifecycle.js.map +1 -0
  43. package/dist/plugins/loader.d.ts +7 -0
  44. package/dist/plugins/loader.d.ts.map +1 -0
  45. package/dist/plugins/loader.js +68 -0
  46. package/dist/plugins/loader.js.map +1 -0
  47. package/dist/plugins/package-requirements.d.ts +16 -0
  48. package/dist/plugins/package-requirements.d.ts.map +1 -0
  49. package/dist/plugins/package-requirements.js +52 -0
  50. package/dist/plugins/package-requirements.js.map +1 -0
  51. package/dist/plugins/registry-kinds.d.ts +58 -0
  52. package/dist/plugins/registry-kinds.d.ts.map +1 -0
  53. package/dist/plugins/registry-kinds.js +139 -0
  54. package/dist/plugins/registry-kinds.js.map +1 -0
  55. package/dist/plugins/toposort.d.ts +24 -0
  56. package/dist/plugins/toposort.d.ts.map +1 -0
  57. package/dist/plugins/toposort.js +67 -0
  58. package/dist/plugins/toposort.js.map +1 -0
  59. package/dist/preferences.d.ts +40 -0
  60. package/dist/preferences.d.ts.map +1 -0
  61. package/dist/preferences.js +58 -0
  62. package/dist/preferences.js.map +1 -0
  63. package/dist/registries/active-backend-registry.d.ts +65 -0
  64. package/dist/registries/active-backend-registry.d.ts.map +1 -0
  65. package/dist/registries/active-backend-registry.js +108 -0
  66. package/dist/registries/active-backend-registry.js.map +1 -0
  67. package/dist/registries/active-def-registry.d.ts +59 -0
  68. package/dist/registries/active-def-registry.d.ts.map +1 -0
  69. package/dist/registries/active-def-registry.js +63 -0
  70. package/dist/registries/active-def-registry.js.map +1 -0
  71. package/dist/registries/agents.d.ts +16 -0
  72. package/dist/registries/agents.d.ts.map +1 -0
  73. package/dist/registries/agents.js +17 -0
  74. package/dist/registries/agents.js.map +1 -0
  75. package/dist/registries/cache-strategies.d.ts +12 -0
  76. package/dist/registries/cache-strategies.d.ts.map +1 -0
  77. package/dist/registries/cache-strategies.js +13 -0
  78. package/dist/registries/cache-strategies.js.map +1 -0
  79. package/dist/registries/channels.d.ts +16 -0
  80. package/dist/registries/channels.d.ts.map +1 -0
  81. package/dist/registries/channels.js +35 -0
  82. package/dist/registries/channels.js.map +1 -0
  83. package/dist/registries/commands.d.ts +29 -0
  84. package/dist/registries/commands.d.ts.map +1 -0
  85. package/dist/registries/commands.js +72 -0
  86. package/dist/registries/commands.js.map +1 -0
  87. package/dist/registries/compactors.d.ts +12 -0
  88. package/dist/registries/compactors.d.ts.map +1 -0
  89. package/dist/registries/compactors.js +13 -0
  90. package/dist/registries/compactors.js.map +1 -0
  91. package/dist/registries/def-map-registry.d.ts +33 -0
  92. package/dist/registries/def-map-registry.d.ts.map +1 -0
  93. package/dist/registries/def-map-registry.js +36 -0
  94. package/dist/registries/def-map-registry.js.map +1 -0
  95. package/dist/registries/embedders.d.ts +16 -0
  96. package/dist/registries/embedders.d.ts.map +1 -0
  97. package/dist/registries/embedders.js +17 -0
  98. package/dist/registries/embedders.js.map +1 -0
  99. package/dist/registries/isolators.d.ts +21 -0
  100. package/dist/registries/isolators.d.ts.map +1 -0
  101. package/dist/registries/isolators.js +32 -0
  102. package/dist/registries/isolators.js.map +1 -0
  103. package/dist/registries/modes.d.ts +28 -0
  104. package/dist/registries/modes.d.ts.map +1 -0
  105. package/dist/registries/modes.js +81 -0
  106. package/dist/registries/modes.js.map +1 -0
  107. package/dist/registries/providers.d.ts +44 -0
  108. package/dist/registries/providers.d.ts.map +1 -0
  109. package/dist/registries/providers.js +95 -0
  110. package/dist/registries/providers.js.map +1 -0
  111. package/dist/registries/skills.d.ts +24 -0
  112. package/dist/registries/skills.d.ts.map +1 -0
  113. package/dist/registries/skills.js +81 -0
  114. package/dist/registries/skills.js.map +1 -0
  115. package/dist/registries/surfaces.d.ts +13 -0
  116. package/dist/registries/surfaces.d.ts.map +1 -0
  117. package/dist/registries/surfaces.js +14 -0
  118. package/dist/registries/surfaces.js.map +1 -0
  119. package/dist/registries/synthesizers.d.ts +26 -0
  120. package/dist/registries/synthesizers.d.ts.map +1 -0
  121. package/dist/registries/synthesizers.js +34 -0
  122. package/dist/registries/synthesizers.js.map +1 -0
  123. package/dist/registries/tools.d.ts +49 -0
  124. package/dist/registries/tools.d.ts.map +1 -0
  125. package/dist/registries/tools.js +98 -0
  126. package/dist/registries/tools.js.map +1 -0
  127. package/dist/registries/transcribers.d.ts +17 -0
  128. package/dist/registries/transcribers.d.ts.map +1 -0
  129. package/dist/registries/transcribers.js +18 -0
  130. package/dist/registries/transcribers.js.map +1 -0
  131. package/dist/registries/tunnel-providers.d.ts +12 -0
  132. package/dist/registries/tunnel-providers.d.ts.map +1 -0
  133. package/dist/registries/tunnel-providers.js +13 -0
  134. package/dist/registries/tunnel-providers.js.map +1 -0
  135. package/dist/registries/view-renderers.d.ts +13 -0
  136. package/dist/registries/view-renderers.d.ts.map +1 -0
  137. package/dist/registries/view-renderers.js +14 -0
  138. package/dist/registries/view-renderers.js.map +1 -0
  139. package/dist/registries/workflow-executors.d.ts +13 -0
  140. package/dist/registries/workflow-executors.d.ts.map +1 -0
  141. package/dist/registries/workflow-executors.js +14 -0
  142. package/dist/registries/workflow-executors.js.map +1 -0
  143. package/dist/requirements.d.ts +59 -0
  144. package/dist/requirements.d.ts.map +1 -0
  145. package/dist/requirements.js +149 -0
  146. package/dist/requirements.js.map +1 -0
  147. package/dist/run-turn.d.ts +6 -0
  148. package/dist/run-turn.d.ts.map +1 -0
  149. package/dist/run-turn.js +141 -0
  150. package/dist/run-turn.js.map +1 -0
  151. package/dist/session-runtime.d.ts +67 -0
  152. package/dist/session-runtime.d.ts.map +1 -0
  153. package/dist/session-runtime.js +2 -0
  154. package/dist/session-runtime.js.map +1 -0
  155. package/dist/session.d.ts +203 -0
  156. package/dist/session.d.ts.map +1 -0
  157. package/dist/session.js +399 -0
  158. package/dist/session.js.map +1 -0
  159. package/dist/sessions/persistence.d.ts +184 -0
  160. package/dist/sessions/persistence.d.ts.map +1 -0
  161. package/dist/sessions/persistence.js +442 -0
  162. package/dist/sessions/persistence.js.map +1 -0
  163. package/dist/setup-agent.d.ts +91 -0
  164. package/dist/setup-agent.d.ts.map +1 -0
  165. package/dist/setup-agent.js +138 -0
  166. package/dist/setup-agent.js.map +1 -0
  167. package/dist/skills/index.d.ts +4 -0
  168. package/dist/skills/index.d.ts.map +1 -0
  169. package/dist/skills/index.js +4 -0
  170. package/dist/skills/index.js.map +1 -0
  171. package/dist/skills/loader.d.ts +16 -0
  172. package/dist/skills/loader.d.ts.map +1 -0
  173. package/dist/skills/loader.js +99 -0
  174. package/dist/skills/loader.js.map +1 -0
  175. package/dist/skills/parse.d.ts +5 -0
  176. package/dist/skills/parse.d.ts.map +1 -0
  177. package/dist/skills/parse.js +8 -0
  178. package/dist/skills/parse.js.map +1 -0
  179. package/dist/skills/synthesize-draft.d.ts +8 -0
  180. package/dist/skills/synthesize-draft.d.ts.map +1 -0
  181. package/dist/skills/synthesize-draft.js +32 -0
  182. package/dist/skills/synthesize-draft.js.map +1 -0
  183. package/dist/skills/synthesize.d.ts +33 -0
  184. package/dist/skills/synthesize.d.ts.map +1 -0
  185. package/dist/skills/synthesize.js +241 -0
  186. package/dist/skills/synthesize.js.map +1 -0
  187. package/dist/subagents/events.d.ts +20 -0
  188. package/dist/subagents/events.d.ts.map +1 -0
  189. package/dist/subagents/events.js +146 -0
  190. package/dist/subagents/events.js.map +1 -0
  191. package/dist/subagents/registry.d.ts +40 -0
  192. package/dist/subagents/registry.d.ts.map +1 -0
  193. package/dist/subagents/registry.js +42 -0
  194. package/dist/subagents/registry.js.map +1 -0
  195. package/dist/subagents/run-child.d.ts +43 -0
  196. package/dist/subagents/run-child.d.ts.map +1 -0
  197. package/dist/subagents/run-child.js +276 -0
  198. package/dist/subagents/run-child.js.map +1 -0
  199. package/dist/subagents/spawn.d.ts +8 -0
  200. package/dist/subagents/spawn.d.ts.map +1 -0
  201. package/dist/subagents/spawn.js +8 -0
  202. package/dist/subagents/spawn.js.map +1 -0
  203. package/dist/subagents/tools.d.ts +4 -0
  204. package/dist/subagents/tools.d.ts.map +1 -0
  205. package/dist/subagents/tools.js +13 -0
  206. package/dist/subagents/tools.js.map +1 -0
  207. package/dist/subagents.d.ts +3 -0
  208. package/dist/subagents.d.ts.map +1 -0
  209. package/dist/subagents.js +3 -0
  210. package/dist/subagents.js.map +1 -0
  211. package/dist/surfaces/host.d.ts +53 -0
  212. package/dist/surfaces/host.d.ts.map +1 -0
  213. package/dist/surfaces/host.js +177 -0
  214. package/dist/surfaces/host.js.map +1 -0
  215. package/dist/tunnel/localhost.d.ts +7 -0
  216. package/dist/tunnel/localhost.d.ts.map +1 -0
  217. package/dist/tunnel/localhost.js +21 -0
  218. package/dist/tunnel/localhost.js.map +1 -0
  219. package/dist/usage-stats.d.ts +42 -0
  220. package/dist/usage-stats.d.ts.map +1 -0
  221. package/dist/usage-stats.js +83 -0
  222. package/dist/usage-stats.js.map +1 -0
  223. package/dist/view/default-renderer.d.ts +7 -0
  224. package/dist/view/default-renderer.d.ts.map +1 -0
  225. package/dist/view/default-renderer.js +14 -0
  226. package/dist/view/default-renderer.js.map +1 -0
  227. package/dist/view/parse.d.ts +7 -0
  228. package/dist/view/parse.d.ts.map +1 -0
  229. package/dist/view/parse.js +479 -0
  230. package/dist/view/parse.js.map +1 -0
  231. package/package.json +61 -0
  232. package/src/events/factory.ts +27 -0
  233. package/src/events/log.test.ts +470 -0
  234. package/src/events/log.ts +256 -0
  235. package/src/index.ts +102 -0
  236. package/src/logger.ts +46 -0
  237. package/src/permissions/engine.test.ts +206 -0
  238. package/src/permissions/engine.ts +247 -0
  239. package/src/permissions/resolvers.test.ts +40 -0
  240. package/src/permissions/resolvers.ts +17 -0
  241. package/src/plugins/discovery.test.ts +115 -0
  242. package/src/plugins/discovery.ts +124 -0
  243. package/src/plugins/host-options.ts +73 -0
  244. package/src/plugins/host-view-tunnel.test.ts +86 -0
  245. package/src/plugins/host.test.ts +434 -0
  246. package/src/plugins/host.ts +327 -0
  247. package/src/plugins/lifecycle.test.ts +187 -0
  248. package/src/plugins/lifecycle.ts +136 -0
  249. package/src/plugins/loader.ts +79 -0
  250. package/src/plugins/package-requirements.ts +50 -0
  251. package/src/plugins/registry-kinds.test.ts +162 -0
  252. package/src/plugins/registry-kinds.ts +210 -0
  253. package/src/plugins/toposort.test.ts +66 -0
  254. package/src/plugins/toposort.ts +69 -0
  255. package/src/preferences.test.ts +105 -0
  256. package/src/preferences.ts +84 -0
  257. package/src/registries/active-backend-registry.ts +142 -0
  258. package/src/registries/active-def-registry.test.ts +103 -0
  259. package/src/registries/active-def-registry.ts +97 -0
  260. package/src/registries/agents.ts +18 -0
  261. package/src/registries/cache-strategies.ts +14 -0
  262. package/src/registries/channels.ts +42 -0
  263. package/src/registries/commands.test.ts +71 -0
  264. package/src/registries/commands.ts +78 -0
  265. package/src/registries/compactors.ts +14 -0
  266. package/src/registries/def-map-registry.test.ts +67 -0
  267. package/src/registries/def-map-registry.ts +59 -0
  268. package/src/registries/embedders.test.ts +54 -0
  269. package/src/registries/embedders.ts +18 -0
  270. package/src/registries/isolators.test.ts +43 -0
  271. package/src/registries/isolators.ts +38 -0
  272. package/src/registries/modes.test.ts +66 -0
  273. package/src/registries/modes.ts +80 -0
  274. package/src/registries/providers.test.ts +50 -0
  275. package/src/registries/providers.ts +97 -0
  276. package/src/registries/semantics.test.ts +117 -0
  277. package/src/registries/skills.ts +87 -0
  278. package/src/registries/surfaces.ts +18 -0
  279. package/src/registries/synthesizers.test.ts +63 -0
  280. package/src/registries/synthesizers.ts +36 -0
  281. package/src/registries/tools.test.ts +142 -0
  282. package/src/registries/tools.ts +145 -0
  283. package/src/registries/transcribers.test.ts +70 -0
  284. package/src/registries/transcribers.ts +19 -0
  285. package/src/registries/tunnel-providers.test.ts +30 -0
  286. package/src/registries/tunnel-providers.ts +14 -0
  287. package/src/registries/view-renderers.test.ts +68 -0
  288. package/src/registries/view-renderers.ts +15 -0
  289. package/src/registries/workflow-executors.test.ts +53 -0
  290. package/src/registries/workflow-executors.ts +15 -0
  291. package/src/requirements.targetinfo.test.ts +70 -0
  292. package/src/requirements.test.ts +111 -0
  293. package/src/requirements.ts +262 -0
  294. package/src/run-turn.test.ts +178 -0
  295. package/src/run-turn.ts +156 -0
  296. package/src/session-runtime.ts +75 -0
  297. package/src/session.test.ts +216 -0
  298. package/src/session.ts +491 -0
  299. package/src/sessions/persistence.flush.test.ts +100 -0
  300. package/src/sessions/persistence.ready.test.ts +91 -0
  301. package/src/sessions/persistence.test.ts +274 -0
  302. package/src/sessions/persistence.ts +498 -0
  303. package/src/setup-agent.test.ts +72 -0
  304. package/src/setup-agent.ts +189 -0
  305. package/src/skills/index.ts +14 -0
  306. package/src/skills/loader.test.ts +102 -0
  307. package/src/skills/loader.ts +114 -0
  308. package/src/skills/parse.test.ts +59 -0
  309. package/src/skills/parse.ts +11 -0
  310. package/src/skills/synthesize-draft.test.ts +79 -0
  311. package/src/skills/synthesize-draft.ts +46 -0
  312. package/src/skills/synthesize.test.ts +170 -0
  313. package/src/skills/synthesize.ts +304 -0
  314. package/src/subagents/events.test.ts +56 -0
  315. package/src/subagents/events.ts +194 -0
  316. package/src/subagents/registry.ts +62 -0
  317. package/src/subagents/run-child.test.ts +185 -0
  318. package/src/subagents/run-child.ts +425 -0
  319. package/src/subagents/spawn.ts +12 -0
  320. package/src/subagents/tools.ts +19 -0
  321. package/src/subagents.ts +2 -0
  322. package/src/surfaces/host.test.ts +330 -0
  323. package/src/surfaces/host.ts +192 -0
  324. package/src/tunnel/localhost.test.ts +22 -0
  325. package/src/tunnel/localhost.ts +21 -0
  326. package/src/usage-stats.test.ts +77 -0
  327. package/src/usage-stats.ts +119 -0
  328. package/src/view/default-renderer.test.ts +29 -0
  329. package/src/view/default-renderer.ts +14 -0
  330. package/src/view/parse-extended.test.ts +255 -0
  331. package/src/view/parse.test.ts +232 -0
  332. package/src/view/parse.ts +520 -0
@@ -0,0 +1,79 @@
1
+ import * as path from 'node:path';
2
+ import { pathToFileURL } from 'node:url';
3
+ import type { Plugin, ResolvedPluginManifest } from '@moxxy/sdk';
4
+ import type { PluginLoader } from './host.js';
5
+
6
+ export interface JitiLoaderOptions {
7
+ readonly cwd: string;
8
+ readonly cacheBust?: () => string;
9
+ }
10
+
11
+ let jitiInstance: ((id: string) => unknown) | null = null;
12
+
13
+ async function getJiti(cwd: string): Promise<((id: string) => unknown) | null> {
14
+ if (jitiInstance) return jitiInstance;
15
+ try {
16
+ const mod = await import('jiti');
17
+ const factory = (mod as { createJiti?: (cwd: string, opts?: unknown) => (id: string) => unknown; default?: (cwd: string, opts?: unknown) => (id: string) => unknown }).createJiti ?? (mod as { default?: (cwd: string, opts?: unknown) => (id: string) => unknown }).default;
18
+ if (!factory) return null;
19
+ jitiInstance = factory(cwd, { interopDefault: true });
20
+ return jitiInstance;
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+
26
+ export function createPluginLoader(opts: JitiLoaderOptions): PluginLoader {
27
+ return {
28
+ async load(manifest: ResolvedPluginManifest): Promise<Plugin> {
29
+ const entry = path.resolve(manifest.packagePath, manifest.entry);
30
+ const isTs = entry.endsWith('.ts') || entry.endsWith('.tsx');
31
+
32
+ let mod: unknown;
33
+ if (isTs) {
34
+ const jiti = await getJiti(opts.cwd);
35
+ if (!jiti) {
36
+ throw new Error(
37
+ `Cannot load .ts plugin entry without jiti: ${entry}. Install 'jiti' as a dependency.`,
38
+ );
39
+ }
40
+ mod = jiti(entry);
41
+ } else {
42
+ const bust = opts.cacheBust?.() ?? `${Date.now()}-${Math.random()}`;
43
+ const url = `${pathToFileURL(entry).href}?v=${bust}`;
44
+ mod = await import(url);
45
+ }
46
+
47
+ const plugin = extractPlugin(mod);
48
+ if (!plugin) {
49
+ throw new Error(
50
+ `Plugin entry did not export a valid Plugin (default export with __moxxy === 'plugin'): ${entry}`,
51
+ );
52
+ }
53
+ // The runtime-reported version is the package.json version — the single
54
+ // source of truth. Plugin authors hardcode a placeholder `version` in
55
+ // definePlugin (commonly '0.0.0'), so stamp the manifest's packageVersion
56
+ // here; otherwise `moxxy plugins list` and PluginRegisteredEvent lie.
57
+ if (manifest.packageVersion && plugin.version !== manifest.packageVersion) {
58
+ return Object.freeze({ ...plugin, version: manifest.packageVersion });
59
+ }
60
+ return plugin;
61
+ },
62
+ };
63
+ }
64
+
65
+ function extractPlugin(mod: unknown): Plugin | null {
66
+ if (!mod || typeof mod !== 'object') return null;
67
+ const candidates: unknown[] = [
68
+ (mod as { default?: unknown }).default,
69
+ (mod as { default?: { default?: unknown } }).default?.default,
70
+ mod,
71
+ (mod as { plugin?: unknown }).plugin,
72
+ ];
73
+ for (const c of candidates) {
74
+ if (c && typeof c === 'object' && (c as { __moxxy?: string }).__moxxy === 'plugin') {
75
+ return c as Plugin;
76
+ }
77
+ }
78
+ return null;
79
+ }
@@ -0,0 +1,50 @@
1
+ import { createRequire } from 'node:module';
2
+ import { promises as fs } from 'node:fs';
3
+ import { moxxyPackageSchema, type MoxxyRequirement } from '@moxxy/sdk';
4
+
5
+ /**
6
+ * Read `moxxy.requirements` from a package's `package.json`, resolved by
7
+ * name from the given working directory. Returns an empty array when:
8
+ * - the package can't be resolved (not installed in the cwd's resolution path)
9
+ * - the package has no `moxxy` block
10
+ * - the `moxxy` block exists but declares no requirements
11
+ * - the `moxxy` block fails schema validation
12
+ *
13
+ * Errors are deliberately swallowed because this is used during boot
14
+ * for every static builtin: a missing or malformed package.json on one
15
+ * builtin shouldn't crash the CLI. The plugin host's registration gate
16
+ * still rejects plugins whose declared requirements aren't satisfied.
17
+ */
18
+ export async function readPackageMoxxyRequirements(
19
+ packageName: string,
20
+ fromDir: string,
21
+ ): Promise<ReadonlyArray<MoxxyRequirement>> {
22
+ const pkgJsonPath = resolvePackageJson(packageName, fromDir);
23
+ if (!pkgJsonPath) return [];
24
+ let raw: string;
25
+ try {
26
+ raw = await fs.readFile(pkgJsonPath, 'utf8');
27
+ } catch {
28
+ return [];
29
+ }
30
+ let parsedJson: unknown;
31
+ try {
32
+ parsedJson = JSON.parse(raw);
33
+ } catch {
34
+ return [];
35
+ }
36
+ const moxxyBlock = (parsedJson as { moxxy?: unknown }).moxxy;
37
+ if (!moxxyBlock) return [];
38
+ const parsed = moxxyPackageSchema.safeParse(moxxyBlock);
39
+ if (!parsed.success) return [];
40
+ return parsed.data.requirements ?? [];
41
+ }
42
+
43
+ function resolvePackageJson(packageName: string, fromDir: string): string | null {
44
+ const require_ = createRequire(`${fromDir.replace(/\/+$/, '')}/`);
45
+ try {
46
+ return require_.resolve(`${packageName}/package.json`);
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
@@ -0,0 +1,162 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { z } from 'zod';
3
+ import {
4
+ defineAgent,
5
+ defineCacheStrategy,
6
+ defineChannel,
7
+ defineCommand,
8
+ defineCompactor,
9
+ defineEmbedder,
10
+ defineMode,
11
+ definePlugin,
12
+ defineProvider,
13
+ defineSurface,
14
+ defineSynthesizer,
15
+ defineTool,
16
+ defineTranscriber,
17
+ defineTunnelProvider,
18
+ defineViewRenderer,
19
+ defineWorkflowExecutor,
20
+ type Isolator,
21
+ } from '@moxxy/sdk';
22
+ import { silentLogger } from '../logger.js';
23
+ import { ToolRegistryImpl } from '../registries/tools.js';
24
+ import { ProviderRegistry } from '../registries/providers.js';
25
+ import { ModeRegistry } from '../registries/modes.js';
26
+ import { CompactorRegistry } from '../registries/compactors.js';
27
+ import { CacheStrategyRegistry } from '../registries/cache-strategies.js';
28
+ import { ViewRendererRegistry } from '../registries/view-renderers.js';
29
+ import { TunnelProviderRegistry } from '../registries/tunnel-providers.js';
30
+ import { ChannelRegistryImpl } from '../registries/channels.js';
31
+ import { SurfaceRegistryImpl } from '../registries/surfaces.js';
32
+ import { AgentRegistry } from '../registries/agents.js';
33
+ import { CommandRegistry } from '../registries/commands.js';
34
+ import { TranscriberRegistry } from '../registries/transcribers.js';
35
+ import { SynthesizerRegistry } from '../registries/synthesizers.js';
36
+ import { EmbedderRegistry } from '../registries/embedders.js';
37
+ import { IsolatorRegistry } from '../registries/isolators.js';
38
+ import { WorkflowExecutorRegistry } from '../registries/workflow-executors.js';
39
+ import { RequirementRegistry } from '../requirements.js';
40
+ import { HookDispatcherImpl } from './lifecycle.js';
41
+ import { PluginHost } from './host.js';
42
+ import { REGISTRY_KINDS } from './registry-kinds.js';
43
+
44
+ // One minimal def per contribution kind, keyed by REGISTRY_KINDS `kind`.
45
+ const isolator: Isolator = {
46
+ name: 'iso-1',
47
+ strength: 'none',
48
+ run: async (_call, handler) => handler(undefined),
49
+ };
50
+
51
+ const everyKindPlugin = definePlugin({
52
+ name: 'every-kind',
53
+ tools: [defineTool({ name: 'tool-1', description: '', inputSchema: z.any(), handler: () => null })],
54
+ providers: [
55
+ defineProvider({
56
+ name: 'prov-1',
57
+ models: [],
58
+ createClient: () => ({ name: 'prov-1', models: [], stream: async function* () {}, countTokens: async () => 0 }),
59
+ }),
60
+ ],
61
+ modes: [defineMode({ name: 'mode-1', run: async function* () {} })],
62
+ compactors: [defineCompactor({ name: 'comp-1', shouldCompact: () => false, compact: async () => ({}) as never })],
63
+ cacheStrategies: [defineCacheStrategy({ name: 'cs-1', plan: () => [] })],
64
+ viewRenderers: [defineViewRenderer({ name: 'vr-1', allowList: [], parse: () => ({ ok: false, errors: [] }), validate: () => [] })],
65
+ tunnelProviders: [defineTunnelProvider({ name: 'tp-1', open: () => Promise.resolve({ url: 'http://x', close: () => Promise.resolve() }) })],
66
+ channels: [defineChannel({ name: 'chan-1', description: '', create: () => ({ start: async () => {}, stop: async () => {} }) as never })],
67
+ surfaces: [defineSurface({ kind: 'surf-1', open: () => ({ kind: 'surf-1' }) as never })],
68
+ agents: [defineAgent({ name: 'agent-1', description: '' })],
69
+ commands: [defineCommand({ name: 'cmd-1', description: '', handler: () => ({ text: '' }) as never })],
70
+ transcribers: [defineTranscriber({ name: 'tr-1', createClient: () => ({}) as never })],
71
+ synthesizers: [defineSynthesizer({ name: 'synth-1', create: () => ({}) as never })],
72
+ embedders: [defineEmbedder({ name: 'emb-1', createClient: () => ({}) as never })],
73
+ isolators: [isolator],
74
+ workflowExecutors: [defineWorkflowExecutor({ name: 'wfx-1', run: async () => ({}) as never })],
75
+ });
76
+
77
+ function makeHost() {
78
+ const registries = {
79
+ tools: new ToolRegistryImpl({ logger: silentLogger, cwd: '/tmp' }),
80
+ providers: new ProviderRegistry(),
81
+ modes: new ModeRegistry(),
82
+ compactors: new CompactorRegistry(),
83
+ cacheStrategies: new CacheStrategyRegistry(),
84
+ viewRenderers: new ViewRendererRegistry(),
85
+ tunnelProviders: new TunnelProviderRegistry(),
86
+ channels: new ChannelRegistryImpl(),
87
+ surfaces: new SurfaceRegistryImpl(),
88
+ agents: new AgentRegistry(),
89
+ commands: new CommandRegistry(),
90
+ transcribers: new TranscriberRegistry(),
91
+ synthesizers: new SynthesizerRegistry(),
92
+ embedders: new EmbedderRegistry(),
93
+ isolators: new IsolatorRegistry(),
94
+ workflowExecutors: new WorkflowExecutorRegistry(),
95
+ };
96
+ const requirements = new RequirementRegistry({
97
+ tools: registries.tools,
98
+ providers: registries.providers,
99
+ modes: registries.modes,
100
+ compactors: registries.compactors,
101
+ channels: registries.channels,
102
+ agents: registries.agents,
103
+ commands: registries.commands,
104
+ transcribers: registries.transcribers,
105
+ });
106
+ const dispatcher = new HookDispatcherImpl({ logger: silentLogger });
107
+ const host = new PluginHost({
108
+ cwd: '/tmp',
109
+ logger: silentLogger,
110
+ ...registries,
111
+ requirements,
112
+ dispatcher,
113
+ });
114
+ // Map each REGISTRY_KINDS entry to its concrete registry instance so the
115
+ // assertions below cover every kind without a hand-maintained second list.
116
+ const byKind: Record<string, { list(): ReadonlyArray<unknown> }> = {
117
+ tool: registries.tools,
118
+ provider: registries.providers,
119
+ mode: registries.modes,
120
+ compactor: registries.compactors,
121
+ cacheStrategy: registries.cacheStrategies,
122
+ viewRenderer: registries.viewRenderers,
123
+ tunnelProvider: registries.tunnelProviders,
124
+ channel: registries.channels,
125
+ surface: registries.surfaces,
126
+ agent: registries.agents,
127
+ command: registries.commands,
128
+ transcriber: registries.transcribers,
129
+ synthesizer: registries.synthesizers,
130
+ embedder: registries.embedders,
131
+ isolator: registries.isolators,
132
+ workflowExecutor: registries.workflowExecutors,
133
+ };
134
+ return { host, byKind };
135
+ }
136
+
137
+ describe('PluginHost register/unregister lockstep (REGISTRY_KINDS)', () => {
138
+ it('the table covers every kind the test exercises', () => {
139
+ const { byKind } = makeHost();
140
+ // Guards that adding a REGISTRY_KINDS entry without updating this test (or
141
+ // vice-versa) is caught — the unload-leak assertion below is only as strong
142
+ // as this coverage.
143
+ expect(REGISTRY_KINDS.map((k) => k.kind).sort()).toEqual(Object.keys(byKind).sort());
144
+ });
145
+
146
+ it('registerStatic populates every registry kind', () => {
147
+ const { host, byKind } = makeHost();
148
+ host.registerStatic(everyKindPlugin);
149
+ for (const kind of REGISTRY_KINDS) {
150
+ expect(byKind[kind.kind]!.list().length, `${kind.kind} should be populated`).toBe(1);
151
+ }
152
+ });
153
+
154
+ it('unload reverses register for EVERY kind — no registry left populated', async () => {
155
+ const { host, byKind } = makeHost();
156
+ host.registerStatic(everyKindPlugin);
157
+ await host.unload('every-kind');
158
+ for (const kind of REGISTRY_KINDS) {
159
+ expect(byKind[kind.kind]!.list().length, `${kind.kind} should be empty after unload`).toBe(0);
160
+ }
161
+ });
162
+ });
@@ -0,0 +1,210 @@
1
+ import type {
2
+ AgentDef,
3
+ CacheStrategyDef,
4
+ ChannelDef,
5
+ CommandDef,
6
+ CompactorDef,
7
+ ModeDef,
8
+ Plugin,
9
+ ProviderDef,
10
+ SurfaceDef,
11
+ ToolDef,
12
+ TranscriberDef,
13
+ SynthesizerDef,
14
+ EmbedderDef,
15
+ Isolator,
16
+ ViewRendererDef,
17
+ TunnelProviderDef,
18
+ WorkflowExecutorDef,
19
+ } from '@moxxy/sdk';
20
+ import type { PluginHostOptions } from './host-options.js';
21
+
22
+ /**
23
+ * The per-registry record of the names a loaded plugin contributed. Keyed by
24
+ * the {@link LoadedRecord} field each kind writes to. PluginHost stores one
25
+ * `LoadedRecord` per loaded plugin so `unload` knows exactly which names to
26
+ * unregister from each registry.
27
+ */
28
+ export interface RegistryNameRecord {
29
+ readonly toolNames: ReadonlyArray<string>;
30
+ readonly providerNames: ReadonlyArray<string>;
31
+ readonly modeNames: ReadonlyArray<string>;
32
+ readonly compactorNames: ReadonlyArray<string>;
33
+ readonly cacheStrategyNames: ReadonlyArray<string>;
34
+ readonly viewRendererNames: ReadonlyArray<string>;
35
+ readonly tunnelProviderNames: ReadonlyArray<string>;
36
+ readonly channelNames: ReadonlyArray<string>;
37
+ readonly surfaceNames: ReadonlyArray<string>;
38
+ readonly agentNames: ReadonlyArray<string>;
39
+ readonly commandNames: ReadonlyArray<string>;
40
+ readonly transcriberNames: ReadonlyArray<string>;
41
+ readonly synthesizerNames: ReadonlyArray<string>;
42
+ readonly embedderNames: ReadonlyArray<string>;
43
+ readonly isolatorNames: ReadonlyArray<string>;
44
+ readonly workflowExecutorNames: ReadonlyArray<string>;
45
+ }
46
+
47
+ /**
48
+ * A single contribution kind, mapping a plugin field + registry to the
49
+ * register / unregister / name-extraction operations PluginHost performs for
50
+ * it. Driving register and unregister from this one table keeps the two sides
51
+ * in lockstep — adding a registry kind is one entry, not edits to two parallel
52
+ * 16-line lists.
53
+ *
54
+ * `TDef` is the contribution def type for this kind. The table is declared with
55
+ * heterogeneous defs, so it is widened to `RegistryKind<any>` for iteration;
56
+ * each entry's closures stay individually type-checked against their own def.
57
+ */
58
+ export interface RegistryKind<TDef> {
59
+ /** Stable identifier for the kind (used by tests/diagnostics). */
60
+ readonly kind: string;
61
+ /** {@link RegistryNameRecord} field this kind writes its names to. */
62
+ readonly recordField: keyof RegistryNameRecord;
63
+ /** The defs this plugin contributes for this kind. */
64
+ readonly defs: (plugin: Plugin) => ReadonlyArray<TDef>;
65
+ /** Extract the registry key from a def (mostly `.name`; surfaces use `.kind`). */
66
+ readonly nameOf: (def: TDef) => string;
67
+ /** Register a single def into its registry (uses `register` or `replace`). */
68
+ readonly register: (opts: PluginHostOptions, def: TDef) => void;
69
+ /** Remove a previously-registered name from its registry. */
70
+ readonly unregister: (opts: PluginHostOptions, name: string) => void;
71
+ }
72
+
73
+ /**
74
+ * The authoritative ordered list of contribution kinds. The ORDER here is the
75
+ * exact register order `applyPlugin` used and the exact unregister order
76
+ * `unload` used; preserve it when editing.
77
+ */
78
+ export const REGISTRY_KINDS: ReadonlyArray<RegistryKind<unknown>> = [
79
+ {
80
+ kind: 'tool',
81
+ recordField: 'toolNames',
82
+ defs: (p) => p.tools ?? [],
83
+ nameOf: (t: ToolDef) => t.name,
84
+ register: (o, t: ToolDef) => o.tools.register(t),
85
+ unregister: (o, n) => o.tools.unregister(n),
86
+ } satisfies RegistryKind<ToolDef>,
87
+ {
88
+ kind: 'provider',
89
+ recordField: 'providerNames',
90
+ defs: (p) => p.providers ?? [],
91
+ nameOf: (d: ProviderDef) => d.name,
92
+ register: (o, d: ProviderDef) => o.providers.register(d),
93
+ unregister: (o, n) => o.providers.unregister(n),
94
+ } satisfies RegistryKind<ProviderDef>,
95
+ {
96
+ kind: 'mode',
97
+ recordField: 'modeNames',
98
+ defs: (p) => p.modes ?? [],
99
+ nameOf: (m: ModeDef) => m.name,
100
+ register: (o, m: ModeDef) => o.modes.register(m),
101
+ unregister: (o, n) => o.modes.unregister(n),
102
+ } satisfies RegistryKind<ModeDef>,
103
+ {
104
+ kind: 'compactor',
105
+ recordField: 'compactorNames',
106
+ defs: (p) => p.compactors ?? [],
107
+ nameOf: (c: CompactorDef) => c.name,
108
+ register: (o, c: CompactorDef) => o.compactors.register(c),
109
+ unregister: (o, n) => o.compactors.unregister(n),
110
+ } satisfies RegistryKind<CompactorDef>,
111
+ {
112
+ kind: 'cacheStrategy',
113
+ recordField: 'cacheStrategyNames',
114
+ defs: (p) => p.cacheStrategies ?? [],
115
+ nameOf: (c: CacheStrategyDef) => c.name,
116
+ register: (o, c: CacheStrategyDef) => o.cacheStrategies.register(c),
117
+ unregister: (o, n) => o.cacheStrategies.unregister(n),
118
+ } satisfies RegistryKind<CacheStrategyDef>,
119
+ {
120
+ kind: 'viewRenderer',
121
+ recordField: 'viewRendererNames',
122
+ defs: (p) => p.viewRenderers ?? [],
123
+ nameOf: (v: ViewRendererDef) => v.name,
124
+ // viewRenderers use `replace` (override-allowed) on the register side.
125
+ register: (o, v: ViewRendererDef) => o.viewRenderers.replace(v),
126
+ unregister: (o, n) => o.viewRenderers.unregister(n),
127
+ } satisfies RegistryKind<ViewRendererDef>,
128
+ {
129
+ kind: 'tunnelProvider',
130
+ recordField: 'tunnelProviderNames',
131
+ defs: (p) => p.tunnelProviders ?? [],
132
+ nameOf: (t: TunnelProviderDef) => t.name,
133
+ // tunnelProviders use `replace` (override-allowed) on the register side.
134
+ register: (o, t: TunnelProviderDef) => o.tunnelProviders.replace(t),
135
+ unregister: (o, n) => o.tunnelProviders.unregister(n),
136
+ } satisfies RegistryKind<TunnelProviderDef>,
137
+ {
138
+ kind: 'channel',
139
+ recordField: 'channelNames',
140
+ defs: (p) => p.channels ?? [],
141
+ nameOf: (c: ChannelDef) => c.name,
142
+ register: (o, c: ChannelDef) => o.channels.register(c),
143
+ unregister: (o, n) => o.channels.unregister(n),
144
+ } satisfies RegistryKind<ChannelDef>,
145
+ {
146
+ kind: 'surface',
147
+ recordField: 'surfaceNames',
148
+ defs: (p) => p.surfaces ?? [],
149
+ // surfaces are keyed by `.kind`, not `.name`.
150
+ nameOf: (s: SurfaceDef) => s.kind,
151
+ register: (o, s: SurfaceDef) => o.surfaces.register(s),
152
+ unregister: (o, n) => o.surfaces.unregister(n),
153
+ } satisfies RegistryKind<SurfaceDef>,
154
+ {
155
+ kind: 'agent',
156
+ recordField: 'agentNames',
157
+ defs: (p) => p.agents ?? [],
158
+ nameOf: (a: AgentDef) => a.name,
159
+ register: (o, a: AgentDef) => o.agents.register(a),
160
+ unregister: (o, n) => o.agents.unregister(n),
161
+ } satisfies RegistryKind<AgentDef>,
162
+ {
163
+ kind: 'command',
164
+ recordField: 'commandNames',
165
+ defs: (p) => p.commands ?? [],
166
+ nameOf: (c: CommandDef) => c.name,
167
+ register: (o, c: CommandDef) => o.commands.register(c),
168
+ unregister: (o, n) => o.commands.unregister(n),
169
+ } satisfies RegistryKind<CommandDef>,
170
+ {
171
+ kind: 'transcriber',
172
+ recordField: 'transcriberNames',
173
+ defs: (p) => p.transcribers ?? [],
174
+ nameOf: (t: TranscriberDef) => t.name,
175
+ register: (o, t: TranscriberDef) => o.transcribers.register(t),
176
+ unregister: (o, n) => o.transcribers.unregister(n),
177
+ } satisfies RegistryKind<TranscriberDef>,
178
+ {
179
+ kind: 'synthesizer',
180
+ recordField: 'synthesizerNames',
181
+ defs: (p) => p.synthesizers ?? [],
182
+ nameOf: (s: SynthesizerDef) => s.name,
183
+ register: (o, s: SynthesizerDef) => o.synthesizers.register(s),
184
+ unregister: (o, n) => o.synthesizers.unregister(n),
185
+ } satisfies RegistryKind<SynthesizerDef>,
186
+ {
187
+ kind: 'embedder',
188
+ recordField: 'embedderNames',
189
+ defs: (p) => p.embedders ?? [],
190
+ nameOf: (e: EmbedderDef) => e.name,
191
+ register: (o, e: EmbedderDef) => o.embedders.register(e),
192
+ unregister: (o, n) => o.embedders.unregister(n),
193
+ } satisfies RegistryKind<EmbedderDef>,
194
+ {
195
+ kind: 'isolator',
196
+ recordField: 'isolatorNames',
197
+ defs: (p) => p.isolators ?? [],
198
+ nameOf: (i: Isolator) => i.name,
199
+ register: (o, i: Isolator) => o.isolators.register(i),
200
+ unregister: (o, n) => o.isolators.unregister(n),
201
+ } satisfies RegistryKind<Isolator>,
202
+ {
203
+ kind: 'workflowExecutor',
204
+ recordField: 'workflowExecutorNames',
205
+ defs: (p) => p.workflowExecutors ?? [],
206
+ nameOf: (w: WorkflowExecutorDef) => w.name,
207
+ register: (o, w: WorkflowExecutorDef) => o.workflowExecutors.register(w),
208
+ unregister: (o, n) => o.workflowExecutors.unregister(n),
209
+ } satisfies RegistryKind<WorkflowExecutorDef>,
210
+ ] as ReadonlyArray<RegistryKind<unknown>>;
@@ -0,0 +1,66 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { ResolvedPluginManifest } from '@moxxy/sdk';
3
+ import { PluginCycleError, toposortPluginManifests } from './toposort.js';
4
+
5
+ const m = (
6
+ packageName: string,
7
+ deps: ReadonlyArray<string> = [],
8
+ extras: ReadonlyArray<{ kind: 'runtime' | 'provider'; name: string }> = [],
9
+ ): ResolvedPluginManifest => ({
10
+ entry: './dist/index.js',
11
+ packageName,
12
+ packageVersion: '0.0.0',
13
+ packagePath: `/tmp/${packageName}`,
14
+ requirements: [
15
+ ...deps.map((d) => ({ kind: 'plugin' as const, name: d })),
16
+ ...extras,
17
+ ],
18
+ });
19
+
20
+ describe('toposortPluginManifests', () => {
21
+ it('orders dependencies before dependents', () => {
22
+ const ordered = toposortPluginManifests([
23
+ m('dependent', ['base']),
24
+ m('base'),
25
+ ]);
26
+
27
+ expect(ordered.map((p) => p.packageName)).toEqual(['base', 'dependent']);
28
+ });
29
+
30
+ it('handles a longer chain', () => {
31
+ const ordered = toposortPluginManifests([
32
+ m('c', ['b']),
33
+ m('a'),
34
+ m('b', ['a']),
35
+ ]);
36
+
37
+ expect(ordered.map((p) => p.packageName)).toEqual(['a', 'b', 'c']);
38
+ });
39
+
40
+ it('ignores non-plugin requirement kinds', () => {
41
+ const ordered = toposortPluginManifests([
42
+ m('only-runtime', [], [{ kind: 'runtime', name: 'auth:ready' }]),
43
+ m('only-provider', [], [{ kind: 'provider', name: 'openai-codex' }]),
44
+ ]);
45
+
46
+ expect(ordered.map((p) => p.packageName).sort()).toEqual(['only-provider', 'only-runtime']);
47
+ });
48
+
49
+ it('leaves unknown plugin dependencies in place for the readiness gate to reject later', () => {
50
+ const ordered = toposortPluginManifests([
51
+ m('dependent', ['unknown']),
52
+ m('other'),
53
+ ]);
54
+
55
+ expect(ordered.map((p) => p.packageName).sort()).toEqual(['dependent', 'other']);
56
+ });
57
+
58
+ it('throws PluginCycleError on a cycle', () => {
59
+ expect(() =>
60
+ toposortPluginManifests([
61
+ m('a', ['b']),
62
+ m('b', ['a']),
63
+ ]),
64
+ ).toThrow(PluginCycleError);
65
+ });
66
+ });
@@ -0,0 +1,69 @@
1
+ import type { MoxxyRequirement, ResolvedPluginManifest } from '@moxxy/sdk';
2
+
3
+ /**
4
+ * Topological sort of discovered plugin manifests by their static
5
+ * `requirements` (from `package.json#moxxy.requirements`). Only
6
+ * `kind: 'plugin'` requirements participate; everything else (runtime,
7
+ * provider-state, etc.) is irrelevant to load order.
8
+ *
9
+ * Behavior:
10
+ * - A manifest whose required plugin name doesn't appear in `manifests`
11
+ * is left in place — the readiness check in PluginHost will reject it
12
+ * later. Toposort doesn't pre-judge.
13
+ * - Optional requirements (`optional: true`) are treated as soft edges:
14
+ * if the target is present we order around it; if not, we skip the
15
+ * edge silently.
16
+ * - Cycles throw `PluginCycleError` with the participating plugin names
17
+ * so the caller can surface a useful diagnostic instead of silently
18
+ * loading in an arbitrary order.
19
+ */
20
+ export class PluginCycleError extends Error {
21
+ constructor(readonly cycle: ReadonlyArray<string>) {
22
+ super(`Plugin requirement cycle detected: ${cycle.join(' -> ')}`);
23
+ this.name = 'PluginCycleError';
24
+ }
25
+ }
26
+
27
+ export function toposortPluginManifests(
28
+ manifests: ReadonlyArray<ResolvedPluginManifest>,
29
+ ): ReadonlyArray<ResolvedPluginManifest> {
30
+ const byPackage = new Map<string, ResolvedPluginManifest>();
31
+ for (const m of manifests) byPackage.set(m.packageName, m);
32
+
33
+ const order: ResolvedPluginManifest[] = [];
34
+ const visited = new Set<string>();
35
+ const onStack = new Set<string>();
36
+ const stack: string[] = [];
37
+
38
+ const visit = (name: string): void => {
39
+ if (visited.has(name)) return;
40
+ if (onStack.has(name)) {
41
+ const startIdx = stack.indexOf(name);
42
+ throw new PluginCycleError(stack.slice(startIdx).concat(name));
43
+ }
44
+ const manifest = byPackage.get(name);
45
+ if (!manifest) return; // unknown dep — leave for readiness gate
46
+
47
+ onStack.add(name);
48
+ stack.push(name);
49
+ for (const dep of pluginDeps(manifest.requirements)) visit(dep);
50
+ onStack.delete(name);
51
+ stack.pop();
52
+ visited.add(name);
53
+ order.push(manifest);
54
+ };
55
+
56
+ for (const m of manifests) visit(m.packageName);
57
+ return order;
58
+ }
59
+
60
+ function pluginDeps(
61
+ requirements: ReadonlyArray<MoxxyRequirement> | undefined,
62
+ ): ReadonlyArray<string> {
63
+ if (!requirements) return [];
64
+ const out: string[] = [];
65
+ for (const req of requirements) {
66
+ if (req.kind === 'plugin') out.push(req.name);
67
+ }
68
+ return out;
69
+ }