limixbot 0.3.22 → 0.3.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{ActivityPanel-ChjWicVw.js → ActivityPanel-BjjmX49M.js} +1 -1
- package/dist/assets/App-BWtQhGWp.js +242 -0
- package/dist/assets/{AppEventEditor-NdlNZYiU.js → AppEventEditor-Bq1-MGxi.js} +1 -1
- package/dist/assets/{BotSettingsDialog-DKIuZdV6.js → BotSettingsDialog-CVlvG4Rb.js} +4 -4
- package/dist/assets/{BrowserPanel-DjEwiL31.js → BrowserPanel-CPLcno_m.js} +1 -1
- package/dist/assets/{BrowserProfilesManager-1DLRt4N9.js → BrowserProfilesManager-D9JsCp1k.js} +1 -1
- package/dist/assets/{CloudAddDialog-C0DMYGOm.js → CloudAddDialog-DcmN7A5a.js} +1 -1
- package/dist/assets/{ComputerFilesPane-Dk8NcRrf.js → ComputerFilesPane-Bjx6TGmM.js} +1 -1
- package/dist/assets/{ComputerPanel-DoVcLtW7.js → ComputerPanel-C9Xx4Ifs.js} +2 -2
- package/dist/assets/{DataPanel-C0Fx7K2G.js → DataPanel-IfpQCi3b.js} +1 -1
- package/dist/assets/{DesktopViewer-DTBmRKHz.js → DesktopViewer-RjjUUlrQ.js} +1 -1
- package/dist/assets/{FilesView-D5s67U7H.js → FilesView-D_0GwHAN.js} +1 -1
- package/dist/assets/{GrokMoveDialog-itIjM39_.js → GrokMoveDialog-CUie-0en.js} +1 -1
- package/dist/assets/{InspectorPanel-NMTlN3H-.js → InspectorPanel-BuFL6wWf.js} +1 -1
- package/dist/assets/{KeyboardShortcutsModal-O3skmvQm.js → KeyboardShortcutsModal-uOhXagTA.js} +1 -1
- package/dist/assets/{LiveDesktop-CO9mKNtY.js → LiveDesktop-BwpZa-Ri.js} +1 -1
- package/dist/assets/{LocalVmWorkspace-BLrh4g6E.js → LocalVmWorkspace-CousHKOb.js} +1 -1
- package/dist/assets/{MacLocalControl-DyhXTfbp.js → MacLocalControl-BKDzplO2.js} +1 -1
- package/dist/assets/{MarkdownFilePreview-CyJn1rQP.js → MarkdownFilePreview-CCwc1Tek.js} +1 -1
- package/dist/assets/{MenuMotion-CobYpP7L.js → MenuMotion-CSgaJLc2.js} +1 -1
- package/dist/assets/NewBotDialog-BtvoCpqN.js +1 -0
- package/dist/assets/{PhoneAppDialog-D_CgApJp.js → PhoneAppDialog-C1hIwyT8.js} +1 -1
- package/dist/assets/{PlacesRail-CUntVv3u.js → PlacesRail-PP97GpM6.js} +1 -1
- package/dist/assets/{PluginsPanel-C2bbrSB3.js → PluginsPanel-DpUEC1zc.js} +1 -1
- package/dist/assets/{ProIntroduction-8BVPIU2J.js → ProIntroduction-C-7uexIW.js} +1 -1
- package/dist/assets/{RemoteAgentSettingsPanel-JMsDeyDs.js → RemoteAgentSettingsPanel-DtofJtgF.js} +1 -1
- package/dist/assets/{RoomPanel-BcFX5BFK.js → RoomPanel-CHbgQ0xd.js} +1 -1
- package/dist/assets/{RoutinesPage-BZhG-dR9.js → RoutinesPage-ZdFTYQc-.js} +2 -2
- package/dist/assets/SettingsModal-Cdz3bEYZ.js +15 -0
- package/dist/assets/{ShareTeamDialog-Cf4VzjYH.js → ShareTeamDialog-CNu6Kwet.js} +1 -1
- package/dist/assets/{SignedInSites-31u9pN2b.js → SignedInSites-CyWcDbLW.js} +1 -1
- package/dist/assets/TeamLibraryPanel-DMhdlaSH.js +1 -0
- package/dist/assets/{TeamMapPage-BrrWMFul.js → TeamMapPage-C2PnNdil.js} +1 -1
- package/dist/assets/{TriggersPage-GuMhNUuU.js → TriggersPage-B_br6xBZ.js} +1 -1
- package/dist/assets/{VisibilitySection-C6eVm1Hd.js → VisibilitySection-DH0WrBCn.js} +1 -1
- package/dist/assets/{WebhookTrigger-BGm39FBT.js → WebhookTrigger-BASBoMb_.js} +1 -1
- package/dist/assets/{WorksOnGrid-9NWPGzbE.js → WorksOnGrid-CboITAAs.js} +1 -1
- package/dist/assets/{abnfDiagram-YUKMZFIV-DEyDkr0Q.js → abnfDiagram-YUKMZFIV-CJ69o_vp.js} +1 -1
- package/dist/assets/{activity-CbpNb0MQ.js → activity-BdRjGS2C.js} +1 -1
- package/dist/assets/{architectureDiagram-47P4ROYG-CGTtcA9N.js → architectureDiagram-47P4ROYG-BH57AmKC.js} +1 -1
- package/dist/assets/{blockDiagram-E7TT5TSB-DT9DFOL_.js → blockDiagram-E7TT5TSB-D3ebMs_o.js} +1 -1
- package/dist/assets/{c4Diagram-CRA5TL53-DpERK75F.js → c4Diagram-CRA5TL53-6TrdcGZR.js} +1 -1
- package/dist/assets/{calendar-days-DE6WL-x2.js → calendar-days-DiMBTH2e.js} +1 -1
- package/dist/assets/channel-k2J2GMwv.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-wHwpVFi3.js → chunk-2Q5K7J3B-BemwHdji.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-CfqwqtdU.js → chunk-5VM5RSS4-CQZbLBld.js} +1 -1
- package/dist/assets/{chunk-7TKQ45FW-BRxQFxT-.js → chunk-7TKQ45FW-Dy6EXFTX.js} +1 -1
- package/dist/assets/{chunk-AW2ZBBNX-Ca3w_5Wz.js → chunk-AW2ZBBNX-BbKGGyK8.js} +1 -1
- package/dist/assets/{chunk-FVRAUYC3-CO8Syt2Z.js → chunk-FVRAUYC3-DHqUalc2.js} +2 -2
- package/dist/assets/{chunk-HTAEGDNF-Cnx9DnWv.js → chunk-HTAEGDNF-B66Vjlwq.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-DefKEriK.js → chunk-JWPE2WC7-DrtdT6L3.js} +1 -1
- package/dist/assets/{chunk-KQW6MTUR-B6WWWz_R.js → chunk-KQW6MTUR-t7RW8fFT.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-BFU1-B3X.js → chunk-XXDRQBXY-CntyS42N.js} +1 -1
- package/dist/assets/{classDiagram-v2-K4WV4PDN-BePCpZI3.js → classDiagram-v2-K4WV4PDN-KlNCgeTt.js} +1 -1
- package/dist/assets/{coins-D1fhjgas.js → coins-BHKpT34i.js} +1 -1
- package/dist/assets/{core-GxNVTB-X.js → core-DEy6KDLR.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-D8emtvin.js → cose-bilkent-JH36ORCC-C_cXoOGT.js} +1 -1
- package/dist/assets/{cynefin-EF2NZ3EQ-DUEsfFVq.js → cynefin-EF2NZ3EQ-DGuwW5zt.js} +1 -1
- package/dist/assets/{cynefinDiagram-3GCD6N5R-duonepNA.js → cynefinDiagram-3GCD6N5R-qMzxXkVk.js} +1 -1
- package/dist/assets/{dagre-W4DXFKR2-D4NywFNB.js → dagre-W4DXFKR2-Q-osY3Yi.js} +1 -1
- package/dist/assets/{diagram-2UJZ2QOL-cjiJTdiH.js → diagram-2UJZ2QOL-DyVJSBOF.js} +1 -1
- package/dist/assets/{diagram-OVF4WLC6-D6M8A9DS.js → diagram-OVF4WLC6-C5qBFdpm.js} +1 -1
- package/dist/assets/{diagram-PFPMY2P6-CVAzAz2N.js → diagram-PFPMY2P6-C1PW3U2P.js} +1 -1
- package/dist/assets/{diagram-UMYRVEAY-DLvfYJwO.js → diagram-UMYRVEAY-D2pa0Mzi.js} +1 -1
- package/dist/assets/{diagram-YEKJPTXX-D8qCH--0.js → diagram-YEKJPTXX-BIzOA1li.js} +1 -1
- package/dist/assets/{diagram-ZIFT7M5P-s55SHvTR.js → diagram-ZIFT7M5P-B1UL3O8m.js} +1 -1
- package/dist/assets/{ebnfDiagram-VR2GEFS7-B93cNJNn.js → ebnfDiagram-VR2GEFS7-Cxu-kU8s.js} +1 -1
- package/dist/assets/{elk-IJKZMXRS-BsJqO8aU.js → elk-IJKZMXRS-DJS39Bm9.js} +1 -1
- package/dist/assets/{erDiagram-O2IAPWRE-BXF1G3Js.js → erDiagram-O2IAPWRE-Cb31BS5L.js} +1 -1
- package/dist/assets/{file-up-Cg2kYQkd.js → file-up-DWGlVRGP.js} +1 -1
- package/dist/assets/{flowDiagram-OXPTDLAJ-BKjjYwz9.js → flowDiagram-OXPTDLAJ-BaPTzS6m.js} +1 -1
- package/dist/assets/{ganttDiagram-R7TSEDQI-DhtZJtPm.js → ganttDiagram-R7TSEDQI-B_Lr-26i.js} +1 -1
- package/dist/assets/{gitGraphDiagram-XJZIOB7I-BfRlcF5z.js → gitGraphDiagram-XJZIOB7I-UHyDFX6a.js} +1 -1
- package/dist/assets/{import-name-B8g_Hj2F.js → import-name-Cxwtm5XO.js} +1 -1
- package/dist/assets/{index-B8LCAG4-.js → index-BME9PdWM.js} +1 -1
- package/dist/assets/{index-LBC94myy.js → index-BSrb6nsc.js} +2 -2
- package/dist/assets/{infoDiagram-5W2HQ5XZ-CywmYwco.js → infoDiagram-5W2HQ5XZ-B7ovBJYA.js} +1 -1
- package/dist/assets/{ishikawaDiagram-K3B6WC7H-DpXXqau5.js → ishikawaDiagram-K3B6WC7H-BYS-TlrF.js} +1 -1
- package/dist/assets/{journeyDiagram-COXZFDF6-D1uMWGbz.js → journeyDiagram-COXZFDF6-jJxKYEtT.js} +1 -1
- package/dist/assets/{kanban-definition-P3RFRI5V-Bz0zjqQs.js → kanban-definition-P3RFRI5V-DyiFYaIK.js} +1 -1
- package/dist/assets/{mcp-servers-C_F4MXjY.js → mcp-servers-DXFuhTlS.js} +1 -1
- package/dist/assets/{mermaid.core-Dnv6mcJ4.js → mermaid.core-D3YJgsiB.js} +4 -4
- package/dist/assets/{mindmap-definition-LZFPQGKD-CFEkTgom.js → mindmap-definition-LZFPQGKD-DnbWodUY.js} +1 -1
- package/dist/assets/{minimize-o-OFVasg.js → minimize-Af1aU3Tz.js} +1 -1
- package/dist/assets/{monitor-7oLB-7rZ.js → monitor-DuYHWIyw.js} +1 -1
- package/dist/assets/{new-bot-CFOYR98I.js → new-bot-TwOnvVO1.js} +1 -1
- package/dist/assets/{org-library-TYYMsAm5.js → org-library-BLD6YHcM.js} +1 -1
- package/dist/assets/{package-format-CrrPupM8.js → package-format-72bbOy2w.js} +1 -1
- package/dist/assets/{pegDiagram-BGZESJAR-DKt4SMwc.js → pegDiagram-BGZESJAR-CcOuP6zx.js} +1 -1
- package/dist/assets/{pieDiagram-CAPJLFHJ-B9taKUge.js → pieDiagram-CAPJLFHJ-DhCU7Sfy.js} +1 -1
- package/dist/assets/{quadrantDiagram-GDMTTRAM-DQjB7Jbz.js → quadrantDiagram-GDMTTRAM-7OziX26h.js} +1 -1
- package/dist/assets/{rail-BBdJiVR1.js → rail-DI2iZJYt.js} +1 -1
- package/dist/assets/{railroadDiagram-SM67HX2B-DQOivC56.js → railroadDiagram-SM67HX2B-rg44R4U2.js} +1 -1
- package/dist/assets/{remote-desktop-panel-BmwpQiDu.js → remote-desktop-panel-BPKKvcHT.js} +1 -1
- package/dist/assets/{requirementDiagram-X7JNWC4A-BLgwXDEl.js → requirementDiagram-X7JNWC4A-DFNvg72O.js} +1 -1
- package/dist/assets/{sankeyDiagram-UM26HJRW-g2GlJNjj.js → sankeyDiagram-UM26HJRW-CUOrQF2i.js} +1 -1
- package/dist/assets/{sequenceDiagram-ZO4K6R2Y-_08U7Ixl.js → sequenceDiagram-ZO4K6R2Y-4HKsH1wt.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-DZPfU3_U.js → sizeCapture-INFHLROL-BEXQDTx-.js} +1 -1
- package/dist/assets/{stateDiagram-v2-TBUQTH76-PXBjCkxd.js → stateDiagram-v2-TBUQTH76-BYBLUqiy.js} +1 -1
- package/dist/assets/{styles-BHtUK2qB.js → styles-C2_XZv1s.js} +1 -1
- package/dist/assets/styles-Di_mG4l3.css +1 -0
- package/dist/assets/{swimlanes-N4OXWK64-q1MhehBF.js → swimlanes-N4OXWK64-D-bp8wWN.js} +1 -1
- package/dist/assets/swimlanesDiagram-K3J5GTZL-DTQBdgKt.js +8 -0
- package/dist/assets/{team-share-xt--eeM_.js → team-share-Dpu1nJNz.js} +1 -1
- package/dist/assets/{timeline-definition-YOQAKGHF-UBKh0GG-.js → timeline-definition-YOQAKGHF-CtLFkcud.js} +1 -1
- package/dist/assets/{usecaseDiagram-VIAY4XPW-CccLioTT.js → usecaseDiagram-VIAY4XPW-AWOhUKvG.js} +1 -1
- package/dist/assets/{vennDiagram-BLWOH2XV-3Q39wQgE.js → vennDiagram-BLWOH2XV-C_OYDBvp.js} +1 -1
- package/dist/assets/{vi-CVDyZGky.js → vi-P4zxF4j0.js} +1 -1
- package/dist/assets/{wardleyDiagram-YMQ3BMBF-DtgEYK7j.js → wardleyDiagram-YMQ3BMBF-CdxMh8tE.js} +1 -1
- package/dist/assets/{xychartDiagram-TAQBALBS-DLTmsflK.js → xychartDiagram-TAQBALBS-BL2spC5z.js} +1 -1
- package/dist/index.html +3 -3
- package/dist/rail.html +5 -5
- package/dist-server/index.js +1591 -1578
- package/dist-server/package.json +1 -1
- package/dist-server/server/index.js +1 -1
- package/dist-server/server/initial-greeting.js +4 -0
- package/dist-server/server/limix-bot-library.js +178 -0
- package/dist-server/server/limix-skill-hub.js +6 -8
- package/dist-server/server/routes/bot-skills.js +6 -0
- package/dist-server/server/routes/teams.js +1 -1
- package/package.json +1 -1
- package/dist/assets/App-D-SXyZXT.js +0 -242
- package/dist/assets/NewBotDialog-coMM5O6x.js +0 -1
- package/dist/assets/SettingsModal-CWtO0RWS.js +0 -11
- package/dist/assets/TeamLibraryPanel-Bzvx_MlH.js +0 -1
- package/dist/assets/channel-CETDvzTH.js +0 -1
- package/dist/assets/styles-B9SGxFnK.css +0 -1
- package/dist/assets/swimlanesDiagram-K3J5GTZL-pLYSpGVX.js +0 -8
package/dist-server/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"type":"module","version":"0.3.
|
|
1
|
+
{"type":"module","version":"0.3.24"}
|
|
@@ -661,7 +661,7 @@ const CLOUD_ENV = { ...process.env, ...CLOUD_SECRETS };
|
|
|
661
661
|
const CLOUD_HOME = cloudHomeConfiguration(CLOUD_ENV);
|
|
662
662
|
const CLOUD_TEAM = CLOUD_HOME?.team ?? null, CLOUD_PERSONAL = CLOUD_HOME !== null && CLOUD_TEAM === null, TEAM_LABEL = CLOUD_TEAM ? { label: CLOUD_TEAM.name } : {};
|
|
663
663
|
/** The language set on this server, else (on a Team Cloud) the team's default from the Admin. threadLanguage: the one a line in a conversation is written in, its one person's, else the place's (server/server-lines.ts). */
|
|
664
|
-
const appLanguage = () => cfg.language ?? CLOUD_TEAM?.language;
|
|
664
|
+
const appLanguage = () => cfg.language ?? CLOUD_TEAM?.language ?? (process.env.OMB_UI_LANGUAGE?.trim() || undefined);
|
|
665
665
|
const personLanguages = createPersonLanguages({ dataDir: DATA_DIR, configured: appLanguage, privateTo: (threadId) => teamHost.privateTo(threadId) });
|
|
666
666
|
const threadLanguage = personLanguages.forThread;
|
|
667
667
|
setConversationLanguage(threadLanguage);
|
|
@@ -6,6 +6,7 @@ const GREETINGS = {
|
|
|
6
6
|
es: (name, person) => `Hola${person ? ` ${person}` : ""}, soy ${name}. ¿Qué te gustaría que hiciera?`,
|
|
7
7
|
"pt-br": (name, person) => `Oi${person ? ` ${person}` : ""}, eu sou ${name}. O que você gostaria que eu fizesse?`,
|
|
8
8
|
ko: (name, person) => `안녕하세요${person ? `, ${person}님` : ""}. 저는 ${name}입니다. 무엇을 도와드릴까요?`,
|
|
9
|
+
vi: (name, person) => `Xin chào${person ? ` ${person}` : ""}, mình là ${name}. Bạn muốn mình giúp việc gì?`,
|
|
9
10
|
"zh-tw": (name, person) => `嗨${person ? ` ${person}` : ""},我是 ${name}。你想要我幫你做什麼?`,
|
|
10
11
|
};
|
|
11
12
|
/** What the bot says the moment Set me up is pressed: no model is asked, so it is written here. */
|
|
@@ -14,6 +15,7 @@ const SETUP_REQUEST_LINES = {
|
|
|
14
15
|
es: "Genial. Cuéntame con tus propias palabras en qué te gustaría que te ayude y me prepararé para ello. Por ejemplo: \"ordena mi bandeja de entrada cada mañana\" o \"investiga a nuestros competidores cada semana\".",
|
|
15
16
|
"pt-br": "Ótimo. Me conte com suas palavras em que você gostaria de ajuda e eu me preparo para isso. Por exemplo: \"organize minha caixa de entrada toda manhã\" ou \"pesquise nossos concorrentes toda semana\".",
|
|
16
17
|
ko: "좋아요. 도움이 필요한 일을 편하게 말씀해 주시면 거기에 맞게 제가 설정할게요. 예: \"매일 아침 받은편지함 정리해 줘\", \"매주 경쟁사 조사해 줘\".",
|
|
18
|
+
vi: "Tuyệt. Bạn cứ kể bằng lời của mình về việc cần giúp, mình sẽ tự thiết lập để làm việc đó. Ví dụ: \"sắp xếp hộp thư của tôi mỗi sáng\" hoặc \"tìm hiểu đối thủ của chúng tôi mỗi tuần\".",
|
|
17
19
|
"zh-tw": "好的。用你自己的話告訴我想要幫忙的事,我會照著設定好自己。例如:「每天早上整理我的收件匣」或「每週研究我們的競爭對手」。",
|
|
18
20
|
};
|
|
19
21
|
/** The answers a blank bot's greeting offers on its setup card. Picking one sets the bot up for its words, as
|
|
@@ -27,6 +29,8 @@ const SETUP_CHOICES = {
|
|
|
27
29
|
"Reservas, compras e tarefas", "Dados, planilhas e relatórios"],
|
|
28
30
|
ko: ["리서치와 주제 깊이 파고들기", "이것저것 지켜보기", "이메일, 캘린더, 메시지",
|
|
29
31
|
"예약, 쇼핑, 심부름", "데이터, 스프레드시트, 보고서"],
|
|
32
|
+
vi: ["Nghiên cứu và tìm hiểu sâu một chủ đề", "Theo dõi mọi việc giúp tôi", "Email, lịch và tin nhắn",
|
|
33
|
+
"Đặt chỗ, mua sắm và việc vặt", "Dữ liệu, bảng tính và báo cáo"],
|
|
30
34
|
"zh-tw": ["研究與深入探討主題", "幫我留意各種事情", "電子郵件、行事曆和訊息",
|
|
31
35
|
"預約、購物和跑腿", "資料、試算表和報告"],
|
|
32
36
|
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// Limix bot templates: the "Bot dựng sẵn" list in Templates comes from github.com/leomieo/limix-bot-teams.
|
|
2
|
+
// GET /api/limix/bots reads only the light list in catalog.json (cached ten minutes); GET /api/limix/bots/<id> fetches
|
|
3
|
+
// that one bot's file when the person presses "Dùng mẫu". Everything is checked here before it reaches the page, and a
|
|
4
|
+
// template only describes who the bot is: its routines always arrive paused and nothing is connected for the person.
|
|
5
|
+
import { MASCOT_BODY_IDS } from "../shared/mascot-bodies.js";
|
|
6
|
+
import { MAUS_COLOR_NAMES } from "../shared/wire.js";
|
|
7
|
+
const TEAM_LIBRARY_RAW_ROOT = "https://raw.githubusercontent.com/leomieo/limix-bot-teams/main";
|
|
8
|
+
const LIST_TTL_MS = 10 * 60 * 1000;
|
|
9
|
+
const MAX_CATALOG_BYTES = 256_000;
|
|
10
|
+
const MAX_BOT_BYTES = 64_000;
|
|
11
|
+
const CATEGORIES = ["everyday", "work", "writing", "design", "sales", "personal"];
|
|
12
|
+
const ID = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
13
|
+
const NOTE_PATH = /^(?:MEMORY\.md|memory\/[a-z0-9][a-z0-9-]{0,63}\.md)$/;
|
|
14
|
+
const TIME = /^(?:[01]\d|2[0-3]):[0-5]\d$/;
|
|
15
|
+
const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
16
|
+
function text(value, field, max, allowEmpty = false) {
|
|
17
|
+
if (typeof value !== "string" || (!allowEmpty && !value.trim()))
|
|
18
|
+
throw new Error(`${field} is required`);
|
|
19
|
+
const trimmed = value.trim();
|
|
20
|
+
if (trimmed.length > max)
|
|
21
|
+
throw new Error(`${field} is too long`);
|
|
22
|
+
return trimmed;
|
|
23
|
+
}
|
|
24
|
+
function look(value, field) {
|
|
25
|
+
if (!isRecord(value))
|
|
26
|
+
throw new Error(`${field} is invalid`);
|
|
27
|
+
const color = text(value.color, `${field}.color`, 20);
|
|
28
|
+
const mascotBody = text(value.mascotBody, `${field}.mascotBody`, 20);
|
|
29
|
+
if (!MAUS_COLOR_NAMES.includes(color))
|
|
30
|
+
throw new Error(`${field}.color is not a known colour`);
|
|
31
|
+
if (!MASCOT_BODY_IDS.includes(mascotBody))
|
|
32
|
+
throw new Error(`${field}.mascotBody is not a known shape`);
|
|
33
|
+
return { color, mascotBody };
|
|
34
|
+
}
|
|
35
|
+
function category(value, field) {
|
|
36
|
+
const found = CATEGORIES.find((entry) => entry === value);
|
|
37
|
+
if (!found)
|
|
38
|
+
throw new Error(`${field} is not a known category`);
|
|
39
|
+
return found;
|
|
40
|
+
}
|
|
41
|
+
function apps(value, field) {
|
|
42
|
+
if (value === undefined)
|
|
43
|
+
return [];
|
|
44
|
+
if (!Array.isArray(value) || value.length > 10)
|
|
45
|
+
throw new Error(`${field} is invalid`);
|
|
46
|
+
return value.map((app, index) => {
|
|
47
|
+
const slug = text(app, `${field}[${index}]`, 40);
|
|
48
|
+
if (!/^[a-z0-9][a-z0-9_-]*$/.test(slug))
|
|
49
|
+
throw new Error(`${field}[${index}] is invalid`);
|
|
50
|
+
return slug;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
export function parseBotListing(value) {
|
|
54
|
+
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
|
+
if (value.bots === undefined)
|
|
57
|
+
return [];
|
|
58
|
+
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
|
+
const seen = new Set();
|
|
61
|
+
const out = [];
|
|
62
|
+
value.bots.forEach((raw, index) => {
|
|
63
|
+
const field = `bots[${index}]`;
|
|
64
|
+
try {
|
|
65
|
+
if (!isRecord(raw))
|
|
66
|
+
throw new Error(`${field} is invalid`);
|
|
67
|
+
const id = text(raw.id, `${field}.id`, 64);
|
|
68
|
+
if (!ID.test(id) || seen.has(id))
|
|
69
|
+
throw new Error(`${field}.id is invalid`);
|
|
70
|
+
const routines = typeof raw.routines === "number" && Number.isSafeInteger(raw.routines) && raw.routines >= 0 && raw.routines <= 10 ? raw.routines : 0;
|
|
71
|
+
out.push({
|
|
72
|
+
id, name: text(raw.name, `${field}.name`, 40), title: text(raw.title, `${field}.title`, 80),
|
|
73
|
+
category: category(raw.category, `${field}.category`), description: text(raw.description, `${field}.description`, 300),
|
|
74
|
+
try: text(raw.try, `${field}.try`, 200), look: look(raw.look, `${field}.look`), apps: apps(raw.apps, `${field}.apps`), routines,
|
|
75
|
+
});
|
|
76
|
+
seen.add(id);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// One broken entry hides that entry only; the rest of the list still shows.
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
export function parseLibraryBot(value, expectedId) {
|
|
85
|
+
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
|
+
const id = text(value.id, "id", 64);
|
|
88
|
+
if (id !== expectedId)
|
|
89
|
+
throw new Error("Tệp bot mẫu không khớp với danh sách");
|
|
90
|
+
if (!Array.isArray(value.tryAsking) || value.tryAsking.length === 0 || value.tryAsking.length > 6)
|
|
91
|
+
throw new Error("tryAsking is invalid");
|
|
92
|
+
const bot = {
|
|
93
|
+
id, category: category(value.category, "category"), name: text(value.name, "name", 40), title: text(value.title, "title", 80),
|
|
94
|
+
description: text(value.description, "description", 300), soul: text(value.soul, "soul", 8_000), look: look(value.look, "look"),
|
|
95
|
+
tryAsking: value.tryAsking.map((line, index) => text(line, `tryAsking[${index}]`, 200)), apps: apps(value.apps, "apps"),
|
|
96
|
+
};
|
|
97
|
+
if (value.notes !== undefined) {
|
|
98
|
+
if (!isRecord(value.notes) || Object.keys(value.notes).length > 6)
|
|
99
|
+
throw new Error("notes is invalid");
|
|
100
|
+
bot.notes = {};
|
|
101
|
+
for (const [path, note] of Object.entries(value.notes)) {
|
|
102
|
+
if (!NOTE_PATH.test(path))
|
|
103
|
+
throw new Error(`notes path ${path} is not allowed`);
|
|
104
|
+
bot.notes[path] = text(note, `notes.${path}`, 8_000) + "\n";
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (value.routines !== undefined) {
|
|
108
|
+
if (!Array.isArray(value.routines) || value.routines.length > 5)
|
|
109
|
+
throw new Error("routines is invalid");
|
|
110
|
+
bot.routines = value.routines.map((raw, index) => {
|
|
111
|
+
const field = `routines[${index}]`;
|
|
112
|
+
if (!isRecord(raw) || !isRecord(raw.schedule) || raw.schedule.type !== "daily")
|
|
113
|
+
throw new Error(`${field} is invalid`);
|
|
114
|
+
const time = text(raw.schedule.time, `${field}.schedule.time`, 5);
|
|
115
|
+
if (!TIME.test(time))
|
|
116
|
+
throw new Error(`${field}.schedule.time is invalid`);
|
|
117
|
+
const weekdays = raw.schedule.weekdays;
|
|
118
|
+
if (!Array.isArray(weekdays) || weekdays.length === 0 || weekdays.length > 7 || weekdays.some((day) => !Number.isInteger(day) || day < 0 || day > 6)) {
|
|
119
|
+
throw new Error(`${field}.schedule.weekdays is invalid`);
|
|
120
|
+
}
|
|
121
|
+
// Paused, always: the person turns a routine on after reading it.
|
|
122
|
+
return { name: text(raw.name, `${field}.name`, 80), prompt: text(raw.prompt, `${field}.prompt`, 2_000),
|
|
123
|
+
schedule: { type: "daily", time, weekdays: [...new Set(weekdays)].sort() }, enabled: false, overlap: "skip" };
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return bot;
|
|
127
|
+
}
|
|
128
|
+
async function fetchJson(url, maxBytes) {
|
|
129
|
+
const response = await fetch(url, { headers: { accept: "application/json, text/plain;q=0.9" }, redirect: "error", signal: AbortSignal.timeout(10_000) });
|
|
130
|
+
if (!response.ok)
|
|
131
|
+
throw new Error(`GitHub trả lỗi HTTP ${response.status}`);
|
|
132
|
+
const raw = await response.text();
|
|
133
|
+
if (Buffer.byteLength(raw) > maxBytes)
|
|
134
|
+
throw new Error("Tệp mẫu quá lớn");
|
|
135
|
+
try {
|
|
136
|
+
return JSON.parse(raw);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
throw new Error("GitHub không trả về JSON hợp lệ");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
let cached = null;
|
|
143
|
+
/** `fresh`: the "Cập nhật" button. Skips this cache and GitHub's (raw files are cached there for about five minutes). */
|
|
144
|
+
async function listing(fresh = false) {
|
|
145
|
+
if (!fresh && cached && Date.now() - cached.at < LIST_TTL_MS)
|
|
146
|
+
return cached.bots;
|
|
147
|
+
try {
|
|
148
|
+
const bots = parseBotListing(await fetchJson(`${TEAM_LIBRARY_RAW_ROOT}/catalog.json${fresh ? `?t=${Date.now()}` : ""}`, MAX_CATALOG_BYTES));
|
|
149
|
+
cached = { at: Date.now(), bots };
|
|
150
|
+
return bots;
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
if (cached)
|
|
154
|
+
return cached.bots; // an older list beats none while GitHub is unreachable
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/** GET /api/limix/bots, POST /api/limix/bots/refresh and GET /api/limix/bots/<id>. Null for any other path. */
|
|
159
|
+
export async function handleLimixBotLibrary(method, path) {
|
|
160
|
+
try {
|
|
161
|
+
if (method === "POST" && path === "/api/limix/bots/refresh")
|
|
162
|
+
return { status: 200, body: { bots: await listing(true) } };
|
|
163
|
+
if (method !== "GET")
|
|
164
|
+
return null;
|
|
165
|
+
if (path === "/api/limix/bots")
|
|
166
|
+
return { status: 200, body: { bots: await listing() } };
|
|
167
|
+
const match = path.match(/^\/api\/limix\/bots\/([a-z0-9][a-z0-9-]{0,63})$/);
|
|
168
|
+
if (!match)
|
|
169
|
+
return null;
|
|
170
|
+
const id = match[1];
|
|
171
|
+
if (!(await listing()).some((bot) => bot.id === id))
|
|
172
|
+
return { status: 404, body: { error: "Không có bot mẫu này" } };
|
|
173
|
+
return { status: 200, body: { bot: parseLibraryBot(await fetchJson(`${TEAM_LIBRARY_RAW_ROOT}/bots/${id}.json?t=${Date.now()}`, MAX_BOT_BYTES), id) } };
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
return { status: 502, body: { error: error instanceof Error ? error.message : String(error) } };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -109,7 +109,8 @@ export function hubSkills() {
|
|
|
109
109
|
// A same-named skill the owner imported by hand is not the hub's: never touched by sync.
|
|
110
110
|
enabled: have?.reviewState === "approved",
|
|
111
111
|
updateAvailable: Boolean(have && have.checksum !== entry.checksum),
|
|
112
|
-
blocked:
|
|
112
|
+
blocked: !have && library[entry.id] ? "Thư viện đã có kỹ năng cùng tên không đến từ Hub." : null,
|
|
113
|
+
risky: hubSkillBlocked(entry),
|
|
113
114
|
};
|
|
114
115
|
});
|
|
115
116
|
}
|
|
@@ -120,7 +121,7 @@ export async function installHubSkill(id, options = {}) {
|
|
|
120
121
|
if (!entry)
|
|
121
122
|
return { error: "Không thấy kỹ năng này trong danh mục Hub. Hãy đồng bộ lại." };
|
|
122
123
|
const blocked = hubSkillBlocked(entry);
|
|
123
|
-
if (blocked)
|
|
124
|
+
if (blocked && !options.allowRisky)
|
|
124
125
|
return { error: blocked };
|
|
125
126
|
const installed = installedFromHub().get(id);
|
|
126
127
|
const existing = readSkillLibraryIndex()[id];
|
|
@@ -136,7 +137,7 @@ export async function installHubSkill(id, options = {}) {
|
|
|
136
137
|
return { error: `Danh mục thiếu mã kiểm tra cho ${path}` };
|
|
137
138
|
let response;
|
|
138
139
|
try {
|
|
139
|
-
response = await fetchText(rawUrl(
|
|
140
|
+
response = await fetchText(rawUrl(`${entry.area === "quarantine" ? "quarantine" : "skills"}/${id}/${path}`));
|
|
140
141
|
}
|
|
141
142
|
catch (cause) {
|
|
142
143
|
return { error: `Không tải được ${path}: ${cause instanceof Error ? cause.message : String(cause)}` };
|
|
@@ -180,9 +181,6 @@ export async function installHubSkill(id, options = {}) {
|
|
|
180
181
|
export function setHubSkillEnabled(id, enabled) {
|
|
181
182
|
if (!installedFromHub().has(id))
|
|
182
183
|
return { error: "Kỹ năng này chưa cài từ Hub" };
|
|
183
|
-
const entry = hubCatalog().find((e) => e.id === id);
|
|
184
|
-
if (enabled && entry && hubSkillBlocked(entry))
|
|
185
|
-
return { error: hubSkillBlocked(entry) };
|
|
186
184
|
const result = setLibrarySkillReviewState(id, enabled ? "approved" : "disabled");
|
|
187
185
|
return "error" in result ? { error: result.error } : { ok: true };
|
|
188
186
|
}
|
|
@@ -203,7 +201,7 @@ async function autoInstallEligible() {
|
|
|
203
201
|
for (const skill of hubSkills()) {
|
|
204
202
|
if (budget <= 0)
|
|
205
203
|
return;
|
|
206
|
-
const eligible = !skill.blocked && skill.kind === "instructions-only" && skill.approval_status === "approved" && (skill.grade === "excellent" || skill.grade === "recommended");
|
|
204
|
+
const eligible = !skill.blocked && !skill.risky && skill.kind === "instructions-only" && skill.approval_status === "approved" && (skill.grade === "excellent" || skill.grade === "recommended");
|
|
207
205
|
if (!eligible || (skill.installed && !skill.updateAvailable))
|
|
208
206
|
continue;
|
|
209
207
|
budget -= 1;
|
|
@@ -232,7 +230,7 @@ export async function handleLimixHub(method, path, body) {
|
|
|
232
230
|
}
|
|
233
231
|
const id = typeof record.id === "string" ? record.id : "";
|
|
234
232
|
if (method === "POST" && (path === "/api/limix/hub/install" || path === "/api/limix/hub/update")) {
|
|
235
|
-
const result = await installHubSkill(id, { allowTools: record.allowTools === true });
|
|
233
|
+
const result = await installHubSkill(id, { allowTools: record.allowTools === true, allowRisky: record.allowRisky === true });
|
|
236
234
|
return "error" in result ? { status: 422, body: result } : { status: 200, body: result };
|
|
237
235
|
}
|
|
238
236
|
if (method === "POST" && path === "/api/limix/hub/enable") {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { handleLimixBotLibrary } from "../limix-bot-library.js";
|
|
2
3
|
import { setImportedSkillEnabled } from "../imported-skills.js";
|
|
3
4
|
import { setNativeSkillEnabled } from "../native-skill-choices.js";
|
|
4
5
|
import { botSkillTemplateSchema } from "../new-bot-defaults.js";
|
|
@@ -14,6 +15,11 @@ export function createBotSkillsRoutes(deps) {
|
|
|
14
15
|
const { store, cfg, listBotSkills, readBotSkillFile, stagedSkillListing } = deps;
|
|
15
16
|
return async ({ req, res, url, path, method, json, readBody }) => {
|
|
16
17
|
let m = null;
|
|
18
|
+
if (path.startsWith("/api/limix/bots")) {
|
|
19
|
+
const library = await handleLimixBotLibrary(method, path);
|
|
20
|
+
if (library)
|
|
21
|
+
return json(res, library.status, library.body);
|
|
22
|
+
}
|
|
17
23
|
if (path.startsWith("/api/limix/hub")) {
|
|
18
24
|
const hub = await handleLimixHub(method, path, method === "POST" ? await readBody(req) : undefined);
|
|
19
25
|
if (hub)
|
|
@@ -159,7 +159,7 @@ export function createTeamRoutes(deps) {
|
|
|
159
159
|
}
|
|
160
160
|
if (method === "GET" && path === "/api/team-library/catalog") {
|
|
161
161
|
try {
|
|
162
|
-
return json(res, 200, await fetchTeamCatalog());
|
|
162
|
+
return json(res, 200, await fetchTeamCatalog(url.searchParams.get("fresh") ? ((input, init) => fetch(`${String(input)}?t=${Date.now()}`, init)) : fetch));
|
|
163
163
|
}
|
|
164
164
|
catch (error) {
|
|
165
165
|
return json(res, 502, { error: error instanceof Error ? error.message : "The team library is unavailable" });
|