limixbot 0.3.24 → 0.3.26

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 (140) hide show
  1. package/dist/assets/{ActivityPanel-BjjmX49M.js → ActivityPanel-DkwIh-YJ.js} +1 -1
  2. package/dist/assets/{App-BWtQhGWp.js → App-DCe3hDbm.js} +119 -119
  3. package/dist/assets/{AppEventEditor-Bq1-MGxi.js → AppEventEditor-25dBAYiN.js} +1 -1
  4. package/dist/assets/BotSettingsDialog-DJxZo-ro.js +16 -0
  5. package/dist/assets/{BrowserPanel-CPLcno_m.js → BrowserPanel-DVji4pZh.js} +1 -1
  6. package/dist/assets/{BrowserProfilesManager-D9JsCp1k.js → BrowserProfilesManager-C4rCEedT.js} +1 -1
  7. package/dist/assets/{CloudAddDialog-DcmN7A5a.js → CloudAddDialog-BybF9Mcz.js} +1 -1
  8. package/dist/assets/{ComputerFilesPane-Bjx6TGmM.js → ComputerFilesPane-D-EgbYKM.js} +1 -1
  9. package/dist/assets/{ComputerPanel-C9Xx4Ifs.js → ComputerPanel-CGKdVGz4.js} +2 -2
  10. package/dist/assets/{DataPanel-IfpQCi3b.js → DataPanel-BXh6perv.js} +1 -1
  11. package/dist/assets/{DesktopViewer-RjjUUlrQ.js → DesktopViewer-CRcjWYHe.js} +1 -1
  12. package/dist/assets/{FilesView-D_0GwHAN.js → FilesView-B67sskTN.js} +1 -1
  13. package/dist/assets/{GrokMoveDialog-CUie-0en.js → GrokMoveDialog-DNJckF5G.js} +1 -1
  14. package/dist/assets/{InspectorPanel-BuFL6wWf.js → InspectorPanel-CjtRMfJu.js} +1 -1
  15. package/dist/assets/{KeyboardShortcutsModal-uOhXagTA.js → KeyboardShortcutsModal-znw4NkA5.js} +1 -1
  16. package/dist/assets/{LiveDesktop-BwpZa-Ri.js → LiveDesktop-BgFrQKAC.js} +1 -1
  17. package/dist/assets/{LocalVmWorkspace-CousHKOb.js → LocalVmWorkspace-Dk61S9xg.js} +1 -1
  18. package/dist/assets/{MacLocalControl-BKDzplO2.js → MacLocalControl-DjyhCaWB.js} +1 -1
  19. package/dist/assets/{MarkdownFilePreview-CCwc1Tek.js → MarkdownFilePreview-Btb--ZSy.js} +1 -1
  20. package/dist/assets/{MenuMotion-CSgaJLc2.js → MenuMotion-BOUToDZz.js} +1 -1
  21. package/dist/assets/{NewBotDialog-BtvoCpqN.js → NewBotDialog-CsQPMX7F.js} +1 -1
  22. package/dist/assets/{PhoneAppDialog-C1hIwyT8.js → PhoneAppDialog-4OG210md.js} +1 -1
  23. package/dist/assets/{PlacesRail-PP97GpM6.js → PlacesRail-dslg5RRJ.js} +1 -1
  24. package/dist/assets/{PluginsPanel-DpUEC1zc.js → PluginsPanel-CaWk4eSi.js} +1 -1
  25. package/dist/assets/{ProIntroduction-C-7uexIW.js → ProIntroduction-BuOIgmKa.js} +1 -1
  26. package/dist/assets/{RemoteAgentSettingsPanel-DtofJtgF.js → RemoteAgentSettingsPanel-Drk4pPKT.js} +1 -1
  27. package/dist/assets/{RoomPanel-CHbgQ0xd.js → RoomPanel-BGLIvmBz.js} +1 -1
  28. package/dist/assets/{RoutinesPage-ZdFTYQc-.js → RoutinesPage-BuAEX1fg.js} +2 -2
  29. package/dist/assets/SettingsModal-CwK4KFqq.js +19 -0
  30. package/dist/assets/{ShareTeamDialog-CNu6Kwet.js → ShareTeamDialog-C5Fv8jWl.js} +1 -1
  31. package/dist/assets/{SignedInSites-CyWcDbLW.js → SignedInSites-BQ2QP4YA.js} +1 -1
  32. package/dist/assets/TeamLibraryPanel-B0HAAvLv.js +1 -0
  33. package/dist/assets/{TeamMapPage-C2PnNdil.js → TeamMapPage-DCkx9eov.js} +1 -1
  34. package/dist/assets/{TriggersPage-B_br6xBZ.js → TriggersPage-B2sqDFfI.js} +1 -1
  35. package/dist/assets/{VisibilitySection-DH0WrBCn.js → VisibilitySection-CqL-TbZp.js} +1 -1
  36. package/dist/assets/{WebhookTrigger-BASBoMb_.js → WebhookTrigger-CorVaJKR.js} +1 -1
  37. package/dist/assets/{WorksOnGrid-CboITAAs.js → WorksOnGrid-1tMl62nD.js} +1 -1
  38. package/dist/assets/{abnfDiagram-YUKMZFIV-CJ69o_vp.js → abnfDiagram-YUKMZFIV-UeQ15ljp.js} +1 -1
  39. package/dist/assets/{activity-BdRjGS2C.js → activity-Rykx5lZJ.js} +1 -1
  40. package/dist/assets/{architectureDiagram-47P4ROYG-BH57AmKC.js → architectureDiagram-47P4ROYG-Cb42ilBV.js} +1 -1
  41. package/dist/assets/{blockDiagram-E7TT5TSB-D3ebMs_o.js → blockDiagram-E7TT5TSB-BF1yg64q.js} +1 -1
  42. package/dist/assets/{c4Diagram-CRA5TL53-6TrdcGZR.js → c4Diagram-CRA5TL53-VaNJpISp.js} +1 -1
  43. package/dist/assets/{calendar-days-DiMBTH2e.js → calendar-days-kC634hX1.js} +1 -1
  44. package/dist/assets/channel-Di74R1Kh.js +1 -0
  45. package/dist/assets/{chunk-2Q5K7J3B-BemwHdji.js → chunk-2Q5K7J3B-NtXjlfnQ.js} +1 -1
  46. package/dist/assets/{chunk-5VM5RSS4-CQZbLBld.js → chunk-5VM5RSS4-CYWCBb40.js} +1 -1
  47. package/dist/assets/{chunk-7TKQ45FW-Dy6EXFTX.js → chunk-7TKQ45FW-CTx1wLnU.js} +1 -1
  48. package/dist/assets/{chunk-AW2ZBBNX-BbKGGyK8.js → chunk-AW2ZBBNX-PDrwpbhk.js} +1 -1
  49. package/dist/assets/{chunk-FVRAUYC3-DHqUalc2.js → chunk-FVRAUYC3-CqIp8Ubp.js} +2 -2
  50. package/dist/assets/{chunk-HTAEGDNF-B66Vjlwq.js → chunk-HTAEGDNF-yA6riuIr.js} +1 -1
  51. package/dist/assets/{chunk-JWPE2WC7-DrtdT6L3.js → chunk-JWPE2WC7-DEE_S031.js} +1 -1
  52. package/dist/assets/{chunk-KQW6MTUR-t7RW8fFT.js → chunk-KQW6MTUR-QybxNtsY.js} +1 -1
  53. package/dist/assets/{chunk-XXDRQBXY-CntyS42N.js → chunk-XXDRQBXY-BjchQyqU.js} +1 -1
  54. package/dist/assets/{classDiagram-v2-K4WV4PDN-KlNCgeTt.js → classDiagram-v2-K4WV4PDN-DtOc8Abr.js} +1 -1
  55. package/dist/assets/{coins-BHKpT34i.js → coins-D0wNJCPg.js} +1 -1
  56. package/dist/assets/{core-DEy6KDLR.js → core-Blwbk7ct.js} +1 -1
  57. package/dist/assets/{cose-bilkent-JH36ORCC-C_cXoOGT.js → cose-bilkent-JH36ORCC-CWHsqn7M.js} +1 -1
  58. package/dist/assets/{cynefin-EF2NZ3EQ-DGuwW5zt.js → cynefin-EF2NZ3EQ-BqS-D9jN.js} +1 -1
  59. package/dist/assets/{cynefinDiagram-3GCD6N5R-qMzxXkVk.js → cynefinDiagram-3GCD6N5R-Fc51YETb.js} +1 -1
  60. package/dist/assets/{dagre-W4DXFKR2-Q-osY3Yi.js → dagre-W4DXFKR2-So1kz52t.js} +1 -1
  61. package/dist/assets/{diagram-2UJZ2QOL-DyVJSBOF.js → diagram-2UJZ2QOL-DpdA_Lk0.js} +1 -1
  62. package/dist/assets/{diagram-OVF4WLC6-C5qBFdpm.js → diagram-OVF4WLC6-BqSu7ih2.js} +1 -1
  63. package/dist/assets/{diagram-PFPMY2P6-C1PW3U2P.js → diagram-PFPMY2P6-BoM6YcIG.js} +1 -1
  64. package/dist/assets/{diagram-UMYRVEAY-D2pa0Mzi.js → diagram-UMYRVEAY-B7wKoLi9.js} +1 -1
  65. package/dist/assets/{diagram-YEKJPTXX-BIzOA1li.js → diagram-YEKJPTXX-BEXKw540.js} +1 -1
  66. package/dist/assets/{diagram-ZIFT7M5P-B1UL3O8m.js → diagram-ZIFT7M5P-BAD7sJRi.js} +1 -1
  67. package/dist/assets/{ebnfDiagram-VR2GEFS7-Cxu-kU8s.js → ebnfDiagram-VR2GEFS7-DILoqvy-.js} +1 -1
  68. package/dist/assets/{elk-IJKZMXRS-DJS39Bm9.js → elk-IJKZMXRS-BHW-Cxin.js} +1 -1
  69. package/dist/assets/{erDiagram-O2IAPWRE-Cb31BS5L.js → erDiagram-O2IAPWRE-Blp0Wwq0.js} +1 -1
  70. package/dist/assets/{file-up-DWGlVRGP.js → file-up-CEuHjVBK.js} +1 -1
  71. package/dist/assets/{flowDiagram-OXPTDLAJ-BaPTzS6m.js → flowDiagram-OXPTDLAJ-6SkVmYje.js} +1 -1
  72. package/dist/assets/{ganttDiagram-R7TSEDQI-B_Lr-26i.js → ganttDiagram-R7TSEDQI-DFDwj8QX.js} +1 -1
  73. package/dist/assets/{gitGraphDiagram-XJZIOB7I-UHyDFX6a.js → gitGraphDiagram-XJZIOB7I-mtJG-sis.js} +1 -1
  74. package/dist/assets/{import-name-Cxwtm5XO.js → import-name-B3mVDURb.js} +1 -1
  75. package/dist/assets/{index-BME9PdWM.js → index-BZOGYABm.js} +1 -1
  76. package/dist/assets/{index-BSrb6nsc.js → index-D2Jm8LAf.js} +2 -2
  77. package/dist/assets/{infoDiagram-5W2HQ5XZ-B7ovBJYA.js → infoDiagram-5W2HQ5XZ-CmyXB17U.js} +1 -1
  78. package/dist/assets/{ishikawaDiagram-K3B6WC7H-BYS-TlrF.js → ishikawaDiagram-K3B6WC7H-BKFtIESa.js} +1 -1
  79. package/dist/assets/{journeyDiagram-COXZFDF6-jJxKYEtT.js → journeyDiagram-COXZFDF6-DuXMREdz.js} +1 -1
  80. package/dist/assets/{kanban-definition-P3RFRI5V-DyiFYaIK.js → kanban-definition-P3RFRI5V-B9mWvKdh.js} +1 -1
  81. package/dist/assets/{mcp-servers-DXFuhTlS.js → mcp-servers-D3PCWkFu.js} +1 -1
  82. package/dist/assets/{mermaid.core-D3YJgsiB.js → mermaid.core-3c-Lql2y.js} +4 -4
  83. package/dist/assets/{mindmap-definition-LZFPQGKD-DnbWodUY.js → mindmap-definition-LZFPQGKD-BmLt9bkD.js} +1 -1
  84. package/dist/assets/{minimize-Af1aU3Tz.js → minimize-BfKI3Nbu.js} +1 -1
  85. package/dist/assets/{monitor-DuYHWIyw.js → monitor-C-EBTsrK.js} +1 -1
  86. package/dist/assets/{new-bot-TwOnvVO1.js → new-bot-3sQib6nJ.js} +1 -1
  87. package/dist/assets/{org-library-BLD6YHcM.js → org-library-DHuACSMf.js} +1 -1
  88. package/dist/assets/{package-format-72bbOy2w.js → package-format-CAZ-xCP-.js} +1 -1
  89. package/dist/assets/{pegDiagram-BGZESJAR-CcOuP6zx.js → pegDiagram-BGZESJAR-5w1MTtLg.js} +1 -1
  90. package/dist/assets/{pieDiagram-CAPJLFHJ-DhCU7Sfy.js → pieDiagram-CAPJLFHJ-C4etNvsK.js} +1 -1
  91. package/dist/assets/{quadrantDiagram-GDMTTRAM-7OziX26h.js → quadrantDiagram-GDMTTRAM-k4cDUeIN.js} +1 -1
  92. package/dist/assets/{rail-DI2iZJYt.js → rail-C6qkoJbO.js} +1 -1
  93. package/dist/assets/{railroadDiagram-SM67HX2B-rg44R4U2.js → railroadDiagram-SM67HX2B-B9d0qasL.js} +1 -1
  94. package/dist/assets/{remote-desktop-panel-BPKKvcHT.js → remote-desktop-panel-sCBdOaQh.js} +1 -1
  95. package/dist/assets/{requirementDiagram-X7JNWC4A-DFNvg72O.js → requirementDiagram-X7JNWC4A-BlYV4z86.js} +1 -1
  96. package/dist/assets/{sankeyDiagram-UM26HJRW-CUOrQF2i.js → sankeyDiagram-UM26HJRW-DaIVWJEd.js} +1 -1
  97. package/dist/assets/{sequenceDiagram-ZO4K6R2Y-4HKsH1wt.js → sequenceDiagram-ZO4K6R2Y-CAnXpud0.js} +1 -1
  98. package/dist/assets/{sizeCapture-INFHLROL-BEXQDTx-.js → sizeCapture-INFHLROL-BIeqIcHi.js} +1 -1
  99. package/dist/assets/{stateDiagram-v2-TBUQTH76-BYBLUqiy.js → stateDiagram-v2-TBUQTH76-7ql5dn9y.js} +1 -1
  100. package/dist/assets/{styles-C2_XZv1s.js → styles-BRMaHK08.js} +3 -3
  101. package/dist/assets/{swimlanes-N4OXWK64-D-bp8wWN.js → swimlanes-N4OXWK64-CItt-UlU.js} +1 -1
  102. package/dist/assets/swimlanesDiagram-K3J5GTZL-DAZsljU6.js +8 -0
  103. package/dist/assets/{team-share-Dpu1nJNz.js → team-share-2OjfOHsk.js} +1 -1
  104. package/dist/assets/{timeline-definition-YOQAKGHF-CtLFkcud.js → timeline-definition-YOQAKGHF-8Xn9QBNo.js} +1 -1
  105. package/dist/assets/{usecaseDiagram-VIAY4XPW-AWOhUKvG.js → usecaseDiagram-VIAY4XPW-B6LxotkO.js} +1 -1
  106. package/dist/assets/{vennDiagram-BLWOH2XV-C_OYDBvp.js → vennDiagram-BLWOH2XV-Ciex0LDC.js} +1 -1
  107. package/dist/assets/{vi-P4zxF4j0.js → vi-C-E3a4Tv.js} +2 -2
  108. package/dist/assets/{wardleyDiagram-YMQ3BMBF-CdxMh8tE.js → wardleyDiagram-YMQ3BMBF-B_TbpQlN.js} +1 -1
  109. package/dist/assets/{xychartDiagram-TAQBALBS-BL2spC5z.js → xychartDiagram-TAQBALBS-Dz_iIYt1.js} +1 -1
  110. package/dist/index.html +2 -2
  111. package/dist/rail.html +4 -4
  112. package/dist-server/index.js +272 -252
  113. package/dist-server/openmausbot.js +1 -1
  114. package/dist-server/package.json +1 -1
  115. package/dist-server/pair-cli.js +1 -1
  116. package/dist-server/server/imported-skills.js +7 -2
  117. package/dist-server/server/index.js +7 -5
  118. package/dist-server/server/limix-bot-library.js +15 -8
  119. package/dist-server/server/limix-lang.js +2 -0
  120. package/dist-server/server/limix-skill-hub.js +20 -19
  121. package/dist-server/server/limix-skill-store.js +180 -0
  122. package/dist-server/server/routes/bot-skills.js +15 -4
  123. package/dist-server/shared/first-reply-rescue.js +4 -3
  124. package/package.json +1 -1
  125. package/skills/windows-computer-automation/SKILL.md +53 -0
  126. package/skills/windows-computer-automation/assets/HERIONIX-LEADER-PROMPT.md +25 -0
  127. package/skills/windows-computer-automation/assets/control-policy.example.json +25 -0
  128. package/skills/windows-computer-automation/assets/mcp-servers.example.json +19 -0
  129. package/skills/windows-computer-automation/manifest.json +27 -0
  130. package/skills/windows-computer-automation/references/herionix-integration.md +72 -0
  131. package/skills/windows-computer-automation/references/routing.md +26 -0
  132. package/skills/windows-computer-automation/references/scenarios.md +44 -0
  133. package/skills/windows-computer-automation/references/security.md +24 -0
  134. package/skills/windows-computer-automation/scripts/policy_engine.py +235 -0
  135. package/skills/windows-computer-automation/scripts/test_policy_engine.py +107 -0
  136. package/dist/assets/BotSettingsDialog-CVlvG4Rb.js +0 -16
  137. package/dist/assets/SettingsModal-Cdz3bEYZ.js +0 -15
  138. package/dist/assets/TeamLibraryPanel-DMhdlaSH.js +0 -1
  139. package/dist/assets/channel-k2J2GMwv.js +0 -1
  140. package/dist/assets/swimlanesDiagram-K3J5GTZL-DTQBdgKt.js +0 -8
@@ -4193,7 +4193,7 @@ string(),skillManifestEntrySchema),stagedSkillWriteSchema=external_exports.objec
4193
4193
  /^[a-f0-9]{64}$/).optional(),warnings:external_exports.array(external_exports.string()),skippedFiles:external_exports.array(external_exports.string()),createdAt:external_exports.string(),baseSha256:external_exports.string().regex(/^[a-f0-9]{64}$/).optional(),baseAppliedStageId:external_exports.string().optional()}).superRefine((entry,ctx)=>{entry.action==="update"&&(!entry.baseSha256||!entry.baseAppliedStageId)&&ctx.addIssue({code:"custom",message:"updated skills require their reviewed base revi\
4194
4194
  sion"})}),stagedStoreSchema=external_exports.object({writes:external_exports.record(external_exports.string(),stagedSkillWriteSchema)})}});import{dirname as dirname29,isAbsolute as isAbsolute14,join as join64,resolve as resolve22}from"node:path";var IMPORTED_SKILLS_DIR,INDEX_PATH,sha256Hex,entrySchema,indexSchema,choicesSchema2,init_imported_skills=__esm({"server/imported-skills.ts"(){"use strict";init_zod();init_skill_fit();init_skill_md();init_atomic();init_config3();init_skill_storage();init_skills();IMPORTED_SKILLS_DIR=join64(DATA_DIR,"imported-skills"),INDEX_PATH=join64(DATA_DIR,"skill-state","imported-skills.json"),sha256Hex=
4195
4195
  external_exports.string().regex(/^[a-f0-9]{64}$/),entrySchema=external_exports.object({description:external_exports.string(),source:external_exports.enum(["claudeCode","codex"]),root:external_exports.string(),origin:external_exports.string(),label:external_exports.string(),sha256:sha256Hex,filesSha256:sha256Hex,importedAt:external_exports.string(),warnings:external_exports.array(external_exports.string()),license:external_exports.string().optional(),compatibility:external_exports.string().optional()}),
4196
- indexSchema=external_exports.record(external_exports.string(),entrySchema),choicesSchema2=external_exports.object({off:external_exports.array(external_exports.string()),on:external_exports.record(external_exports.string(),sha256Hex).default({}),listed:external_exports.array(external_exports.string()).optional()})}});function parseToml(source){let text13=source.charCodeAt(0)===65279?source.slice(1):source,i2=0,root=table(),current=root,fail3=message5=>{throw new TomlError(`line ${text13.slice(0,i2).split(`
4196
+ indexSchema=external_exports.record(external_exports.string(),entrySchema),choicesSchema2=external_exports.object({off:external_exports.array(external_exports.string()),on:external_exports.record(external_exports.string(),sha256Hex).default({}),listed:external_exports.array(external_exports.string()).optional(),picked:external_exports.array(external_exports.string()).optional()})}});function parseToml(source){let text13=source.charCodeAt(0)===65279?source.slice(1):source,i2=0,root=table(),current=root,fail3=message5=>{throw new TomlError(`line ${text13.slice(0,i2).split(`
4197
4197
  `).length}: ${message5}`)},spaces=()=>{for(;text13[i2]===" "||text13[i2]===" ";)i2++},comment=()=>{if(text13[i2]==="#")for(;i2<text13.length&&text13[i2]!==`
4198
4198
  `;)i2++},newline=()=>{if(text13[i2]===`
4199
4199
  `)i2+=1;else if(text13[i2]==="\r"&&text13[i2+1]===`
@@ -1 +1 @@
1
- {"type":"module","version":"0.3.24"}
1
+ {"type":"module","version":"0.3.26"}
@@ -4192,7 +4192,7 @@ string(),skillManifestEntrySchema),stagedSkillWriteSchema=external_exports.objec
4192
4192
  /^[a-f0-9]{64}$/).optional(),warnings:external_exports.array(external_exports.string()),skippedFiles:external_exports.array(external_exports.string()),createdAt:external_exports.string(),baseSha256:external_exports.string().regex(/^[a-f0-9]{64}$/).optional(),baseAppliedStageId:external_exports.string().optional()}).superRefine((entry,ctx)=>{entry.action==="update"&&(!entry.baseSha256||!entry.baseAppliedStageId)&&ctx.addIssue({code:"custom",message:"updated skills require their reviewed base revi\
4193
4193
  sion"})}),stagedStoreSchema=external_exports.object({writes:external_exports.record(external_exports.string(),stagedSkillWriteSchema)})}});import{dirname as dirname29,isAbsolute as isAbsolute14,join as join64,resolve as resolve22}from"node:path";var IMPORTED_SKILLS_DIR,INDEX_PATH,sha256Hex,entrySchema,indexSchema,choicesSchema2,init_imported_skills=__esm({"server/imported-skills.ts"(){"use strict";init_zod();init_skill_fit();init_skill_md();init_atomic();init_config3();init_skill_storage();init_skills();IMPORTED_SKILLS_DIR=join64(DATA_DIR,"imported-skills"),INDEX_PATH=join64(DATA_DIR,"skill-state","imported-skills.json"),sha256Hex=
4194
4194
  external_exports.string().regex(/^[a-f0-9]{64}$/),entrySchema=external_exports.object({description:external_exports.string(),source:external_exports.enum(["claudeCode","codex"]),root:external_exports.string(),origin:external_exports.string(),label:external_exports.string(),sha256:sha256Hex,filesSha256:sha256Hex,importedAt:external_exports.string(),warnings:external_exports.array(external_exports.string()),license:external_exports.string().optional(),compatibility:external_exports.string().optional()}),
4195
- indexSchema=external_exports.record(external_exports.string(),entrySchema),choicesSchema2=external_exports.object({off:external_exports.array(external_exports.string()),on:external_exports.record(external_exports.string(),sha256Hex).default({}),listed:external_exports.array(external_exports.string()).optional()})}});function parseToml(source){let text13=source.charCodeAt(0)===65279?source.slice(1):source,i2=0,root=table(),current=root,fail3=message5=>{throw new TomlError(`line ${text13.slice(0,i2).split(`
4195
+ indexSchema=external_exports.record(external_exports.string(),entrySchema),choicesSchema2=external_exports.object({off:external_exports.array(external_exports.string()),on:external_exports.record(external_exports.string(),sha256Hex).default({}),listed:external_exports.array(external_exports.string()).optional(),picked:external_exports.array(external_exports.string()).optional()})}});function parseToml(source){let text13=source.charCodeAt(0)===65279?source.slice(1):source,i2=0,root=table(),current=root,fail3=message5=>{throw new TomlError(`line ${text13.slice(0,i2).split(`
4196
4196
  `).length}: ${message5}`)},spaces=()=>{for(;text13[i2]===" "||text13[i2]===" ";)i2++},comment=()=>{if(text13[i2]==="#")for(;i2<text13.length&&text13[i2]!==`
4197
4197
  `;)i2++},newline=()=>{if(text13[i2]===`
4198
4198
  `)i2+=1;else if(text13[i2]==="\r"&&text13[i2+1]===`
@@ -47,6 +47,8 @@ const choicesSchema = z.object({
47
47
  off: z.array(z.string()),
48
48
  on: z.record(z.string(), sha256Hex).default({}),
49
49
  listed: z.array(z.string()).optional(),
50
+ /** Limix Bot: skills from this computer are off for a bot until a person adds one from its store. */
51
+ picked: z.array(z.string()).optional(),
50
52
  });
51
53
  /** Every imported skill by name; empty when there are none or the index cannot be trusted. */
52
54
  export function readImportedSkills() {
@@ -279,6 +281,8 @@ export function storeImportedSkills(inputs) {
279
281
  function enabledFor(name, entry, choices) {
280
282
  if (choices.off.includes(name))
281
283
  return false;
284
+ if (!(choices.picked ?? []).includes(name))
285
+ return false;
282
286
  return entry.warnings.length === 0 || choices.on[name] === entry.filesSha256;
283
287
  }
284
288
  const comparable = (path) => {
@@ -361,9 +365,10 @@ export function setImportedSkillEnabled(botId, name, enabled) {
361
365
  const off = choices.off.filter((other) => other !== name);
362
366
  const { [name]: _read, ...on } = choices.on;
363
367
  const listed = choices.listed?.filter((other) => other !== name);
368
+ const picked = (choices.picked ?? []).filter((other) => other !== name);
364
369
  const next = enabled
365
- ? { off, on: entry.warnings.length ? { ...on, [name]: entry.filesSha256 } : on, ...(listed ? { listed: [...listed, name].sort() } : {}) }
366
- : { off: [...off, name].sort(), on, ...(listed ? { listed } : {}) };
370
+ ? { off, on: entry.warnings.length ? { ...on, [name]: entry.filesSha256 } : on, ...(listed ? { listed: [...listed, name].sort() } : {}), picked: [...picked, name].sort() }
371
+ : { off: [...off, name].sort(), on, ...(listed ? { listed } : {}), picked };
367
372
  mkdirSync(dirname(choicesPath(botId)), { recursive: true, mode: 0o700 });
368
373
  writeFileAtomic(choicesPath(botId), `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
369
374
  return importedSkillListings(botId, new Set()).find((skill) => skill.name === name);
@@ -442,7 +442,7 @@ import { AGENTS_NODE_FLAG, SPAWNED_PROXIES } from "./proxy-paths.js";
442
442
  import { agentsProxyLaunch, agentsToolBridgeEnv, applyAgentsBridge, bridgedToolTitle, turnEngine } from "./agents-bridge-switch.js";
443
443
  import { agentsServerEnv, botDrivenConversation, COMPUTER_OFF_REFUSAL, computerPickRefusal, directTurnKind, roomSkillsRefusal } from "./agents-turn.js";
444
444
  import { harnessMcpTokenEnv } from "./harness-mcp-token.js";
445
- import { loadBundledSkills, loadUserSkills, mergeSkills, renderSkillInstructions, selectBundledSkills, skillsLibraryEnabled, readSkillLibraryIndex, } from "./skill-library.js";
445
+ import { loadBundledSkills, loadUserSkills, mergeSkills, renderSkillInstructions, skillsLibraryEnabled, readSkillLibraryIndex, } from "./skill-library.js";
446
446
  import { runSkillsLibraryBootSweep } from "./skills-library-migration.js";
447
447
  import { installedPlaybookInstructions } from "./installed-playbooks.js";
448
448
  import { picture as sharedPicture } from "./package-export.js";
@@ -512,6 +512,7 @@ import { createBotOrderRoutes } from "./routes/bot-order.js";
512
512
  import { createBotLifecycleRoutes } from "./routes/bot-lifecycle.js";
513
513
  import { createBotSkillsRoutes } from "./routes/bot-skills.js";
514
514
  import { startHubAutoSync } from "./limix-skill-hub.js";
515
+ import { limixBundledFor, limixSelectBundled, limixSkillDefaultsMigration } from "./limix-skill-store.js";
515
516
  import { createSkillShareRoutes } from "./routes/skill-share.js";
516
517
  import { createTeamContextRoutes } from "./routes/team-context.js";
517
518
  import { createAppSuggestions } from "./host/app-suggestions.js";
@@ -1436,6 +1437,7 @@ function resyncOpenCodeProviderKeys() {
1436
1437
  const firstEngineRead = holdUntilFirstEngineRead();
1437
1438
  await registry.load(providerConfigs(), decorateHostedProvider);
1438
1439
  const bundledSkills = loadBundledSkills();
1440
+ limixSkillDefaultsMigration();
1439
1441
  const availableSkills = () => mergeSkills(bundledSkills, loadUserSkills(join(DATA_DIR, "skills")));
1440
1442
  /** Every turn the engine can mount it, whatever its words: they pick only the skill's instructions. A Cloud home or a hosted workspace has no USB port. */
1441
1443
  const usbPhoneOffered = (instance, skills) => instance.adapter.capabilities.phoneMcp === true &&
@@ -8084,8 +8086,8 @@ async function beginTurn(botId, text, opts) {
8084
8086
  handoffs.begin(threadId, dispatchClaimId, dispatchContext.handoff);
8085
8087
  const integrations = {};
8086
8088
  let browser = null;
8087
- const skills = availableSkills(), phoneOffered = usbPhoneOffered(instance, skills) && !proactive.fence.confines(threadId) && !botApiThread(threadId);
8088
- const selectedSkills = selectBundledSkills(providerText, [
8089
+ const skills = limixBundledFor(bot.id, availableSkills()), phoneOffered = usbPhoneOffered(instance, skills) && !proactive.fence.confines(threadId) && !botApiThread(threadId);
8090
+ const selectedSkills = limixSelectBundled(providerText, [
8089
8091
  ...(phoneOffered ? ["phoneMcp"] : []),
8090
8092
  ...(skillAuthoring ? ["skillAuthoring"] : []),
8091
8093
  ], skills);
@@ -10048,8 +10050,8 @@ setupRetry = 0) {
10048
10050
  : undefined, bot.id);
10049
10051
  const roomContext = roomWindow.text;
10050
10052
  const turnImages = usesNativeImageInput ? [...burstImages, ...resolvedLatestImages.images, ...await videoFramesWithin(burstImages.length + resolvedLatestImages.images.length, resolvedLatestImages.videos)] : [];
10051
- const skills = availableSkills(), phoneOffered = usbPhoneOffered(instance, skills);
10052
- const selectedSkills = mergeSkills(selectBundledSkills(roomContext, phoneOffered ? ["phoneMcp"] : [], skills), selectBundledSkills(latestUser?.text ?? "", skillAuthoring ? ["skillAuthoring"] : [], skills));
10053
+ const skills = limixBundledFor(bot.id, availableSkills()), phoneOffered = usbPhoneOffered(instance, skills);
10054
+ const selectedSkills = mergeSkills(limixSelectBundled(roomContext, phoneOffered ? ["phoneMcp"] : [], skills), limixSelectBundled(latestUser?.text ?? "", skillAuthoring ? ["skillAuthoring"] : [], skills));
10053
10055
  if (phoneOffered)
10054
10056
  integrations.phone = phoneIntegration(bot.id, threadId, internalGeneration);
10055
10057
  try {
@@ -4,6 +4,7 @@
4
4
  // template only describes who the bot is: its routines always arrive paused and nothing is connected for the person.
5
5
  import { MASCOT_BODY_IDS } from "../shared/mascot-bodies.js";
6
6
  import { MAUS_COLOR_NAMES } from "../shared/wire.js";
7
+ import { lxs } from "./limix-lang.js";
7
8
  const TEAM_LIBRARY_RAW_ROOT = "https://raw.githubusercontent.com/leomieo/limix-bot-teams/main";
8
9
  const LIST_TTL_MS = 10 * 60 * 1000;
9
10
  const MAX_CATALOG_BYTES = 256_000;
@@ -52,11 +53,11 @@ function apps(value, field) {
52
53
  }
53
54
  export function parseBotListing(value) {
54
55
  if (!isRecord(value) || value.format !== "openmaus.catalog" || value.version !== 1)
55
- throw new Error("Danh sách mẫu không đúng định dạng");
56
+ throw new Error(lxs("Danh sách mẫu không đúng định dạng", "The template list has the wrong format"));
56
57
  if (value.bots === undefined)
57
58
  return [];
58
59
  if (!Array.isArray(value.bots) || value.bots.length > 200)
59
- throw new Error("Danh sách bot mẫu không hợp lệ");
60
+ throw new Error(lxs("Danh sách bot mẫu không hợp lệ", "The template bot list is not valid"));
60
61
  const seen = new Set();
61
62
  const out = [];
62
63
  value.bots.forEach((raw, index) => {
@@ -83,10 +84,10 @@ export function parseBotListing(value) {
83
84
  }
84
85
  export function parseLibraryBot(value, expectedId) {
85
86
  if (!isRecord(value) || value.format !== "limix.bot" || value.version !== 1)
86
- throw new Error("Tệp bot mẫu không đúng định dạng");
87
+ throw new Error(lxs("Tệp bot mẫu không đúng định dạng", "The template bot file has the wrong format"));
87
88
  const id = text(value.id, "id", 64);
88
89
  if (id !== expectedId)
89
- throw new Error("Tệp bot mẫu không khớp với danh sách");
90
+ throw new Error(lxs("Tệp bot mẫu không khớp với danh sách", "The template bot file does not match the list"));
90
91
  if (!Array.isArray(value.tryAsking) || value.tryAsking.length === 0 || value.tryAsking.length > 6)
91
92
  throw new Error("tryAsking is invalid");
92
93
  const bot = {
@@ -123,20 +124,26 @@ export function parseLibraryBot(value, expectedId) {
123
124
  schedule: { type: "daily", time, weekdays: [...new Set(weekdays)].sort() }, enabled: false, overlap: "skip" };
124
125
  });
125
126
  }
127
+ if (value.skills !== undefined) {
128
+ if (!Array.isArray(value.skills) || value.skills.length > 20 || value.skills.some((name) => typeof name !== "string" || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name) || name.length > 64)) {
129
+ throw new Error("skills is invalid");
130
+ }
131
+ bot.skills = [...new Set(value.skills)];
132
+ }
126
133
  return bot;
127
134
  }
128
135
  async function fetchJson(url, maxBytes) {
129
136
  const response = await fetch(url, { headers: { accept: "application/json, text/plain;q=0.9" }, redirect: "error", signal: AbortSignal.timeout(10_000) });
130
137
  if (!response.ok)
131
- throw new Error(`GitHub trả lỗi HTTP ${response.status}`);
138
+ throw new Error(lxs(`GitHub trả lỗi HTTP ${response.status}`, `GitHub returned HTTP ${response.status}`));
132
139
  const raw = await response.text();
133
140
  if (Buffer.byteLength(raw) > maxBytes)
134
- throw new Error("Tệp mẫu quá lớn");
141
+ throw new Error(lxs("Tệp mẫu quá lớn", "The template file is too large"));
135
142
  try {
136
143
  return JSON.parse(raw);
137
144
  }
138
145
  catch {
139
- throw new Error("GitHub không trả về JSON hợp lệ");
146
+ throw new Error(lxs("GitHub không trả về JSON hợp lệ", "GitHub did not return valid JSON"));
140
147
  }
141
148
  }
142
149
  let cached = null;
@@ -169,7 +176,7 @@ export async function handleLimixBotLibrary(method, path) {
169
176
  return null;
170
177
  const id = match[1];
171
178
  if (!(await listing()).some((bot) => bot.id === id))
172
- return { status: 404, body: { error: "Không có bot mẫu này" } };
179
+ return { status: 404, body: { error: lxs("Không có bot mẫu này", "No such template bot") } };
173
180
  return { status: 200, body: { bot: parseLibraryBot(await fetchJson(`${TEAM_LIBRARY_RAW_ROOT}/bots/${id}.json?t=${Date.now()}`, MAX_BOT_BYTES), id) } };
174
181
  }
175
182
  catch (error) {
@@ -0,0 +1,2 @@
1
+ // Limix Bot's own server messages: Vietnamese when the app's language is Vietnamese, English otherwise.
2
+ export const lxs = (vi, en) => (/^vi/i.test(process.env.OMB_UI_LANGUAGE?.trim() ?? "") ? vi : en);
@@ -1,6 +1,6 @@
1
1
  // Limix Skill Hub client: keeps a cached copy of the hub catalog, installs a skill only after every file matches the
2
2
  // SHA-256 the catalog pins, and never installs scripts, binaries or MCP files unless the owner allows tools for that
3
- // one skill. Installed skills land DISABLED; the owner turns them on. Auto Sync (catalog refresh) is on by default,
3
+ // one skill. Installed skills wait in the store ("Kho Kĩ Năng (Skills)"), switched on but on no bot until one is added to it. Auto Sync (catalog refresh) is on by default,
4
4
  // Auto Install is off by default.
5
5
  import { createHash } from "node:crypto";
6
6
  import { existsSync, mkdirSync, readFileSync } from "node:fs";
@@ -9,6 +9,7 @@ import { isSkillName } from "../shared/skill-md.js";
9
9
  import { writeFileAtomic } from "./atomic.js";
10
10
  import { DATA_DIR } from "./config.js";
11
11
  import { installLibrarySkill, readSkillLibraryIndex, retireLibrarySkill, setLibrarySkillReviewState } from "./skill-library.js";
12
+ import { lxs } from "./limix-lang.js";
12
13
  export const HUB_REPO = "leomieo/limix-skill-hub";
13
14
  const HUB_BRANCH = "main";
14
15
  const SYNC_MIN_GAP_MS = 20 * 60 * 60 * 1000;
@@ -54,11 +55,11 @@ export function syncHub() {
54
55
  return { changed: false, count: hubCatalog().length };
55
56
  }
56
57
  if (!response.ok)
57
- throw new Error(`Hub trả về ${response.status}`);
58
+ throw new Error(lxs(`Hub trả về ${response.status}`, `The hub returned ${response.status}`));
58
59
  const text = await response.text();
59
60
  const parsed = JSON.parse(text);
60
61
  if (!Array.isArray(parsed.skills))
61
- throw new Error("Danh mục Hub không đúng định dạng");
62
+ throw new Error(lxs("Danh mục Hub không đúng định dạng", "The hub catalog has the wrong format"));
62
63
  mkdirSync(hubDir(), { recursive: true, mode: 0o700 });
63
64
  writeFileAtomic(catalogFile(), text, { mode: 0o600 });
64
65
  saveSettings({ ...settings, etag: response.headers.get("etag"), lastSync: new Date().toISOString(), lastError: null });
@@ -83,9 +84,9 @@ const isToolFile = (path) => SCRIPT.test(path) || MCP_FILE.test(path);
83
84
  const sha = (data) => createHash("sha256").update(data).digest("hex");
84
85
  export function hubSkillBlocked(entry) {
85
86
  if (entry.area === "quarantine" || entry.security_status === "quarantined")
86
- return "Skill nằm trong khu cách ly vì có mã nguy hiểm.";
87
+ return lxs("Skill nằm trong khu cách ly vì có mã nguy hiểm.", "This skill is quarantined because it contains dangerous code.");
87
88
  if (entry.approval_status === "quarantined" || entry.approval_status === "rejected")
88
- return "Skill bị từ chối.";
89
+ return lxs("Skill bị từ chối.", "This skill was rejected.");
89
90
  return null;
90
91
  }
91
92
  /** installed state of every catalog entry that came from the hub */
@@ -109,24 +110,24 @@ export function hubSkills() {
109
110
  // A same-named skill the owner imported by hand is not the hub's: never touched by sync.
110
111
  enabled: have?.reviewState === "approved",
111
112
  updateAvailable: Boolean(have && have.checksum !== entry.checksum),
112
- blocked: !have && library[entry.id] ? "Thư viện đã có kỹ năng cùng tên không đến từ Hub." : null,
113
+ blocked: !have && library[entry.id] ? lxs("Thư viện đã có kỹ năng cùng tên không đến từ Hub.", "The library already has a skill with this name that did not come from the hub.") : null,
113
114
  risky: hubSkillBlocked(entry),
114
115
  };
115
116
  });
116
117
  }
117
118
  export async function installHubSkill(id, options = {}) {
118
119
  if (!isSkillName(id))
119
- return { error: "Tên kỹ năng không hợp lệ" };
120
+ return { error: lxs("Tên kỹ năng không hợp lệ", "Invalid skill name") };
120
121
  const entry = hubCatalog().find((e) => e.id === id);
121
122
  if (!entry)
122
- return { error: "Không thấy kỹ năng này trong danh mục Hub. Hãy đồng bộ lại." };
123
+ return { error: lxs("Không thấy kỹ năng này trong danh mục Hub. Hãy đồng bộ lại.", "This skill is not in the hub catalog. Sync again.") };
123
124
  const blocked = hubSkillBlocked(entry);
124
125
  if (blocked && !options.allowRisky)
125
126
  return { error: blocked };
126
127
  const installed = installedFromHub().get(id);
127
128
  const existing = readSkillLibraryIndex()[id];
128
129
  if (existing && !installed)
129
- return { error: "Thư viện đã có kỹ năng cùng tên không đến từ Hub." };
130
+ return { error: lxs("Thư viện đã có kỹ năng cùng tên không đến từ Hub.", "The library already has a skill with this name that did not come from the hub.") };
130
131
  const wanted = Object.keys(entry.file_hashes).filter((path) => path !== "SKILL.md" && (options.allowTools || !isToolFile(path)));
131
132
  const skipped = Object.keys(entry.file_hashes).filter((path) => path !== "SKILL.md" && !wanted.includes(path));
132
133
  const files = [];
@@ -134,22 +135,22 @@ export async function installHubSkill(id, options = {}) {
134
135
  for (const path of ["SKILL.md", ...wanted]) {
135
136
  const expected = entry.file_hashes[path];
136
137
  if (!expected)
137
- return { error: `Danh mục thiếu mã kiểm tra cho ${path}` };
138
+ return { error: lxs(`Danh mục thiếu mã kiểm tra cho ${path}`, `The catalog has no checksum for ${path}`) };
138
139
  let response;
139
140
  try {
140
141
  response = await fetchText(rawUrl(`${entry.area === "quarantine" ? "quarantine" : "skills"}/${id}/${path}`));
141
142
  }
142
143
  catch (cause) {
143
- return { error: `Không tải được ${path}: ${cause instanceof Error ? cause.message : String(cause)}` };
144
+ return { error: lxs(`Không tải được ${path}: `, `Couldn't download ${path}: `) + (cause instanceof Error ? cause.message : String(cause)) };
144
145
  }
145
146
  if (!response.ok)
146
- return { error: `Không tải được ${path} (${response.status})` };
147
+ return { error: lxs(`Không tải được ${path} (${response.status})`, `Couldn't download ${path} (${response.status})`) };
147
148
  const bytes = new Uint8Array(await response.arrayBuffer());
148
149
  if (bytes.byteLength > FILE_MAX_BYTES)
149
- return { error: `${path} quá lớn` };
150
+ return { error: lxs(`${path} quá lớn`, `${path} is too large`) };
150
151
  // Pinned content: a file the hub no longer serves as catalogued is refused, never installed.
151
152
  if (sha(bytes) !== expected)
152
- return { error: `${path} không khớp mã SHA-256 trong danh mục — đã dừng cài` };
153
+ return { error: lxs(`${path} không khớp mã SHA-256 trong danh mục — đã dừng cài`, `${path} does not match the SHA-256 in the catalog — install stopped`) };
153
154
  if (path === "SKILL.md")
154
155
  skillMd = new TextDecoder().decode(bytes);
155
156
  else
@@ -160,7 +161,7 @@ export async function installHubSkill(id, options = {}) {
160
161
  if ("error" in retired)
161
162
  return { error: retired.error };
162
163
  if ("stale" in retired)
163
- return { error: "Kỹ năng đã bị sửa tay sau khi cài, không cập nhật đè." };
164
+ return { error: lxs("Kỹ năng đã bị sửa tay sau khi cài, không cập nhật đè.", "This skill was edited by hand after installing, so it is not overwritten.") };
164
165
  }
165
166
  const result = installLibrarySkill({
166
167
  name: id,
@@ -171,7 +172,7 @@ export async function installHubSkill(id, options = {}) {
171
172
  ...(entry.tags.length ? { tags: entry.tags } : {}),
172
173
  warnings: entry.notes,
173
174
  skippedFiles: skipped,
174
- reviewState: "disabled",
175
+ reviewState: "approved",
175
176
  ...(files.length ? { files } : {}),
176
177
  });
177
178
  if ("error" in result)
@@ -180,19 +181,19 @@ export async function installHubSkill(id, options = {}) {
180
181
  }
181
182
  export function setHubSkillEnabled(id, enabled) {
182
183
  if (!installedFromHub().has(id))
183
- return { error: "Kỹ năng này chưa cài từ Hub" };
184
+ return { error: lxs("Kỹ năng này chưa cài từ Hub", "This skill was not installed from the hub") };
184
185
  const result = setLibrarySkillReviewState(id, enabled ? "approved" : "disabled");
185
186
  return "error" in result ? { error: result.error } : { ok: true };
186
187
  }
187
188
  export function removeHubSkill(id) {
188
189
  const installed = installedFromHub().get(id);
189
190
  if (!installed)
190
- return { error: "Kỹ năng này chưa cài từ Hub" };
191
+ return { error: lxs("Kỹ năng này chưa cài từ Hub", "This skill was not installed from the hub") };
191
192
  const result = retireLibrarySkill(id, installed.sha256);
192
193
  if ("error" in result)
193
194
  return { error: result.error };
194
195
  if ("stale" in result)
195
- return { error: "Kỹ năng đã bị sửa tay sau khi cài, không gỡ tự động." };
196
+ return { error: lxs("Kỹ năng đã bị sửa tay sau khi cài, không gỡ tự động.", "This skill was edited by hand after installing, so it is not removed automatically.") };
196
197
  return { ok: true }; // the bytes stay in the library archive, so a removal can be undone by hand
197
198
  }
198
199
  /** Auto Install (off by default): only excellent/recommended, approved, instructions-only skills; max 5 per sync. */
@@ -0,0 +1,180 @@
1
+ // Limix Bot: skills are chosen per bot. A new bot starts with only Limix's core skills (learning new skills, reading
2
+ // links with Agent Reach, operating Windows, the phone helpers) plus the skills it learns itself; everything else waits
3
+ // in the store ("Kho Kĩ Năng (Skills)") until a person adds it to one bot. Settings → Kĩ Năng (Skills) only downloads and
4
+ // installs skills into the store; it never turns a skill on for every bot.
5
+ //
6
+ // Three kinds of skill can be added to a bot from the store:
7
+ // bundled — shipped in skills/<id> (ui-ux-pro-max, data-dashboards…): the bot's picks live in
8
+ // <data>/skill-state/<bot>/limix-bundled.json; core ones are always on and cannot be removed.
9
+ // library — installed from the Limix Skill Hub or pasted in Settings: the bot's assignedSkills.
10
+ // imported — brought in from this computer's Claude Code or Codex: the bot's own switch (opt-in in Limix).
11
+ // `/name` calls any skill the bot has, bundled ones included (limixSelectBundled).
12
+ import { existsSync, mkdirSync, readFileSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ import { isSkillName } from "../shared/skill-md.js";
15
+ import { writeFileAtomic } from "./atomic.js";
16
+ import { DATA_DIR } from "./config.js";
17
+ import { importedSkillListings, setImportedSkillEnabled } from "./imported-skills.js";
18
+ import { listLibrarySkills, loadBundledSkills, mergeSkills, readSkillLibraryIndex, selectBundledSkills, setLibrarySkillEveryBot, setLibrarySkillOptOut, setLibrarySkillReviewState, unassignLibrarySkill, } from "./skill-library.js";
19
+ import { lxs } from "./limix-lang.js";
20
+ /** Always on for every bot. "Learning new skills" itself is the skill-authoring feature, on for every bot already. */
21
+ export const LIMIX_CORE_SKILLS = new Set([
22
+ "create-verification-skill", "agent-reach", "windows-computer-automation", "phone-connection", "phone-harness",
23
+ ]);
24
+ const BOT_ID = /^[\w-]+$/;
25
+ const picksPath = (botId) => join(DATA_DIR, "skill-state", botId, "limix-bundled.json");
26
+ export function bundledPicks(botId) {
27
+ if (!BOT_ID.test(botId))
28
+ return new Set();
29
+ try {
30
+ const parsed = JSON.parse(readFileSync(picksPath(botId), "utf8"));
31
+ return new Set(Array.isArray(parsed) ? parsed.filter((name) => typeof name === "string" && isSkillName(name)) : []);
32
+ }
33
+ catch {
34
+ return new Set();
35
+ }
36
+ }
37
+ function setBundledPick(botId, id, on) {
38
+ if (!BOT_ID.test(botId) || !isSkillName(id))
39
+ return;
40
+ const picks = bundledPicks(botId);
41
+ if (picks.has(id) === on)
42
+ return;
43
+ if (on)
44
+ picks.add(id);
45
+ else
46
+ picks.delete(id);
47
+ mkdirSync(join(DATA_DIR, "skill-state", botId), { recursive: true, mode: 0o700 });
48
+ writeFileAtomic(picksPath(botId), `${JSON.stringify([...picks].sort(), null, 2)}\n`, { mode: 0o600 });
49
+ }
50
+ /** The shipped skills one bot has: the core ones and the ones a person added to it. */
51
+ export function limixBundledFor(botId, skills) {
52
+ const picks = bundledPicks(botId);
53
+ return skills.filter(({ manifest }) => LIMIX_CORE_SKILLS.has(manifest.id) || picks.has(manifest.id));
54
+ }
55
+ const slashCalled = (text, id) => new RegExp(`(^|\\s)/${id.replace(/[-]/g, "\\-")}(?=$|[\\s.,:;!?])`, "m").test(text);
56
+ /** selectBundledSkills, plus every skill of `skills` the text calls by `/id`. */
57
+ export function limixSelectBundled(text, capabilities, skills) {
58
+ const available = new Set(capabilities);
59
+ const called = skills.filter(({ manifest }) => manifest.requiredCapabilities.every((capability) => available.has(capability)) && slashCalled(text, manifest.id));
60
+ return mergeSkills(selectBundledSkills(text, available, skills), called);
61
+ }
62
+ /** The bot's shipped skills as rows of its Skills page. */
63
+ export function limixBundledListings(botId) {
64
+ return limixBundledFor(botId, loadBundledSkills()).map(({ manifest }) => ({
65
+ name: manifest.id, description: manifest.description, enabled: true, editable: false,
66
+ source: LIMIX_CORE_SKILLS.has(manifest.id) ? lxs("Kĩ năng gốc của Limix Bot", "Limix Bot core skill") : lxs("Kho Kĩ Năng (Skills) · có sẵn", "Skills Store · built in"),
67
+ sha256: "", importedAt: "", warnings: [], skippedFiles: [], origin: "bundled", core: LIMIX_CORE_SKILLS.has(manifest.id),
68
+ }));
69
+ }
70
+ /** A bot's switch for one of its shipped skills: off takes an added one away; a core one stays. Null: not shipped. */
71
+ export function limixBundledSwitch(botId, name, enabled) {
72
+ if (!loadBundledSkills().some(({ manifest }) => manifest.id === name))
73
+ return null;
74
+ if (LIMIX_CORE_SKILLS.has(name)) {
75
+ return enabled ? { status: 200, body: { skill: limixBundledListings(botId).find((skill) => skill.name === name) } }
76
+ : { status: 409, body: { error: lxs("Kĩ năng gốc của Limix Bot luôn bật cho mọi bot.", "Limix Bot core skills are always on for every bot.") } };
77
+ }
78
+ setBundledPick(botId, name, enabled);
79
+ return { status: 200, body: { skill: { ...limixBundledListings(botId).find((skill) => skill.name === name), name, enabled } } };
80
+ }
81
+ function storeItems(bots, botId) {
82
+ const bot = bots.bot(botId);
83
+ const assigned = new Set(bot.assignedSkills ?? []);
84
+ const picks = bundledPicks(botId);
85
+ const items = new Map();
86
+ for (const { manifest } of loadBundledSkills()) {
87
+ const core = LIMIX_CORE_SKILLS.has(manifest.id);
88
+ items.set(manifest.id, { name: manifest.id, description: manifest.description, kind: "bundled", added: core || picks.has(manifest.id), core, ready: true, warnings: 0, source: lxs("Có sẵn trong Limix Bot", "Built into Limix Bot") });
89
+ }
90
+ for (const skill of listLibrarySkills()) {
91
+ if (items.has(skill.name))
92
+ continue;
93
+ const intact = !skill.warnings.some((warning) => warning.includes("enablement is blocked"));
94
+ items.set(skill.name, {
95
+ name: skill.name, description: skill.description, kind: "library", added: assigned.has(skill.name) && skill.enabled, core: false,
96
+ ready: intact, warnings: skill.warnings.length, source: skill.source.startsWith("limix-hub:") ? lxs("Thư viện kĩ năng Limix", "Limix skill library") : lxs("Đã cài trong Cài đặt", "Installed in Settings"),
97
+ });
98
+ }
99
+ for (const skill of importedSkillListings(botId, new Set())) {
100
+ if (items.has(skill.name))
101
+ continue;
102
+ items.set(skill.name, { name: skill.name, description: skill.description, kind: "imported", added: skill.enabled, core: false, ready: true, warnings: skill.warnings.length, source: skill.source || lxs("Từ máy tính này", "From this computer") });
103
+ }
104
+ return [...items.values()].sort((a, b) => Number(b.core) - Number(a.core) || a.name.localeCompare(b.name));
105
+ }
106
+ /** Add one skill to one bot, or take it away. `kind` may be left out: the store finds the name. */
107
+ export function limixStoreSet(bots, botId, name, add, kind) {
108
+ const bot = bots.bot(botId);
109
+ if (!bot)
110
+ return { error: lxs("Không tìm thấy bot.", "Bot not found.") };
111
+ if (!isSkillName(name))
112
+ return { error: lxs("Tên kĩ năng không hợp lệ.", "Invalid skill name.") };
113
+ const item = storeItems(bots, botId).find((entry) => entry.name === name && (!kind || entry.kind === kind));
114
+ if (!item)
115
+ return { error: lxs(`Kho chưa có kĩ năng "${name}".`, `The store has no skill "${name}".`) };
116
+ if (item.kind === "bundled") {
117
+ if (item.core)
118
+ return add ? { ok: true } : { error: lxs("Kĩ năng gốc của Limix Bot luôn bật cho mọi bot.", "Limix Bot core skills are always on for every bot.") };
119
+ setBundledPick(botId, name, add);
120
+ return { ok: true };
121
+ }
122
+ if (item.kind === "imported") {
123
+ const result = setImportedSkillEnabled(botId, name, add);
124
+ return "error" in result ? { error: result.error } : { ok: true };
125
+ }
126
+ const assigned = bot.assignedSkills ?? [];
127
+ if (add) {
128
+ // Adding from the store is the person's choice for this bot, so a skill installed but not yet switched on is switched on.
129
+ const listing = listLibrarySkills().find((skill) => skill.name === name);
130
+ if (listing && !listing.enabled) {
131
+ const approved = setLibrarySkillReviewState(name, "approved");
132
+ if ("error" in approved)
133
+ return { error: approved.error };
134
+ }
135
+ setLibrarySkillOptOut(botId, name, false);
136
+ if (!assigned.includes(name))
137
+ bots.patchBot(botId, { assignedSkills: [...assigned, name] });
138
+ return { ok: true };
139
+ }
140
+ const result = unassignLibrarySkill(botId, name, assigned);
141
+ if ("unassigned" in result && result.assignedSkills)
142
+ bots.patchBot(botId, { assignedSkills: result.assignedSkills });
143
+ return { ok: true };
144
+ }
145
+ /** GET /api/limix/skill-store/<bot> lists the store for that bot; POST {name, add, kind?} adds or takes away one skill. */
146
+ export function handleLimixSkillStore(bots, method, path, body) {
147
+ const m = path.match(/^\/api\/limix\/skill-store\/([\w-]+)$/);
148
+ if (!m)
149
+ return null;
150
+ const botId = m[1];
151
+ if (!bots.bot(botId))
152
+ return { status: 404, body: { error: lxs("Không tìm thấy bot.", "Bot not found.") } };
153
+ if (method === "GET")
154
+ return { status: 200, body: { skills: storeItems(bots, botId) } };
155
+ if (method === "POST") {
156
+ const raw = (body && typeof body === "object" ? body : {});
157
+ const kind = raw.kind === "bundled" || raw.kind === "library" || raw.kind === "imported" ? raw.kind : undefined;
158
+ if (typeof raw.name !== "string" || typeof raw.add !== "boolean")
159
+ return { status: 400, body: { error: lxs("Cần tên kĩ năng và add.", "A skill name and add are required.") } };
160
+ const result = limixStoreSet(bots, botId, raw.name, raw.add, kind);
161
+ return "error" in result ? { status: 422, body: result } : { status: 200, body: { skills: storeItems(bots, botId) } };
162
+ }
163
+ return { status: 405, body: { error: "method not allowed" } };
164
+ }
165
+ /** Once: skills that earlier versions turned on for every bot go back to the store. */
166
+ export function limixSkillDefaultsMigration() {
167
+ const marker = join(DATA_DIR, "skill-state", "limix-skill-defaults-v1.json");
168
+ if (existsSync(marker))
169
+ return;
170
+ try {
171
+ for (const [name, entry] of Object.entries(readSkillLibraryIndex()))
172
+ if (entry.everyBot)
173
+ setLibrarySkillEveryBot(name, false);
174
+ mkdirSync(join(DATA_DIR, "skill-state"), { recursive: true, mode: 0o700 });
175
+ writeFileAtomic(marker, `${JSON.stringify({ at: new Date().toISOString() })}\n`, { mode: 0o600 });
176
+ }
177
+ catch {
178
+ // Retried at the next start.
179
+ }
180
+ }
@@ -7,6 +7,7 @@ import { fetchSkillFromSource } from "../skill-fetch.js";
7
7
  import { readLocalSkills } from "../limix-skill-folder.js";
8
8
  import { loadBundledSkills } from "../skill-library.js";
9
9
  import { handleLimixHub } from "../limix-skill-hub.js";
10
+ import { handleLimixSkillStore, LIMIX_CORE_SKILLS, limixBundledListings, limixBundledSwitch } from "../limix-skill-store.js";
10
11
  import { skillImportSummary } from "../skill-folder.js";
11
12
  import { installLibrarySkill, librarySkillOptOuts, listLibrarySkills, readLibrarySkillFile, readSkillLibraryIndex, setLibrarySkillEveryBot, setLibrarySkillOptOut, setLibrarySkillReviewState, skillsLibraryEnabled, unassignLibrarySkill } from "../skill-library.js";
12
13
  import { installSkill, listBotSkillsWithOrigin, listStagedSkillWrites, parseSkillMd, removeSkill, scanSkillText, setSkillEnabled, } from "../skills.js";
@@ -25,8 +26,13 @@ export function createBotSkillsRoutes(deps) {
25
26
  if (hub)
26
27
  return json(res, hub.status, hub.body);
27
28
  }
29
+ if (path.startsWith("/api/limix/skill-store/")) {
30
+ const skillStore = handleLimixSkillStore(store, method, path, method === "POST" ? await readBody(req) : undefined);
31
+ if (skillStore)
32
+ return json(res, skillStore.status, skillStore.body);
33
+ }
28
34
  if (method === "GET" && path === "/api/limix/bundled-skills") {
29
- const skills = loadBundledSkills().map(({ manifest }) => ({ id: manifest.id, name: manifest.name, description: manifest.description, defaultEnabled: manifest.defaultEnabled, triggerTerms: manifest.triggerTerms }));
35
+ const skills = loadBundledSkills().map(({ manifest }) => ({ id: manifest.id, name: manifest.name, description: manifest.description, defaultEnabled: manifest.defaultEnabled, triggerTerms: manifest.triggerTerms, core: LIMIX_CORE_SKILLS.has(manifest.id) }));
30
36
  return json(res, 200, { skills });
31
37
  }
32
38
  // Imports land DISABLED: a person enables one after reading SKILL.md and its scan warnings.
@@ -63,14 +69,15 @@ export function createBotSkillsRoutes(deps) {
63
69
  const { skills, library } = listBotSkillsWithOrigin(m[1], store.bot(m[1]).assignedSkills);
64
70
  const names = new Set(skills.map((skill) => skill.name));
65
71
  return json(res, 200, {
66
- skills: [...skills, ...listBotSkills(bot, threadId).filter((skill) => !names.has(skill.name))],
72
+ skills: [...skills, ...listBotSkills(bot, threadId).filter((skill) => !names.has(skill.name)),
73
+ ...limixBundledListings(bot.id).filter((skill) => !names.has(skill.name) && !listBotSkills(bot, threadId).some((other) => other.name === skill.name))],
67
74
  library,
68
75
  assignedSkills: store.bot(m[1]).assignedSkills,
69
76
  staged: listStagedSkillWrites(m[1]).map(stagedSkillListing),
70
77
  });
71
78
  }
72
79
  return json(res, 200, {
73
- skills: listBotSkills(bot, threadId),
80
+ skills: [...listBotSkills(bot, threadId), ...limixBundledListings(bot.id)],
74
81
  staged: listStagedSkillWrites(m[1]).map(stagedSkillListing),
75
82
  });
76
83
  }
@@ -121,8 +128,12 @@ export function createBotSkillsRoutes(deps) {
121
128
  return json(res, 200, { skill });
122
129
  }
123
130
  const result = setSkillEnabled(m[1], m[2], parsed.data.enabled);
124
- if ("error" in result)
131
+ if ("error" in result) {
132
+ const shipped = limixBundledSwitch(m[1], m[2], parsed.data.enabled);
133
+ if (shipped)
134
+ return json(res, shipped.status, shipped.body);
125
135
  return json(res, 404, { error: result.error });
136
+ }
126
137
  return json(res, 200, { skill: result });
127
138
  }
128
139
  if (m && method === "DELETE") {
@@ -2,15 +2,16 @@
2
2
  // notice row with what the person can do about it. The row's tool name is a
3
3
  // whole plain sentence, so a client that only prints tool names (the phones)
4
4
  // still reads it; this payload is what lets the desktop draw its buttons.
5
+ const limixVi = () => /^vi/i.test((typeof process !== "undefined" ? process.env?.OMB_UI_LANGUAGE : "") ?? "");
5
6
  /** The waiting row's words, for clients that print the tool name. */
6
7
  export function firstReplyWaitingName(botName, ai) {
7
- return `notice: ${botName} vẫn chưa nhận được phản hồi từ ${ai}.`;
8
+ return limixVi() ? `notice: ${botName} vẫn chưa nhận được phản hồi từ ${ai}.` : `notice: ${botName} hasn't heard back from ${ai} yet.`;
8
9
  }
9
10
  /** The same row once its turn ended without an answer: history, true as it stands. */
10
11
  export function firstReplyOverName(botName, ai) {
11
- return `notice: ${botName} không nhận được phản hồi từ ${ai}.`;
12
+ return limixVi() ? `notice: ${botName} không nhận được phản hồi từ ${ai}.` : `notice: ${botName} didn't hear back from ${ai}.`;
12
13
  }
13
14
  /** The same row once the AI's first output arrived. */
14
15
  export function firstReplySettledName(ai) {
15
- return `notice: ${ai} đã trả lời, sau một lúc chậm.`;
16
+ return limixVi() ? `notice: ${ai} đã trả lời, sau một lúc chậm.` : `notice: ${ai} answered after a slow start.`;
16
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "limixbot",
3
- "version": "0.3.24",
3
+ "version": "0.3.26",
4
4
  "description": "Run the Limix Bot server anywhere and pair your devices to it (Leox ৹ Limix AI)",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",