limixbot 0.3.25 → 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 (123) hide show
  1. package/dist/assets/{ActivityPanel-CgPkGKZZ.js → ActivityPanel-DkwIh-YJ.js} +1 -1
  2. package/dist/assets/{App-lurtH-qI.js → App-DCe3hDbm.js} +119 -119
  3. package/dist/assets/{AppEventEditor-G_71EWui.js → AppEventEditor-25dBAYiN.js} +1 -1
  4. package/dist/assets/BotSettingsDialog-DJxZo-ro.js +16 -0
  5. package/dist/assets/{BrowserPanel-DrKZNyYB.js → BrowserPanel-DVji4pZh.js} +1 -1
  6. package/dist/assets/{BrowserProfilesManager-CtiUbbdv.js → BrowserProfilesManager-C4rCEedT.js} +1 -1
  7. package/dist/assets/{CloudAddDialog-D4BX2927.js → CloudAddDialog-BybF9Mcz.js} +1 -1
  8. package/dist/assets/{ComputerFilesPane-RnmlJhlD.js → ComputerFilesPane-D-EgbYKM.js} +1 -1
  9. package/dist/assets/{ComputerPanel-D8upQSmd.js → ComputerPanel-CGKdVGz4.js} +2 -2
  10. package/dist/assets/{DataPanel-MZcku4SL.js → DataPanel-BXh6perv.js} +1 -1
  11. package/dist/assets/{DesktopViewer-p7N7cqle.js → DesktopViewer-CRcjWYHe.js} +1 -1
  12. package/dist/assets/{FilesView-CDbtoPxh.js → FilesView-B67sskTN.js} +1 -1
  13. package/dist/assets/{GrokMoveDialog-BebZb43R.js → GrokMoveDialog-DNJckF5G.js} +1 -1
  14. package/dist/assets/{InspectorPanel-BMkRIuTz.js → InspectorPanel-CjtRMfJu.js} +1 -1
  15. package/dist/assets/{KeyboardShortcutsModal-3XyVcNTZ.js → KeyboardShortcutsModal-znw4NkA5.js} +1 -1
  16. package/dist/assets/{LiveDesktop-BkRR5lCn.js → LiveDesktop-BgFrQKAC.js} +1 -1
  17. package/dist/assets/{LocalVmWorkspace-9l9iWtAy.js → LocalVmWorkspace-Dk61S9xg.js} +1 -1
  18. package/dist/assets/{MacLocalControl-B45d0l7H.js → MacLocalControl-DjyhCaWB.js} +1 -1
  19. package/dist/assets/{MarkdownFilePreview-ChR-f9oD.js → MarkdownFilePreview-Btb--ZSy.js} +1 -1
  20. package/dist/assets/{MenuMotion-BBaxdyL_.js → MenuMotion-BOUToDZz.js} +1 -1
  21. package/dist/assets/{NewBotDialog-C_uQRhxy.js → NewBotDialog-CsQPMX7F.js} +1 -1
  22. package/dist/assets/{PhoneAppDialog-CDO1xUXD.js → PhoneAppDialog-4OG210md.js} +1 -1
  23. package/dist/assets/{PlacesRail-iu-rjh5s.js → PlacesRail-dslg5RRJ.js} +1 -1
  24. package/dist/assets/{PluginsPanel-DlQBRYQB.js → PluginsPanel-CaWk4eSi.js} +1 -1
  25. package/dist/assets/{ProIntroduction-RbtTSiR6.js → ProIntroduction-BuOIgmKa.js} +1 -1
  26. package/dist/assets/{RemoteAgentSettingsPanel-C8r-80J4.js → RemoteAgentSettingsPanel-Drk4pPKT.js} +1 -1
  27. package/dist/assets/{RoomPanel-BIUTnm5N.js → RoomPanel-BGLIvmBz.js} +1 -1
  28. package/dist/assets/{RoutinesPage-DROK-336.js → RoutinesPage-BuAEX1fg.js} +2 -2
  29. package/dist/assets/SettingsModal-CwK4KFqq.js +19 -0
  30. package/dist/assets/{ShareTeamDialog-CVnQoRVQ.js → ShareTeamDialog-C5Fv8jWl.js} +1 -1
  31. package/dist/assets/{SignedInSites-B-iBIlfL.js → SignedInSites-BQ2QP4YA.js} +1 -1
  32. package/dist/assets/TeamLibraryPanel-B0HAAvLv.js +1 -0
  33. package/dist/assets/{TeamMapPage-kXk9hCNu.js → TeamMapPage-DCkx9eov.js} +1 -1
  34. package/dist/assets/{TriggersPage-prZBUP3d.js → TriggersPage-B2sqDFfI.js} +1 -1
  35. package/dist/assets/{VisibilitySection-DJjvTyLx.js → VisibilitySection-CqL-TbZp.js} +1 -1
  36. package/dist/assets/{WebhookTrigger-BAFikH71.js → WebhookTrigger-CorVaJKR.js} +1 -1
  37. package/dist/assets/{WorksOnGrid-DJmfLtP8.js → WorksOnGrid-1tMl62nD.js} +1 -1
  38. package/dist/assets/{abnfDiagram-YUKMZFIV-DUI6uevQ.js → abnfDiagram-YUKMZFIV-UeQ15ljp.js} +1 -1
  39. package/dist/assets/{activity-DVLFekjx.js → activity-Rykx5lZJ.js} +1 -1
  40. package/dist/assets/{architectureDiagram-47P4ROYG-CMELW5_4.js → architectureDiagram-47P4ROYG-Cb42ilBV.js} +1 -1
  41. package/dist/assets/{blockDiagram-E7TT5TSB-C3xD0z1e.js → blockDiagram-E7TT5TSB-BF1yg64q.js} +1 -1
  42. package/dist/assets/{c4Diagram-CRA5TL53-Cm9X0GEV.js → c4Diagram-CRA5TL53-VaNJpISp.js} +1 -1
  43. package/dist/assets/{calendar-days-CDvwJ_d4.js → calendar-days-kC634hX1.js} +1 -1
  44. package/dist/assets/channel-Di74R1Kh.js +1 -0
  45. package/dist/assets/{chunk-2Q5K7J3B-LWcbjcjL.js → chunk-2Q5K7J3B-NtXjlfnQ.js} +1 -1
  46. package/dist/assets/{chunk-5VM5RSS4-B3rNoWeE.js → chunk-5VM5RSS4-CYWCBb40.js} +1 -1
  47. package/dist/assets/{chunk-7TKQ45FW-FL9R0rHe.js → chunk-7TKQ45FW-CTx1wLnU.js} +1 -1
  48. package/dist/assets/{chunk-AW2ZBBNX-JRef_4rd.js → chunk-AW2ZBBNX-PDrwpbhk.js} +1 -1
  49. package/dist/assets/{chunk-FVRAUYC3-C__loQFR.js → chunk-FVRAUYC3-CqIp8Ubp.js} +2 -2
  50. package/dist/assets/{chunk-HTAEGDNF-Bx74_xmN.js → chunk-HTAEGDNF-yA6riuIr.js} +1 -1
  51. package/dist/assets/{chunk-JWPE2WC7-B20vr3uH.js → chunk-JWPE2WC7-DEE_S031.js} +1 -1
  52. package/dist/assets/{chunk-KQW6MTUR-DfO9J2pt.js → chunk-KQW6MTUR-QybxNtsY.js} +1 -1
  53. package/dist/assets/{chunk-XXDRQBXY-BAPGqnqj.js → chunk-XXDRQBXY-BjchQyqU.js} +1 -1
  54. package/dist/assets/{classDiagram-v2-K4WV4PDN-C3VR3yN8.js → classDiagram-v2-K4WV4PDN-DtOc8Abr.js} +1 -1
  55. package/dist/assets/{coins-CueFc0mY.js → coins-D0wNJCPg.js} +1 -1
  56. package/dist/assets/{core-Bw6kaGwe.js → core-Blwbk7ct.js} +1 -1
  57. package/dist/assets/{cose-bilkent-JH36ORCC-19p3m04s.js → cose-bilkent-JH36ORCC-CWHsqn7M.js} +1 -1
  58. package/dist/assets/{cynefin-EF2NZ3EQ-Tv0tce7B.js → cynefin-EF2NZ3EQ-BqS-D9jN.js} +1 -1
  59. package/dist/assets/{cynefinDiagram-3GCD6N5R-Iin0JwlW.js → cynefinDiagram-3GCD6N5R-Fc51YETb.js} +1 -1
  60. package/dist/assets/{dagre-W4DXFKR2-00TRmSmB.js → dagre-W4DXFKR2-So1kz52t.js} +1 -1
  61. package/dist/assets/{diagram-2UJZ2QOL-DslMBjKt.js → diagram-2UJZ2QOL-DpdA_Lk0.js} +1 -1
  62. package/dist/assets/{diagram-OVF4WLC6-C3o3f1eL.js → diagram-OVF4WLC6-BqSu7ih2.js} +1 -1
  63. package/dist/assets/{diagram-PFPMY2P6-RRy9F9wH.js → diagram-PFPMY2P6-BoM6YcIG.js} +1 -1
  64. package/dist/assets/{diagram-UMYRVEAY-CWfRKtkp.js → diagram-UMYRVEAY-B7wKoLi9.js} +1 -1
  65. package/dist/assets/{diagram-YEKJPTXX-SlJKvYay.js → diagram-YEKJPTXX-BEXKw540.js} +1 -1
  66. package/dist/assets/{diagram-ZIFT7M5P-DqzI8_eN.js → diagram-ZIFT7M5P-BAD7sJRi.js} +1 -1
  67. package/dist/assets/{ebnfDiagram-VR2GEFS7-CDsrBNMu.js → ebnfDiagram-VR2GEFS7-DILoqvy-.js} +1 -1
  68. package/dist/assets/{elk-IJKZMXRS-iikFqO-X.js → elk-IJKZMXRS-BHW-Cxin.js} +1 -1
  69. package/dist/assets/{erDiagram-O2IAPWRE-DzFCc0Gp.js → erDiagram-O2IAPWRE-Blp0Wwq0.js} +1 -1
  70. package/dist/assets/{file-up-BzW-KJx-.js → file-up-CEuHjVBK.js} +1 -1
  71. package/dist/assets/{flowDiagram-OXPTDLAJ-U5nLRus2.js → flowDiagram-OXPTDLAJ-6SkVmYje.js} +1 -1
  72. package/dist/assets/{ganttDiagram-R7TSEDQI-h7xqrZ23.js → ganttDiagram-R7TSEDQI-DFDwj8QX.js} +1 -1
  73. package/dist/assets/{gitGraphDiagram-XJZIOB7I-Bo9ZX2g5.js → gitGraphDiagram-XJZIOB7I-mtJG-sis.js} +1 -1
  74. package/dist/assets/{import-name-BN_-MYip.js → import-name-B3mVDURb.js} +1 -1
  75. package/dist/assets/{index-DkDFNBNq.js → index-BZOGYABm.js} +1 -1
  76. package/dist/assets/{index-BEjJ5c6H.js → index-D2Jm8LAf.js} +2 -2
  77. package/dist/assets/{infoDiagram-5W2HQ5XZ-CAZmFoiz.js → infoDiagram-5W2HQ5XZ-CmyXB17U.js} +1 -1
  78. package/dist/assets/{ishikawaDiagram-K3B6WC7H-T1M7DtvG.js → ishikawaDiagram-K3B6WC7H-BKFtIESa.js} +1 -1
  79. package/dist/assets/{journeyDiagram-COXZFDF6-DzO0Gzq9.js → journeyDiagram-COXZFDF6-DuXMREdz.js} +1 -1
  80. package/dist/assets/{kanban-definition-P3RFRI5V-C0fM1UHu.js → kanban-definition-P3RFRI5V-B9mWvKdh.js} +1 -1
  81. package/dist/assets/{mcp-servers-C7oRQpN5.js → mcp-servers-D3PCWkFu.js} +1 -1
  82. package/dist/assets/{mermaid.core-mKZTdBWz.js → mermaid.core-3c-Lql2y.js} +4 -4
  83. package/dist/assets/{mindmap-definition-LZFPQGKD-DEgoC28p.js → mindmap-definition-LZFPQGKD-BmLt9bkD.js} +1 -1
  84. package/dist/assets/{minimize-D-CvaOV-.js → minimize-BfKI3Nbu.js} +1 -1
  85. package/dist/assets/{monitor-DRcE92Rj.js → monitor-C-EBTsrK.js} +1 -1
  86. package/dist/assets/{new-bot-COj1uEQa.js → new-bot-3sQib6nJ.js} +1 -1
  87. package/dist/assets/{org-library-BIb28h24.js → org-library-DHuACSMf.js} +1 -1
  88. package/dist/assets/{package-format-hON5OXLZ.js → package-format-CAZ-xCP-.js} +1 -1
  89. package/dist/assets/{pegDiagram-BGZESJAR-Vatn56u_.js → pegDiagram-BGZESJAR-5w1MTtLg.js} +1 -1
  90. package/dist/assets/{pieDiagram-CAPJLFHJ-DniOgFo3.js → pieDiagram-CAPJLFHJ-C4etNvsK.js} +1 -1
  91. package/dist/assets/{quadrantDiagram-GDMTTRAM-BqhAQt5w.js → quadrantDiagram-GDMTTRAM-k4cDUeIN.js} +1 -1
  92. package/dist/assets/{rail-BWcrFQ2_.js → rail-C6qkoJbO.js} +1 -1
  93. package/dist/assets/{railroadDiagram-SM67HX2B-CxHs3JOs.js → railroadDiagram-SM67HX2B-B9d0qasL.js} +1 -1
  94. package/dist/assets/{remote-desktop-panel-9XdtvJrd.js → remote-desktop-panel-sCBdOaQh.js} +1 -1
  95. package/dist/assets/{requirementDiagram-X7JNWC4A-ChjhMFon.js → requirementDiagram-X7JNWC4A-BlYV4z86.js} +1 -1
  96. package/dist/assets/{sankeyDiagram-UM26HJRW-B67bYF8E.js → sankeyDiagram-UM26HJRW-DaIVWJEd.js} +1 -1
  97. package/dist/assets/{sequenceDiagram-ZO4K6R2Y-CTy7B-eu.js → sequenceDiagram-ZO4K6R2Y-CAnXpud0.js} +1 -1
  98. package/dist/assets/{sizeCapture-INFHLROL-uy6qiY-2.js → sizeCapture-INFHLROL-BIeqIcHi.js} +1 -1
  99. package/dist/assets/{stateDiagram-v2-TBUQTH76-BvYYPNqL.js → stateDiagram-v2-TBUQTH76-7ql5dn9y.js} +1 -1
  100. package/dist/assets/{styles-DlOxUga6.js → styles-BRMaHK08.js} +2 -2
  101. package/dist/assets/{swimlanes-N4OXWK64-CMSAKu-2.js → swimlanes-N4OXWK64-CItt-UlU.js} +1 -1
  102. package/dist/assets/swimlanesDiagram-K3J5GTZL-DAZsljU6.js +8 -0
  103. package/dist/assets/{team-share-BbQqJ5O6.js → team-share-2OjfOHsk.js} +1 -1
  104. package/dist/assets/{timeline-definition-YOQAKGHF-BOK1V5EC.js → timeline-definition-YOQAKGHF-8Xn9QBNo.js} +1 -1
  105. package/dist/assets/{usecaseDiagram-VIAY4XPW-DEywfSfC.js → usecaseDiagram-VIAY4XPW-B6LxotkO.js} +1 -1
  106. package/dist/assets/{vennDiagram-BLWOH2XV-DN1iWw97.js → vennDiagram-BLWOH2XV-Ciex0LDC.js} +1 -1
  107. package/dist/assets/{wardleyDiagram-YMQ3BMBF-BAIfK1We.js → wardleyDiagram-YMQ3BMBF-B_TbpQlN.js} +1 -1
  108. package/dist/assets/{xychartDiagram-TAQBALBS-BLBD8FaQ.js → xychartDiagram-TAQBALBS-Dz_iIYt1.js} +1 -1
  109. package/dist/index.html +2 -2
  110. package/dist/rail.html +4 -4
  111. package/dist-server/index.js +44 -39
  112. package/dist-server/package.json +1 -1
  113. package/dist-server/server/limix-bot-library.js +9 -8
  114. package/dist-server/server/limix-lang.js +2 -0
  115. package/dist-server/server/limix-skill-hub.js +18 -17
  116. package/dist-server/server/limix-skill-store.js +12 -11
  117. package/dist-server/shared/first-reply-rescue.js +4 -3
  118. package/package.json +1 -1
  119. package/dist/assets/BotSettingsDialog-BUvuLu53.js +0 -16
  120. package/dist/assets/SettingsModal--yYGUYK2.js +0 -15
  121. package/dist/assets/TeamLibraryPanel-CwVaJVC_.js +0 -1
  122. package/dist/assets/channel-CsOUN1ZX.js +0 -1
  123. package/dist/assets/swimlanesDiagram-K3J5GTZL-BaaZdhur.js +0 -8
@@ -29812,52 +29812,56 @@ return json7(res,400,{error:"botIds must be a non-empty array of distinct bot id
29812
29812
  id)).map(bot=>bot.id);return person2&&deps.own.told(person2,shown3),json7(res,200,{botIds:shown3})}}function createBotLifecycleRoutes(deps){let{store:store5,interruptAllDirectThreads:interruptAllDirectThreads2,cancelDirectTurnDispatch:cancelDirectTurnDispatch2,revokeInternalCapabilitiesForThread:revokeInternalCapabilitiesForThread2,activeGroupTurnForBot:activeGroupTurnForBot2,cancelGroupTurnOperations:cancelGroupTurnOperations2,runningTurnInstance:runningTurnInstance2,closeOpenApprovals:closeOpenApprovals2,deleteBotWithLifecycle:deleteBotWithLifecycle2}=deps;return async({req,res,path:path6,method:method2,
29813
29813
  json:json7})=>{let m3=null,routines2=deps.routines();if(method2==="POST"&&path6==="/api/local-computer/interrupt")return String(req.headers["content-type"]??"").toLowerCase().startsWith("application/json")?(await Promise.allSettled(store5.bots.filter(bot=>bot.computer==="local").map(async bot=>{await interruptAllDirectThreads2(bot.id);let routineRun=routines2.activeBotRunForBot(bot.id);routineRun&&(cancelDirectTurnDispatch2(bot.id,routineRun.threadId),routineRun.threadId&&revokeInternalCapabilitiesForThread2(
29814
29814
  routineRun.threadId),await routines2.cancelRun(routineRun.id));let groupTurn=activeGroupTurnForBot2(bot.id);groupTurn&&(cancelGroupTurnOperations2(groupTurn.group.id,groupTurn.threadId),revokeInternalCapabilitiesForThread2(groupTurn.threadId),await runningTurnInstance2(bot,groupTurn.threadId)?.adapter.interruptTurn(groupTurn.threadId).catch(()=>{}),closeOpenApprovals2(groupTurn.threadId))})),json7(res,200,{ok:!0})):json7(res,415,{error:"content-type must be application/json"});if(m3=path6.match(
29815
- /^\/api\/bots\/([\w-]+)$/),m3&&method2==="DELETE"){let result2=await deleteBotWithLifecycle2(m3[1]);return json7(res,result2.status,result2.body)}return PASS}}init_zod();init_mascot_bodies();init_wire();var TEAM_LIBRARY_RAW_ROOT="https://raw.githubusercontent.com/leomieo/limix-bot-teams/main",LIST_TTL_MS=600*1e3,MAX_CATALOG_BYTES=256e3,MAX_BOT_BYTES=64e3,CATEGORIES=["everyday","work","writing","design","sales","personal"],ID20=/^[a-z0-9][a-z0-9-]{0,63}$/,NOTE_PATH=/^(?:MEMORY\.md|memory\/[a-z0-9][a-z0-9-]{0,63}\.md)$/,TIME3=/^(?:[01]\d|2[0-3]):[0-5]\d$/,isRecord38=value3=>!!value3&&typeof value3=="object"&&!Array.isArray(value3);function text27(value3,field5,max4,allowEmpty=!1){
29816
- if(typeof value3!="string"||!allowEmpty&&!value3.trim())throw new Error(`${field5} is required`);let trimmed=value3.trim();if(trimmed.length>max4)throw new Error(`${field5} is too long`);return trimmed}function look(value3,field5){if(!isRecord38(value3))throw new Error(`${field5} is invalid`);let color6=text27(value3.color,`${field5}.color`,20),mascotBody=text27(value3.mascotBody,`${field5}.mascotBody`,20);if(!MAUS_COLOR_NAMES.includes(color6))throw new Error(`${field5}.color is not a known colo\
29817
- ur`);if(!MASCOT_BODY_IDS.includes(mascotBody))throw new Error(`${field5}.mascotBody is not a known shape`);return{color:color6,mascotBody}}function category(value3,field5){let found2=CATEGORIES.find(entry2=>entry2===value3);if(!found2)throw new Error(`${field5} is not a known category`);return found2}function apps(value3,field5){if(value3===void 0)return[];if(!Array.isArray(value3)||value3.length>10)throw new Error(`${field5} is invalid`);return value3.map((app,index4)=>{let slug3=text27(app,`${field5}\
29818
- [${index4}]`,40);if(!/^[a-z0-9][a-z0-9_-]*$/.test(slug3))throw new Error(`${field5}[${index4}] is invalid`);return slug3})}function parseBotListing(value3){if(!isRecord38(value3)||value3.format!=="openmaus.catalog"||value3.version!==1)throw new Error("Danh s\xE1ch m\u1EABu kh\xF4ng \u0111\xFAng \u0111\u1ECBnh d\u1EA1ng");if(value3.bots===void 0)return[];if(!Array.isArray(value3.bots)||value3.bots.length>200)throw new Error("Danh s\xE1ch bot m\u1EABu kh\xF4ng h\u1EE3p l\u1EC7");let seen=new Set,out=[];
29819
- return value3.bots.forEach((raw,index4)=>{let field5=`bots[${index4}]`;try{if(!isRecord38(raw))throw new Error(`${field5} is invalid`);let id12=text27(raw.id,`${field5}.id`,64);if(!ID20.test(id12)||seen.has(id12))throw new Error(`${field5}.id is invalid`);let routines2=typeof raw.routines=="number"&&Number.isSafeInteger(raw.routines)&&raw.routines>=0&&raw.routines<=10?raw.routines:0;out.push({id:id12,name:text27(raw.name,`${field5}.name`,40),title:text27(raw.title,`${field5}.title`,80),category:category(
29820
- raw.category,`${field5}.category`),description:text27(raw.description,`${field5}.description`,300),try:text27(raw.try,`${field5}.try`,200),look:look(raw.look,`${field5}.look`),apps:apps(raw.apps,`${field5}.apps`),routines:routines2}),seen.add(id12)}catch{}}),out}function parseLibraryBot(value3,expectedId){if(!isRecord38(value3)||value3.format!=="limix.bot"||value3.version!==1)throw new Error("T\u1EC7p bot m\u1EABu kh\xF4ng \u0111\xFAng \u0111\u1ECBnh d\u1EA1ng");let id12=text27(value3.id,"id",64);
29821
- if(id12!==expectedId)throw new Error("T\u1EC7p bot m\u1EABu kh\xF4ng kh\u1EDBp v\u1EDBi danh s\xE1ch");if(!Array.isArray(value3.tryAsking)||value3.tryAsking.length===0||value3.tryAsking.length>6)throw new Error("tryAsking is invalid");let bot={id:id12,category:category(value3.category,"category"),name:text27(value3.name,"name",40),title:text27(value3.title,"title",80),description:text27(value3.description,"description",300),soul:text27(value3.soul,"soul",8e3),look:look(value3.look,"look"),tryAsking:value3.
29822
- tryAsking.map((line6,index4)=>text27(line6,`tryAsking[${index4}]`,200)),apps:apps(value3.apps,"apps")};if(value3.notes!==void 0){if(!isRecord38(value3.notes)||Object.keys(value3.notes).length>6)throw new Error("notes is invalid");bot.notes={};for(let[path6,note2]of Object.entries(value3.notes)){if(!NOTE_PATH.test(path6))throw new Error(`notes path ${path6} is not allowed`);bot.notes[path6]=text27(note2,`notes.${path6}`,8e3)+`
29815
+ /^\/api\/bots\/([\w-]+)$/),m3&&method2==="DELETE"){let result2=await deleteBotWithLifecycle2(m3[1]);return json7(res,result2.status,result2.body)}return PASS}}init_zod();init_mascot_bodies();init_wire();var lxs=(vi4,en4)=>/^vi/i.test(process.env.OMB_UI_LANGUAGE?.trim()??"")?vi4:en4;var TEAM_LIBRARY_RAW_ROOT="https://raw.githubusercontent.com/leomieo/limix-bot-teams/main",LIST_TTL_MS=600*1e3,MAX_CATALOG_BYTES=256e3,MAX_BOT_BYTES=64e3,CATEGORIES=["everyday","work","writing","design","sales","personal"],ID20=/^[a-z0-9][a-z0-9-]{0,63}$/,NOTE_PATH=/^(?:MEMORY\.md|memory\/[a-z0-9][a-z0-9-]{0,63}\.md)$/,TIME3=/^(?:[01]\d|2[0-3]):[0-5]\d$/,isRecord38=value3=>!!value3&&typeof value3=="object"&&!Array.isArray(value3);function text27(value3,field5,max4,allowEmpty=!1){if(typeof value3!=
29816
+ "string"||!allowEmpty&&!value3.trim())throw new Error(`${field5} is required`);let trimmed=value3.trim();if(trimmed.length>max4)throw new Error(`${field5} is too long`);return trimmed}function look(value3,field5){if(!isRecord38(value3))throw new Error(`${field5} is invalid`);let color6=text27(value3.color,`${field5}.color`,20),mascotBody=text27(value3.mascotBody,`${field5}.mascotBody`,20);if(!MAUS_COLOR_NAMES.includes(color6))throw new Error(`${field5}.color is not a known colour`);if(!MASCOT_BODY_IDS.
29817
+ includes(mascotBody))throw new Error(`${field5}.mascotBody is not a known shape`);return{color:color6,mascotBody}}function category(value3,field5){let found2=CATEGORIES.find(entry2=>entry2===value3);if(!found2)throw new Error(`${field5} is not a known category`);return found2}function apps(value3,field5){if(value3===void 0)return[];if(!Array.isArray(value3)||value3.length>10)throw new Error(`${field5} is invalid`);return value3.map((app,index4)=>{let slug3=text27(app,`${field5}[${index4}]`,40);if(!/^[a-z0-9][a-z0-9_-]*$/.
29818
+ test(slug3))throw new Error(`${field5}[${index4}] is invalid`);return slug3})}function parseBotListing(value3){if(!isRecord38(value3)||value3.format!=="openmaus.catalog"||value3.version!==1)throw new Error(lxs("Danh s\xE1ch m\u1EABu kh\xF4ng \u0111\xFAng \u0111\u1ECBnh d\u1EA1ng","The template list has the wrong format"));if(value3.bots===void 0)return[];if(!Array.isArray(value3.bots)||value3.bots.length>200)throw new Error(lxs("Danh s\xE1ch bot m\u1EABu kh\xF4ng h\u1EE3p l\u1EC7","The template \
29819
+ bot list is not valid"));let seen=new Set,out=[];return value3.bots.forEach((raw,index4)=>{let field5=`bots[${index4}]`;try{if(!isRecord38(raw))throw new Error(`${field5} is invalid`);let id12=text27(raw.id,`${field5}.id`,64);if(!ID20.test(id12)||seen.has(id12))throw new Error(`${field5}.id is invalid`);let routines2=typeof raw.routines=="number"&&Number.isSafeInteger(raw.routines)&&raw.routines>=0&&raw.routines<=10?raw.routines:0;out.push({id:id12,name:text27(raw.name,`${field5}.name`,40),title:text27(
29820
+ raw.title,`${field5}.title`,80),category:category(raw.category,`${field5}.category`),description:text27(raw.description,`${field5}.description`,300),try:text27(raw.try,`${field5}.try`,200),look:look(raw.look,`${field5}.look`),apps:apps(raw.apps,`${field5}.apps`),routines:routines2}),seen.add(id12)}catch{}}),out}function parseLibraryBot(value3,expectedId){if(!isRecord38(value3)||value3.format!=="limix.bot"||value3.version!==1)throw new Error(lxs("T\u1EC7p bot m\u1EABu kh\xF4ng \u0111\xFAng \u0111\u1ECBnh d\u1EA1ng",
29821
+ "The template bot file has the wrong format"));let id12=text27(value3.id,"id",64);if(id12!==expectedId)throw new Error(lxs("T\u1EC7p bot m\u1EABu kh\xF4ng kh\u1EDBp v\u1EDBi danh s\xE1ch","The template bot file does not match the list"));if(!Array.isArray(value3.tryAsking)||value3.tryAsking.length===0||value3.tryAsking.length>6)throw new Error("tryAsking is invalid");let bot={id:id12,category:category(value3.category,"category"),name:text27(value3.name,"name",40),title:text27(value3.title,"title",
29822
+ 80),description:text27(value3.description,"description",300),soul:text27(value3.soul,"soul",8e3),look:look(value3.look,"look"),tryAsking:value3.tryAsking.map((line6,index4)=>text27(line6,`tryAsking[${index4}]`,200)),apps:apps(value3.apps,"apps")};if(value3.notes!==void 0){if(!isRecord38(value3.notes)||Object.keys(value3.notes).length>6)throw new Error("notes is invalid");bot.notes={};for(let[path6,note2]of Object.entries(value3.notes)){if(!NOTE_PATH.test(path6))throw new Error(`notes path ${path6}\
29823
+ is not allowed`);bot.notes[path6]=text27(note2,`notes.${path6}`,8e3)+`
29823
29824
  `}}if(value3.routines!==void 0){if(!Array.isArray(value3.routines)||value3.routines.length>5)throw new Error("routines is invalid");bot.routines=value3.routines.map((raw,index4)=>{let field5=`routines[${index4}]`;if(!isRecord38(raw)||!isRecord38(raw.schedule)||raw.schedule.type!=="daily")throw new Error(`${field5} is invalid`);let time6=text27(raw.schedule.time,`${field5}.schedule.time`,5);if(!TIME3.test(time6))throw new Error(`${field5}.schedule.time is invalid`);let weekdays3=raw.schedule.weekdays;
29824
29825
  if(!Array.isArray(weekdays3)||weekdays3.length===0||weekdays3.length>7||weekdays3.some(day2=>!Number.isInteger(day2)||day2<0||day2>6))throw new Error(`${field5}.schedule.weekdays is invalid`);return{name:text27(raw.name,`${field5}.name`,80),prompt:text27(raw.prompt,`${field5}.prompt`,2e3),schedule:{type:"daily",time:time6,weekdays:[...new Set(weekdays3)].sort()},enabled:!1,overlap:"skip"}})}if(value3.skills!==void 0){if(!Array.isArray(value3.skills)||value3.skills.length>20||value3.skills.some(name6=>typeof name6!=
29825
- "string"||!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name6)||name6.length>64))throw new Error("skills is invalid");bot.skills=[...new Set(value3.skills)]}return bot}async function fetchJson2(url2,maxBytes){let response=await fetch(url2,{headers:{accept:"application/json, text/plain;q=0.9"},redirect:"error",signal:AbortSignal.timeout(1e4)});if(!response.ok)throw new Error(`GitHub tr\u1EA3 l\u1ED7i HTTP ${response.status}`);let raw=await response.text();if(Buffer.byteLength(raw)>maxBytes)throw new Error("\
29826
- T\u1EC7p m\u1EABu qu\xE1 l\u1EDBn");try{return JSON.parse(raw)}catch{throw new Error("GitHub kh\xF4ng tr\u1EA3 v\u1EC1 JSON h\u1EE3p l\u1EC7")}}var cached4=null;async function listing2(fresh=!1){if(!fresh&&cached4&&Date.now()-cached4.at<LIST_TTL_MS)return cached4.bots;try{let bots=parseBotListing(await fetchJson2(`${TEAM_LIBRARY_RAW_ROOT}/catalog.json${fresh?`?t=${Date.now()}`:""}`,MAX_CATALOG_BYTES));return cached4={at:Date.now(),bots},bots}catch(error66){if(cached4)return cached4.bots;throw error66}}
29827
- async function handleLimixBotLibrary(method2,path6){try{if(method2==="POST"&&path6==="/api/limix/bots/refresh")return{status:200,body:{bots:await listing2(!0)}};if(method2!=="GET")return null;if(path6==="/api/limix/bots")return{status:200,body:{bots:await listing2()}};let match5=path6.match(/^\/api\/limix\/bots\/([a-z0-9][a-z0-9-]{0,63})$/);if(!match5)return null;let id12=match5[1];return(await listing2()).some(bot=>bot.id===id12)?{status:200,body:{bot:parseLibraryBot(await fetchJson2(`${TEAM_LIBRARY_RAW_ROOT}\
29828
- /bots/${id12}.json?t=${Date.now()}`,MAX_BOT_BYTES),id12)}}:{status:404,body:{error:"Kh\xF4ng c\xF3 bot m\u1EABu n\xE0y"}}}catch(error66){return{status:502,body:{error:error66 instanceof Error?error66.message:String(error66)}}}}init_new_bot_defaults();import{lstatSync as lstatSync45,readdirSync as readdirSync58,readFileSync as readFileSync185,statSync as statSync47}from"node:fs";import{basename as basename34,isAbsolute as isAbsolute41,join as join255}from"node:path";var MAX_ENTRIES2=400,MAX_DEPTH5=8;function listEntries(root3){let out=[],walk6=(dir,prefix,depth2)=>{if(!(depth2>MAX_DEPTH5||out.length>MAX_ENTRIES2))for(let name6 of readdirSync58(dir)){if(out.length>MAX_ENTRIES2)return;let absolute=join255(dir,name6),path6=prefix?`${prefix}/${name6}`:name6,info2=lstatSync45(absolute);info2.isSymbolicLink()?out.push({path:path6,kind:"link",size:0,absolute}):info2.isDirectory()?(out.push({path:path6,kind:"folder",size:0,absolute}),walk6(absolute,path6,depth2+1)):
29826
+ "string"||!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name6)||name6.length>64))throw new Error("skills is invalid");bot.skills=[...new Set(value3.skills)]}return bot}async function fetchJson2(url2,maxBytes){let response=await fetch(url2,{headers:{accept:"application/json, text/plain;q=0.9"},redirect:"error",signal:AbortSignal.timeout(1e4)});if(!response.ok)throw new Error(lxs(`GitHub tr\u1EA3 l\u1ED7i HTTP ${response.status}`,`GitHub returned HTTP ${response.status}`));let raw=await response.text();if(Buffer.
29827
+ byteLength(raw)>maxBytes)throw new Error(lxs("T\u1EC7p m\u1EABu qu\xE1 l\u1EDBn","The template file is too large"));try{return JSON.parse(raw)}catch{throw new Error(lxs("GitHub kh\xF4ng tr\u1EA3 v\u1EC1 JSON h\u1EE3p l\u1EC7","GitHub did not return valid JSON"))}}var cached4=null;async function listing2(fresh=!1){if(!fresh&&cached4&&Date.now()-cached4.at<LIST_TTL_MS)return cached4.bots;try{let bots=parseBotListing(await fetchJson2(`${TEAM_LIBRARY_RAW_ROOT}/catalog.json${fresh?`?t=${Date.now()}`:
29828
+ ""}`,MAX_CATALOG_BYTES));return cached4={at:Date.now(),bots},bots}catch(error66){if(cached4)return cached4.bots;throw error66}}async function handleLimixBotLibrary(method2,path6){try{if(method2==="POST"&&path6==="/api/limix/bots/refresh")return{status:200,body:{bots:await listing2(!0)}};if(method2!=="GET")return null;if(path6==="/api/limix/bots")return{status:200,body:{bots:await listing2()}};let match5=path6.match(/^\/api\/limix\/bots\/([a-z0-9][a-z0-9-]{0,63})$/);if(!match5)return null;let id12=match5[1];
29829
+ return(await listing2()).some(bot=>bot.id===id12)?{status:200,body:{bot:parseLibraryBot(await fetchJson2(`${TEAM_LIBRARY_RAW_ROOT}/bots/${id12}.json?t=${Date.now()}`,MAX_BOT_BYTES),id12)}}:{status:404,body:{error:lxs("Kh\xF4ng c\xF3 bot m\u1EABu n\xE0y","No such template bot")}}}catch(error66){return{status:502,body:{error:error66 instanceof Error?error66.message:String(error66)}}}}init_new_bot_defaults();import{lstatSync as lstatSync45,readdirSync as readdirSync58,readFileSync as readFileSync185,statSync as statSync47}from"node:fs";import{basename as basename34,isAbsolute as isAbsolute41,join as join255}from"node:path";var MAX_ENTRIES2=400,MAX_DEPTH5=8;function listEntries(root3){let out=[],walk6=(dir,prefix,depth2)=>{if(!(depth2>MAX_DEPTH5||out.length>MAX_ENTRIES2))for(let name6 of readdirSync58(dir)){if(out.length>MAX_ENTRIES2)return;let absolute=join255(dir,name6),path6=prefix?`${prefix}/${name6}`:name6,info2=lstatSync45(absolute);info2.isSymbolicLink()?out.push({path:path6,kind:"link",size:0,absolute}):info2.isDirectory()?(out.push({path:path6,kind:"folder",size:0,absolute}),walk6(absolute,path6,depth2+1)):
29829
29830
  info2.isFile()&&out.push({path:path6,kind:"file",size:info2.size,executable:process.platform!=="win32"&&(info2.mode&73)!==0,absolute})}};return walk6(root3,"",1),out}function readOne(dir){let{take:take2,skipped:skipped2}=planSkillFolder(listEntries(dir));if(take2[0]?.path!=="SKILL.md")throw new Error(`no SKILL.md in ${basename34(dir)}`);let files2=take2.filter(entry2=>entry2.kind==="file").map(entry2=>({path:entry2.path,content:readFileSync185(entry2.absolute),executable:entry2.executable===!0}));
29830
29831
  return{source:`local:${basename34(dir)}`,files:files2,skipped:skipped2}}function readLocalSkills(folder3){try{if(!isAbsolute41(folder3)||!statSync47(folder3).isDirectory())return{error:"Choose a folder on this computer."};let dirs=[],has2=dir=>{try{return statSync47(join255(dir,"SKILL.md")).isFile()}catch{return!1}};if(has2(folder3))dirs.push(folder3);else for(let name6 of readdirSync58(folder3)){let sub=join255(folder3,name6);try{statSync47(sub).isDirectory()&&has2(sub)&&dirs.push(sub)}catch{}}
29831
29832
  return dirs.length?{skills:dirs.slice(0,50).map(readOne)}:{error:"No SKILL.md found in that folder or in its subfolders."}}catch(error66){return{error:error66 instanceof Error?error66.message:"Could not read that folder."}}}import{createHash as createHash117}from"node:crypto";import{existsSync as existsSync99,mkdirSync as mkdirSync121,readFileSync as readFileSync186}from"node:fs";import{join as join256}from"node:path";init_atomic();init_config3();var HUB_REPO="leomieo/limix-skill-hub",HUB_BRANCH="main",SYNC_MIN_GAP_MS=1200*60*1e3,FILE_MAX_BYTES3=1024*1024,DEFAULT_SETTINGS={autoSync:!0,autoInstall:!1,lastSync:null,lastError:null,etag:null},hubDir=()=>join256(DATA_DIR,"skill-hub"),settingsFile=()=>join256(hubDir(),"settings.json"),catalogFile=()=>join256(hubDir(),"catalog.json");function readJson6(file3,fallback2){try{return JSON.parse(readFileSync186(file3,"utf8"))}catch{return fallback2}}function hubSettings(){return{...DEFAULT_SETTINGS,
29832
29833
  ...readJson6(settingsFile(),{})}}function saveSettings(next){mkdirSync121(hubDir(),{recursive:!0,mode:448}),writeFileAtomic(settingsFile(),JSON.stringify(next,null,2),{mode:384})}function hubCatalog(){let body2=readJson6(catalogFile(),{});return Array.isArray(body2.skills)?body2.skills:[]}var rawUrl=path6=>`https://raw.githubusercontent.com/${HUB_REPO}/${HUB_BRANCH}/${path6}`;async function fetchText(url2,headers={}){return fetch(url2,{signal:AbortSignal.timeout(3e4),headers:{"User-Agent":"limix\
29833
- -bot",...headers}})}var syncing=null;function syncHub(){return syncing||(syncing=(async()=>{let settings=hubSettings();try{let response=await fetchText(rawUrl("registry/catalog.json"),settings.etag&&existsSync99(catalogFile())?{"If-None-Match":settings.etag}:{});if(response.status===304)return saveSettings({...settings,lastSync:new Date().toISOString(),lastError:null}),{changed:!1,count:hubCatalog().length};if(!response.ok)throw new Error(`Hub tr\u1EA3 v\u1EC1 ${response.status}`);let text31=await response.
29834
- text(),parsed2=JSON.parse(text31);if(!Array.isArray(parsed2.skills))throw new Error("Danh m\u1EE5c Hub kh\xF4ng \u0111\xFAng \u0111\u1ECBnh d\u1EA1ng");return mkdirSync121(hubDir(),{recursive:!0,mode:448}),writeFileAtomic(catalogFile(),text31,{mode:384}),saveSettings({...settings,etag:response.headers.get("etag"),lastSync:new Date().toISOString(),lastError:null}),settings.autoInstall&&await autoInstallEligible(),{changed:!0,count:parsed2.skills.length}}catch(cause){let message8=cause instanceof Error?
29835
- cause.message:String(cause);return saveSettings({...settings,lastError:message8}),{error:message8}}finally{syncing=null}})(),syncing)}var SCRIPT4=/\.(py|js|mjs|cjs|ts|sh|bash|zsh|ps1|bat|cmd|rb|pl|php|go|rs|exe|dll|so|dylib|bin|jar|class|scr|msi|com|apk|dex|node|wasm)$/i,MCP_FILE=/(^|\/)(\.mcp\.json|mcp\.json|mcp-servers?\.json|claude_desktop_config\.json)$/i,isToolFile=path6=>SCRIPT4.test(path6)||MCP_FILE.test(path6),sha=data4=>createHash117("sha256").update(data4).digest("hex");function hubSkillBlocked(entry2){
29836
- return entry2.area==="quarantine"||entry2.security_status==="quarantined"?"Skill n\u1EB1m trong khu c\xE1ch ly v\xEC c\xF3 m\xE3 nguy hi\u1EC3m.":entry2.approval_status==="quarantined"||entry2.approval_status==="rejected"?"Skill b\u1ECB t\u1EEB ch\u1ED1i.":null}function installedFromHub(){let out=new Map;for(let entry2 of Object.values(readSkillLibraryIndex())){let match5=/^limix-hub:([a-z0-9-]+)@([0-9a-f]{64})/.exec(entry2.source);match5&&out.set(match5[1],{checksum:match5[2],sha256:entry2.sha256,
29837
- reviewState:entry2.reviewState})}return out}function hubSkills(){let installed2=installedFromHub(),library=readSkillLibraryIndex();return hubCatalog().map(entry2=>{let have=installed2.get(entry2.id);return{...entry2,installed:!!have,enabled:have?.reviewState==="approved",updateAvailable:!!(have&&have.checksum!==entry2.checksum),blocked:!have&&library[entry2.id]?"Th\u01B0 vi\u1EC7n \u0111\xE3 c\xF3 k\u1EF9 n\u0103ng c\xF9ng t\xEAn kh\xF4ng \u0111\u1EBFn t\u1EEB Hub.":null,risky:hubSkillBlocked(entry2)}})}
29838
- async function installHubSkill(id12,options2={}){if(!isSkillName(id12))return{error:"T\xEAn k\u1EF9 n\u0103ng kh\xF4ng h\u1EE3p l\u1EC7"};let entry2=hubCatalog().find(e=>e.id===id12);if(!entry2)return{error:"Kh\xF4ng th\u1EA5y k\u1EF9 n\u0103ng n\xE0y trong danh m\u1EE5c Hub. H\xE3y \u0111\u1ED3ng b\u1ED9 l\u1EA1i."};let blocked=hubSkillBlocked(entry2);if(blocked&&!options2.allowRisky)return{error:blocked};let installed2=installedFromHub().get(id12);if(readSkillLibraryIndex()[id12]&&!installed2)
29839
- return{error:"Th\u01B0 vi\u1EC7n \u0111\xE3 c\xF3 k\u1EF9 n\u0103ng c\xF9ng t\xEAn kh\xF4ng \u0111\u1EBFn t\u1EEB Hub."};let wanted=Object.keys(entry2.file_hashes).filter(path6=>path6!=="SKILL.md"&&(options2.allowTools||!isToolFile(path6))),skipped2=Object.keys(entry2.file_hashes).filter(path6=>path6!=="SKILL.md"&&!wanted.includes(path6)),files2=[],skillMd="";for(let path6 of["SKILL.md",...wanted]){let expected=entry2.file_hashes[path6];if(!expected)return{error:`Danh m\u1EE5c thi\u1EBFu m\xE3 ki\u1EC3m tra cho ${path6}`};
29840
- let response;try{response=await fetchText(rawUrl(`${entry2.area==="quarantine"?"quarantine":"skills"}/${id12}/${path6}`))}catch(cause){return{error:`Kh\xF4ng t\u1EA3i \u0111\u01B0\u1EE3c ${path6}: ${cause instanceof Error?cause.message:String(cause)}`}}if(!response.ok)return{error:`Kh\xF4ng t\u1EA3i \u0111\u01B0\u1EE3c ${path6} (${response.status})`};let bytes4=new Uint8Array(await response.arrayBuffer());if(bytes4.byteLength>FILE_MAX_BYTES3)return{error:`${path6} qu\xE1 l\u1EDBn`};if(sha(bytes4)!==
29841
- expected)return{error:`${path6} kh\xF4ng kh\u1EDBp m\xE3 SHA-256 trong danh m\u1EE5c \u2014 \u0111\xE3 d\u1EEBng c\xE0i`};path6==="SKILL.md"?skillMd=new TextDecoder().decode(bytes4):files2.push({path:path6,content:bytes4})}if(installed2){let retired=retireLibrarySkill(id12,installed2.sha256);if("error"in retired)return{error:retired.error};if("stale"in retired)return{error:"K\u1EF9 n\u0103ng \u0111\xE3 b\u1ECB s\u1EEDa tay sau khi c\xE0i, kh\xF4ng c\u1EADp nh\u1EADt \u0111\xE8."}}let result2=installLibrarySkill(
29842
- {name:id12,instructions:skillMd,source:`limix-hub:${id12}@${entry2.checksum} (${entry2.source.repository??"?"}@${(entry2.source.commit??"").slice(0,7)})`,...entry2.license?{license:entry2.license}:{},...entry2.compatibility.text?{compatibility:entry2.compatibility.text}:{},...entry2.tags.length?{tags:entry2.tags}:{},warnings:entry2.notes,skippedFiles:skipped2,reviewState:"approved",...files2.length?{files:files2}:{}});return"error"in result2?{error:result2.error}:{ok:!0,skipped:skipped2}}function setHubSkillEnabled(id12,enabled){
29843
- if(!installedFromHub().has(id12))return{error:"K\u1EF9 n\u0103ng n\xE0y ch\u01B0a c\xE0i t\u1EEB Hub"};let result2=setLibrarySkillReviewState(id12,enabled?"approved":"disabled");return"error"in result2?{error:result2.error}:{ok:!0}}function removeHubSkill(id12){let installed2=installedFromHub().get(id12);if(!installed2)return{error:"K\u1EF9 n\u0103ng n\xE0y ch\u01B0a c\xE0i t\u1EEB Hub"};let result2=retireLibrarySkill(id12,installed2.sha256);return"error"in result2?{error:result2.error}:"stale"in
29844
- result2?{error:"K\u1EF9 n\u0103ng \u0111\xE3 b\u1ECB s\u1EEDa tay sau khi c\xE0i, kh\xF4ng g\u1EE1 t\u1EF1 \u0111\u1ED9ng."}:{ok:!0}}async function autoInstallEligible(){let budget=5;for(let skill of hubSkills()){if(budget<=0)return;if(!(!skill.blocked&&!skill.risky&&skill.kind==="instructions-only"&&skill.approval_status==="approved"&&(skill.grade==="excellent"||skill.grade==="recommended"))||skill.installed&&!skill.updateAvailable)continue;budget-=1,"ok"in await installHubSkill(skill.id)&&skill.
29845
- updateAvailable&&skill.enabled&&setHubSkillEnabled(skill.id,!0)}}async function handleLimixHub(method2,path6,body2){let record31=body2&&typeof body2=="object"?body2:{};if(method2==="GET"&&path6==="/api/limix/hub")return{status:200,body:{repo:HUB_REPO,settings:hubSettings(),skills:hubSkills()}};if(method2==="POST"&&path6==="/api/limix/hub/sync"){let result2=await syncHub();return"error"in result2?{status:502,body:result2}:{status:200,body:result2}}if(method2==="POST"&&path6==="/api/limix/hub/sett\
29846
- ings"){let current3=hubSettings();return saveSettings({...current3,autoSync:typeof record31.autoSync=="boolean"?record31.autoSync:current3.autoSync,autoInstall:typeof record31.autoInstall=="boolean"?record31.autoInstall:current3.autoInstall}),{status:200,body:{settings:hubSettings()}}}let id12=typeof record31.id=="string"?record31.id:"";if(method2==="POST"&&(path6==="/api/limix/hub/install"||path6==="/api/limix/hub/update")){let result2=await installHubSkill(id12,{allowTools:record31.allowTools===
29847
- !0,allowRisky:record31.allowRisky===!0});return"error"in result2?{status:422,body:result2}:{status:200,body:result2}}if(method2==="POST"&&path6==="/api/limix/hub/enable"){let result2=setHubSkillEnabled(id12,record31.enabled===!0);return"error"in result2?{status:422,body:result2}:{status:200,body:result2}}if(method2==="POST"&&path6==="/api/limix/hub/remove"){let result2=removeHubSkill(id12);return"error"in result2?{status:422,body:result2}:{status:200,body:result2}}return null}var started2=!1;function startHubAutoSync(){
29848
- if(started2)return;started2=!0;let tick2=()=>{let settings=hubSettings();settings.autoSync&&(settings.lastSync&&Date.now()-Date.parse(settings.lastSync)<SYNC_MIN_GAP_MS||syncHub())};setTimeout(tick2,6e4).unref(),setInterval(tick2,1800*1e3).unref()}import{existsSync as existsSync100,mkdirSync as mkdirSync122,readFileSync as readFileSync187}from"node:fs";import{join as join257}from"node:path";init_atomic();init_config3();var LIMIX_CORE_SKILLS=new Set(["create-verification-skill","agent-reach","windows-computer-automation","phone-connection","phone-harness"]),BOT_ID8=/^[\w-]+$/,picksPath=botId=>join257(DATA_DIR,"skill-state",botId,"limix-bundled.json");function bundledPicks(botId){if(!BOT_ID8.test(botId))return new Set;try{let parsed2=JSON.parse(readFileSync187(picksPath(botId),"utf8"));return new Set(Array.isArray(parsed2)?parsed2.filter(name6=>typeof name6=="string"&&isSkillName(name6)):[])}catch{return new Set}}
29834
+ -bot",...headers}})}var syncing=null;function syncHub(){return syncing||(syncing=(async()=>{let settings=hubSettings();try{let response=await fetchText(rawUrl("registry/catalog.json"),settings.etag&&existsSync99(catalogFile())?{"If-None-Match":settings.etag}:{});if(response.status===304)return saveSettings({...settings,lastSync:new Date().toISOString(),lastError:null}),{changed:!1,count:hubCatalog().length};if(!response.ok)throw new Error(lxs(`Hub tr\u1EA3 v\u1EC1 ${response.status}`,`The hub re\
29835
+ turned ${response.status}`));let text31=await response.text(),parsed2=JSON.parse(text31);if(!Array.isArray(parsed2.skills))throw new Error(lxs("Danh m\u1EE5c Hub kh\xF4ng \u0111\xFAng \u0111\u1ECBnh d\u1EA1ng","The hub catalog has the wrong format"));return mkdirSync121(hubDir(),{recursive:!0,mode:448}),writeFileAtomic(catalogFile(),text31,{mode:384}),saveSettings({...settings,etag:response.headers.get("etag"),lastSync:new Date().toISOString(),lastError:null}),settings.autoInstall&&await autoInstallEligible(),
29836
+ {changed:!0,count:parsed2.skills.length}}catch(cause){let message8=cause instanceof Error?cause.message:String(cause);return saveSettings({...settings,lastError:message8}),{error:message8}}finally{syncing=null}})(),syncing)}var SCRIPT4=/\.(py|js|mjs|cjs|ts|sh|bash|zsh|ps1|bat|cmd|rb|pl|php|go|rs|exe|dll|so|dylib|bin|jar|class|scr|msi|com|apk|dex|node|wasm)$/i,MCP_FILE=/(^|\/)(\.mcp\.json|mcp\.json|mcp-servers?\.json|claude_desktop_config\.json)$/i,isToolFile=path6=>SCRIPT4.test(path6)||MCP_FILE.
29837
+ test(path6),sha=data4=>createHash117("sha256").update(data4).digest("hex");function hubSkillBlocked(entry2){return entry2.area==="quarantine"||entry2.security_status==="quarantined"?lxs("Skill n\u1EB1m trong khu c\xE1ch ly v\xEC c\xF3 m\xE3 nguy hi\u1EC3m.","This skill is quarantined because it contains dangerous code."):entry2.approval_status==="quarantined"||entry2.approval_status==="rejected"?lxs("Skill b\u1ECB t\u1EEB ch\u1ED1i.","This skill was rejected."):null}function installedFromHub(){let out=new Map;
29838
+ for(let entry2 of Object.values(readSkillLibraryIndex())){let match5=/^limix-hub:([a-z0-9-]+)@([0-9a-f]{64})/.exec(entry2.source);match5&&out.set(match5[1],{checksum:match5[2],sha256:entry2.sha256,reviewState:entry2.reviewState})}return out}function hubSkills(){let installed2=installedFromHub(),library=readSkillLibraryIndex();return hubCatalog().map(entry2=>{let have=installed2.get(entry2.id);return{...entry2,installed:!!have,enabled:have?.reviewState==="approved",updateAvailable:!!(have&&have.checksum!==
29839
+ entry2.checksum),blocked:!have&&library[entry2.id]?lxs("Th\u01B0 vi\u1EC7n \u0111\xE3 c\xF3 k\u1EF9 n\u0103ng c\xF9ng t\xEAn kh\xF4ng \u0111\u1EBFn t\u1EEB Hub.","The library already has a skill with this name that did not come from the hub."):null,risky:hubSkillBlocked(entry2)}})}async function installHubSkill(id12,options2={}){if(!isSkillName(id12))return{error:lxs("T\xEAn k\u1EF9 n\u0103ng kh\xF4ng h\u1EE3p l\u1EC7","Invalid skill name")};let entry2=hubCatalog().find(e=>e.id===id12);if(!entry2)
29840
+ return{error:lxs("Kh\xF4ng th\u1EA5y k\u1EF9 n\u0103ng n\xE0y trong danh m\u1EE5c Hub. H\xE3y \u0111\u1ED3ng b\u1ED9 l\u1EA1i.","This skill is not in the hub catalog. Sync again.")};let blocked=hubSkillBlocked(entry2);if(blocked&&!options2.allowRisky)return{error:blocked};let installed2=installedFromHub().get(id12);if(readSkillLibraryIndex()[id12]&&!installed2)return{error:lxs("Th\u01B0 vi\u1EC7n \u0111\xE3 c\xF3 k\u1EF9 n\u0103ng c\xF9ng t\xEAn kh\xF4ng \u0111\u1EBFn t\u1EEB Hub.","The library \
29841
+ already has a skill with this name that did not come from the hub.")};let wanted=Object.keys(entry2.file_hashes).filter(path6=>path6!=="SKILL.md"&&(options2.allowTools||!isToolFile(path6))),skipped2=Object.keys(entry2.file_hashes).filter(path6=>path6!=="SKILL.md"&&!wanted.includes(path6)),files2=[],skillMd="";for(let path6 of["SKILL.md",...wanted]){let expected=entry2.file_hashes[path6];if(!expected)return{error:lxs(`Danh m\u1EE5c thi\u1EBFu m\xE3 ki\u1EC3m tra cho ${path6}`,`The catalog has no \
29842
+ checksum for ${path6}`)};let response;try{response=await fetchText(rawUrl(`${entry2.area==="quarantine"?"quarantine":"skills"}/${id12}/${path6}`))}catch(cause){return{error:lxs(`Kh\xF4ng t\u1EA3i \u0111\u01B0\u1EE3c ${path6}: `,`Couldn't download ${path6}: `)+(cause instanceof Error?cause.message:String(cause))}}if(!response.ok)return{error:lxs(`Kh\xF4ng t\u1EA3i \u0111\u01B0\u1EE3c ${path6} (${response.status})`,`Couldn't download ${path6} (${response.status})`)};let bytes4=new Uint8Array(await response.
29843
+ arrayBuffer());if(bytes4.byteLength>FILE_MAX_BYTES3)return{error:lxs(`${path6} qu\xE1 l\u1EDBn`,`${path6} is too large`)};if(sha(bytes4)!==expected)return{error:lxs(`${path6} kh\xF4ng kh\u1EDBp m\xE3 SHA-256 trong danh m\u1EE5c \u2014 \u0111\xE3 d\u1EEBng c\xE0i`,`${path6} does not match the SHA-256 in the catalog \u2014 install stopped`)};path6==="SKILL.md"?skillMd=new TextDecoder().decode(bytes4):files2.push({path:path6,content:bytes4})}if(installed2){let retired=retireLibrarySkill(id12,installed2.
29844
+ sha256);if("error"in retired)return{error:retired.error};if("stale"in retired)return{error:lxs("K\u1EF9 n\u0103ng \u0111\xE3 b\u1ECB s\u1EEDa tay sau khi c\xE0i, kh\xF4ng c\u1EADp nh\u1EADt \u0111\xE8.","This skill was edited by hand after installing, so it is not overwritten.")}}let result2=installLibrarySkill({name:id12,instructions:skillMd,source:`limix-hub:${id12}@${entry2.checksum} (${entry2.source.repository??"?"}@${(entry2.source.commit??"").slice(0,7)})`,...entry2.license?{license:entry2.
29845
+ license}:{},...entry2.compatibility.text?{compatibility:entry2.compatibility.text}:{},...entry2.tags.length?{tags:entry2.tags}:{},warnings:entry2.notes,skippedFiles:skipped2,reviewState:"approved",...files2.length?{files:files2}:{}});return"error"in result2?{error:result2.error}:{ok:!0,skipped:skipped2}}function setHubSkillEnabled(id12,enabled){if(!installedFromHub().has(id12))return{error:lxs("K\u1EF9 n\u0103ng n\xE0y ch\u01B0a c\xE0i t\u1EEB Hub","This skill was not installed from the hub")};let result2=setLibrarySkillReviewState(
29846
+ id12,enabled?"approved":"disabled");return"error"in result2?{error:result2.error}:{ok:!0}}function removeHubSkill(id12){let installed2=installedFromHub().get(id12);if(!installed2)return{error:lxs("K\u1EF9 n\u0103ng n\xE0y ch\u01B0a c\xE0i t\u1EEB Hub","This skill was not installed from the hub")};let result2=retireLibrarySkill(id12,installed2.sha256);return"error"in result2?{error:result2.error}:"stale"in result2?{error:lxs("K\u1EF9 n\u0103ng \u0111\xE3 b\u1ECB s\u1EEDa tay sau khi c\xE0i, kh\xF4ng g\u1EE1 t\u1EF1 \u0111\u1ED9ng.",
29847
+ "This skill was edited by hand after installing, so it is not removed automatically.")}:{ok:!0}}async function autoInstallEligible(){let budget=5;for(let skill of hubSkills()){if(budget<=0)return;if(!(!skill.blocked&&!skill.risky&&skill.kind==="instructions-only"&&skill.approval_status==="approved"&&(skill.grade==="excellent"||skill.grade==="recommended"))||skill.installed&&!skill.updateAvailable)continue;budget-=1,"ok"in await installHubSkill(skill.id)&&skill.updateAvailable&&skill.enabled&&setHubSkillEnabled(
29848
+ skill.id,!0)}}async function handleLimixHub(method2,path6,body2){let record31=body2&&typeof body2=="object"?body2:{};if(method2==="GET"&&path6==="/api/limix/hub")return{status:200,body:{repo:HUB_REPO,settings:hubSettings(),skills:hubSkills()}};if(method2==="POST"&&path6==="/api/limix/hub/sync"){let result2=await syncHub();return"error"in result2?{status:502,body:result2}:{status:200,body:result2}}if(method2==="POST"&&path6==="/api/limix/hub/settings"){let current3=hubSettings();return saveSettings(
29849
+ {...current3,autoSync:typeof record31.autoSync=="boolean"?record31.autoSync:current3.autoSync,autoInstall:typeof record31.autoInstall=="boolean"?record31.autoInstall:current3.autoInstall}),{status:200,body:{settings:hubSettings()}}}let id12=typeof record31.id=="string"?record31.id:"";if(method2==="POST"&&(path6==="/api/limix/hub/install"||path6==="/api/limix/hub/update")){let result2=await installHubSkill(id12,{allowTools:record31.allowTools===!0,allowRisky:record31.allowRisky===!0});return"erro\
29850
+ r"in result2?{status:422,body:result2}:{status:200,body:result2}}if(method2==="POST"&&path6==="/api/limix/hub/enable"){let result2=setHubSkillEnabled(id12,record31.enabled===!0);return"error"in result2?{status:422,body:result2}:{status:200,body:result2}}if(method2==="POST"&&path6==="/api/limix/hub/remove"){let result2=removeHubSkill(id12);return"error"in result2?{status:422,body:result2}:{status:200,body:result2}}return null}var started2=!1;function startHubAutoSync(){if(started2)return;started2=
29851
+ !0;let tick2=()=>{let settings=hubSettings();settings.autoSync&&(settings.lastSync&&Date.now()-Date.parse(settings.lastSync)<SYNC_MIN_GAP_MS||syncHub())};setTimeout(tick2,6e4).unref(),setInterval(tick2,1800*1e3).unref()}import{existsSync as existsSync100,mkdirSync as mkdirSync122,readFileSync as readFileSync187}from"node:fs";import{join as join257}from"node:path";init_atomic();init_config3();var LIMIX_CORE_SKILLS=new Set(["create-verification-skill","agent-reach","windows-computer-automation","phone-connection","phone-harness"]),BOT_ID8=/^[\w-]+$/,picksPath=botId=>join257(DATA_DIR,"skill-state",botId,"limix-bundled.json");function bundledPicks(botId){if(!BOT_ID8.test(botId))return new Set;try{let parsed2=JSON.parse(readFileSync187(picksPath(botId),"utf8"));return new Set(Array.isArray(parsed2)?parsed2.filter(name6=>typeof name6=="string"&&isSkillName(name6)):[])}catch{return new Set}}
29849
29852
  function setBundledPick(botId,id12,on3){if(!BOT_ID8.test(botId)||!isSkillName(id12))return;let picks2=bundledPicks(botId);picks2.has(id12)!==on3&&(on3?picks2.add(id12):picks2.delete(id12),mkdirSync122(join257(DATA_DIR,"skill-state",botId),{recursive:!0,mode:448}),writeFileAtomic(picksPath(botId),`${JSON.stringify([...picks2].sort(),null,2)}
29850
29853
  `,{mode:384}))}function limixBundledFor(botId,skills){let picks2=bundledPicks(botId);return skills.filter(({manifest})=>LIMIX_CORE_SKILLS.has(manifest.id)||picks2.has(manifest.id))}var slashCalled=(text31,id12)=>new RegExp(`(^|\\s)/${id12.replace(/[-]/g,"\\-")}(?=$|[\\s.,:;!?])`,"m").test(text31);function limixSelectBundled(text31,capabilities,skills){let available=new Set(capabilities),called=skills.filter(({manifest})=>manifest.requiredCapabilities.every(capability2=>available.has(capability2))&&
29851
- slashCalled(text31,manifest.id));return mergeSkills(selectBundledSkills(text31,available,skills),called)}function limixBundledListings(botId){return limixBundledFor(botId,loadBundledSkills()).map(({manifest})=>({name:manifest.id,description:manifest.description,enabled:!0,editable:!1,source:LIMIX_CORE_SKILLS.has(manifest.id)?"K\u0129 n\u0103ng g\u1ED1c c\u1EE7a Limix Bot":"Kho K\u0129 N\u0103ng (Skills) \xB7 c\xF3 s\u1EB5n",sha256:"",importedAt:"",warnings:[],skippedFiles:[],origin:"bundled",core:LIMIX_CORE_SKILLS.
29852
- has(manifest.id)}))}function limixBundledSwitch(botId,name6,enabled){return loadBundledSkills().some(({manifest})=>manifest.id===name6)?LIMIX_CORE_SKILLS.has(name6)?enabled?{status:200,body:{skill:limixBundledListings(botId).find(skill=>skill.name===name6)}}:{status:409,body:{error:"K\u0129 n\u0103ng g\u1ED1c c\u1EE7a Limix Bot lu\xF4n b\u1EADt cho m\u1ECDi bot."}}:(setBundledPick(botId,name6,enabled),{status:200,body:{skill:{...limixBundledListings(botId).find(skill=>skill.name===name6),name:name6,
29853
- enabled}}}):null}function storeItems(bots,botId){let bot=bots.bot(botId),assigned=new Set(bot.assignedSkills??[]),picks2=bundledPicks(botId),items=new Map;for(let{manifest}of loadBundledSkills()){let core=LIMIX_CORE_SKILLS.has(manifest.id);items.set(manifest.id,{name:manifest.id,description:manifest.description,kind:"bundled",added:core||picks2.has(manifest.id),core,ready:!0,warnings:0,source:"C\xF3 s\u1EB5n trong Limix Bot"})}for(let skill of listLibrarySkills()){if(items.has(skill.name))continue;
29854
- let intact=!skill.warnings.some(warning=>warning.includes("enablement is blocked"));items.set(skill.name,{name:skill.name,description:skill.description,kind:"library",added:assigned.has(skill.name)&&skill.enabled,core:!1,ready:intact,warnings:skill.warnings.length,source:skill.source.startsWith("limix-hub:")?"Th\u01B0 vi\u1EC7n k\u0129 n\u0103ng Limix":"\u0110\xE3 c\xE0i trong C\xE0i \u0111\u1EB7t"})}for(let skill of importedSkillListings(botId,new Set))items.has(skill.name)||items.set(skill.name,
29855
- {name:skill.name,description:skill.description,kind:"imported",added:skill.enabled,core:!1,ready:!0,warnings:skill.warnings.length,source:skill.source||"T\u1EEB m\xE1y t\xEDnh n\xE0y"});return[...items.values()].sort((a,b11)=>Number(b11.core)-Number(a.core)||a.name.localeCompare(b11.name))}function limixStoreSet(bots,botId,name6,add11,kind2){let bot=bots.bot(botId);if(!bot)return{error:"Kh\xF4ng t\xECm th\u1EA5y bot."};if(!isSkillName(name6))return{error:"T\xEAn k\u0129 n\u0103ng kh\xF4ng h\u1EE3p l\u1EC7."};
29856
- let item=storeItems(bots,botId).find(entry2=>entry2.name===name6&&(!kind2||entry2.kind===kind2));if(!item)return{error:`Kho ch\u01B0a c\xF3 k\u0129 n\u0103ng "${name6}".`};if(item.kind==="bundled")return item.core?add11?{ok:!0}:{error:"K\u0129 n\u0103ng g\u1ED1c c\u1EE7a Limix Bot lu\xF4n b\u1EADt cho m\u1ECDi bot."}:(setBundledPick(botId,name6,add11),{ok:!0});if(item.kind==="imported"){let result3=setImportedSkillEnabled(botId,name6,add11);return"error"in result3?{error:result3.error}:{ok:!0}}let assigned=bot.
29857
- assignedSkills??[];if(add11){let listing3=listLibrarySkills().find(skill=>skill.name===name6);if(listing3&&!listing3.enabled){let approved=setLibrarySkillReviewState(name6,"approved");if("error"in approved)return{error:approved.error}}return setLibrarySkillOptOut(botId,name6,!1),assigned.includes(name6)||bots.patchBot(botId,{assignedSkills:[...assigned,name6]}),{ok:!0}}let result2=unassignLibrarySkill(botId,name6,assigned);return"unassigned"in result2&&result2.assignedSkills&&bots.patchBot(botId,
29858
- {assignedSkills:result2.assignedSkills}),{ok:!0}}function handleLimixSkillStore(bots,method2,path6,body2){let m3=path6.match(/^\/api\/limix\/skill-store\/([\w-]+)$/);if(!m3)return null;let botId=m3[1];if(!bots.bot(botId))return{status:404,body:{error:"Kh\xF4ng t\xECm th\u1EA5y bot."}};if(method2==="GET")return{status:200,body:{skills:storeItems(bots,botId)}};if(method2==="POST"){let raw=body2&&typeof body2=="object"?body2:{},kind2=raw.kind==="bundled"||raw.kind==="library"||raw.kind==="imported"?
29859
- raw.kind:void 0;if(typeof raw.name!="string"||typeof raw.add!="boolean")return{status:400,body:{error:"C\u1EA7n t\xEAn k\u0129 n\u0103ng v\xE0 add."}};let result2=limixStoreSet(bots,botId,raw.name,raw.add,kind2);return"error"in result2?{status:422,body:result2}:{status:200,body:{skills:storeItems(bots,botId)}}}return{status:405,body:{error:"method not allowed"}}}function limixSkillDefaultsMigration(){let marker=join257(DATA_DIR,"skill-state","limix-skill-defaults-v1.json");if(!existsSync100(marker))
29860
- try{for(let[name6,entry2]of Object.entries(readSkillLibraryIndex()))entry2.everyBot&&setLibrarySkillEveryBot(name6,!1);mkdirSync122(join257(DATA_DIR,"skill-state"),{recursive:!0,mode:448}),writeFileAtomic(marker,`${JSON.stringify({at:new Date().toISOString()})}
29854
+ slashCalled(text31,manifest.id));return mergeSkills(selectBundledSkills(text31,available,skills),called)}function limixBundledListings(botId){return limixBundledFor(botId,loadBundledSkills()).map(({manifest})=>({name:manifest.id,description:manifest.description,enabled:!0,editable:!1,source:LIMIX_CORE_SKILLS.has(manifest.id)?lxs("K\u0129 n\u0103ng g\u1ED1c c\u1EE7a Limix Bot","Limix Bot core skill"):lxs("Kho K\u0129 N\u0103ng (Skills) \xB7 c\xF3 s\u1EB5n","Skills Store \xB7 built in"),sha256:"",
29855
+ importedAt:"",warnings:[],skippedFiles:[],origin:"bundled",core:LIMIX_CORE_SKILLS.has(manifest.id)}))}function limixBundledSwitch(botId,name6,enabled){return loadBundledSkills().some(({manifest})=>manifest.id===name6)?LIMIX_CORE_SKILLS.has(name6)?enabled?{status:200,body:{skill:limixBundledListings(botId).find(skill=>skill.name===name6)}}:{status:409,body:{error:lxs("K\u0129 n\u0103ng g\u1ED1c c\u1EE7a Limix Bot lu\xF4n b\u1EADt cho m\u1ECDi bot.","Limix Bot core skills are always on for every b\
29856
+ ot.")}}:(setBundledPick(botId,name6,enabled),{status:200,body:{skill:{...limixBundledListings(botId).find(skill=>skill.name===name6),name:name6,enabled}}}):null}function storeItems(bots,botId){let bot=bots.bot(botId),assigned=new Set(bot.assignedSkills??[]),picks2=bundledPicks(botId),items=new Map;for(let{manifest}of loadBundledSkills()){let core=LIMIX_CORE_SKILLS.has(manifest.id);items.set(manifest.id,{name:manifest.id,description:manifest.description,kind:"bundled",added:core||picks2.has(manifest.
29857
+ id),core,ready:!0,warnings:0,source:lxs("C\xF3 s\u1EB5n trong Limix Bot","Built into Limix Bot")})}for(let skill of listLibrarySkills()){if(items.has(skill.name))continue;let intact=!skill.warnings.some(warning=>warning.includes("enablement is blocked"));items.set(skill.name,{name:skill.name,description:skill.description,kind:"library",added:assigned.has(skill.name)&&skill.enabled,core:!1,ready:intact,warnings:skill.warnings.length,source:skill.source.startsWith("limix-hub:")?lxs("Th\u01B0 vi\u1EC7n k\u0129 n\u0103\
29858
+ ng Limix","Limix skill library"):lxs("\u0110\xE3 c\xE0i trong C\xE0i \u0111\u1EB7t","Installed in Settings")})}for(let skill of importedSkillListings(botId,new Set))items.has(skill.name)||items.set(skill.name,{name:skill.name,description:skill.description,kind:"imported",added:skill.enabled,core:!1,ready:!0,warnings:skill.warnings.length,source:skill.source||lxs("T\u1EEB m\xE1y t\xEDnh n\xE0y","From this computer")});return[...items.values()].sort((a,b11)=>Number(b11.core)-Number(a.core)||a.name.
29859
+ localeCompare(b11.name))}function limixStoreSet(bots,botId,name6,add11,kind2){let bot=bots.bot(botId);if(!bot)return{error:lxs("Kh\xF4ng t\xECm th\u1EA5y bot.","Bot not found.")};if(!isSkillName(name6))return{error:lxs("T\xEAn k\u0129 n\u0103ng kh\xF4ng h\u1EE3p l\u1EC7.","Invalid skill name.")};let item=storeItems(bots,botId).find(entry2=>entry2.name===name6&&(!kind2||entry2.kind===kind2));if(!item)return{error:lxs(`Kho ch\u01B0a c\xF3 k\u0129 n\u0103ng "${name6}".`,`The store has no skill "${name6}\
29860
+ ".`)};if(item.kind==="bundled")return item.core?add11?{ok:!0}:{error:lxs("K\u0129 n\u0103ng g\u1ED1c c\u1EE7a Limix Bot lu\xF4n b\u1EADt cho m\u1ECDi bot.","Limix Bot core skills are always on for every bot.")}:(setBundledPick(botId,name6,add11),{ok:!0});if(item.kind==="imported"){let result3=setImportedSkillEnabled(botId,name6,add11);return"error"in result3?{error:result3.error}:{ok:!0}}let assigned=bot.assignedSkills??[];if(add11){let listing3=listLibrarySkills().find(skill=>skill.name===name6);
29861
+ if(listing3&&!listing3.enabled){let approved=setLibrarySkillReviewState(name6,"approved");if("error"in approved)return{error:approved.error}}return setLibrarySkillOptOut(botId,name6,!1),assigned.includes(name6)||bots.patchBot(botId,{assignedSkills:[...assigned,name6]}),{ok:!0}}let result2=unassignLibrarySkill(botId,name6,assigned);return"unassigned"in result2&&result2.assignedSkills&&bots.patchBot(botId,{assignedSkills:result2.assignedSkills}),{ok:!0}}function handleLimixSkillStore(bots,method2,path6,body2){
29862
+ let m3=path6.match(/^\/api\/limix\/skill-store\/([\w-]+)$/);if(!m3)return null;let botId=m3[1];if(!bots.bot(botId))return{status:404,body:{error:lxs("Kh\xF4ng t\xECm th\u1EA5y bot.","Bot not found.")}};if(method2==="GET")return{status:200,body:{skills:storeItems(bots,botId)}};if(method2==="POST"){let raw=body2&&typeof body2=="object"?body2:{},kind2=raw.kind==="bundled"||raw.kind==="library"||raw.kind==="imported"?raw.kind:void 0;if(typeof raw.name!="string"||typeof raw.add!="boolean")return{status:400,
29863
+ body:{error:lxs("C\u1EA7n t\xEAn k\u0129 n\u0103ng v\xE0 add.","A skill name and add are required.")}};let result2=limixStoreSet(bots,botId,raw.name,raw.add,kind2);return"error"in result2?{status:422,body:result2}:{status:200,body:{skills:storeItems(bots,botId)}}}return{status:405,body:{error:"method not allowed"}}}function limixSkillDefaultsMigration(){let marker=join257(DATA_DIR,"skill-state","limix-skill-defaults-v1.json");if(!existsSync100(marker))try{for(let[name6,entry2]of Object.entries(readSkillLibraryIndex()))
29864
+ entry2.everyBot&&setLibrarySkillEveryBot(name6,!1);mkdirSync122(join257(DATA_DIR,"skill-state"),{recursive:!0,mode:448}),writeFileAtomic(marker,`${JSON.stringify({at:new Date().toISOString()})}
29861
29865
  `,{mode:384})}catch{}}function createBotSkillsRoutes(deps){let{store:store5,cfg:cfg2,listBotSkills:listBotSkills2,readBotSkillFile:readBotSkillFile2,stagedSkillListing:stagedSkillListing2}=deps;return async({req,res,url:url2,path:path6,method:method2,json:json7,readBody:readBody2})=>{let m3=null;if(path6.startsWith("/api/limix/bots")){let library=await handleLimixBotLibrary(method2,path6);if(library)return json7(res,library.status,library.body)}if(path6.startsWith("/api/limix/hub")){let hub=await handleLimixHub(method2,
29862
29866
  path6,method2==="POST"?await readBody2(req):void 0);if(hub)return json7(res,hub.status,hub.body)}if(path6.startsWith("/api/limix/skill-store/")){let skillStore=handleLimixSkillStore(store5,method2,path6,method2==="POST"?await readBody2(req):void 0);if(skillStore)return json7(res,skillStore.status,skillStore.body)}if(method2==="GET"&&path6==="/api/limix/bundled-skills"){let skills=loadBundledSkills().map(({manifest})=>({id:manifest.id,name:manifest.name,description:manifest.description,defaultEnabled:manifest.
29863
29867
  defaultEnabled,triggerTerms:manifest.triggerTerms,core:LIMIX_CORE_SKILLS.has(manifest.id)}));return json7(res,200,{skills})}if(m3=path6.match(/^\/api\/bots\/([\w-]+)\/skill-template$/),m3&&method2==="POST"){if(!store5.bot(m3[1]))return json7(res,404,{error:"no such bot"});let checked=botSkillTemplateSchema.safeParse(await readBody2(req));if(!checked.success)return json7(res,400,{error:checked.error.message});let input2=checked.data,parsed2=parseSkillMd(input2.text);if("error"in parsed2||parsed2.
@@ -30833,7 +30837,8 @@ bot.id,body2.threadId);let expectedThreadId=body2.threadId;if(expectedThreadId!=
30833
30837
  code:"stop_not_yours"}),refusal7!==null};if(typeof expectedThreadId=="string"&&store5.taskByThread(bot.id,expectedThreadId))return stopReachesLiveTurn(turnId,liveTurn(expectedThreadId))?stopRefused(expectedThreadId)?void 0:(await stopThreadForPerson2(bot.id,expectedThreadId,messageSender2(auth)),json7(res,200,{ok:!0})):json7(res,200,{ok:!0,outcome:"already-stopped"});let directClaim=directTurnDispatchClaims2.get(bot.threadId),routineRun=routines2.activeBotRunForBot(bot.id);if(routineRun)return expectedThreadId!==
30834
30838
  void 0&&routineRun.threadId!==expectedThreadId?json7(res,409,userError("serverError.chat.routineElsewhere")):(cancelDirectTurnDispatch2(bot.id,routineRun.threadId??expectedThreadId),routineRun.threadId&&revokeInternalCapabilitiesForThread2(routineRun.threadId),await routines2.cancelRun(routineRun.id),json7(res,200,{ok:!0}));let instance=registry3.get((botForThread2(bot.id,expectedThreadId??bot.threadId)??bot).modelSelection.instanceId),busyGroup=activeGroupTurnForBot2(bot.id);return busyGroup?expectedThreadId!==
30835
30839
  void 0&&busyGroup.threadId!==expectedThreadId?json7(res,409,userError("serverError.chat.busyInRoom",{room:busyGroup.group.name})):stopRefused(busyGroup.threadId)?void 0:(cancelGroupTurnOperations2(busyGroup.group.id,busyGroup.threadId),revokeInternalCapabilitiesForThread2(busyGroup.threadId),await instance?.adapter.interruptTurn(busyGroup.threadId).catch(()=>{}),closeOpenApprovals2(busyGroup.threadId),json7(res,200,{ok:!0})):expectedThreadId!==void 0&&!busyGroup&&bot.threadId!==expectedThreadId&&
30836
- directClaim?.threadId!==expectedThreadId?json7(res,409,{error:"the bot switched tasks before it could be interrupted"}):stopReachesLiveTurn(turnId,liveTurn(expectedThreadId??bot.threadId))?stopRefused(expectedThreadId??bot.threadId)?void 0:(await stopThreadForPerson2(bot.id,expectedThreadId??bot.threadId,messageSender2(auth)),json7(res,200,{ok:!0})):json7(res,200,{ok:!0,outcome:"already-stopped"})}return PASS};return route}var internalTurnThreads=new Set;function markInternalTurn(threadId){internalTurnThreads.add(threadId)}function clearInternalTurn(threadId){internalTurnThreads.delete(threadId)}function isInternalTurn(threadId){return internalTurnThreads.has(threadId)}function firstReplyWaitingName(botName,ai2){return`notice: ${botName} v\u1EABn ch\u01B0a nh\u1EADn \u0111\u01B0\u1EE3c ph\u1EA3n h\u1ED3i t\u1EEB ${ai2}.`}function firstReplyOverName(botName,ai2){return`notice: ${botName} kh\xF4ng nh\u1EADn \u0111\u01B0\u1EE3c ph\u1EA3n h\u1ED3i t\u1EEB ${ai2}.`}function firstReplySettledName(ai2){return`notice: ${ai2} \u0111\xE3 tr\u1EA3 l\u1EDDi, sau m\u1ED9t l\xFAc ch\u1EADm.`}var DEFAULT_FIRST_REPLY_NOTICE_MS=2e4;function firstReplyNoticeMs(env=process.env){let raw=env.OMB_FIRST_REPLY_NOTICE_MS?.trim();if(!raw)return DEFAULT_FIRST_REPLY_NOTICE_MS;let ms2=Number(raw);return Number.isFinite(ms2)&&ms2>=0?ms2:DEFAULT_FIRST_REPLY_NOTICE_MS}var AI_OUTPUT=new Set(["content.delta","item.started","item.updated","item.completed","request.opened","turn.progress"]);function createFirstReplyRescue(deps){let watches=new Map;closeLeftOpen(deps);let pause=watch=>{watch.timer&&clearTimeout(
30840
+ directClaim?.threadId!==expectedThreadId?json7(res,409,{error:"the bot switched tasks before it could be interrupted"}):stopReachesLiveTurn(turnId,liveTurn(expectedThreadId??bot.threadId))?stopRefused(expectedThreadId??bot.threadId)?void 0:(await stopThreadForPerson2(bot.id,expectedThreadId??bot.threadId,messageSender2(auth)),json7(res,200,{ok:!0})):json7(res,200,{ok:!0,outcome:"already-stopped"})}return PASS};return route}var internalTurnThreads=new Set;function markInternalTurn(threadId){internalTurnThreads.add(threadId)}function clearInternalTurn(threadId){internalTurnThreads.delete(threadId)}function isInternalTurn(threadId){return internalTurnThreads.has(threadId)}var limixVi=()=>/^vi/i.test((typeof process<"u"?process.env?.OMB_UI_LANGUAGE:"")??"");function firstReplyWaitingName(botName,ai2){return limixVi()?`notice: ${botName} v\u1EABn ch\u01B0a nh\u1EADn \u0111\u01B0\u1EE3c ph\u1EA3n h\u1ED3i t\u1EEB ${ai2}.`:`notice: ${botName} hasn't heard back from ${ai2} yet.`}function firstReplyOverName(botName,ai2){return limixVi()?`notice: ${botName} kh\xF4ng nh\u1EADn \u0111\u01B0\u1EE3c ph\u1EA3n h\u1ED3i t\u1EEB ${ai2}.`:`notice: ${botName} didn't hear back fr\
30841
+ om ${ai2}.`}function firstReplySettledName(ai2){return limixVi()?`notice: ${ai2} \u0111\xE3 tr\u1EA3 l\u1EDDi, sau m\u1ED9t l\xFAc ch\u1EADm.`:`notice: ${ai2} answered after a slow start.`}var DEFAULT_FIRST_REPLY_NOTICE_MS=2e4;function firstReplyNoticeMs(env=process.env){let raw=env.OMB_FIRST_REPLY_NOTICE_MS?.trim();if(!raw)return DEFAULT_FIRST_REPLY_NOTICE_MS;let ms2=Number(raw);return Number.isFinite(ms2)&&ms2>=0?ms2:DEFAULT_FIRST_REPLY_NOTICE_MS}var AI_OUTPUT=new Set(["content.delta","item.started","item.updated","item.completed","request.opened","turn.progress"]);function createFirstReplyRescue(deps){let watches=new Map;closeLeftOpen(deps);let pause=watch=>{watch.timer&&clearTimeout(
30837
30842
  watch.timer),watch.timer=null},arm=(threadId,watch)=>{pause(watch);let ms2=firstReplyNoticeMs();ms2<=0||(watch.timer=setTimeout(()=>{watch.timer=null;try{post3(threadId,watch)}catch(error66){console.error(`first-reply rescue: could not post in ${threadId}:`,error66 instanceof Error?error66.message:error66)}},ms2),watch.timer.unref?.())},post3=(threadId,watch)=>{if(watch.phase!=="waiting"||watches.get(threadId)!==watch||(watch.phase="over",deps.quiet(threadId)||!deps.live(threadId,watch.turnId)))
30838
30843
  return;let speaker2=deps.speaker(threadId),group2=speaker2?deps.store.groupByThread(threadId):void 0,owner2=speaker2?deps.store.bot(speaker2.botId):deps.store.botByThread(threadId),bot=owner2&&!group2?deps.botForThread(owner2.id,threadId)??owner2:owner2;if(!bot)return;let instanceId=deps.runningEngine(threadId)??bot.modelSelection.instanceId,ai2=deps.engineName(instanceId),request9=group2?void 0:deps.requests.get(threadId),requestMessageId=request9&&!request9.stopped&&request9.turnId===watch.turnId?
30839
30844
  request9.messageId:void 0,rescue={botId:bot.id,threadId,turnId:watch.turnId,...requestMessageId?{requestMessageId}:{},instanceId,ai:ai2,...group2?{groupId:group2.id}:{}},name6=firstReplyWaitingName(bot.name,ai2),row=deps.store.appendMessage(threadId,{role:"bot",kind:"activity",turnId:watch.turnId,...speaker2&&group2?{from:{botId:speaker2.botId,name:speaker2.name,color:speaker2.color}}:{},tool:{name:name6,ok:!0,rescue}});watch.row={messageId:row.id,botName:bot.name,rescue},watch.phase="posted"},close2=(threadId,watch,how)=>{
@@ -1 +1 @@
1
- {"type":"module","version":"0.3.25"}
1
+ {"type":"module","version":"0.3.26"}
@@ -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 = {
@@ -134,15 +135,15 @@ export function parseLibraryBot(value, expectedId) {
134
135
  async function fetchJson(url, maxBytes) {
135
136
  const response = await fetch(url, { headers: { accept: "application/json, text/plain;q=0.9" }, redirect: "error", signal: AbortSignal.timeout(10_000) });
136
137
  if (!response.ok)
137
- 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}`));
138
139
  const raw = await response.text();
139
140
  if (Buffer.byteLength(raw) > maxBytes)
140
- 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"));
141
142
  try {
142
143
  return JSON.parse(raw);
143
144
  }
144
145
  catch {
145
- 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"));
146
147
  }
147
148
  }
148
149
  let cached = null;
@@ -175,7 +176,7 @@ export async function handleLimixBotLibrary(method, path) {
175
176
  return null;
176
177
  const id = match[1];
177
178
  if (!(await listing()).some((bot) => bot.id === id))
178
- 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") } };
179
180
  return { status: 200, body: { bot: parseLibraryBot(await fetchJson(`${TEAM_LIBRARY_RAW_ROOT}/bots/${id}.json?t=${Date.now()}`, MAX_BOT_BYTES), id) } };
180
181
  }
181
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);
@@ -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,
@@ -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. */
@@ -16,6 +16,7 @@ import { writeFileAtomic } from "./atomic.js";
16
16
  import { DATA_DIR } from "./config.js";
17
17
  import { importedSkillListings, setImportedSkillEnabled } from "./imported-skills.js";
18
18
  import { listLibrarySkills, loadBundledSkills, mergeSkills, readSkillLibraryIndex, selectBundledSkills, setLibrarySkillEveryBot, setLibrarySkillOptOut, setLibrarySkillReviewState, unassignLibrarySkill, } from "./skill-library.js";
19
+ import { lxs } from "./limix-lang.js";
19
20
  /** Always on for every bot. "Learning new skills" itself is the skill-authoring feature, on for every bot already. */
20
21
  export const LIMIX_CORE_SKILLS = new Set([
21
22
  "create-verification-skill", "agent-reach", "windows-computer-automation", "phone-connection", "phone-harness",
@@ -62,7 +63,7 @@ export function limixSelectBundled(text, capabilities, skills) {
62
63
  export function limixBundledListings(botId) {
63
64
  return limixBundledFor(botId, loadBundledSkills()).map(({ manifest }) => ({
64
65
  name: manifest.id, description: manifest.description, enabled: true, editable: false,
65
- source: LIMIX_CORE_SKILLS.has(manifest.id) ? "Kĩ năng gốc của Limix Bot" : "Kho Kĩ Năng (Skills) · có sẵn",
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"),
66
67
  sha256: "", importedAt: "", warnings: [], skippedFiles: [], origin: "bundled", core: LIMIX_CORE_SKILLS.has(manifest.id),
67
68
  }));
68
69
  }
@@ -72,7 +73,7 @@ export function limixBundledSwitch(botId, name, enabled) {
72
73
  return null;
73
74
  if (LIMIX_CORE_SKILLS.has(name)) {
74
75
  return enabled ? { status: 200, body: { skill: limixBundledListings(botId).find((skill) => skill.name === name) } }
75
- : { status: 409, body: { error: "Kĩ năng gốc của Limix Bot luôn bật cho mọi bot." } };
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.") } };
76
77
  }
77
78
  setBundledPick(botId, name, enabled);
78
79
  return { status: 200, body: { skill: { ...limixBundledListings(botId).find((skill) => skill.name === name), name, enabled } } };
@@ -84,7 +85,7 @@ function storeItems(bots, botId) {
84
85
  const items = new Map();
85
86
  for (const { manifest } of loadBundledSkills()) {
86
87
  const core = LIMIX_CORE_SKILLS.has(manifest.id);
87
- items.set(manifest.id, { name: manifest.id, description: manifest.description, kind: "bundled", added: core || picks.has(manifest.id), core, ready: true, warnings: 0, source: "Có sẵn trong Limix Bot" });
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") });
88
89
  }
89
90
  for (const skill of listLibrarySkills()) {
90
91
  if (items.has(skill.name))
@@ -92,13 +93,13 @@ function storeItems(bots, botId) {
92
93
  const intact = !skill.warnings.some((warning) => warning.includes("enablement is blocked"));
93
94
  items.set(skill.name, {
94
95
  name: skill.name, description: skill.description, kind: "library", added: assigned.has(skill.name) && skill.enabled, core: false,
95
- ready: intact, warnings: skill.warnings.length, source: skill.source.startsWith("limix-hub:") ? "Thư viện kĩ năng Limix" : "Đã cài trong Cài đặt",
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"),
96
97
  });
97
98
  }
98
99
  for (const skill of importedSkillListings(botId, new Set())) {
99
100
  if (items.has(skill.name))
100
101
  continue;
101
- 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 || "Từ máy tính này" });
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") });
102
103
  }
103
104
  return [...items.values()].sort((a, b) => Number(b.core) - Number(a.core) || a.name.localeCompare(b.name));
104
105
  }
@@ -106,15 +107,15 @@ function storeItems(bots, botId) {
106
107
  export function limixStoreSet(bots, botId, name, add, kind) {
107
108
  const bot = bots.bot(botId);
108
109
  if (!bot)
109
- return { error: "Không tìm thấy bot." };
110
+ return { error: lxs("Không tìm thấy bot.", "Bot not found.") };
110
111
  if (!isSkillName(name))
111
- return { error: "Tên kĩ năng không hợp lệ." };
112
+ return { error: lxs("Tên kĩ năng không hợp lệ.", "Invalid skill name.") };
112
113
  const item = storeItems(bots, botId).find((entry) => entry.name === name && (!kind || entry.kind === kind));
113
114
  if (!item)
114
- return { error: `Kho chưa có kĩ năng "${name}".` };
115
+ return { error: lxs(`Kho chưa có kĩ năng "${name}".`, `The store has no skill "${name}".`) };
115
116
  if (item.kind === "bundled") {
116
117
  if (item.core)
117
- return add ? { ok: true } : { error: "Kĩ năng gốc của Limix Bot luôn bật cho mọi bot." };
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.") };
118
119
  setBundledPick(botId, name, add);
119
120
  return { ok: true };
120
121
  }
@@ -148,14 +149,14 @@ export function handleLimixSkillStore(bots, method, path, body) {
148
149
  return null;
149
150
  const botId = m[1];
150
151
  if (!bots.bot(botId))
151
- return { status: 404, body: { error: "Không tìm thấy bot." } };
152
+ return { status: 404, body: { error: lxs("Không tìm thấy bot.", "Bot not found.") } };
152
153
  if (method === "GET")
153
154
  return { status: 200, body: { skills: storeItems(bots, botId) } };
154
155
  if (method === "POST") {
155
156
  const raw = (body && typeof body === "object" ? body : {});
156
157
  const kind = raw.kind === "bundled" || raw.kind === "library" || raw.kind === "imported" ? raw.kind : undefined;
157
158
  if (typeof raw.name !== "string" || typeof raw.add !== "boolean")
158
- return { status: 400, body: { error: "Cần tên kĩ năng và add." } };
159
+ return { status: 400, body: { error: lxs("Cần tên kĩ năng và add.", "A skill name and add are required.") } };
159
160
  const result = limixStoreSet(bots, botId, raw.name, raw.add, kind);
160
161
  return "error" in result ? { status: 422, body: result } : { status: 200, body: { skills: storeItems(bots, botId) } };
161
162
  }
@@ -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.25",
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",