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.
- package/dist/assets/{ActivityPanel-BjjmX49M.js → ActivityPanel-DkwIh-YJ.js} +1 -1
- package/dist/assets/{App-BWtQhGWp.js → App-DCe3hDbm.js} +119 -119
- package/dist/assets/{AppEventEditor-Bq1-MGxi.js → AppEventEditor-25dBAYiN.js} +1 -1
- package/dist/assets/BotSettingsDialog-DJxZo-ro.js +16 -0
- package/dist/assets/{BrowserPanel-CPLcno_m.js → BrowserPanel-DVji4pZh.js} +1 -1
- package/dist/assets/{BrowserProfilesManager-D9JsCp1k.js → BrowserProfilesManager-C4rCEedT.js} +1 -1
- package/dist/assets/{CloudAddDialog-DcmN7A5a.js → CloudAddDialog-BybF9Mcz.js} +1 -1
- package/dist/assets/{ComputerFilesPane-Bjx6TGmM.js → ComputerFilesPane-D-EgbYKM.js} +1 -1
- package/dist/assets/{ComputerPanel-C9Xx4Ifs.js → ComputerPanel-CGKdVGz4.js} +2 -2
- package/dist/assets/{DataPanel-IfpQCi3b.js → DataPanel-BXh6perv.js} +1 -1
- package/dist/assets/{DesktopViewer-RjjUUlrQ.js → DesktopViewer-CRcjWYHe.js} +1 -1
- package/dist/assets/{FilesView-D_0GwHAN.js → FilesView-B67sskTN.js} +1 -1
- package/dist/assets/{GrokMoveDialog-CUie-0en.js → GrokMoveDialog-DNJckF5G.js} +1 -1
- package/dist/assets/{InspectorPanel-BuFL6wWf.js → InspectorPanel-CjtRMfJu.js} +1 -1
- package/dist/assets/{KeyboardShortcutsModal-uOhXagTA.js → KeyboardShortcutsModal-znw4NkA5.js} +1 -1
- package/dist/assets/{LiveDesktop-BwpZa-Ri.js → LiveDesktop-BgFrQKAC.js} +1 -1
- package/dist/assets/{LocalVmWorkspace-CousHKOb.js → LocalVmWorkspace-Dk61S9xg.js} +1 -1
- package/dist/assets/{MacLocalControl-BKDzplO2.js → MacLocalControl-DjyhCaWB.js} +1 -1
- package/dist/assets/{MarkdownFilePreview-CCwc1Tek.js → MarkdownFilePreview-Btb--ZSy.js} +1 -1
- package/dist/assets/{MenuMotion-CSgaJLc2.js → MenuMotion-BOUToDZz.js} +1 -1
- package/dist/assets/{NewBotDialog-BtvoCpqN.js → NewBotDialog-CsQPMX7F.js} +1 -1
- package/dist/assets/{PhoneAppDialog-C1hIwyT8.js → PhoneAppDialog-4OG210md.js} +1 -1
- package/dist/assets/{PlacesRail-PP97GpM6.js → PlacesRail-dslg5RRJ.js} +1 -1
- package/dist/assets/{PluginsPanel-DpUEC1zc.js → PluginsPanel-CaWk4eSi.js} +1 -1
- package/dist/assets/{ProIntroduction-C-7uexIW.js → ProIntroduction-BuOIgmKa.js} +1 -1
- package/dist/assets/{RemoteAgentSettingsPanel-DtofJtgF.js → RemoteAgentSettingsPanel-Drk4pPKT.js} +1 -1
- package/dist/assets/{RoomPanel-CHbgQ0xd.js → RoomPanel-BGLIvmBz.js} +1 -1
- package/dist/assets/{RoutinesPage-ZdFTYQc-.js → RoutinesPage-BuAEX1fg.js} +2 -2
- package/dist/assets/SettingsModal-CwK4KFqq.js +19 -0
- package/dist/assets/{ShareTeamDialog-CNu6Kwet.js → ShareTeamDialog-C5Fv8jWl.js} +1 -1
- package/dist/assets/{SignedInSites-CyWcDbLW.js → SignedInSites-BQ2QP4YA.js} +1 -1
- package/dist/assets/TeamLibraryPanel-B0HAAvLv.js +1 -0
- package/dist/assets/{TeamMapPage-C2PnNdil.js → TeamMapPage-DCkx9eov.js} +1 -1
- package/dist/assets/{TriggersPage-B_br6xBZ.js → TriggersPage-B2sqDFfI.js} +1 -1
- package/dist/assets/{VisibilitySection-DH0WrBCn.js → VisibilitySection-CqL-TbZp.js} +1 -1
- package/dist/assets/{WebhookTrigger-BASBoMb_.js → WebhookTrigger-CorVaJKR.js} +1 -1
- package/dist/assets/{WorksOnGrid-CboITAAs.js → WorksOnGrid-1tMl62nD.js} +1 -1
- package/dist/assets/{abnfDiagram-YUKMZFIV-CJ69o_vp.js → abnfDiagram-YUKMZFIV-UeQ15ljp.js} +1 -1
- package/dist/assets/{activity-BdRjGS2C.js → activity-Rykx5lZJ.js} +1 -1
- package/dist/assets/{architectureDiagram-47P4ROYG-BH57AmKC.js → architectureDiagram-47P4ROYG-Cb42ilBV.js} +1 -1
- package/dist/assets/{blockDiagram-E7TT5TSB-D3ebMs_o.js → blockDiagram-E7TT5TSB-BF1yg64q.js} +1 -1
- package/dist/assets/{c4Diagram-CRA5TL53-6TrdcGZR.js → c4Diagram-CRA5TL53-VaNJpISp.js} +1 -1
- package/dist/assets/{calendar-days-DiMBTH2e.js → calendar-days-kC634hX1.js} +1 -1
- package/dist/assets/channel-Di74R1Kh.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-BemwHdji.js → chunk-2Q5K7J3B-NtXjlfnQ.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-CQZbLBld.js → chunk-5VM5RSS4-CYWCBb40.js} +1 -1
- package/dist/assets/{chunk-7TKQ45FW-Dy6EXFTX.js → chunk-7TKQ45FW-CTx1wLnU.js} +1 -1
- package/dist/assets/{chunk-AW2ZBBNX-BbKGGyK8.js → chunk-AW2ZBBNX-PDrwpbhk.js} +1 -1
- package/dist/assets/{chunk-FVRAUYC3-DHqUalc2.js → chunk-FVRAUYC3-CqIp8Ubp.js} +2 -2
- package/dist/assets/{chunk-HTAEGDNF-B66Vjlwq.js → chunk-HTAEGDNF-yA6riuIr.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-DrtdT6L3.js → chunk-JWPE2WC7-DEE_S031.js} +1 -1
- package/dist/assets/{chunk-KQW6MTUR-t7RW8fFT.js → chunk-KQW6MTUR-QybxNtsY.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-CntyS42N.js → chunk-XXDRQBXY-BjchQyqU.js} +1 -1
- package/dist/assets/{classDiagram-v2-K4WV4PDN-KlNCgeTt.js → classDiagram-v2-K4WV4PDN-DtOc8Abr.js} +1 -1
- package/dist/assets/{coins-BHKpT34i.js → coins-D0wNJCPg.js} +1 -1
- package/dist/assets/{core-DEy6KDLR.js → core-Blwbk7ct.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-C_cXoOGT.js → cose-bilkent-JH36ORCC-CWHsqn7M.js} +1 -1
- package/dist/assets/{cynefin-EF2NZ3EQ-DGuwW5zt.js → cynefin-EF2NZ3EQ-BqS-D9jN.js} +1 -1
- package/dist/assets/{cynefinDiagram-3GCD6N5R-qMzxXkVk.js → cynefinDiagram-3GCD6N5R-Fc51YETb.js} +1 -1
- package/dist/assets/{dagre-W4DXFKR2-Q-osY3Yi.js → dagre-W4DXFKR2-So1kz52t.js} +1 -1
- package/dist/assets/{diagram-2UJZ2QOL-DyVJSBOF.js → diagram-2UJZ2QOL-DpdA_Lk0.js} +1 -1
- package/dist/assets/{diagram-OVF4WLC6-C5qBFdpm.js → diagram-OVF4WLC6-BqSu7ih2.js} +1 -1
- package/dist/assets/{diagram-PFPMY2P6-C1PW3U2P.js → diagram-PFPMY2P6-BoM6YcIG.js} +1 -1
- package/dist/assets/{diagram-UMYRVEAY-D2pa0Mzi.js → diagram-UMYRVEAY-B7wKoLi9.js} +1 -1
- package/dist/assets/{diagram-YEKJPTXX-BIzOA1li.js → diagram-YEKJPTXX-BEXKw540.js} +1 -1
- package/dist/assets/{diagram-ZIFT7M5P-B1UL3O8m.js → diagram-ZIFT7M5P-BAD7sJRi.js} +1 -1
- package/dist/assets/{ebnfDiagram-VR2GEFS7-Cxu-kU8s.js → ebnfDiagram-VR2GEFS7-DILoqvy-.js} +1 -1
- package/dist/assets/{elk-IJKZMXRS-DJS39Bm9.js → elk-IJKZMXRS-BHW-Cxin.js} +1 -1
- package/dist/assets/{erDiagram-O2IAPWRE-Cb31BS5L.js → erDiagram-O2IAPWRE-Blp0Wwq0.js} +1 -1
- package/dist/assets/{file-up-DWGlVRGP.js → file-up-CEuHjVBK.js} +1 -1
- package/dist/assets/{flowDiagram-OXPTDLAJ-BaPTzS6m.js → flowDiagram-OXPTDLAJ-6SkVmYje.js} +1 -1
- package/dist/assets/{ganttDiagram-R7TSEDQI-B_Lr-26i.js → ganttDiagram-R7TSEDQI-DFDwj8QX.js} +1 -1
- package/dist/assets/{gitGraphDiagram-XJZIOB7I-UHyDFX6a.js → gitGraphDiagram-XJZIOB7I-mtJG-sis.js} +1 -1
- package/dist/assets/{import-name-Cxwtm5XO.js → import-name-B3mVDURb.js} +1 -1
- package/dist/assets/{index-BME9PdWM.js → index-BZOGYABm.js} +1 -1
- package/dist/assets/{index-BSrb6nsc.js → index-D2Jm8LAf.js} +2 -2
- package/dist/assets/{infoDiagram-5W2HQ5XZ-B7ovBJYA.js → infoDiagram-5W2HQ5XZ-CmyXB17U.js} +1 -1
- package/dist/assets/{ishikawaDiagram-K3B6WC7H-BYS-TlrF.js → ishikawaDiagram-K3B6WC7H-BKFtIESa.js} +1 -1
- package/dist/assets/{journeyDiagram-COXZFDF6-jJxKYEtT.js → journeyDiagram-COXZFDF6-DuXMREdz.js} +1 -1
- package/dist/assets/{kanban-definition-P3RFRI5V-DyiFYaIK.js → kanban-definition-P3RFRI5V-B9mWvKdh.js} +1 -1
- package/dist/assets/{mcp-servers-DXFuhTlS.js → mcp-servers-D3PCWkFu.js} +1 -1
- package/dist/assets/{mermaid.core-D3YJgsiB.js → mermaid.core-3c-Lql2y.js} +4 -4
- package/dist/assets/{mindmap-definition-LZFPQGKD-DnbWodUY.js → mindmap-definition-LZFPQGKD-BmLt9bkD.js} +1 -1
- package/dist/assets/{minimize-Af1aU3Tz.js → minimize-BfKI3Nbu.js} +1 -1
- package/dist/assets/{monitor-DuYHWIyw.js → monitor-C-EBTsrK.js} +1 -1
- package/dist/assets/{new-bot-TwOnvVO1.js → new-bot-3sQib6nJ.js} +1 -1
- package/dist/assets/{org-library-BLD6YHcM.js → org-library-DHuACSMf.js} +1 -1
- package/dist/assets/{package-format-72bbOy2w.js → package-format-CAZ-xCP-.js} +1 -1
- package/dist/assets/{pegDiagram-BGZESJAR-CcOuP6zx.js → pegDiagram-BGZESJAR-5w1MTtLg.js} +1 -1
- package/dist/assets/{pieDiagram-CAPJLFHJ-DhCU7Sfy.js → pieDiagram-CAPJLFHJ-C4etNvsK.js} +1 -1
- package/dist/assets/{quadrantDiagram-GDMTTRAM-7OziX26h.js → quadrantDiagram-GDMTTRAM-k4cDUeIN.js} +1 -1
- package/dist/assets/{rail-DI2iZJYt.js → rail-C6qkoJbO.js} +1 -1
- package/dist/assets/{railroadDiagram-SM67HX2B-rg44R4U2.js → railroadDiagram-SM67HX2B-B9d0qasL.js} +1 -1
- package/dist/assets/{remote-desktop-panel-BPKKvcHT.js → remote-desktop-panel-sCBdOaQh.js} +1 -1
- package/dist/assets/{requirementDiagram-X7JNWC4A-DFNvg72O.js → requirementDiagram-X7JNWC4A-BlYV4z86.js} +1 -1
- package/dist/assets/{sankeyDiagram-UM26HJRW-CUOrQF2i.js → sankeyDiagram-UM26HJRW-DaIVWJEd.js} +1 -1
- package/dist/assets/{sequenceDiagram-ZO4K6R2Y-4HKsH1wt.js → sequenceDiagram-ZO4K6R2Y-CAnXpud0.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-BEXQDTx-.js → sizeCapture-INFHLROL-BIeqIcHi.js} +1 -1
- package/dist/assets/{stateDiagram-v2-TBUQTH76-BYBLUqiy.js → stateDiagram-v2-TBUQTH76-7ql5dn9y.js} +1 -1
- package/dist/assets/{styles-C2_XZv1s.js → styles-BRMaHK08.js} +3 -3
- package/dist/assets/{swimlanes-N4OXWK64-D-bp8wWN.js → swimlanes-N4OXWK64-CItt-UlU.js} +1 -1
- package/dist/assets/swimlanesDiagram-K3J5GTZL-DAZsljU6.js +8 -0
- package/dist/assets/{team-share-Dpu1nJNz.js → team-share-2OjfOHsk.js} +1 -1
- package/dist/assets/{timeline-definition-YOQAKGHF-CtLFkcud.js → timeline-definition-YOQAKGHF-8Xn9QBNo.js} +1 -1
- package/dist/assets/{usecaseDiagram-VIAY4XPW-AWOhUKvG.js → usecaseDiagram-VIAY4XPW-B6LxotkO.js} +1 -1
- package/dist/assets/{vennDiagram-BLWOH2XV-C_OYDBvp.js → vennDiagram-BLWOH2XV-Ciex0LDC.js} +1 -1
- package/dist/assets/{vi-P4zxF4j0.js → vi-C-E3a4Tv.js} +2 -2
- package/dist/assets/{wardleyDiagram-YMQ3BMBF-CdxMh8tE.js → wardleyDiagram-YMQ3BMBF-B_TbpQlN.js} +1 -1
- package/dist/assets/{xychartDiagram-TAQBALBS-BL2spC5z.js → xychartDiagram-TAQBALBS-Dz_iIYt1.js} +1 -1
- package/dist/index.html +2 -2
- package/dist/rail.html +4 -4
- package/dist-server/index.js +272 -252
- package/dist-server/openmausbot.js +1 -1
- package/dist-server/package.json +1 -1
- package/dist-server/pair-cli.js +1 -1
- package/dist-server/server/imported-skills.js +7 -2
- package/dist-server/server/index.js +7 -5
- package/dist-server/server/limix-bot-library.js +15 -8
- package/dist-server/server/limix-lang.js +2 -0
- package/dist-server/server/limix-skill-hub.js +20 -19
- package/dist-server/server/limix-skill-store.js +180 -0
- package/dist-server/server/routes/bot-skills.js +15 -4
- package/dist-server/shared/first-reply-rescue.js +4 -3
- package/package.json +1 -1
- package/skills/windows-computer-automation/SKILL.md +53 -0
- package/skills/windows-computer-automation/assets/HERIONIX-LEADER-PROMPT.md +25 -0
- package/skills/windows-computer-automation/assets/control-policy.example.json +25 -0
- package/skills/windows-computer-automation/assets/mcp-servers.example.json +19 -0
- package/skills/windows-computer-automation/manifest.json +27 -0
- package/skills/windows-computer-automation/references/herionix-integration.md +72 -0
- package/skills/windows-computer-automation/references/routing.md +26 -0
- package/skills/windows-computer-automation/references/scenarios.md +44 -0
- package/skills/windows-computer-automation/references/security.md +24 -0
- package/skills/windows-computer-automation/scripts/policy_engine.py +235 -0
- package/skills/windows-computer-automation/scripts/test_policy_engine.py +107 -0
- package/dist/assets/BotSettingsDialog-CVlvG4Rb.js +0 -16
- package/dist/assets/SettingsModal-Cdz3bEYZ.js +0 -15
- package/dist/assets/TeamLibraryPanel-DMhdlaSH.js +0 -1
- package/dist/assets/channel-k2J2GMwv.js +0 -1
- 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]===`
|
package/dist-server/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"type":"module","version":"0.3.
|
|
1
|
+
{"type":"module","version":"0.3.26"}
|
package/dist-server/pair-cli.js
CHANGED
|
@@ -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,
|
|
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 =
|
|
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(
|
|
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) {
|
|
@@ -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
|
|
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: "
|
|
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
|
}
|