@better-openclaw/core 1.0.23 → 1.0.24

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 (254) hide show
  1. package/dist/bare-metal-partition.test.cjs +3 -4
  2. package/dist/bare-metal-partition.test.cjs.map +1 -1
  3. package/dist/bare-metal-partition.test.mjs +3 -4
  4. package/dist/bare-metal-partition.test.mjs.map +1 -1
  5. package/dist/composer.cjs +13 -1
  6. package/dist/composer.cjs.map +1 -1
  7. package/dist/composer.d.cts.map +1 -1
  8. package/dist/composer.d.mts.map +1 -1
  9. package/dist/composer.mjs +13 -1
  10. package/dist/composer.mjs.map +1 -1
  11. package/dist/composer.snapshot.test.cjs +1 -1
  12. package/dist/composer.snapshot.test.mjs +1 -1
  13. package/dist/composer.test.cjs +3 -2
  14. package/dist/composer.test.cjs.map +1 -1
  15. package/dist/composer.test.mjs +3 -2
  16. package/dist/composer.test.mjs.map +1 -1
  17. package/dist/deployers/strip-host-ports.test.cjs +1 -1
  18. package/dist/deployers/strip-host-ports.test.mjs +1 -1
  19. package/dist/generate.cjs +6 -2
  20. package/dist/generate.cjs.map +1 -1
  21. package/dist/generate.d.cts.map +1 -1
  22. package/dist/generate.d.mts.map +1 -1
  23. package/dist/generate.mjs +6 -2
  24. package/dist/generate.mjs.map +1 -1
  25. package/dist/generate.test.cjs +2 -2
  26. package/dist/generate.test.cjs.map +1 -1
  27. package/dist/generate.test.mjs +2 -2
  28. package/dist/generate.test.mjs.map +1 -1
  29. package/dist/generators/bare-metal-install.test.cjs +1 -1
  30. package/dist/generators/bare-metal-install.test.mjs +1 -1
  31. package/dist/generators/caddy.test.cjs +1 -1
  32. package/dist/generators/caddy.test.mjs +1 -1
  33. package/dist/generators/clone-repos.cjs +140 -0
  34. package/dist/generators/clone-repos.cjs.map +1 -0
  35. package/dist/generators/clone-repos.d.cts +11 -0
  36. package/dist/generators/clone-repos.d.cts.map +1 -0
  37. package/dist/generators/clone-repos.d.mts +11 -0
  38. package/dist/generators/clone-repos.d.mts.map +1 -0
  39. package/dist/generators/clone-repos.mjs +139 -0
  40. package/dist/generators/clone-repos.mjs.map +1 -0
  41. package/dist/generators/clone-repos.test.cjs +140 -0
  42. package/dist/generators/clone-repos.test.cjs.map +1 -0
  43. package/dist/generators/clone-repos.test.d.cts +1 -0
  44. package/dist/generators/clone-repos.test.d.mts +1 -0
  45. package/dist/generators/clone-repos.test.mjs +141 -0
  46. package/dist/generators/clone-repos.test.mjs.map +1 -0
  47. package/dist/generators/env.test.cjs +1 -1
  48. package/dist/generators/env.test.mjs +1 -1
  49. package/dist/generators/health-check.test.cjs +1 -1
  50. package/dist/generators/health-check.test.mjs +1 -1
  51. package/dist/generators/postgres-init.cjs +20 -0
  52. package/dist/generators/postgres-init.cjs.map +1 -1
  53. package/dist/generators/postgres-init.d.cts.map +1 -1
  54. package/dist/generators/postgres-init.d.mts.map +1 -1
  55. package/dist/generators/postgres-init.mjs +20 -0
  56. package/dist/generators/postgres-init.mjs.map +1 -1
  57. package/dist/generators/scripts.cjs +332 -3
  58. package/dist/generators/scripts.cjs.map +1 -1
  59. package/dist/generators/scripts.d.cts +3 -1
  60. package/dist/generators/scripts.d.cts.map +1 -1
  61. package/dist/generators/scripts.d.mts +3 -1
  62. package/dist/generators/scripts.d.mts.map +1 -1
  63. package/dist/generators/scripts.mjs +332 -3
  64. package/dist/generators/scripts.mjs.map +1 -1
  65. package/dist/generators/scripts.test.cjs +39 -5
  66. package/dist/generators/scripts.test.cjs.map +1 -1
  67. package/dist/generators/scripts.test.mjs +39 -5
  68. package/dist/generators/scripts.test.mjs.map +1 -1
  69. package/dist/generators/stack-manifest.cjs +1 -0
  70. package/dist/generators/stack-manifest.cjs.map +1 -1
  71. package/dist/generators/stack-manifest.d.cts +3 -2
  72. package/dist/generators/stack-manifest.d.cts.map +1 -1
  73. package/dist/generators/stack-manifest.d.mts +3 -2
  74. package/dist/generators/stack-manifest.d.mts.map +1 -1
  75. package/dist/generators/stack-manifest.mjs +1 -0
  76. package/dist/generators/stack-manifest.mjs.map +1 -1
  77. package/dist/generators/traefik.test.cjs +1 -1
  78. package/dist/generators/traefik.test.mjs +1 -1
  79. package/dist/index.cjs +8 -1
  80. package/dist/index.d.cts +5 -3
  81. package/dist/index.d.mts +5 -3
  82. package/dist/index.mjs +5 -3
  83. package/dist/migrations.test.cjs +1 -1
  84. package/dist/migrations.test.mjs +1 -1
  85. package/dist/presets/registry.cjs.map +1 -1
  86. package/dist/presets/registry.d.cts.map +1 -1
  87. package/dist/presets/registry.d.mts.map +1 -1
  88. package/dist/presets/registry.mjs.map +1 -1
  89. package/dist/presets/registry.test.cjs +1 -1
  90. package/dist/presets/registry.test.mjs +1 -1
  91. package/dist/resolver.cjs +8 -0
  92. package/dist/resolver.cjs.map +1 -1
  93. package/dist/resolver.mjs +9 -1
  94. package/dist/resolver.mjs.map +1 -1
  95. package/dist/resolver.test.cjs +47 -12
  96. package/dist/resolver.test.cjs.map +1 -1
  97. package/dist/resolver.test.mjs +47 -12
  98. package/dist/resolver.test.mjs.map +1 -1
  99. package/dist/{schema-B4c64P8N.d.cts → schema-eX44HhRp.d.mts} +62 -8
  100. package/dist/schema-eX44HhRp.d.mts.map +1 -0
  101. package/dist/{schema-CXNhYci1.d.mts → schema-tn5RK8CM.d.cts} +62 -8
  102. package/dist/schema-tn5RK8CM.d.cts.map +1 -0
  103. package/dist/schema.cjs +22 -4
  104. package/dist/schema.cjs.map +1 -1
  105. package/dist/schema.d.cts +2 -2
  106. package/dist/schema.d.mts +2 -2
  107. package/dist/schema.mjs +21 -5
  108. package/dist/schema.mjs.map +1 -1
  109. package/dist/schema.test.cjs +1 -1
  110. package/dist/schema.test.mjs +1 -1
  111. package/dist/services/definitions/apptension-saas.cjs +87 -0
  112. package/dist/services/definitions/apptension-saas.cjs.map +1 -0
  113. package/dist/services/definitions/apptension-saas.d.cts +7 -0
  114. package/dist/services/definitions/apptension-saas.d.cts.map +1 -0
  115. package/dist/services/definitions/apptension-saas.d.mts +7 -0
  116. package/dist/services/definitions/apptension-saas.d.mts.map +1 -0
  117. package/dist/services/definitions/apptension-saas.mjs +86 -0
  118. package/dist/services/definitions/apptension-saas.mjs.map +1 -0
  119. package/dist/services/definitions/boxyhq-saas.cjs +88 -0
  120. package/dist/services/definitions/boxyhq-saas.cjs.map +1 -0
  121. package/dist/services/definitions/boxyhq-saas.d.cts +7 -0
  122. package/dist/services/definitions/boxyhq-saas.d.cts.map +1 -0
  123. package/dist/services/definitions/boxyhq-saas.d.mts +7 -0
  124. package/dist/services/definitions/boxyhq-saas.d.mts.map +1 -0
  125. package/dist/services/definitions/boxyhq-saas.mjs +87 -0
  126. package/dist/services/definitions/boxyhq-saas.mjs.map +1 -0
  127. package/dist/services/definitions/cmsaas-starter.cjs +86 -0
  128. package/dist/services/definitions/cmsaas-starter.cjs.map +1 -0
  129. package/dist/services/definitions/cmsaas-starter.d.cts +7 -0
  130. package/dist/services/definitions/cmsaas-starter.d.cts.map +1 -0
  131. package/dist/services/definitions/cmsaas-starter.d.mts +7 -0
  132. package/dist/services/definitions/cmsaas-starter.d.mts.map +1 -0
  133. package/dist/services/definitions/cmsaas-starter.mjs +85 -0
  134. package/dist/services/definitions/cmsaas-starter.mjs.map +1 -0
  135. package/dist/services/definitions/index.cjs +51 -36
  136. package/dist/services/definitions/index.cjs.map +1 -1
  137. package/dist/services/definitions/index.d.cts +30 -25
  138. package/dist/services/definitions/index.d.cts.map +1 -1
  139. package/dist/services/definitions/index.d.mts +30 -25
  140. package/dist/services/definitions/index.d.mts.map +1 -1
  141. package/dist/services/definitions/index.mjs +47 -37
  142. package/dist/services/definitions/index.mjs.map +1 -1
  143. package/dist/services/definitions/ixartz-saas.cjs +88 -0
  144. package/dist/services/definitions/ixartz-saas.cjs.map +1 -0
  145. package/dist/services/definitions/ixartz-saas.d.cts +7 -0
  146. package/dist/services/definitions/ixartz-saas.d.cts.map +1 -0
  147. package/dist/services/definitions/ixartz-saas.d.mts +7 -0
  148. package/dist/services/definitions/ixartz-saas.d.mts.map +1 -0
  149. package/dist/services/definitions/ixartz-saas.mjs +87 -0
  150. package/dist/services/definitions/ixartz-saas.mjs.map +1 -0
  151. package/dist/services/definitions/mission-control.cjs +16 -2
  152. package/dist/services/definitions/mission-control.cjs.map +1 -1
  153. package/dist/services/definitions/mission-control.mjs +16 -2
  154. package/dist/services/definitions/mission-control.mjs.map +1 -1
  155. package/dist/services/definitions/open-saas.cjs +81 -0
  156. package/dist/services/definitions/open-saas.cjs.map +1 -0
  157. package/dist/services/definitions/open-saas.d.cts +7 -0
  158. package/dist/services/definitions/open-saas.d.cts.map +1 -0
  159. package/dist/services/definitions/open-saas.d.mts +7 -0
  160. package/dist/services/definitions/open-saas.d.mts.map +1 -0
  161. package/dist/services/definitions/open-saas.mjs +80 -0
  162. package/dist/services/definitions/open-saas.mjs.map +1 -0
  163. package/dist/services/registry.cjs +3 -0
  164. package/dist/services/registry.cjs.map +1 -1
  165. package/dist/services/registry.d.cts.map +1 -1
  166. package/dist/services/registry.d.mts.map +1 -1
  167. package/dist/services/registry.mjs +3 -0
  168. package/dist/services/registry.mjs.map +1 -1
  169. package/dist/services/registry.test.cjs +8 -1
  170. package/dist/services/registry.test.cjs.map +1 -1
  171. package/dist/services/registry.test.mjs +8 -1
  172. package/dist/services/registry.test.mjs.map +1 -1
  173. package/dist/{skill-manifest-BVUXU0__.mjs → skill-manifest-6XhrhWsG.mjs} +49 -1
  174. package/dist/{skill-manifest--IgY9REK.cjs.map → skill-manifest-6XhrhWsG.mjs.map} +1 -1
  175. package/dist/{skill-manifest--IgY9REK.cjs → skill-manifest-B8znSsym.cjs} +49 -1
  176. package/dist/{skill-manifest-BVUXU0__.mjs.map → skill-manifest-B8znSsym.cjs.map} +1 -1
  177. package/dist/skills/registry.cjs +3 -3
  178. package/dist/skills/registry.cjs.map +1 -1
  179. package/dist/skills/registry.mjs +3 -3
  180. package/dist/skills/registry.mjs.map +1 -1
  181. package/dist/skills/skill-manifest.cjs +1 -1
  182. package/dist/skills/skill-manifest.mjs +1 -1
  183. package/dist/track-analytics.cjs +50 -0
  184. package/dist/track-analytics.cjs.map +1 -0
  185. package/dist/track-analytics.d.cts +34 -0
  186. package/dist/track-analytics.d.cts.map +1 -0
  187. package/dist/track-analytics.d.mts +34 -0
  188. package/dist/track-analytics.d.mts.map +1 -0
  189. package/dist/track-analytics.mjs +48 -0
  190. package/dist/track-analytics.mjs.map +1 -0
  191. package/dist/track-analytics.test.cjs +91 -0
  192. package/dist/track-analytics.test.cjs.map +1 -0
  193. package/dist/track-analytics.test.d.cts +1 -0
  194. package/dist/track-analytics.test.d.mts +1 -0
  195. package/dist/track-analytics.test.mjs +92 -0
  196. package/dist/track-analytics.test.mjs.map +1 -0
  197. package/dist/types.cjs +7 -0
  198. package/dist/types.cjs.map +1 -1
  199. package/dist/types.d.cts +4 -2
  200. package/dist/types.d.cts.map +1 -1
  201. package/dist/types.d.mts +4 -2
  202. package/dist/types.d.mts.map +1 -1
  203. package/dist/types.mjs +7 -0
  204. package/dist/types.mjs.map +1 -1
  205. package/dist/validator.test.cjs +1 -1
  206. package/dist/validator.test.mjs +1 -1
  207. package/dist/version-manager.cjs +1 -1
  208. package/dist/version-manager.cjs.map +1 -1
  209. package/dist/version-manager.mjs +1 -1
  210. package/dist/version-manager.mjs.map +1 -1
  211. package/dist/version-manager.test.cjs +7 -5
  212. package/dist/version-manager.test.cjs.map +1 -1
  213. package/dist/version-manager.test.mjs +7 -5
  214. package/dist/version-manager.test.mjs.map +1 -1
  215. package/dist/{vi.2VT5v0um-DvC3SVNc.mjs → vi.2VT5v0um-C_jmO7m2.mjs} +5 -5
  216. package/dist/{vi.2VT5v0um-DvC3SVNc.mjs.map → vi.2VT5v0um-C_jmO7m2.mjs.map} +1 -1
  217. package/dist/{vi.2VT5v0um-CRqXre87.cjs → vi.2VT5v0um-iVBt6Fyq.cjs} +5 -5
  218. package/dist/{vi.2VT5v0um-CRqXre87.cjs.map → vi.2VT5v0um-iVBt6Fyq.cjs.map} +1 -1
  219. package/package.json +1 -1
  220. package/src/__snapshots__/composer.snapshot.test.ts.snap +155 -0
  221. package/src/bare-metal-partition.test.ts +4 -3
  222. package/src/composer.test.ts +4 -2
  223. package/src/composer.ts +20 -1
  224. package/src/generate.test.ts +2 -1
  225. package/src/generate.ts +10 -1
  226. package/src/generators/clone-repos.test.ts +154 -0
  227. package/src/generators/clone-repos.ts +159 -0
  228. package/src/generators/postgres-init.ts +17 -0
  229. package/src/generators/scripts.test.ts +52 -4
  230. package/src/generators/scripts.ts +351 -3
  231. package/src/generators/stack-manifest.ts +4 -2
  232. package/src/index.ts +8 -0
  233. package/src/presets/registry.ts +241 -329
  234. package/src/resolver.test.ts +53 -15
  235. package/src/resolver.ts +13 -1
  236. package/src/schema.ts +33 -4
  237. package/src/services/definitions/apptension-saas.ts +84 -0
  238. package/src/services/definitions/boxyhq-saas.ts +84 -0
  239. package/src/services/definitions/cmsaas-starter.ts +84 -0
  240. package/src/services/definitions/index.ts +90 -70
  241. package/src/services/definitions/ixartz-saas.ts +84 -0
  242. package/src/services/definitions/mission-control.ts +19 -2
  243. package/src/services/definitions/open-saas.ts +79 -0
  244. package/src/services/registry.test.ts +8 -0
  245. package/src/services/registry.ts +7 -0
  246. package/src/skills/manifest.json +64 -0
  247. package/src/skills/registry.ts +3 -3
  248. package/src/track-analytics.test.ts +82 -0
  249. package/src/track-analytics.ts +76 -0
  250. package/src/types.ts +11 -0
  251. package/src/version-manager.test.ts +10 -5
  252. package/src/version-manager.ts +1 -1
  253. package/dist/schema-B4c64P8N.d.cts.map +0 -1
  254. package/dist/schema-CXNhYci1.d.mts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"resolver.cjs","names":["getSkillPackById","getServiceById"],"sources":["../src/resolver.ts"],"sourcesContent":["import { getServiceById } from \"./services/registry.js\";\nimport { getSkillPackById } from \"./skills/registry.js\";\nimport type {\n\tAddedDependency,\n\tResolvedService,\n\tResolverError,\n\tResolverInput,\n\tResolverOutput,\n\tServiceDefinition,\n\tWarning,\n} from \"./types.js\";\n\nexport interface MemoryThresholds {\n\tinfo: number;\n\twarning: number;\n\tcritical: number;\n}\n\nconst DEFAULT_MEMORY_THRESHOLDS: MemoryThresholds = {\n\tinfo: 2048,\n\twarning: 4096,\n\tcritical: 8192,\n};\n\n/**\n * Resolves user selections into a complete, valid service list.\n *\n * Algorithm:\n * 1. Expand skill pack requirements into service list\n * 2. Resolve transitive `requires` dependencies (iterate until stable)\n * 3. Detect `conflictsWith` violations\n * 4. Check platform compatibility and GPU requirements\n * 5. Estimate total memory (sum minMemoryMB)\n * 6. Deduplicate\n * 7. Topological sort by dependency graph, alphabetical for ties\n *\n * Deterministic: same input -> same output, always.\n */\nexport function resolve(input: ResolverInput): ResolverOutput {\n\tconst addedDependencies: AddedDependency[] = [];\n\tconst warnings: Warning[] = [];\n\tconst errors: ResolverError[] = [];\n\n\t// Track all service IDs needed\n\tconst serviceIds = new Set<string>(input.services);\n\tconst serviceAddedBy = new Map<string, ResolvedService[\"addedBy\"]>();\n\n\t// Mark user-selected services\n\tfor (const id of input.services) {\n\t\tserviceAddedBy.set(id, \"user\");\n\t}\n\n\t// 1. Expand skill pack requirements\n\tfor (const packId of input.skillPacks) {\n\t\tconst pack = getSkillPackById(packId);\n\t\tif (!pack) {\n\t\t\terrors.push({\n\t\t\t\ttype: \"unknown_skill_pack\",\n\t\t\t\tmessage: `Unknown skill pack: \"${packId}\"`,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tfor (const requiredService of pack.requiredServices) {\n\t\t\tif (!serviceIds.has(requiredService)) {\n\t\t\t\tserviceIds.add(requiredService);\n\t\t\t\tserviceAddedBy.set(requiredService, \"skill-pack\");\n\t\t\t\taddedDependencies.push({\n\t\t\t\t\tservice: requiredService,\n\t\t\t\t\treason: `Required by skill pack: ${pack.name}`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Add proxy if specified\n\tif (input.proxy && input.proxy !== \"none\") {\n\t\tif (!serviceIds.has(input.proxy)) {\n\t\t\tserviceIds.add(input.proxy);\n\t\t\tserviceAddedBy.set(input.proxy, \"proxy\");\n\t\t\taddedDependencies.push({\n\t\t\t\tservice: input.proxy,\n\t\t\t\treason: `Selected as reverse proxy`,\n\t\t\t});\n\t\t}\n\t}\n\n\t// Add monitoring stack if requested\n\tif (input.monitoring) {\n\t\tconst monitoringServices = [\"uptime-kuma\", \"grafana\", \"prometheus\"];\n\t\tfor (const svc of monitoringServices) {\n\t\t\tif (!serviceIds.has(svc)) {\n\t\t\t\tserviceIds.add(svc);\n\t\t\t\tserviceAddedBy.set(svc, \"monitoring\");\n\t\t\t\taddedDependencies.push({\n\t\t\t\t\tservice: svc,\n\t\t\t\t\treason: \"Included with monitoring stack\",\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate all service IDs exist\n\tconst unknownIds: string[] = [];\n\tfor (const id of serviceIds) {\n\t\tif (!getServiceById(id)) {\n\t\t\tunknownIds.push(id);\n\t\t}\n\t}\n\tif (unknownIds.length > 0) {\n\t\tfor (const id of unknownIds) {\n\t\t\terrors.push({\n\t\t\t\ttype: \"unknown_service\",\n\t\t\t\tmessage: `Unknown service: \"${id}\"`,\n\t\t\t});\n\t\t\tserviceIds.delete(id);\n\t\t}\n\t}\n\n\t// 2. Resolve transitive dependencies (iterate until stable)\n\tlet changed = true;\n\tconst maxIterations = 50; // safety bound\n\tlet iteration = 0;\n\twhile (changed && iteration < maxIterations) {\n\t\tchanged = false;\n\t\titeration++;\n\t\tfor (const id of [...serviceIds]) {\n\t\t\tconst def = getServiceById(id);\n\t\t\tif (!def) continue;\n\t\t\tfor (const reqId of def.requires) {\n\t\t\t\tif (!serviceIds.has(reqId)) {\n\t\t\t\t\tserviceIds.add(reqId);\n\t\t\t\t\tserviceAddedBy.set(reqId, \"dependency\");\n\t\t\t\t\taddedDependencies.push({\n\t\t\t\t\t\tservice: reqId,\n\t\t\t\t\t\tserviceId: reqId,\n\t\t\t\t\t\trequiredBy: def.name,\n\t\t\t\t\t\treason: `Required by ${def.name}`,\n\t\t\t\t\t});\n\t\t\t\t\tchanged = true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (iteration >= maxIterations) {\n\t\twarnings.push({\n\t\t\ttype: \"resolution\",\n\t\t\tmessage: `Dependency resolution reached maximum iterations (${maxIterations}). Some transitive dependencies may not be fully resolved.`,\n\t\t});\n\t}\n\n\t// Check recommended services\n\tfor (const id of serviceIds) {\n\t\tconst def = getServiceById(id);\n\t\tif (!def) continue;\n\t\tfor (const recId of def.recommends) {\n\t\t\tif (!serviceIds.has(recId) && getServiceById(recId)) {\n\t\t\t\twarnings.push({\n\t\t\t\t\ttype: \"recommendation\",\n\t\t\t\t\tmessage: `${def.name} recommends \"${recId}\" for enhanced functionality`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// 3. Detect conflicts\n\tconst resolvedDefs: ServiceDefinition[] = [];\n\tfor (const id of serviceIds) {\n\t\tconst def = getServiceById(id);\n\t\tif (def) resolvedDefs.push(def);\n\t}\n\n\tfor (let i = 0; i < resolvedDefs.length; i++) {\n\t\tfor (let j = i + 1; j < resolvedDefs.length; j++) {\n\t\t\tconst a = resolvedDefs[i]!;\n\t\t\tconst b = resolvedDefs[j]!;\n\t\t\tif (a.conflictsWith.includes(b.id) || b.conflictsWith.includes(a.id)) {\n\t\t\t\terrors.push({\n\t\t\t\t\ttype: \"conflict\",\n\t\t\t\t\tmessage: `${a.name} and ${b.name} cannot be used together`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// 4. Check platform compatibility\n\tif (input.platform) {\n\t\tfor (const def of resolvedDefs) {\n\t\t\tif (def.platforms && def.platforms.length > 0 && !def.platforms.includes(input.platform)) {\n\t\t\t\twarnings.push({\n\t\t\t\t\ttype: \"platform\",\n\t\t\t\t\tmessage: `${def.name} may not be compatible with ${input.platform}. Supported: ${def.platforms.join(\", \")}`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check GPU requirements\n\tconst gpuServices = resolvedDefs.filter((d) => d.gpuRequired);\n\tif (gpuServices.length > 0 && !input.gpu) {\n\t\tfor (const svc of gpuServices) {\n\t\t\twarnings.push({\n\t\t\t\ttype: \"gpu\",\n\t\t\t\tmessage: `${svc.name} requires GPU passthrough. Enable --gpu flag for optimal performance.`,\n\t\t\t});\n\t\t}\n\t}\n\n\t// 5. Estimate total memory\n\tlet estimatedMemoryMB = 512; // Base for OpenClaw itself\n\tfor (const def of resolvedDefs) {\n\t\testimatedMemoryMB += def.minMemoryMB ?? 128;\n\t}\n\n\t// Memory warnings\n\tconst thresholds = input.memoryThresholds ?? DEFAULT_MEMORY_THRESHOLDS;\n\tif (estimatedMemoryMB > thresholds.critical) {\n\t\twarnings.push({\n\t\t\ttype: \"memory\",\n\t\t\tmessage: `Estimated ${(estimatedMemoryMB / 1024).toFixed(1)}GB RAM required. Ensure your server has sufficient resources.`,\n\t\t});\n\t} else if (estimatedMemoryMB > thresholds.warning) {\n\t\twarnings.push({\n\t\t\ttype: \"memory\",\n\t\t\tmessage: `Estimated ${(estimatedMemoryMB / 1024).toFixed(1)}GB RAM required. A server with at least 8GB RAM is recommended.`,\n\t\t});\n\t} else if (estimatedMemoryMB > thresholds.info) {\n\t\twarnings.push({\n\t\t\ttype: \"memory\",\n\t\t\tmessage: `Estimated ${(estimatedMemoryMB / 1024).toFixed(1)}GB RAM required.`,\n\t\t});\n\t}\n\n\t// 7. Topological sort by dependency graph\n\tconst sorted = topologicalSort(resolvedDefs);\n\n\t// Build final resolved services list\n\tconst services: ResolvedService[] = sorted.map((def) => ({\n\t\tdefinition: def,\n\t\taddedBy: serviceAddedBy.get(def.id) ?? \"user\",\n\t}));\n\n\tconst isValid = errors.length === 0;\n\n\treturn {\n\t\tservices,\n\t\taddedDependencies,\n\t\tremovedConflicts: [],\n\t\twarnings,\n\t\terrors,\n\t\tisValid,\n\t\testimatedMemoryMB,\n\t\taiProviders: input.aiProviders ?? [],\n\t\tgsdRuntimes: [],\n\t};\n}\n\n/**\n * Topological sort using Kahn's algorithm.\n * Ties broken alphabetically by service ID for determinism.\n */\nfunction topologicalSort(definitions: ServiceDefinition[]): ServiceDefinition[] {\n\tconst idSet = new Set(definitions.map((d) => d.id));\n\tconst graph = new Map<string, string[]>(); // id -> list of IDs that depend on it\n\tconst inDegree = new Map<string, number>();\n\n\t// Initialize\n\tfor (const def of definitions) {\n\t\tgraph.set(def.id, []);\n\t\tinDegree.set(def.id, 0);\n\t}\n\n\t// Build edges: if A requires B, then B -> A (B must come before A)\n\tfor (const def of definitions) {\n\t\tfor (const reqId of [...def.requires, ...def.dependsOn]) {\n\t\t\tif (idSet.has(reqId)) {\n\t\t\t\tgraph.get(reqId)?.push(def.id);\n\t\t\t\tinDegree.set(def.id, (inDegree.get(def.id) ?? 0) + 1);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Kahn's algorithm with alphabetical tie-breaking\n\tconst queue: string[] = [];\n\tfor (const [id, deg] of inDegree) {\n\t\tif (deg === 0) queue.push(id);\n\t}\n\tqueue.sort(); // alphabetical for determinism\n\n\tconst sorted: ServiceDefinition[] = [];\n\tconst defMap = new Map(definitions.map((d) => [d.id, d]));\n\n\twhile (queue.length > 0) {\n\t\tconst id = queue.shift()!;\n\t\tconst def = defMap.get(id);\n\t\tif (def) sorted.push(def);\n\n\t\tconst neighbors = graph.get(id) ?? [];\n\t\tconst newReady: string[] = [];\n\t\tfor (const neighbor of neighbors) {\n\t\t\tconst deg = (inDegree.get(neighbor) ?? 0) - 1;\n\t\t\tinDegree.set(neighbor, deg);\n\t\t\tif (deg === 0) newReady.push(neighbor);\n\t\t}\n\t\t// Sort newly ready nodes alphabetically and add to queue in order\n\t\tnewReady.sort();\n\t\tqueue.push(...newReady);\n\t}\n\n\t// If not all nodes are in sorted, there's a cycle\n\tif (sorted.length < definitions.length) {\n\t\t// Return what we have plus remaining (cycle detected but we still produce output)\n\t\tconst sortedIds = new Set(sorted.map((d) => d.id));\n\t\tconst remaining = definitions.filter((d) => !sortedIds.has(d.id));\n\t\tremaining.sort((a, b) => a.id.localeCompare(b.id));\n\t\tsorted.push(...remaining);\n\t}\n\n\treturn sorted;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"resolver.cjs","names":["getSkillPackById","getAllServices","getServiceById"],"sources":["../src/resolver.ts"],"sourcesContent":["import { getAllServices, getServiceById } from \"./services/registry.js\";\nimport { getSkillPackById } from \"./skills/registry.js\";\nimport type {\n\tAddedDependency,\n\tResolvedService,\n\tResolverError,\n\tResolverInput,\n\tResolverOutput,\n\tServiceDefinition,\n\tWarning,\n} from \"./types.js\";\n\nexport interface MemoryThresholds {\n\tinfo: number;\n\twarning: number;\n\tcritical: number;\n}\n\nconst DEFAULT_MEMORY_THRESHOLDS: MemoryThresholds = {\n\tinfo: 2048,\n\twarning: 4096,\n\tcritical: 8192,\n};\n\n/**\n * Resolves user selections into a complete, valid service list.\n *\n * Algorithm:\n * 1. Expand skill pack requirements into service list\n * 2. Resolve transitive `requires` dependencies (iterate until stable)\n * 3. Detect `conflictsWith` violations\n * 4. Check platform compatibility and GPU requirements\n * 5. Estimate total memory (sum minMemoryMB)\n * 6. Deduplicate\n * 7. Topological sort by dependency graph, alphabetical for ties\n *\n * Deterministic: same input -> same output, always.\n */\nexport function resolve(input: ResolverInput): ResolverOutput {\n\tconst addedDependencies: AddedDependency[] = [];\n\tconst warnings: Warning[] = [];\n\tconst errors: ResolverError[] = [];\n\n\t// Track all service IDs needed\n\tconst serviceIds = new Set<string>(input.services);\n\tconst serviceAddedBy = new Map<string, ResolvedService[\"addedBy\"]>();\n\n\t// Mark user-selected services\n\tfor (const id of input.services) {\n\t\tserviceAddedBy.set(id, \"user\");\n\t}\n\n\t// 1. Expand skill pack requirements\n\tfor (const packId of input.skillPacks) {\n\t\tconst pack = getSkillPackById(packId);\n\t\tif (!pack) {\n\t\t\terrors.push({\n\t\t\t\ttype: \"unknown_skill_pack\",\n\t\t\t\tmessage: `Unknown skill pack: \"${packId}\"`,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tfor (const requiredService of pack.requiredServices) {\n\t\t\tif (!serviceIds.has(requiredService)) {\n\t\t\t\tserviceIds.add(requiredService);\n\t\t\t\tserviceAddedBy.set(requiredService, \"skill-pack\");\n\t\t\t\taddedDependencies.push({\n\t\t\t\t\tservice: requiredService,\n\t\t\t\t\treason: `Required by skill pack: ${pack.name}`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Add proxy if specified\n\tif (input.proxy && input.proxy !== \"none\") {\n\t\tif (!serviceIds.has(input.proxy)) {\n\t\t\tserviceIds.add(input.proxy);\n\t\t\tserviceAddedBy.set(input.proxy, \"proxy\");\n\t\t\taddedDependencies.push({\n\t\t\t\tservice: input.proxy,\n\t\t\t\treason: `Selected as reverse proxy`,\n\t\t\t});\n\t\t}\n\t}\n\n\t// Add monitoring stack if requested\n\tif (input.monitoring) {\n\t\tconst monitoringServices = [\"uptime-kuma\", \"grafana\", \"prometheus\"];\n\t\tfor (const svc of monitoringServices) {\n\t\t\tif (!serviceIds.has(svc)) {\n\t\t\t\tserviceIds.add(svc);\n\t\t\t\tserviceAddedBy.set(svc, \"monitoring\");\n\t\t\t\taddedDependencies.push({\n\t\t\t\t\tservice: svc,\n\t\t\t\t\treason: \"Included with monitoring stack\",\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Add mandatory platform services (mission-control, convex, tailscale, etc.)\n\tfor (const def of getAllServices()) {\n\t\tif (def.mandatory && !serviceIds.has(def.id)) {\n\t\t\tserviceIds.add(def.id);\n\t\t\tserviceAddedBy.set(def.id, \"mandatory\");\n\t\t\taddedDependencies.push({\n\t\t\t\tservice: def.id,\n\t\t\t\treason: \"Mandatory OpenClaw platform service\",\n\t\t\t});\n\t\t}\n\t}\n\n\t// Validate all service IDs exist\n\tconst unknownIds: string[] = [];\n\tfor (const id of serviceIds) {\n\t\tif (!getServiceById(id)) {\n\t\t\tunknownIds.push(id);\n\t\t}\n\t}\n\tif (unknownIds.length > 0) {\n\t\tfor (const id of unknownIds) {\n\t\t\terrors.push({\n\t\t\t\ttype: \"unknown_service\",\n\t\t\t\tmessage: `Unknown service: \"${id}\"`,\n\t\t\t});\n\t\t\tserviceIds.delete(id);\n\t\t}\n\t}\n\n\t// 2. Resolve transitive dependencies (iterate until stable)\n\tlet changed = true;\n\tconst maxIterations = 50; // safety bound\n\tlet iteration = 0;\n\twhile (changed && iteration < maxIterations) {\n\t\tchanged = false;\n\t\titeration++;\n\t\tfor (const id of [...serviceIds]) {\n\t\t\tconst def = getServiceById(id);\n\t\t\tif (!def) continue;\n\t\t\tfor (const reqId of def.requires) {\n\t\t\t\tif (!serviceIds.has(reqId)) {\n\t\t\t\t\tserviceIds.add(reqId);\n\t\t\t\t\tserviceAddedBy.set(reqId, \"dependency\");\n\t\t\t\t\taddedDependencies.push({\n\t\t\t\t\t\tservice: reqId,\n\t\t\t\t\t\tserviceId: reqId,\n\t\t\t\t\t\trequiredBy: def.name,\n\t\t\t\t\t\treason: `Required by ${def.name}`,\n\t\t\t\t\t});\n\t\t\t\t\tchanged = true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (iteration >= maxIterations) {\n\t\twarnings.push({\n\t\t\ttype: \"resolution\",\n\t\t\tmessage: `Dependency resolution reached maximum iterations (${maxIterations}). Some transitive dependencies may not be fully resolved.`,\n\t\t});\n\t}\n\n\t// Check recommended services\n\tfor (const id of serviceIds) {\n\t\tconst def = getServiceById(id);\n\t\tif (!def) continue;\n\t\tfor (const recId of def.recommends) {\n\t\t\tif (!serviceIds.has(recId) && getServiceById(recId)) {\n\t\t\t\twarnings.push({\n\t\t\t\t\ttype: \"recommendation\",\n\t\t\t\t\tmessage: `${def.name} recommends \"${recId}\" for enhanced functionality`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// 3. Detect conflicts\n\tconst resolvedDefs: ServiceDefinition[] = [];\n\tfor (const id of serviceIds) {\n\t\tconst def = getServiceById(id);\n\t\tif (def) resolvedDefs.push(def);\n\t}\n\n\tfor (let i = 0; i < resolvedDefs.length; i++) {\n\t\tfor (let j = i + 1; j < resolvedDefs.length; j++) {\n\t\t\tconst a = resolvedDefs[i]!;\n\t\t\tconst b = resolvedDefs[j]!;\n\t\t\tif (a.conflictsWith.includes(b.id) || b.conflictsWith.includes(a.id)) {\n\t\t\t\terrors.push({\n\t\t\t\t\ttype: \"conflict\",\n\t\t\t\t\tmessage: `${a.name} and ${b.name} cannot be used together`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// 4. Check platform compatibility\n\tif (input.platform) {\n\t\tfor (const def of resolvedDefs) {\n\t\t\tif (def.platforms && def.platforms.length > 0 && !def.platforms.includes(input.platform)) {\n\t\t\t\twarnings.push({\n\t\t\t\t\ttype: \"platform\",\n\t\t\t\t\tmessage: `${def.name} may not be compatible with ${input.platform}. Supported: ${def.platforms.join(\", \")}`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check GPU requirements\n\tconst gpuServices = resolvedDefs.filter((d) => d.gpuRequired);\n\tif (gpuServices.length > 0 && !input.gpu) {\n\t\tfor (const svc of gpuServices) {\n\t\t\twarnings.push({\n\t\t\t\ttype: \"gpu\",\n\t\t\t\tmessage: `${svc.name} requires GPU passthrough. Enable --gpu flag for optimal performance.`,\n\t\t\t});\n\t\t}\n\t}\n\n\t// 5. Estimate total memory\n\tlet estimatedMemoryMB = 512; // Base for OpenClaw itself\n\tfor (const def of resolvedDefs) {\n\t\testimatedMemoryMB += def.minMemoryMB ?? 128;\n\t}\n\n\t// Memory warnings\n\tconst thresholds = input.memoryThresholds ?? DEFAULT_MEMORY_THRESHOLDS;\n\tif (estimatedMemoryMB > thresholds.critical) {\n\t\twarnings.push({\n\t\t\ttype: \"memory\",\n\t\t\tmessage: `Estimated ${(estimatedMemoryMB / 1024).toFixed(1)}GB RAM required. Ensure your server has sufficient resources.`,\n\t\t});\n\t} else if (estimatedMemoryMB > thresholds.warning) {\n\t\twarnings.push({\n\t\t\ttype: \"memory\",\n\t\t\tmessage: `Estimated ${(estimatedMemoryMB / 1024).toFixed(1)}GB RAM required. A server with at least 8GB RAM is recommended.`,\n\t\t});\n\t} else if (estimatedMemoryMB > thresholds.info) {\n\t\twarnings.push({\n\t\t\ttype: \"memory\",\n\t\t\tmessage: `Estimated ${(estimatedMemoryMB / 1024).toFixed(1)}GB RAM required.`,\n\t\t});\n\t}\n\n\t// 7. Topological sort by dependency graph\n\tconst sorted = topologicalSort(resolvedDefs);\n\n\t// Build final resolved services list\n\tconst services: ResolvedService[] = sorted.map((def) => ({\n\t\tdefinition: def,\n\t\taddedBy: serviceAddedBy.get(def.id) ?? \"user\",\n\t}));\n\n\tconst isValid = errors.length === 0;\n\n\treturn {\n\t\tservices,\n\t\taddedDependencies,\n\t\tremovedConflicts: [],\n\t\twarnings,\n\t\terrors,\n\t\tisValid,\n\t\testimatedMemoryMB,\n\t\taiProviders: input.aiProviders ?? [],\n\t\tgsdRuntimes: [],\n\t};\n}\n\n/**\n * Topological sort using Kahn's algorithm.\n * Ties broken alphabetically by service ID for determinism.\n */\nfunction topologicalSort(definitions: ServiceDefinition[]): ServiceDefinition[] {\n\tconst idSet = new Set(definitions.map((d) => d.id));\n\tconst graph = new Map<string, string[]>(); // id -> list of IDs that depend on it\n\tconst inDegree = new Map<string, number>();\n\n\t// Initialize\n\tfor (const def of definitions) {\n\t\tgraph.set(def.id, []);\n\t\tinDegree.set(def.id, 0);\n\t}\n\n\t// Build edges: if A requires B, then B -> A (B must come before A)\n\tfor (const def of definitions) {\n\t\tfor (const reqId of [...def.requires, ...def.dependsOn]) {\n\t\t\tif (idSet.has(reqId)) {\n\t\t\t\tgraph.get(reqId)?.push(def.id);\n\t\t\t\tinDegree.set(def.id, (inDegree.get(def.id) ?? 0) + 1);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Kahn's algorithm with alphabetical tie-breaking\n\tconst queue: string[] = [];\n\tfor (const [id, deg] of inDegree) {\n\t\tif (deg === 0) queue.push(id);\n\t}\n\tqueue.sort(); // alphabetical for determinism\n\n\tconst sorted: ServiceDefinition[] = [];\n\tconst defMap = new Map(definitions.map((d) => [d.id, d]));\n\n\twhile (queue.length > 0) {\n\t\tconst id = queue.shift()!;\n\t\tconst def = defMap.get(id);\n\t\tif (def) sorted.push(def);\n\n\t\tconst neighbors = graph.get(id) ?? [];\n\t\tconst newReady: string[] = [];\n\t\tfor (const neighbor of neighbors) {\n\t\t\tconst deg = (inDegree.get(neighbor) ?? 0) - 1;\n\t\t\tinDegree.set(neighbor, deg);\n\t\t\tif (deg === 0) newReady.push(neighbor);\n\t\t}\n\t\t// Sort newly ready nodes alphabetically and add to queue in order\n\t\tnewReady.sort();\n\t\tqueue.push(...newReady);\n\t}\n\n\t// If not all nodes are in sorted, there's a cycle\n\tif (sorted.length < definitions.length) {\n\t\t// Return what we have plus remaining (cycle detected but we still produce output)\n\t\tconst sortedIds = new Set(sorted.map((d) => d.id));\n\t\tconst remaining = definitions.filter((d) => !sortedIds.has(d.id));\n\t\tremaining.sort((a, b) => a.id.localeCompare(b.id));\n\t\tsorted.push(...remaining);\n\t}\n\n\treturn sorted;\n}\n"],"mappings":"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"}
package/dist/resolver.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { getServiceById } from "./services/registry.mjs";
1
+ import { getAllServices, getServiceById } from "./services/registry.mjs";
2
2
  import { getSkillPackById } from "./skills/registry.mjs";
3
3
  //#region src/resolver.ts
4
4
  const DEFAULT_MEMORY_THRESHOLDS = {
@@ -69,6 +69,14 @@ function resolve(input) {
69
69
  });
70
70
  }
71
71
  }
72
+ for (const def of getAllServices()) if (def.mandatory && !serviceIds.has(def.id)) {
73
+ serviceIds.add(def.id);
74
+ serviceAddedBy.set(def.id, "mandatory");
75
+ addedDependencies.push({
76
+ service: def.id,
77
+ reason: "Mandatory OpenClaw platform service"
78
+ });
79
+ }
72
80
  const unknownIds = [];
73
81
  for (const id of serviceIds) if (!getServiceById(id)) unknownIds.push(id);
74
82
  if (unknownIds.length > 0) for (const id of unknownIds) {
@@ -1 +1 @@
1
- {"version":3,"file":"resolver.mjs","names":[],"sources":["../src/resolver.ts"],"sourcesContent":["import { getServiceById } from \"./services/registry.js\";\nimport { getSkillPackById } from \"./skills/registry.js\";\nimport type {\n\tAddedDependency,\n\tResolvedService,\n\tResolverError,\n\tResolverInput,\n\tResolverOutput,\n\tServiceDefinition,\n\tWarning,\n} from \"./types.js\";\n\nexport interface MemoryThresholds {\n\tinfo: number;\n\twarning: number;\n\tcritical: number;\n}\n\nconst DEFAULT_MEMORY_THRESHOLDS: MemoryThresholds = {\n\tinfo: 2048,\n\twarning: 4096,\n\tcritical: 8192,\n};\n\n/**\n * Resolves user selections into a complete, valid service list.\n *\n * Algorithm:\n * 1. Expand skill pack requirements into service list\n * 2. Resolve transitive `requires` dependencies (iterate until stable)\n * 3. Detect `conflictsWith` violations\n * 4. Check platform compatibility and GPU requirements\n * 5. Estimate total memory (sum minMemoryMB)\n * 6. Deduplicate\n * 7. Topological sort by dependency graph, alphabetical for ties\n *\n * Deterministic: same input -> same output, always.\n */\nexport function resolve(input: ResolverInput): ResolverOutput {\n\tconst addedDependencies: AddedDependency[] = [];\n\tconst warnings: Warning[] = [];\n\tconst errors: ResolverError[] = [];\n\n\t// Track all service IDs needed\n\tconst serviceIds = new Set<string>(input.services);\n\tconst serviceAddedBy = new Map<string, ResolvedService[\"addedBy\"]>();\n\n\t// Mark user-selected services\n\tfor (const id of input.services) {\n\t\tserviceAddedBy.set(id, \"user\");\n\t}\n\n\t// 1. Expand skill pack requirements\n\tfor (const packId of input.skillPacks) {\n\t\tconst pack = getSkillPackById(packId);\n\t\tif (!pack) {\n\t\t\terrors.push({\n\t\t\t\ttype: \"unknown_skill_pack\",\n\t\t\t\tmessage: `Unknown skill pack: \"${packId}\"`,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tfor (const requiredService of pack.requiredServices) {\n\t\t\tif (!serviceIds.has(requiredService)) {\n\t\t\t\tserviceIds.add(requiredService);\n\t\t\t\tserviceAddedBy.set(requiredService, \"skill-pack\");\n\t\t\t\taddedDependencies.push({\n\t\t\t\t\tservice: requiredService,\n\t\t\t\t\treason: `Required by skill pack: ${pack.name}`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Add proxy if specified\n\tif (input.proxy && input.proxy !== \"none\") {\n\t\tif (!serviceIds.has(input.proxy)) {\n\t\t\tserviceIds.add(input.proxy);\n\t\t\tserviceAddedBy.set(input.proxy, \"proxy\");\n\t\t\taddedDependencies.push({\n\t\t\t\tservice: input.proxy,\n\t\t\t\treason: `Selected as reverse proxy`,\n\t\t\t});\n\t\t}\n\t}\n\n\t// Add monitoring stack if requested\n\tif (input.monitoring) {\n\t\tconst monitoringServices = [\"uptime-kuma\", \"grafana\", \"prometheus\"];\n\t\tfor (const svc of monitoringServices) {\n\t\t\tif (!serviceIds.has(svc)) {\n\t\t\t\tserviceIds.add(svc);\n\t\t\t\tserviceAddedBy.set(svc, \"monitoring\");\n\t\t\t\taddedDependencies.push({\n\t\t\t\t\tservice: svc,\n\t\t\t\t\treason: \"Included with monitoring stack\",\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate all service IDs exist\n\tconst unknownIds: string[] = [];\n\tfor (const id of serviceIds) {\n\t\tif (!getServiceById(id)) {\n\t\t\tunknownIds.push(id);\n\t\t}\n\t}\n\tif (unknownIds.length > 0) {\n\t\tfor (const id of unknownIds) {\n\t\t\terrors.push({\n\t\t\t\ttype: \"unknown_service\",\n\t\t\t\tmessage: `Unknown service: \"${id}\"`,\n\t\t\t});\n\t\t\tserviceIds.delete(id);\n\t\t}\n\t}\n\n\t// 2. Resolve transitive dependencies (iterate until stable)\n\tlet changed = true;\n\tconst maxIterations = 50; // safety bound\n\tlet iteration = 0;\n\twhile (changed && iteration < maxIterations) {\n\t\tchanged = false;\n\t\titeration++;\n\t\tfor (const id of [...serviceIds]) {\n\t\t\tconst def = getServiceById(id);\n\t\t\tif (!def) continue;\n\t\t\tfor (const reqId of def.requires) {\n\t\t\t\tif (!serviceIds.has(reqId)) {\n\t\t\t\t\tserviceIds.add(reqId);\n\t\t\t\t\tserviceAddedBy.set(reqId, \"dependency\");\n\t\t\t\t\taddedDependencies.push({\n\t\t\t\t\t\tservice: reqId,\n\t\t\t\t\t\tserviceId: reqId,\n\t\t\t\t\t\trequiredBy: def.name,\n\t\t\t\t\t\treason: `Required by ${def.name}`,\n\t\t\t\t\t});\n\t\t\t\t\tchanged = true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (iteration >= maxIterations) {\n\t\twarnings.push({\n\t\t\ttype: \"resolution\",\n\t\t\tmessage: `Dependency resolution reached maximum iterations (${maxIterations}). Some transitive dependencies may not be fully resolved.`,\n\t\t});\n\t}\n\n\t// Check recommended services\n\tfor (const id of serviceIds) {\n\t\tconst def = getServiceById(id);\n\t\tif (!def) continue;\n\t\tfor (const recId of def.recommends) {\n\t\t\tif (!serviceIds.has(recId) && getServiceById(recId)) {\n\t\t\t\twarnings.push({\n\t\t\t\t\ttype: \"recommendation\",\n\t\t\t\t\tmessage: `${def.name} recommends \"${recId}\" for enhanced functionality`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// 3. Detect conflicts\n\tconst resolvedDefs: ServiceDefinition[] = [];\n\tfor (const id of serviceIds) {\n\t\tconst def = getServiceById(id);\n\t\tif (def) resolvedDefs.push(def);\n\t}\n\n\tfor (let i = 0; i < resolvedDefs.length; i++) {\n\t\tfor (let j = i + 1; j < resolvedDefs.length; j++) {\n\t\t\tconst a = resolvedDefs[i]!;\n\t\t\tconst b = resolvedDefs[j]!;\n\t\t\tif (a.conflictsWith.includes(b.id) || b.conflictsWith.includes(a.id)) {\n\t\t\t\terrors.push({\n\t\t\t\t\ttype: \"conflict\",\n\t\t\t\t\tmessage: `${a.name} and ${b.name} cannot be used together`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// 4. Check platform compatibility\n\tif (input.platform) {\n\t\tfor (const def of resolvedDefs) {\n\t\t\tif (def.platforms && def.platforms.length > 0 && !def.platforms.includes(input.platform)) {\n\t\t\t\twarnings.push({\n\t\t\t\t\ttype: \"platform\",\n\t\t\t\t\tmessage: `${def.name} may not be compatible with ${input.platform}. Supported: ${def.platforms.join(\", \")}`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check GPU requirements\n\tconst gpuServices = resolvedDefs.filter((d) => d.gpuRequired);\n\tif (gpuServices.length > 0 && !input.gpu) {\n\t\tfor (const svc of gpuServices) {\n\t\t\twarnings.push({\n\t\t\t\ttype: \"gpu\",\n\t\t\t\tmessage: `${svc.name} requires GPU passthrough. Enable --gpu flag for optimal performance.`,\n\t\t\t});\n\t\t}\n\t}\n\n\t// 5. Estimate total memory\n\tlet estimatedMemoryMB = 512; // Base for OpenClaw itself\n\tfor (const def of resolvedDefs) {\n\t\testimatedMemoryMB += def.minMemoryMB ?? 128;\n\t}\n\n\t// Memory warnings\n\tconst thresholds = input.memoryThresholds ?? DEFAULT_MEMORY_THRESHOLDS;\n\tif (estimatedMemoryMB > thresholds.critical) {\n\t\twarnings.push({\n\t\t\ttype: \"memory\",\n\t\t\tmessage: `Estimated ${(estimatedMemoryMB / 1024).toFixed(1)}GB RAM required. Ensure your server has sufficient resources.`,\n\t\t});\n\t} else if (estimatedMemoryMB > thresholds.warning) {\n\t\twarnings.push({\n\t\t\ttype: \"memory\",\n\t\t\tmessage: `Estimated ${(estimatedMemoryMB / 1024).toFixed(1)}GB RAM required. A server with at least 8GB RAM is recommended.`,\n\t\t});\n\t} else if (estimatedMemoryMB > thresholds.info) {\n\t\twarnings.push({\n\t\t\ttype: \"memory\",\n\t\t\tmessage: `Estimated ${(estimatedMemoryMB / 1024).toFixed(1)}GB RAM required.`,\n\t\t});\n\t}\n\n\t// 7. Topological sort by dependency graph\n\tconst sorted = topologicalSort(resolvedDefs);\n\n\t// Build final resolved services list\n\tconst services: ResolvedService[] = sorted.map((def) => ({\n\t\tdefinition: def,\n\t\taddedBy: serviceAddedBy.get(def.id) ?? \"user\",\n\t}));\n\n\tconst isValid = errors.length === 0;\n\n\treturn {\n\t\tservices,\n\t\taddedDependencies,\n\t\tremovedConflicts: [],\n\t\twarnings,\n\t\terrors,\n\t\tisValid,\n\t\testimatedMemoryMB,\n\t\taiProviders: input.aiProviders ?? [],\n\t\tgsdRuntimes: [],\n\t};\n}\n\n/**\n * Topological sort using Kahn's algorithm.\n * Ties broken alphabetically by service ID for determinism.\n */\nfunction topologicalSort(definitions: ServiceDefinition[]): ServiceDefinition[] {\n\tconst idSet = new Set(definitions.map((d) => d.id));\n\tconst graph = new Map<string, string[]>(); // id -> list of IDs that depend on it\n\tconst inDegree = new Map<string, number>();\n\n\t// Initialize\n\tfor (const def of definitions) {\n\t\tgraph.set(def.id, []);\n\t\tinDegree.set(def.id, 0);\n\t}\n\n\t// Build edges: if A requires B, then B -> A (B must come before A)\n\tfor (const def of definitions) {\n\t\tfor (const reqId of [...def.requires, ...def.dependsOn]) {\n\t\t\tif (idSet.has(reqId)) {\n\t\t\t\tgraph.get(reqId)?.push(def.id);\n\t\t\t\tinDegree.set(def.id, (inDegree.get(def.id) ?? 0) + 1);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Kahn's algorithm with alphabetical tie-breaking\n\tconst queue: string[] = [];\n\tfor (const [id, deg] of inDegree) {\n\t\tif (deg === 0) queue.push(id);\n\t}\n\tqueue.sort(); // alphabetical for determinism\n\n\tconst sorted: ServiceDefinition[] = [];\n\tconst defMap = new Map(definitions.map((d) => [d.id, d]));\n\n\twhile (queue.length > 0) {\n\t\tconst id = queue.shift()!;\n\t\tconst def = defMap.get(id);\n\t\tif (def) sorted.push(def);\n\n\t\tconst neighbors = graph.get(id) ?? [];\n\t\tconst newReady: string[] = [];\n\t\tfor (const neighbor of neighbors) {\n\t\t\tconst deg = (inDegree.get(neighbor) ?? 0) - 1;\n\t\t\tinDegree.set(neighbor, deg);\n\t\t\tif (deg === 0) newReady.push(neighbor);\n\t\t}\n\t\t// Sort newly ready nodes alphabetically and add to queue in order\n\t\tnewReady.sort();\n\t\tqueue.push(...newReady);\n\t}\n\n\t// If not all nodes are in sorted, there's a cycle\n\tif (sorted.length < definitions.length) {\n\t\t// Return what we have plus remaining (cycle detected but we still produce output)\n\t\tconst sortedIds = new Set(sorted.map((d) => d.id));\n\t\tconst remaining = definitions.filter((d) => !sortedIds.has(d.id));\n\t\tremaining.sort((a, b) => a.id.localeCompare(b.id));\n\t\tsorted.push(...remaining);\n\t}\n\n\treturn sorted;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"resolver.mjs","names":[],"sources":["../src/resolver.ts"],"sourcesContent":["import { getAllServices, getServiceById } from \"./services/registry.js\";\nimport { getSkillPackById } from \"./skills/registry.js\";\nimport type {\n\tAddedDependency,\n\tResolvedService,\n\tResolverError,\n\tResolverInput,\n\tResolverOutput,\n\tServiceDefinition,\n\tWarning,\n} from \"./types.js\";\n\nexport interface MemoryThresholds {\n\tinfo: number;\n\twarning: number;\n\tcritical: number;\n}\n\nconst DEFAULT_MEMORY_THRESHOLDS: MemoryThresholds = {\n\tinfo: 2048,\n\twarning: 4096,\n\tcritical: 8192,\n};\n\n/**\n * Resolves user selections into a complete, valid service list.\n *\n * Algorithm:\n * 1. Expand skill pack requirements into service list\n * 2. Resolve transitive `requires` dependencies (iterate until stable)\n * 3. Detect `conflictsWith` violations\n * 4. Check platform compatibility and GPU requirements\n * 5. Estimate total memory (sum minMemoryMB)\n * 6. Deduplicate\n * 7. Topological sort by dependency graph, alphabetical for ties\n *\n * Deterministic: same input -> same output, always.\n */\nexport function resolve(input: ResolverInput): ResolverOutput {\n\tconst addedDependencies: AddedDependency[] = [];\n\tconst warnings: Warning[] = [];\n\tconst errors: ResolverError[] = [];\n\n\t// Track all service IDs needed\n\tconst serviceIds = new Set<string>(input.services);\n\tconst serviceAddedBy = new Map<string, ResolvedService[\"addedBy\"]>();\n\n\t// Mark user-selected services\n\tfor (const id of input.services) {\n\t\tserviceAddedBy.set(id, \"user\");\n\t}\n\n\t// 1. Expand skill pack requirements\n\tfor (const packId of input.skillPacks) {\n\t\tconst pack = getSkillPackById(packId);\n\t\tif (!pack) {\n\t\t\terrors.push({\n\t\t\t\ttype: \"unknown_skill_pack\",\n\t\t\t\tmessage: `Unknown skill pack: \"${packId}\"`,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tfor (const requiredService of pack.requiredServices) {\n\t\t\tif (!serviceIds.has(requiredService)) {\n\t\t\t\tserviceIds.add(requiredService);\n\t\t\t\tserviceAddedBy.set(requiredService, \"skill-pack\");\n\t\t\t\taddedDependencies.push({\n\t\t\t\t\tservice: requiredService,\n\t\t\t\t\treason: `Required by skill pack: ${pack.name}`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Add proxy if specified\n\tif (input.proxy && input.proxy !== \"none\") {\n\t\tif (!serviceIds.has(input.proxy)) {\n\t\t\tserviceIds.add(input.proxy);\n\t\t\tserviceAddedBy.set(input.proxy, \"proxy\");\n\t\t\taddedDependencies.push({\n\t\t\t\tservice: input.proxy,\n\t\t\t\treason: `Selected as reverse proxy`,\n\t\t\t});\n\t\t}\n\t}\n\n\t// Add monitoring stack if requested\n\tif (input.monitoring) {\n\t\tconst monitoringServices = [\"uptime-kuma\", \"grafana\", \"prometheus\"];\n\t\tfor (const svc of monitoringServices) {\n\t\t\tif (!serviceIds.has(svc)) {\n\t\t\t\tserviceIds.add(svc);\n\t\t\t\tserviceAddedBy.set(svc, \"monitoring\");\n\t\t\t\taddedDependencies.push({\n\t\t\t\t\tservice: svc,\n\t\t\t\t\treason: \"Included with monitoring stack\",\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Add mandatory platform services (mission-control, convex, tailscale, etc.)\n\tfor (const def of getAllServices()) {\n\t\tif (def.mandatory && !serviceIds.has(def.id)) {\n\t\t\tserviceIds.add(def.id);\n\t\t\tserviceAddedBy.set(def.id, \"mandatory\");\n\t\t\taddedDependencies.push({\n\t\t\t\tservice: def.id,\n\t\t\t\treason: \"Mandatory OpenClaw platform service\",\n\t\t\t});\n\t\t}\n\t}\n\n\t// Validate all service IDs exist\n\tconst unknownIds: string[] = [];\n\tfor (const id of serviceIds) {\n\t\tif (!getServiceById(id)) {\n\t\t\tunknownIds.push(id);\n\t\t}\n\t}\n\tif (unknownIds.length > 0) {\n\t\tfor (const id of unknownIds) {\n\t\t\terrors.push({\n\t\t\t\ttype: \"unknown_service\",\n\t\t\t\tmessage: `Unknown service: \"${id}\"`,\n\t\t\t});\n\t\t\tserviceIds.delete(id);\n\t\t}\n\t}\n\n\t// 2. Resolve transitive dependencies (iterate until stable)\n\tlet changed = true;\n\tconst maxIterations = 50; // safety bound\n\tlet iteration = 0;\n\twhile (changed && iteration < maxIterations) {\n\t\tchanged = false;\n\t\titeration++;\n\t\tfor (const id of [...serviceIds]) {\n\t\t\tconst def = getServiceById(id);\n\t\t\tif (!def) continue;\n\t\t\tfor (const reqId of def.requires) {\n\t\t\t\tif (!serviceIds.has(reqId)) {\n\t\t\t\t\tserviceIds.add(reqId);\n\t\t\t\t\tserviceAddedBy.set(reqId, \"dependency\");\n\t\t\t\t\taddedDependencies.push({\n\t\t\t\t\t\tservice: reqId,\n\t\t\t\t\t\tserviceId: reqId,\n\t\t\t\t\t\trequiredBy: def.name,\n\t\t\t\t\t\treason: `Required by ${def.name}`,\n\t\t\t\t\t});\n\t\t\t\t\tchanged = true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (iteration >= maxIterations) {\n\t\twarnings.push({\n\t\t\ttype: \"resolution\",\n\t\t\tmessage: `Dependency resolution reached maximum iterations (${maxIterations}). Some transitive dependencies may not be fully resolved.`,\n\t\t});\n\t}\n\n\t// Check recommended services\n\tfor (const id of serviceIds) {\n\t\tconst def = getServiceById(id);\n\t\tif (!def) continue;\n\t\tfor (const recId of def.recommends) {\n\t\t\tif (!serviceIds.has(recId) && getServiceById(recId)) {\n\t\t\t\twarnings.push({\n\t\t\t\t\ttype: \"recommendation\",\n\t\t\t\t\tmessage: `${def.name} recommends \"${recId}\" for enhanced functionality`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// 3. Detect conflicts\n\tconst resolvedDefs: ServiceDefinition[] = [];\n\tfor (const id of serviceIds) {\n\t\tconst def = getServiceById(id);\n\t\tif (def) resolvedDefs.push(def);\n\t}\n\n\tfor (let i = 0; i < resolvedDefs.length; i++) {\n\t\tfor (let j = i + 1; j < resolvedDefs.length; j++) {\n\t\t\tconst a = resolvedDefs[i]!;\n\t\t\tconst b = resolvedDefs[j]!;\n\t\t\tif (a.conflictsWith.includes(b.id) || b.conflictsWith.includes(a.id)) {\n\t\t\t\terrors.push({\n\t\t\t\t\ttype: \"conflict\",\n\t\t\t\t\tmessage: `${a.name} and ${b.name} cannot be used together`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// 4. Check platform compatibility\n\tif (input.platform) {\n\t\tfor (const def of resolvedDefs) {\n\t\t\tif (def.platforms && def.platforms.length > 0 && !def.platforms.includes(input.platform)) {\n\t\t\t\twarnings.push({\n\t\t\t\t\ttype: \"platform\",\n\t\t\t\t\tmessage: `${def.name} may not be compatible with ${input.platform}. Supported: ${def.platforms.join(\", \")}`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check GPU requirements\n\tconst gpuServices = resolvedDefs.filter((d) => d.gpuRequired);\n\tif (gpuServices.length > 0 && !input.gpu) {\n\t\tfor (const svc of gpuServices) {\n\t\t\twarnings.push({\n\t\t\t\ttype: \"gpu\",\n\t\t\t\tmessage: `${svc.name} requires GPU passthrough. Enable --gpu flag for optimal performance.`,\n\t\t\t});\n\t\t}\n\t}\n\n\t// 5. Estimate total memory\n\tlet estimatedMemoryMB = 512; // Base for OpenClaw itself\n\tfor (const def of resolvedDefs) {\n\t\testimatedMemoryMB += def.minMemoryMB ?? 128;\n\t}\n\n\t// Memory warnings\n\tconst thresholds = input.memoryThresholds ?? DEFAULT_MEMORY_THRESHOLDS;\n\tif (estimatedMemoryMB > thresholds.critical) {\n\t\twarnings.push({\n\t\t\ttype: \"memory\",\n\t\t\tmessage: `Estimated ${(estimatedMemoryMB / 1024).toFixed(1)}GB RAM required. Ensure your server has sufficient resources.`,\n\t\t});\n\t} else if (estimatedMemoryMB > thresholds.warning) {\n\t\twarnings.push({\n\t\t\ttype: \"memory\",\n\t\t\tmessage: `Estimated ${(estimatedMemoryMB / 1024).toFixed(1)}GB RAM required. A server with at least 8GB RAM is recommended.`,\n\t\t});\n\t} else if (estimatedMemoryMB > thresholds.info) {\n\t\twarnings.push({\n\t\t\ttype: \"memory\",\n\t\t\tmessage: `Estimated ${(estimatedMemoryMB / 1024).toFixed(1)}GB RAM required.`,\n\t\t});\n\t}\n\n\t// 7. Topological sort by dependency graph\n\tconst sorted = topologicalSort(resolvedDefs);\n\n\t// Build final resolved services list\n\tconst services: ResolvedService[] = sorted.map((def) => ({\n\t\tdefinition: def,\n\t\taddedBy: serviceAddedBy.get(def.id) ?? \"user\",\n\t}));\n\n\tconst isValid = errors.length === 0;\n\n\treturn {\n\t\tservices,\n\t\taddedDependencies,\n\t\tremovedConflicts: [],\n\t\twarnings,\n\t\terrors,\n\t\tisValid,\n\t\testimatedMemoryMB,\n\t\taiProviders: input.aiProviders ?? [],\n\t\tgsdRuntimes: [],\n\t};\n}\n\n/**\n * Topological sort using Kahn's algorithm.\n * Ties broken alphabetically by service ID for determinism.\n */\nfunction topologicalSort(definitions: ServiceDefinition[]): ServiceDefinition[] {\n\tconst idSet = new Set(definitions.map((d) => d.id));\n\tconst graph = new Map<string, string[]>(); // id -> list of IDs that depend on it\n\tconst inDegree = new Map<string, number>();\n\n\t// Initialize\n\tfor (const def of definitions) {\n\t\tgraph.set(def.id, []);\n\t\tinDegree.set(def.id, 0);\n\t}\n\n\t// Build edges: if A requires B, then B -> A (B must come before A)\n\tfor (const def of definitions) {\n\t\tfor (const reqId of [...def.requires, ...def.dependsOn]) {\n\t\t\tif (idSet.has(reqId)) {\n\t\t\t\tgraph.get(reqId)?.push(def.id);\n\t\t\t\tinDegree.set(def.id, (inDegree.get(def.id) ?? 0) + 1);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Kahn's algorithm with alphabetical tie-breaking\n\tconst queue: string[] = [];\n\tfor (const [id, deg] of inDegree) {\n\t\tif (deg === 0) queue.push(id);\n\t}\n\tqueue.sort(); // alphabetical for determinism\n\n\tconst sorted: ServiceDefinition[] = [];\n\tconst defMap = new Map(definitions.map((d) => [d.id, d]));\n\n\twhile (queue.length > 0) {\n\t\tconst id = queue.shift()!;\n\t\tconst def = defMap.get(id);\n\t\tif (def) sorted.push(def);\n\n\t\tconst neighbors = graph.get(id) ?? [];\n\t\tconst newReady: string[] = [];\n\t\tfor (const neighbor of neighbors) {\n\t\t\tconst deg = (inDegree.get(neighbor) ?? 0) - 1;\n\t\t\tinDegree.set(neighbor, deg);\n\t\t\tif (deg === 0) newReady.push(neighbor);\n\t\t}\n\t\t// Sort newly ready nodes alphabetically and add to queue in order\n\t\tnewReady.sort();\n\t\tqueue.push(...newReady);\n\t}\n\n\t// If not all nodes are in sorted, there's a cycle\n\tif (sorted.length < definitions.length) {\n\t\t// Return what we have plus remaining (cycle detected but we still produce output)\n\t\tconst sortedIds = new Set(sorted.map((d) => d.id));\n\t\tconst remaining = definitions.filter((d) => !sortedIds.has(d.id));\n\t\tremaining.sort((a, b) => a.id.localeCompare(b.id));\n\t\tsorted.push(...remaining);\n\t}\n\n\treturn sorted;\n}\n"],"mappings":"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"}
@@ -1,23 +1,31 @@
1
- const require_vi_2VT5v0um = require("./vi.2VT5v0um-CRqXre87.cjs");
1
+ const require_vi_2VT5v0um = require("./vi.2VT5v0um-iVBt6Fyq.cjs");
2
2
  const require_resolver = require("./resolver.cjs");
3
3
  //#region src/resolver.test.ts
4
+ const MANDATORY_IDS = [
5
+ "convex",
6
+ "mission-control",
7
+ "tailscale"
8
+ ];
9
+ const MANDATORY_MEMORY = 448;
4
10
  require_vi_2VT5v0um.describe("resolve", () => {
5
- require_vi_2VT5v0um.it("returns empty services for empty input", () => {
11
+ require_vi_2VT5v0um.it("returns only mandatory services for empty input", () => {
6
12
  const result = require_resolver.resolve({
7
13
  services: [],
8
14
  skillPacks: []
9
15
  });
10
- require_vi_2VT5v0um.globalExpect(result.services).toHaveLength(0);
16
+ const ids = result.services.map((s) => s.definition.id);
17
+ for (const id of MANDATORY_IDS) require_vi_2VT5v0um.globalExpect(ids).toContain(id);
11
18
  require_vi_2VT5v0um.globalExpect(result.isValid).toBe(true);
12
- require_vi_2VT5v0um.globalExpect(result.estimatedMemoryMB).toBe(512);
19
+ require_vi_2VT5v0um.globalExpect(result.estimatedMemoryMB).toBe(512 + MANDATORY_MEMORY);
13
20
  });
14
- require_vi_2VT5v0um.it("resolves a single service with no dependencies", () => {
21
+ require_vi_2VT5v0um.it("resolves a single service alongside mandatory services", () => {
15
22
  const result = require_resolver.resolve({
16
23
  services: ["redis"],
17
24
  skillPacks: []
18
25
  });
19
- require_vi_2VT5v0um.globalExpect(result.services).toHaveLength(1);
20
- require_vi_2VT5v0um.globalExpect(result.services[0].definition.id).toBe("redis");
26
+ const ids = result.services.map((s) => s.definition.id);
27
+ require_vi_2VT5v0um.globalExpect(ids).toContain("redis");
28
+ for (const id of MANDATORY_IDS) require_vi_2VT5v0um.globalExpect(ids).toContain(id);
21
29
  require_vi_2VT5v0um.globalExpect(result.isValid).toBe(true);
22
30
  });
23
31
  require_vi_2VT5v0um.it("auto-adds PostgreSQL when n8n is selected", () => {
@@ -114,7 +122,7 @@ require_vi_2VT5v0um.describe("resolve", () => {
114
122
  require_vi_2VT5v0um.globalExpect(require_resolver.resolve({
115
123
  services: ["redis"],
116
124
  skillPacks: []
117
- }).estimatedMemoryMB).toBe(640);
125
+ }).estimatedMemoryMB).toBe(640 + MANDATORY_MEMORY);
118
126
  });
119
127
  require_vi_2VT5v0um.it("adds proxy service when specified", () => {
120
128
  require_vi_2VT5v0um.globalExpect(require_resolver.resolve({
@@ -164,13 +172,12 @@ require_vi_2VT5v0um.describe("resolve", () => {
164
172
  gpu: false
165
173
  }).warnings.filter((w) => w.type === "gpu")).toHaveLength(0);
166
174
  });
167
- require_vi_2VT5v0um.it("resolves tailscale as single service with no dependencies", () => {
175
+ require_vi_2VT5v0um.it("resolves tailscale (mandatory, always present)", () => {
168
176
  const result = require_resolver.resolve({
169
- services: ["tailscale"],
177
+ services: [],
170
178
  skillPacks: []
171
179
  });
172
- require_vi_2VT5v0um.globalExpect(result.services).toHaveLength(1);
173
- require_vi_2VT5v0um.globalExpect(result.services[0].definition.id).toBe("tailscale");
180
+ require_vi_2VT5v0um.globalExpect(result.services.map((s) => s.definition.id)).toContain("tailscale");
174
181
  require_vi_2VT5v0um.globalExpect(result.isValid).toBe(true);
175
182
  });
176
183
  require_vi_2VT5v0um.it("resolves coolify and dokploy as single services", () => {
@@ -229,6 +236,34 @@ require_vi_2VT5v0um.describe("resolve", () => {
229
236
  require_vi_2VT5v0um.globalExpect(backendIdx).toBeGreaterThan(livekitIdx);
230
237
  require_vi_2VT5v0um.globalExpect(frontendIdx).toBeGreaterThan(backendIdx);
231
238
  });
239
+ require_vi_2VT5v0um.it("auto-includes mandatory services even when not selected", () => {
240
+ const result = require_resolver.resolve({
241
+ services: ["redis"],
242
+ skillPacks: []
243
+ });
244
+ const ids = result.services.map((s) => s.definition.id);
245
+ require_vi_2VT5v0um.globalExpect(ids).toContain("mission-control");
246
+ require_vi_2VT5v0um.globalExpect(ids).toContain("convex");
247
+ require_vi_2VT5v0um.globalExpect(ids).toContain("tailscale");
248
+ require_vi_2VT5v0um.globalExpect(result.services.find((s) => s.definition.id === "mission-control").addedBy).toBe("mandatory");
249
+ });
250
+ require_vi_2VT5v0um.it("marks user-selected mandatory services as 'user' not 'mandatory'", () => {
251
+ const result = require_resolver.resolve({
252
+ services: ["mission-control"],
253
+ skillPacks: []
254
+ });
255
+ require_vi_2VT5v0um.globalExpect(result.services.find((s) => s.definition.id === "mission-control").addedBy).toBe("user");
256
+ require_vi_2VT5v0um.globalExpect(result.services.find((s) => s.definition.id === "convex")).toBeDefined();
257
+ });
258
+ require_vi_2VT5v0um.it("orders convex before mission-control (dependency ordering)", () => {
259
+ const ids = require_resolver.resolve({
260
+ services: ["redis"],
261
+ skillPacks: []
262
+ }).services.map((s) => s.definition.id);
263
+ const convexIdx = ids.indexOf("convex");
264
+ const mcIdx = ids.indexOf("mission-control");
265
+ require_vi_2VT5v0um.globalExpect(convexIdx).toBeLessThan(mcIdx);
266
+ });
232
267
  });
233
268
  //#endregion
234
269
 
@@ -1 +1 @@
1
- {"version":3,"file":"resolver.test.cjs","names":["describe","resolve","expect"],"sources":["../src/resolver.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { resolve } from \"./resolver.js\";\n\ndescribe(\"resolve\", () => {\n\tit(\"returns empty services for empty input\", () => {\n\t\tconst result = resolve({ services: [], skillPacks: [] });\n\t\texpect(result.services).toHaveLength(0);\n\t\texpect(result.isValid).toBe(true);\n\t\texpect(result.estimatedMemoryMB).toBe(512); // base OpenClaw only\n\t});\n\n\tit(\"resolves a single service with no dependencies\", () => {\n\t\tconst result = resolve({ services: [\"redis\"], skillPacks: [] });\n\t\texpect(result.services).toHaveLength(1);\n\t\texpect(result.services[0]!.definition.id).toBe(\"redis\");\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"auto-adds PostgreSQL when n8n is selected\", () => {\n\t\tconst result = resolve({ services: [\"n8n\"], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"n8n\");\n\t\texpect(ids).toContain(\"postgresql\");\n\t\texpect(result.addedDependencies).toContainEqual(\n\t\t\texpect.objectContaining({ service: \"postgresql\" }),\n\t\t);\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"auto-adds Prometheus when Grafana is selected\", () => {\n\t\tconst result = resolve({ services: [\"grafana\"], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"grafana\");\n\t\texpect(ids).toContain(\"prometheus\");\n\t});\n\n\tit(\"detects Redis + Valkey conflict\", () => {\n\t\tconst result = resolve({ services: [\"redis\", \"valkey\"], skillPacks: [] });\n\t\texpect(result.isValid).toBe(false);\n\t\texpect(result.errors).toContainEqual(expect.objectContaining({ type: \"conflict\" }));\n\t});\n\n\tit(\"detects Caddy + Traefik conflict\", () => {\n\t\tconst result = resolve({ services: [\"caddy\", \"traefik\"], skillPacks: [] });\n\t\texpect(result.isValid).toBe(false);\n\t\texpect(result.errors).toContainEqual(expect.objectContaining({ type: \"conflict\" }));\n\t});\n\n\tit(\"expands research-agent skill pack\", () => {\n\t\tconst result = resolve({ services: [], skillPacks: [\"research-agent\"] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"qdrant\");\n\t\texpect(ids).toContain(\"searxng\");\n\t\texpect(ids).toContain(\"browserless\");\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"expands video-creator skill pack\", () => {\n\t\tconst result = resolve({ services: [], skillPacks: [\"video-creator\"] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"ffmpeg\");\n\t\texpect(ids).toContain(\"remotion\");\n\t\texpect(ids).toContain(\"minio\");\n\t});\n\n\tit(\"expands dev-ops skill pack with transitive deps\", () => {\n\t\tconst result = resolve({ services: [], skillPacks: [\"dev-ops\"] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"n8n\");\n\t\texpect(ids).toContain(\"redis\");\n\t\texpect(ids).toContain(\"uptime-kuma\");\n\t\texpect(ids).toContain(\"grafana\");\n\t\texpect(ids).toContain(\"prometheus\");\n\t\t// n8n requires postgresql (transitive)\n\t\texpect(ids).toContain(\"postgresql\");\n\t});\n\n\tit(\"reports unknown service IDs\", () => {\n\t\tconst result = resolve({ services: [\"nonexistent\"], skillPacks: [] });\n\t\texpect(result.errors).toContainEqual(expect.objectContaining({ type: \"unknown_service\" }));\n\t});\n\n\tit(\"reports unknown skill pack IDs\", () => {\n\t\tconst result = resolve({ services: [], skillPacks: [\"nonexistent-pack\"] });\n\t\texpect(result.errors).toContainEqual(expect.objectContaining({ type: \"unknown_skill_pack\" }));\n\t});\n\n\tit(\"does not duplicate services already selected by user\", () => {\n\t\tconst result = resolve({\n\t\t\tservices: [\"qdrant\", \"searxng\", \"browserless\"],\n\t\t\tskillPacks: [\"research-agent\"],\n\t\t});\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\tconst qdrantCount = ids.filter((id) => id === \"qdrant\").length;\n\t\texpect(qdrantCount).toBe(1);\n\t});\n\n\tit(\"estimates memory correctly\", () => {\n\t\tconst result = resolve({ services: [\"redis\"], skillPacks: [] });\n\t\t// 512 (base) + 128 (redis) = 640\n\t\texpect(result.estimatedMemoryMB).toBe(640);\n\t});\n\n\tit(\"adds proxy service when specified\", () => {\n\t\tconst result = resolve({ services: [\"redis\"], skillPacks: [], proxy: \"caddy\" });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"caddy\");\n\t});\n\n\tit(\"adds monitoring stack when flag is set\", () => {\n\t\tconst result = resolve({ services: [], skillPacks: [], monitoring: true });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"uptime-kuma\");\n\t\texpect(ids).toContain(\"grafana\");\n\t\texpect(ids).toContain(\"prometheus\");\n\t});\n\n\tit(\"is deterministic - same input gives same output\", () => {\n\t\tconst input = { services: [\"redis\", \"qdrant\", \"n8n\"], skillPacks: [\"research-agent\"] };\n\t\tconst result1 = resolve(input);\n\t\tconst result2 = resolve(input);\n\t\tconst ids1 = result1.services.map((s) => s.definition.id);\n\t\tconst ids2 = result2.services.map((s) => s.definition.id);\n\t\texpect(ids1).toEqual(ids2);\n\t});\n\n\tit(\"topologically sorts dependencies before dependents\", () => {\n\t\tconst result = resolve({ services: [\"n8n\"], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\tconst pgIndex = ids.indexOf(\"postgresql\");\n\t\tconst n8nIndex = ids.indexOf(\"n8n\");\n\t\texpect(pgIndex).toBeLessThan(n8nIndex);\n\t});\n\n\tit(\"warns about GPU when AI services selected without gpu flag\", () => {\n\t\t// Ollama doesn't have gpuRequired=true (it's recommended not required)\n\t\t// but we should still check GPU warning logic works\n\t\tconst result = resolve({ services: [\"redis\"], skillPacks: [], gpu: false });\n\t\t// Redis doesn't need GPU, so no GPU warnings\n\t\tconst gpuWarnings = result.warnings.filter((w) => w.type === \"gpu\");\n\t\texpect(gpuWarnings).toHaveLength(0);\n\t});\n\n\tit(\"resolves tailscale as single service with no dependencies\", () => {\n\t\tconst result = resolve({ services: [\"tailscale\"], skillPacks: [] });\n\t\texpect(result.services).toHaveLength(1);\n\t\texpect(result.services[0]!.definition.id).toBe(\"tailscale\");\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"resolves coolify and dokploy as single services\", () => {\n\t\tconst result = resolve({ services: [\"coolify\", \"dokploy\"], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"coolify\");\n\t\texpect(ids).toContain(\"dokploy\");\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"auto-adds postgresql, redis, and livekit when lasuite-meet-backend is selected\", () => {\n\t\tconst result = resolve({ services: [\"lasuite-meet-backend\"], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"lasuite-meet-backend\");\n\t\texpect(ids).toContain(\"postgresql\");\n\t\texpect(ids).toContain(\"redis\");\n\t\texpect(ids).toContain(\"livekit\");\n\t\texpect(result.addedDependencies.some((a) => a.service === \"postgresql\")).toBe(true);\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"resolves La Suite Meet preset (postgresql, redis, livekit, backend, frontend, agents)\", () => {\n\t\tconst lasuiteMeetServices = [\n\t\t\t\"postgresql\",\n\t\t\t\"redis\",\n\t\t\t\"livekit\",\n\t\t\t\"lasuite-meet-backend\",\n\t\t\t\"lasuite-meet-frontend\",\n\t\t\t\"lasuite-meet-agents\",\n\t\t];\n\t\tconst result = resolve({ services: lasuiteMeetServices, skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\tfor (const id of lasuiteMeetServices) {\n\t\t\texpect(ids).toContain(id);\n\t\t}\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"orders lasuite-meet-backend after postgresql, redis, and livekit\", () => {\n\t\tconst result = resolve({\n\t\t\tservices: [\"lasuite-meet-backend\", \"lasuite-meet-frontend\"],\n\t\t\tskillPacks: [],\n\t\t});\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\tconst pgIdx = ids.indexOf(\"postgresql\");\n\t\tconst redisIdx = ids.indexOf(\"redis\");\n\t\tconst livekitIdx = ids.indexOf(\"livekit\");\n\t\tconst backendIdx = ids.indexOf(\"lasuite-meet-backend\");\n\t\tconst frontendIdx = ids.indexOf(\"lasuite-meet-frontend\");\n\t\texpect(pgIdx).toBeGreaterThanOrEqual(0);\n\t\texpect(backendIdx).toBeGreaterThan(pgIdx);\n\t\texpect(backendIdx).toBeGreaterThan(redisIdx);\n\t\texpect(backendIdx).toBeGreaterThan(livekitIdx);\n\t\texpect(frontendIdx).toBeGreaterThan(backendIdx);\n\t});\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"resolver.test.cjs","names":["describe","resolve","expect"],"sources":["../src/resolver.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { resolve } from \"./resolver.js\";\n\n// Mandatory services (convex, mission-control, tailscale) are always auto-included.\n// These IDs and their combined memory are accounted for in all tests.\nconst MANDATORY_IDS = [\"convex\", \"mission-control\", \"tailscale\"];\nconst MANDATORY_MEMORY = 256 + 128 + 64; // convex + mission-control + tailscale\n\ndescribe(\"resolve\", () => {\n\tit(\"returns only mandatory services for empty input\", () => {\n\t\tconst result = resolve({ services: [], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\tfor (const id of MANDATORY_IDS) {\n\t\t\texpect(ids).toContain(id);\n\t\t}\n\t\texpect(result.isValid).toBe(true);\n\t\t// 512 (base) + mandatory services memory\n\t\texpect(result.estimatedMemoryMB).toBe(512 + MANDATORY_MEMORY);\n\t});\n\n\tit(\"resolves a single service alongside mandatory services\", () => {\n\t\tconst result = resolve({ services: [\"redis\"], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"redis\");\n\t\tfor (const id of MANDATORY_IDS) {\n\t\t\texpect(ids).toContain(id);\n\t\t}\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"auto-adds PostgreSQL when n8n is selected\", () => {\n\t\tconst result = resolve({ services: [\"n8n\"], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"n8n\");\n\t\texpect(ids).toContain(\"postgresql\");\n\t\texpect(result.addedDependencies).toContainEqual(\n\t\t\texpect.objectContaining({ service: \"postgresql\" }),\n\t\t);\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"auto-adds Prometheus when Grafana is selected\", () => {\n\t\tconst result = resolve({ services: [\"grafana\"], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"grafana\");\n\t\texpect(ids).toContain(\"prometheus\");\n\t});\n\n\tit(\"detects Redis + Valkey conflict\", () => {\n\t\tconst result = resolve({ services: [\"redis\", \"valkey\"], skillPacks: [] });\n\t\texpect(result.isValid).toBe(false);\n\t\texpect(result.errors).toContainEqual(expect.objectContaining({ type: \"conflict\" }));\n\t});\n\n\tit(\"detects Caddy + Traefik conflict\", () => {\n\t\tconst result = resolve({ services: [\"caddy\", \"traefik\"], skillPacks: [] });\n\t\texpect(result.isValid).toBe(false);\n\t\texpect(result.errors).toContainEqual(expect.objectContaining({ type: \"conflict\" }));\n\t});\n\n\tit(\"expands research-agent skill pack\", () => {\n\t\tconst result = resolve({ services: [], skillPacks: [\"research-agent\"] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"qdrant\");\n\t\texpect(ids).toContain(\"searxng\");\n\t\texpect(ids).toContain(\"browserless\");\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"expands video-creator skill pack\", () => {\n\t\tconst result = resolve({ services: [], skillPacks: [\"video-creator\"] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"ffmpeg\");\n\t\texpect(ids).toContain(\"remotion\");\n\t\texpect(ids).toContain(\"minio\");\n\t});\n\n\tit(\"expands dev-ops skill pack with transitive deps\", () => {\n\t\tconst result = resolve({ services: [], skillPacks: [\"dev-ops\"] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"n8n\");\n\t\texpect(ids).toContain(\"redis\");\n\t\texpect(ids).toContain(\"uptime-kuma\");\n\t\texpect(ids).toContain(\"grafana\");\n\t\texpect(ids).toContain(\"prometheus\");\n\t\t// n8n requires postgresql (transitive)\n\t\texpect(ids).toContain(\"postgresql\");\n\t});\n\n\tit(\"reports unknown service IDs\", () => {\n\t\tconst result = resolve({ services: [\"nonexistent\"], skillPacks: [] });\n\t\texpect(result.errors).toContainEqual(expect.objectContaining({ type: \"unknown_service\" }));\n\t});\n\n\tit(\"reports unknown skill pack IDs\", () => {\n\t\tconst result = resolve({ services: [], skillPacks: [\"nonexistent-pack\"] });\n\t\texpect(result.errors).toContainEqual(expect.objectContaining({ type: \"unknown_skill_pack\" }));\n\t});\n\n\tit(\"does not duplicate services already selected by user\", () => {\n\t\tconst result = resolve({\n\t\t\tservices: [\"qdrant\", \"searxng\", \"browserless\"],\n\t\t\tskillPacks: [\"research-agent\"],\n\t\t});\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\tconst qdrantCount = ids.filter((id) => id === \"qdrant\").length;\n\t\texpect(qdrantCount).toBe(1);\n\t});\n\n\tit(\"estimates memory correctly\", () => {\n\t\tconst result = resolve({ services: [\"redis\"], skillPacks: [] });\n\t\t// 512 (base) + 128 (redis) + mandatory services\n\t\texpect(result.estimatedMemoryMB).toBe(512 + 128 + MANDATORY_MEMORY);\n\t});\n\n\tit(\"adds proxy service when specified\", () => {\n\t\tconst result = resolve({ services: [\"redis\"], skillPacks: [], proxy: \"caddy\" });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"caddy\");\n\t});\n\n\tit(\"adds monitoring stack when flag is set\", () => {\n\t\tconst result = resolve({ services: [], skillPacks: [], monitoring: true });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"uptime-kuma\");\n\t\texpect(ids).toContain(\"grafana\");\n\t\texpect(ids).toContain(\"prometheus\");\n\t});\n\n\tit(\"is deterministic - same input gives same output\", () => {\n\t\tconst input = { services: [\"redis\", \"qdrant\", \"n8n\"], skillPacks: [\"research-agent\"] };\n\t\tconst result1 = resolve(input);\n\t\tconst result2 = resolve(input);\n\t\tconst ids1 = result1.services.map((s) => s.definition.id);\n\t\tconst ids2 = result2.services.map((s) => s.definition.id);\n\t\texpect(ids1).toEqual(ids2);\n\t});\n\n\tit(\"topologically sorts dependencies before dependents\", () => {\n\t\tconst result = resolve({ services: [\"n8n\"], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\tconst pgIndex = ids.indexOf(\"postgresql\");\n\t\tconst n8nIndex = ids.indexOf(\"n8n\");\n\t\texpect(pgIndex).toBeLessThan(n8nIndex);\n\t});\n\n\tit(\"warns about GPU when AI services selected without gpu flag\", () => {\n\t\tconst result = resolve({ services: [\"redis\"], skillPacks: [], gpu: false });\n\t\tconst gpuWarnings = result.warnings.filter((w) => w.type === \"gpu\");\n\t\texpect(gpuWarnings).toHaveLength(0);\n\t});\n\n\tit(\"resolves tailscale (mandatory, always present)\", () => {\n\t\tconst result = resolve({ services: [], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"tailscale\");\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"resolves coolify and dokploy as single services\", () => {\n\t\tconst result = resolve({ services: [\"coolify\", \"dokploy\"], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"coolify\");\n\t\texpect(ids).toContain(\"dokploy\");\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"auto-adds postgresql, redis, and livekit when lasuite-meet-backend is selected\", () => {\n\t\tconst result = resolve({ services: [\"lasuite-meet-backend\"], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"lasuite-meet-backend\");\n\t\texpect(ids).toContain(\"postgresql\");\n\t\texpect(ids).toContain(\"redis\");\n\t\texpect(ids).toContain(\"livekit\");\n\t\texpect(result.addedDependencies.some((a) => a.service === \"postgresql\")).toBe(true);\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"resolves La Suite Meet preset (postgresql, redis, livekit, backend, frontend, agents)\", () => {\n\t\tconst lasuiteMeetServices = [\n\t\t\t\"postgresql\",\n\t\t\t\"redis\",\n\t\t\t\"livekit\",\n\t\t\t\"lasuite-meet-backend\",\n\t\t\t\"lasuite-meet-frontend\",\n\t\t\t\"lasuite-meet-agents\",\n\t\t];\n\t\tconst result = resolve({ services: lasuiteMeetServices, skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\tfor (const id of lasuiteMeetServices) {\n\t\t\texpect(ids).toContain(id);\n\t\t}\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"orders lasuite-meet-backend after postgresql, redis, and livekit\", () => {\n\t\tconst result = resolve({\n\t\t\tservices: [\"lasuite-meet-backend\", \"lasuite-meet-frontend\"],\n\t\t\tskillPacks: [],\n\t\t});\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\tconst pgIdx = ids.indexOf(\"postgresql\");\n\t\tconst redisIdx = ids.indexOf(\"redis\");\n\t\tconst livekitIdx = ids.indexOf(\"livekit\");\n\t\tconst backendIdx = ids.indexOf(\"lasuite-meet-backend\");\n\t\tconst frontendIdx = ids.indexOf(\"lasuite-meet-frontend\");\n\t\texpect(pgIdx).toBeGreaterThanOrEqual(0);\n\t\texpect(backendIdx).toBeGreaterThan(pgIdx);\n\t\texpect(backendIdx).toBeGreaterThan(redisIdx);\n\t\texpect(backendIdx).toBeGreaterThan(livekitIdx);\n\t\texpect(frontendIdx).toBeGreaterThan(backendIdx);\n\t});\n\n\t// ── Mandatory services enforcement ──────────────────────────────────────\n\n\tit(\"auto-includes mandatory services even when not selected\", () => {\n\t\tconst result = resolve({ services: [\"redis\"], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"mission-control\");\n\t\texpect(ids).toContain(\"convex\");\n\t\texpect(ids).toContain(\"tailscale\");\n\t\tconst mc = result.services.find((s) => s.definition.id === \"mission-control\");\n\t\texpect(mc!.addedBy).toBe(\"mandatory\");\n\t});\n\n\tit(\"marks user-selected mandatory services as 'user' not 'mandatory'\", () => {\n\t\tconst result = resolve({ services: [\"mission-control\"], skillPacks: [] });\n\t\tconst mc = result.services.find((s) => s.definition.id === \"mission-control\");\n\t\texpect(mc!.addedBy).toBe(\"user\");\n\t\t// convex should still be auto-added as dependency of mission-control\n\t\tconst convex = result.services.find((s) => s.definition.id === \"convex\");\n\t\texpect(convex).toBeDefined();\n\t});\n\n\tit(\"orders convex before mission-control (dependency ordering)\", () => {\n\t\tconst result = resolve({ services: [\"redis\"], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\tconst convexIdx = ids.indexOf(\"convex\");\n\t\tconst mcIdx = ids.indexOf(\"mission-control\");\n\t\texpect(convexIdx).toBeLessThan(mcIdx);\n\t});\n});\n"],"mappings":"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"}
@@ -1,23 +1,31 @@
1
- import { n as describe, r as it, t as globalExpect } from "./vi.2VT5v0um-DvC3SVNc.mjs";
1
+ import { n as describe, r as it, t as globalExpect } from "./vi.2VT5v0um-C_jmO7m2.mjs";
2
2
  import { resolve } from "./resolver.mjs";
3
3
  //#region src/resolver.test.ts
4
+ const MANDATORY_IDS = [
5
+ "convex",
6
+ "mission-control",
7
+ "tailscale"
8
+ ];
9
+ const MANDATORY_MEMORY = 448;
4
10
  describe("resolve", () => {
5
- it("returns empty services for empty input", () => {
11
+ it("returns only mandatory services for empty input", () => {
6
12
  const result = resolve({
7
13
  services: [],
8
14
  skillPacks: []
9
15
  });
10
- globalExpect(result.services).toHaveLength(0);
16
+ const ids = result.services.map((s) => s.definition.id);
17
+ for (const id of MANDATORY_IDS) globalExpect(ids).toContain(id);
11
18
  globalExpect(result.isValid).toBe(true);
12
- globalExpect(result.estimatedMemoryMB).toBe(512);
19
+ globalExpect(result.estimatedMemoryMB).toBe(512 + MANDATORY_MEMORY);
13
20
  });
14
- it("resolves a single service with no dependencies", () => {
21
+ it("resolves a single service alongside mandatory services", () => {
15
22
  const result = resolve({
16
23
  services: ["redis"],
17
24
  skillPacks: []
18
25
  });
19
- globalExpect(result.services).toHaveLength(1);
20
- globalExpect(result.services[0].definition.id).toBe("redis");
26
+ const ids = result.services.map((s) => s.definition.id);
27
+ globalExpect(ids).toContain("redis");
28
+ for (const id of MANDATORY_IDS) globalExpect(ids).toContain(id);
21
29
  globalExpect(result.isValid).toBe(true);
22
30
  });
23
31
  it("auto-adds PostgreSQL when n8n is selected", () => {
@@ -114,7 +122,7 @@ describe("resolve", () => {
114
122
  globalExpect(resolve({
115
123
  services: ["redis"],
116
124
  skillPacks: []
117
- }).estimatedMemoryMB).toBe(640);
125
+ }).estimatedMemoryMB).toBe(640 + MANDATORY_MEMORY);
118
126
  });
119
127
  it("adds proxy service when specified", () => {
120
128
  globalExpect(resolve({
@@ -164,13 +172,12 @@ describe("resolve", () => {
164
172
  gpu: false
165
173
  }).warnings.filter((w) => w.type === "gpu")).toHaveLength(0);
166
174
  });
167
- it("resolves tailscale as single service with no dependencies", () => {
175
+ it("resolves tailscale (mandatory, always present)", () => {
168
176
  const result = resolve({
169
- services: ["tailscale"],
177
+ services: [],
170
178
  skillPacks: []
171
179
  });
172
- globalExpect(result.services).toHaveLength(1);
173
- globalExpect(result.services[0].definition.id).toBe("tailscale");
180
+ globalExpect(result.services.map((s) => s.definition.id)).toContain("tailscale");
174
181
  globalExpect(result.isValid).toBe(true);
175
182
  });
176
183
  it("resolves coolify and dokploy as single services", () => {
@@ -229,6 +236,34 @@ describe("resolve", () => {
229
236
  globalExpect(backendIdx).toBeGreaterThan(livekitIdx);
230
237
  globalExpect(frontendIdx).toBeGreaterThan(backendIdx);
231
238
  });
239
+ it("auto-includes mandatory services even when not selected", () => {
240
+ const result = resolve({
241
+ services: ["redis"],
242
+ skillPacks: []
243
+ });
244
+ const ids = result.services.map((s) => s.definition.id);
245
+ globalExpect(ids).toContain("mission-control");
246
+ globalExpect(ids).toContain("convex");
247
+ globalExpect(ids).toContain("tailscale");
248
+ globalExpect(result.services.find((s) => s.definition.id === "mission-control").addedBy).toBe("mandatory");
249
+ });
250
+ it("marks user-selected mandatory services as 'user' not 'mandatory'", () => {
251
+ const result = resolve({
252
+ services: ["mission-control"],
253
+ skillPacks: []
254
+ });
255
+ globalExpect(result.services.find((s) => s.definition.id === "mission-control").addedBy).toBe("user");
256
+ globalExpect(result.services.find((s) => s.definition.id === "convex")).toBeDefined();
257
+ });
258
+ it("orders convex before mission-control (dependency ordering)", () => {
259
+ const ids = resolve({
260
+ services: ["redis"],
261
+ skillPacks: []
262
+ }).services.map((s) => s.definition.id);
263
+ const convexIdx = ids.indexOf("convex");
264
+ const mcIdx = ids.indexOf("mission-control");
265
+ globalExpect(convexIdx).toBeLessThan(mcIdx);
266
+ });
232
267
  });
233
268
  //#endregion
234
269
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"resolver.test.mjs","names":["expect"],"sources":["../src/resolver.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { resolve } from \"./resolver.js\";\n\ndescribe(\"resolve\", () => {\n\tit(\"returns empty services for empty input\", () => {\n\t\tconst result = resolve({ services: [], skillPacks: [] });\n\t\texpect(result.services).toHaveLength(0);\n\t\texpect(result.isValid).toBe(true);\n\t\texpect(result.estimatedMemoryMB).toBe(512); // base OpenClaw only\n\t});\n\n\tit(\"resolves a single service with no dependencies\", () => {\n\t\tconst result = resolve({ services: [\"redis\"], skillPacks: [] });\n\t\texpect(result.services).toHaveLength(1);\n\t\texpect(result.services[0]!.definition.id).toBe(\"redis\");\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"auto-adds PostgreSQL when n8n is selected\", () => {\n\t\tconst result = resolve({ services: [\"n8n\"], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"n8n\");\n\t\texpect(ids).toContain(\"postgresql\");\n\t\texpect(result.addedDependencies).toContainEqual(\n\t\t\texpect.objectContaining({ service: \"postgresql\" }),\n\t\t);\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"auto-adds Prometheus when Grafana is selected\", () => {\n\t\tconst result = resolve({ services: [\"grafana\"], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"grafana\");\n\t\texpect(ids).toContain(\"prometheus\");\n\t});\n\n\tit(\"detects Redis + Valkey conflict\", () => {\n\t\tconst result = resolve({ services: [\"redis\", \"valkey\"], skillPacks: [] });\n\t\texpect(result.isValid).toBe(false);\n\t\texpect(result.errors).toContainEqual(expect.objectContaining({ type: \"conflict\" }));\n\t});\n\n\tit(\"detects Caddy + Traefik conflict\", () => {\n\t\tconst result = resolve({ services: [\"caddy\", \"traefik\"], skillPacks: [] });\n\t\texpect(result.isValid).toBe(false);\n\t\texpect(result.errors).toContainEqual(expect.objectContaining({ type: \"conflict\" }));\n\t});\n\n\tit(\"expands research-agent skill pack\", () => {\n\t\tconst result = resolve({ services: [], skillPacks: [\"research-agent\"] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"qdrant\");\n\t\texpect(ids).toContain(\"searxng\");\n\t\texpect(ids).toContain(\"browserless\");\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"expands video-creator skill pack\", () => {\n\t\tconst result = resolve({ services: [], skillPacks: [\"video-creator\"] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"ffmpeg\");\n\t\texpect(ids).toContain(\"remotion\");\n\t\texpect(ids).toContain(\"minio\");\n\t});\n\n\tit(\"expands dev-ops skill pack with transitive deps\", () => {\n\t\tconst result = resolve({ services: [], skillPacks: [\"dev-ops\"] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"n8n\");\n\t\texpect(ids).toContain(\"redis\");\n\t\texpect(ids).toContain(\"uptime-kuma\");\n\t\texpect(ids).toContain(\"grafana\");\n\t\texpect(ids).toContain(\"prometheus\");\n\t\t// n8n requires postgresql (transitive)\n\t\texpect(ids).toContain(\"postgresql\");\n\t});\n\n\tit(\"reports unknown service IDs\", () => {\n\t\tconst result = resolve({ services: [\"nonexistent\"], skillPacks: [] });\n\t\texpect(result.errors).toContainEqual(expect.objectContaining({ type: \"unknown_service\" }));\n\t});\n\n\tit(\"reports unknown skill pack IDs\", () => {\n\t\tconst result = resolve({ services: [], skillPacks: [\"nonexistent-pack\"] });\n\t\texpect(result.errors).toContainEqual(expect.objectContaining({ type: \"unknown_skill_pack\" }));\n\t});\n\n\tit(\"does not duplicate services already selected by user\", () => {\n\t\tconst result = resolve({\n\t\t\tservices: [\"qdrant\", \"searxng\", \"browserless\"],\n\t\t\tskillPacks: [\"research-agent\"],\n\t\t});\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\tconst qdrantCount = ids.filter((id) => id === \"qdrant\").length;\n\t\texpect(qdrantCount).toBe(1);\n\t});\n\n\tit(\"estimates memory correctly\", () => {\n\t\tconst result = resolve({ services: [\"redis\"], skillPacks: [] });\n\t\t// 512 (base) + 128 (redis) = 640\n\t\texpect(result.estimatedMemoryMB).toBe(640);\n\t});\n\n\tit(\"adds proxy service when specified\", () => {\n\t\tconst result = resolve({ services: [\"redis\"], skillPacks: [], proxy: \"caddy\" });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"caddy\");\n\t});\n\n\tit(\"adds monitoring stack when flag is set\", () => {\n\t\tconst result = resolve({ services: [], skillPacks: [], monitoring: true });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"uptime-kuma\");\n\t\texpect(ids).toContain(\"grafana\");\n\t\texpect(ids).toContain(\"prometheus\");\n\t});\n\n\tit(\"is deterministic - same input gives same output\", () => {\n\t\tconst input = { services: [\"redis\", \"qdrant\", \"n8n\"], skillPacks: [\"research-agent\"] };\n\t\tconst result1 = resolve(input);\n\t\tconst result2 = resolve(input);\n\t\tconst ids1 = result1.services.map((s) => s.definition.id);\n\t\tconst ids2 = result2.services.map((s) => s.definition.id);\n\t\texpect(ids1).toEqual(ids2);\n\t});\n\n\tit(\"topologically sorts dependencies before dependents\", () => {\n\t\tconst result = resolve({ services: [\"n8n\"], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\tconst pgIndex = ids.indexOf(\"postgresql\");\n\t\tconst n8nIndex = ids.indexOf(\"n8n\");\n\t\texpect(pgIndex).toBeLessThan(n8nIndex);\n\t});\n\n\tit(\"warns about GPU when AI services selected without gpu flag\", () => {\n\t\t// Ollama doesn't have gpuRequired=true (it's recommended not required)\n\t\t// but we should still check GPU warning logic works\n\t\tconst result = resolve({ services: [\"redis\"], skillPacks: [], gpu: false });\n\t\t// Redis doesn't need GPU, so no GPU warnings\n\t\tconst gpuWarnings = result.warnings.filter((w) => w.type === \"gpu\");\n\t\texpect(gpuWarnings).toHaveLength(0);\n\t});\n\n\tit(\"resolves tailscale as single service with no dependencies\", () => {\n\t\tconst result = resolve({ services: [\"tailscale\"], skillPacks: [] });\n\t\texpect(result.services).toHaveLength(1);\n\t\texpect(result.services[0]!.definition.id).toBe(\"tailscale\");\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"resolves coolify and dokploy as single services\", () => {\n\t\tconst result = resolve({ services: [\"coolify\", \"dokploy\"], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"coolify\");\n\t\texpect(ids).toContain(\"dokploy\");\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"auto-adds postgresql, redis, and livekit when lasuite-meet-backend is selected\", () => {\n\t\tconst result = resolve({ services: [\"lasuite-meet-backend\"], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"lasuite-meet-backend\");\n\t\texpect(ids).toContain(\"postgresql\");\n\t\texpect(ids).toContain(\"redis\");\n\t\texpect(ids).toContain(\"livekit\");\n\t\texpect(result.addedDependencies.some((a) => a.service === \"postgresql\")).toBe(true);\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"resolves La Suite Meet preset (postgresql, redis, livekit, backend, frontend, agents)\", () => {\n\t\tconst lasuiteMeetServices = [\n\t\t\t\"postgresql\",\n\t\t\t\"redis\",\n\t\t\t\"livekit\",\n\t\t\t\"lasuite-meet-backend\",\n\t\t\t\"lasuite-meet-frontend\",\n\t\t\t\"lasuite-meet-agents\",\n\t\t];\n\t\tconst result = resolve({ services: lasuiteMeetServices, skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\tfor (const id of lasuiteMeetServices) {\n\t\t\texpect(ids).toContain(id);\n\t\t}\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"orders lasuite-meet-backend after postgresql, redis, and livekit\", () => {\n\t\tconst result = resolve({\n\t\t\tservices: [\"lasuite-meet-backend\", \"lasuite-meet-frontend\"],\n\t\t\tskillPacks: [],\n\t\t});\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\tconst pgIdx = ids.indexOf(\"postgresql\");\n\t\tconst redisIdx = ids.indexOf(\"redis\");\n\t\tconst livekitIdx = ids.indexOf(\"livekit\");\n\t\tconst backendIdx = ids.indexOf(\"lasuite-meet-backend\");\n\t\tconst frontendIdx = ids.indexOf(\"lasuite-meet-frontend\");\n\t\texpect(pgIdx).toBeGreaterThanOrEqual(0);\n\t\texpect(backendIdx).toBeGreaterThan(pgIdx);\n\t\texpect(backendIdx).toBeGreaterThan(redisIdx);\n\t\texpect(backendIdx).toBeGreaterThan(livekitIdx);\n\t\texpect(frontendIdx).toBeGreaterThan(backendIdx);\n\t});\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"resolver.test.mjs","names":["expect"],"sources":["../src/resolver.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { resolve } from \"./resolver.js\";\n\n// Mandatory services (convex, mission-control, tailscale) are always auto-included.\n// These IDs and their combined memory are accounted for in all tests.\nconst MANDATORY_IDS = [\"convex\", \"mission-control\", \"tailscale\"];\nconst MANDATORY_MEMORY = 256 + 128 + 64; // convex + mission-control + tailscale\n\ndescribe(\"resolve\", () => {\n\tit(\"returns only mandatory services for empty input\", () => {\n\t\tconst result = resolve({ services: [], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\tfor (const id of MANDATORY_IDS) {\n\t\t\texpect(ids).toContain(id);\n\t\t}\n\t\texpect(result.isValid).toBe(true);\n\t\t// 512 (base) + mandatory services memory\n\t\texpect(result.estimatedMemoryMB).toBe(512 + MANDATORY_MEMORY);\n\t});\n\n\tit(\"resolves a single service alongside mandatory services\", () => {\n\t\tconst result = resolve({ services: [\"redis\"], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"redis\");\n\t\tfor (const id of MANDATORY_IDS) {\n\t\t\texpect(ids).toContain(id);\n\t\t}\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"auto-adds PostgreSQL when n8n is selected\", () => {\n\t\tconst result = resolve({ services: [\"n8n\"], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"n8n\");\n\t\texpect(ids).toContain(\"postgresql\");\n\t\texpect(result.addedDependencies).toContainEqual(\n\t\t\texpect.objectContaining({ service: \"postgresql\" }),\n\t\t);\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"auto-adds Prometheus when Grafana is selected\", () => {\n\t\tconst result = resolve({ services: [\"grafana\"], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"grafana\");\n\t\texpect(ids).toContain(\"prometheus\");\n\t});\n\n\tit(\"detects Redis + Valkey conflict\", () => {\n\t\tconst result = resolve({ services: [\"redis\", \"valkey\"], skillPacks: [] });\n\t\texpect(result.isValid).toBe(false);\n\t\texpect(result.errors).toContainEqual(expect.objectContaining({ type: \"conflict\" }));\n\t});\n\n\tit(\"detects Caddy + Traefik conflict\", () => {\n\t\tconst result = resolve({ services: [\"caddy\", \"traefik\"], skillPacks: [] });\n\t\texpect(result.isValid).toBe(false);\n\t\texpect(result.errors).toContainEqual(expect.objectContaining({ type: \"conflict\" }));\n\t});\n\n\tit(\"expands research-agent skill pack\", () => {\n\t\tconst result = resolve({ services: [], skillPacks: [\"research-agent\"] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"qdrant\");\n\t\texpect(ids).toContain(\"searxng\");\n\t\texpect(ids).toContain(\"browserless\");\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"expands video-creator skill pack\", () => {\n\t\tconst result = resolve({ services: [], skillPacks: [\"video-creator\"] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"ffmpeg\");\n\t\texpect(ids).toContain(\"remotion\");\n\t\texpect(ids).toContain(\"minio\");\n\t});\n\n\tit(\"expands dev-ops skill pack with transitive deps\", () => {\n\t\tconst result = resolve({ services: [], skillPacks: [\"dev-ops\"] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"n8n\");\n\t\texpect(ids).toContain(\"redis\");\n\t\texpect(ids).toContain(\"uptime-kuma\");\n\t\texpect(ids).toContain(\"grafana\");\n\t\texpect(ids).toContain(\"prometheus\");\n\t\t// n8n requires postgresql (transitive)\n\t\texpect(ids).toContain(\"postgresql\");\n\t});\n\n\tit(\"reports unknown service IDs\", () => {\n\t\tconst result = resolve({ services: [\"nonexistent\"], skillPacks: [] });\n\t\texpect(result.errors).toContainEqual(expect.objectContaining({ type: \"unknown_service\" }));\n\t});\n\n\tit(\"reports unknown skill pack IDs\", () => {\n\t\tconst result = resolve({ services: [], skillPacks: [\"nonexistent-pack\"] });\n\t\texpect(result.errors).toContainEqual(expect.objectContaining({ type: \"unknown_skill_pack\" }));\n\t});\n\n\tit(\"does not duplicate services already selected by user\", () => {\n\t\tconst result = resolve({\n\t\t\tservices: [\"qdrant\", \"searxng\", \"browserless\"],\n\t\t\tskillPacks: [\"research-agent\"],\n\t\t});\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\tconst qdrantCount = ids.filter((id) => id === \"qdrant\").length;\n\t\texpect(qdrantCount).toBe(1);\n\t});\n\n\tit(\"estimates memory correctly\", () => {\n\t\tconst result = resolve({ services: [\"redis\"], skillPacks: [] });\n\t\t// 512 (base) + 128 (redis) + mandatory services\n\t\texpect(result.estimatedMemoryMB).toBe(512 + 128 + MANDATORY_MEMORY);\n\t});\n\n\tit(\"adds proxy service when specified\", () => {\n\t\tconst result = resolve({ services: [\"redis\"], skillPacks: [], proxy: \"caddy\" });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"caddy\");\n\t});\n\n\tit(\"adds monitoring stack when flag is set\", () => {\n\t\tconst result = resolve({ services: [], skillPacks: [], monitoring: true });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"uptime-kuma\");\n\t\texpect(ids).toContain(\"grafana\");\n\t\texpect(ids).toContain(\"prometheus\");\n\t});\n\n\tit(\"is deterministic - same input gives same output\", () => {\n\t\tconst input = { services: [\"redis\", \"qdrant\", \"n8n\"], skillPacks: [\"research-agent\"] };\n\t\tconst result1 = resolve(input);\n\t\tconst result2 = resolve(input);\n\t\tconst ids1 = result1.services.map((s) => s.definition.id);\n\t\tconst ids2 = result2.services.map((s) => s.definition.id);\n\t\texpect(ids1).toEqual(ids2);\n\t});\n\n\tit(\"topologically sorts dependencies before dependents\", () => {\n\t\tconst result = resolve({ services: [\"n8n\"], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\tconst pgIndex = ids.indexOf(\"postgresql\");\n\t\tconst n8nIndex = ids.indexOf(\"n8n\");\n\t\texpect(pgIndex).toBeLessThan(n8nIndex);\n\t});\n\n\tit(\"warns about GPU when AI services selected without gpu flag\", () => {\n\t\tconst result = resolve({ services: [\"redis\"], skillPacks: [], gpu: false });\n\t\tconst gpuWarnings = result.warnings.filter((w) => w.type === \"gpu\");\n\t\texpect(gpuWarnings).toHaveLength(0);\n\t});\n\n\tit(\"resolves tailscale (mandatory, always present)\", () => {\n\t\tconst result = resolve({ services: [], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"tailscale\");\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"resolves coolify and dokploy as single services\", () => {\n\t\tconst result = resolve({ services: [\"coolify\", \"dokploy\"], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"coolify\");\n\t\texpect(ids).toContain(\"dokploy\");\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"auto-adds postgresql, redis, and livekit when lasuite-meet-backend is selected\", () => {\n\t\tconst result = resolve({ services: [\"lasuite-meet-backend\"], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"lasuite-meet-backend\");\n\t\texpect(ids).toContain(\"postgresql\");\n\t\texpect(ids).toContain(\"redis\");\n\t\texpect(ids).toContain(\"livekit\");\n\t\texpect(result.addedDependencies.some((a) => a.service === \"postgresql\")).toBe(true);\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"resolves La Suite Meet preset (postgresql, redis, livekit, backend, frontend, agents)\", () => {\n\t\tconst lasuiteMeetServices = [\n\t\t\t\"postgresql\",\n\t\t\t\"redis\",\n\t\t\t\"livekit\",\n\t\t\t\"lasuite-meet-backend\",\n\t\t\t\"lasuite-meet-frontend\",\n\t\t\t\"lasuite-meet-agents\",\n\t\t];\n\t\tconst result = resolve({ services: lasuiteMeetServices, skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\tfor (const id of lasuiteMeetServices) {\n\t\t\texpect(ids).toContain(id);\n\t\t}\n\t\texpect(result.isValid).toBe(true);\n\t});\n\n\tit(\"orders lasuite-meet-backend after postgresql, redis, and livekit\", () => {\n\t\tconst result = resolve({\n\t\t\tservices: [\"lasuite-meet-backend\", \"lasuite-meet-frontend\"],\n\t\t\tskillPacks: [],\n\t\t});\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\tconst pgIdx = ids.indexOf(\"postgresql\");\n\t\tconst redisIdx = ids.indexOf(\"redis\");\n\t\tconst livekitIdx = ids.indexOf(\"livekit\");\n\t\tconst backendIdx = ids.indexOf(\"lasuite-meet-backend\");\n\t\tconst frontendIdx = ids.indexOf(\"lasuite-meet-frontend\");\n\t\texpect(pgIdx).toBeGreaterThanOrEqual(0);\n\t\texpect(backendIdx).toBeGreaterThan(pgIdx);\n\t\texpect(backendIdx).toBeGreaterThan(redisIdx);\n\t\texpect(backendIdx).toBeGreaterThan(livekitIdx);\n\t\texpect(frontendIdx).toBeGreaterThan(backendIdx);\n\t});\n\n\t// ── Mandatory services enforcement ──────────────────────────────────────\n\n\tit(\"auto-includes mandatory services even when not selected\", () => {\n\t\tconst result = resolve({ services: [\"redis\"], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\texpect(ids).toContain(\"mission-control\");\n\t\texpect(ids).toContain(\"convex\");\n\t\texpect(ids).toContain(\"tailscale\");\n\t\tconst mc = result.services.find((s) => s.definition.id === \"mission-control\");\n\t\texpect(mc!.addedBy).toBe(\"mandatory\");\n\t});\n\n\tit(\"marks user-selected mandatory services as 'user' not 'mandatory'\", () => {\n\t\tconst result = resolve({ services: [\"mission-control\"], skillPacks: [] });\n\t\tconst mc = result.services.find((s) => s.definition.id === \"mission-control\");\n\t\texpect(mc!.addedBy).toBe(\"user\");\n\t\t// convex should still be auto-added as dependency of mission-control\n\t\tconst convex = result.services.find((s) => s.definition.id === \"convex\");\n\t\texpect(convex).toBeDefined();\n\t});\n\n\tit(\"orders convex before mission-control (dependency ordering)\", () => {\n\t\tconst result = resolve({ services: [\"redis\"], skillPacks: [] });\n\t\tconst ids = result.services.map((s) => s.definition.id);\n\t\tconst convexIdx = ids.indexOf(\"convex\");\n\t\tconst mcIdx = ids.indexOf(\"mission-control\");\n\t\texpect(convexIdx).toBeLessThan(mcIdx);\n\t});\n});\n"],"mappings":"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"}