volute 0.33.0 → 0.34.0
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/{accept-D5VBM7JW.js → accept-TW6V4WI4.js} +6 -6
- package/dist/{activity-events-XJO3P4RR.js → activity-events-BN7V6KCC.js} +4 -4
- package/dist/{ai-service-SBY2WG7O.js → ai-service-PSILB5WD.js} +5 -5
- package/dist/{api-client-YPKOZP2O.js → api-client-XUXOB7LI.js} +1 -1
- package/dist/api.d.ts +426 -3
- package/dist/{archive-INXYFVCW.js → archive-C2VEMQOR.js} +4 -4
- package/dist/{auth-GKCDSO4T.js → auth-ZFZXJZDQ.js} +5 -5
- package/dist/{bridge-TXWWPPOJ.js → bridge-O753D5F4.js} +6 -6
- package/dist/{chat-U5ZOME3O.js → chat-BHYX7DJ4.js} +9 -9
- package/dist/{chunk-M7UL5S3Q.js → chunk-2IOP6PHB.js} +1 -1
- package/dist/{chunk-NPKSDYA2.js → chunk-47XDEWWV.js} +5 -5
- package/dist/{chunk-RSX4OPZY.js → chunk-47ZPNLF4.js} +7 -7
- package/dist/{chunk-RPZZSXV3.js → chunk-4JSR7YO7.js} +20 -1
- package/dist/{chunk-N432I7QH.js → chunk-6OWJXUAR.js} +1 -1
- package/dist/{chunk-I5KY25PQ.js → chunk-6WAWMWR5.js} +1 -1
- package/dist/{chunk-NNB4WIG7.js → chunk-7F2SW2KD.js} +2 -2
- package/dist/chunk-7KJOFUNN.js +22 -0
- package/dist/{chunk-7J3HEVR7.js → chunk-B2BVAIZ4.js} +15 -9
- package/dist/{chunk-VH33ZWMW.js → chunk-BDK73LK6.js} +1 -1
- package/dist/{chunk-QTUVYI7W.js → chunk-BFWHBQK4.js} +1 -1
- package/dist/{chunk-JYVGHWEJ.js → chunk-BM474GX6.js} +3 -3
- package/dist/{chunk-LOEJ4HPQ.js → chunk-BTWAGDV5.js} +1 -1
- package/dist/{chunk-A2A4KLFE.js → chunk-CVL5IGIR.js} +596 -40
- package/dist/{chunk-RVGLDGMI.js → chunk-E5C7OWZ2.js} +20 -22
- package/dist/chunk-FYCALD4Q.js +23 -0
- package/dist/{chunk-SKLSMHXO.js → chunk-IS7WJ56Q.js} +1 -1
- package/dist/{chunk-2NGTS5UU.js → chunk-M3K5AARV.js} +1 -1
- package/dist/{chunk-ALEF47VT.js → chunk-MLOQKQNB.js} +1 -1
- package/dist/{chunk-C7I35G4R.js → chunk-N3DNFPVA.js} +41 -5
- package/dist/{chunk-LRCG2JLP.js → chunk-N7BLAHNE.js} +5 -1
- package/dist/{chunk-UKVWJRKN.js → chunk-PLDWHR4D.js} +1 -1
- package/dist/{chunk-3Z2DPESO.js → chunk-TAHX36HZ.js} +126 -81
- package/dist/{chunk-KIEPMIM5.js → chunk-U5BTYSAL.js} +1 -1
- package/dist/{chunk-GY5HBI7A.js → chunk-V45JXOWY.js} +2 -2
- package/dist/{chunk-JUKK7FPS.js → chunk-V6ZCNULL.js} +2 -2
- package/dist/{chunk-KVK2DLWI.js → chunk-XWXBJQBE.js} +2 -2
- package/dist/cli.js +23 -23
- package/dist/{clock-BVH3V6E3.js → clock-3X4DSC2N.js} +40 -25
- package/dist/{cloud-sync-4NWLMFVH.js → cloud-sync-TG3TIX5H.js} +21 -21
- package/dist/{config-H2H4UIF7.js → config-OROA5DUA.js} +4 -4
- package/dist/connectors/discord-bridge.js +1 -1
- package/dist/connectors/slack-bridge.js +1 -1
- package/dist/connectors/telegram-bridge.js +1 -1
- package/dist/{conversations-AWI5SZW2.js → conversations-HL2JP5GI.js} +5 -5
- package/dist/{create-YWD2TIP4.js → create-3SEKKI6P.js} +6 -6
- package/dist/{create-2FK7Z46Y.js → create-UOSOQ2HN.js} +4 -4
- package/dist/daemon-client-WOAQXXBM.js +12 -0
- package/dist/{daemon-restart-GOBUKLX7.js → daemon-restart-5ABHNXJZ.js} +9 -9
- package/dist/daemon.js +1747 -688
- package/dist/{db-RA45JBFG.js → db-PLEDCBHZ.js} +1 -1
- package/dist/db-RYX3SS2W.js +9 -0
- package/dist/{delete-QTGWEDBI.js → delete-KYOVWR23.js} +3 -3
- package/dist/delivery-manager-2BR5NZKF.js +32 -0
- package/dist/{delivery-router-FL45JL7N.js → delivery-router-D5ELDMS2.js} +4 -4
- package/dist/down-QVFN4UPK.js +15 -0
- package/dist/{env-JCOF2222.js → env-R34DT7XL.js} +12 -8
- package/dist/exec-DVLXKRIO.js +17 -0
- package/dist/{export-SUYRLI5Q.js → export-6ZXAXATG.js} +6 -6
- package/dist/extension-PM42QCID.js +97 -0
- package/dist/extensions-BBGVL5JC.js +38 -0
- package/dist/{files-65PMW5IK.js → files-VQV2VZQO.js} +7 -7
- package/dist/{import-DDUFE7AY.js → import-MK2I2T6F.js} +5 -5
- package/dist/{isolation-LLAYQYDY.js → isolation-62MKDZN3.js} +4 -4
- package/dist/{join-I5QEE3LG.js → join-DGYHTJUH.js} +3 -3
- package/dist/lib-DYEZMGW7.js +6588 -0
- package/dist/{list-JQ463EDA.js → list-C644WTHV.js} +18 -10
- package/dist/{login-D7ETSU4R.js → login-IIGEQPHL.js} +6 -6
- package/dist/{login-RIJF2F4G.js → login-KZQLMAWE.js} +4 -4
- package/dist/{logout-5MLHZALK.js → logout-AGTZVRGP.js} +4 -4
- package/dist/{logout-UZJRGY4Z.js → logout-KD6GXIJJ.js} +4 -4
- package/dist/message-delivery-V3R6NXJP.js +42 -0
- package/dist/{mind-IOJFLEM5.js → mind-BI4EPBVZ.js} +19 -19
- package/dist/{mind-activity-tracker-F6O4Q2SL.js → mind-activity-tracker-2ACNHA7B.js} +5 -5
- package/dist/mind-history-WOYFLQAI.js +264 -0
- package/dist/{mind-list-WUPMQDYQ.js → mind-list-6VPM7GUQ.js} +4 -4
- package/dist/mind-manager-MWW3BTS4.js +32 -0
- package/dist/{mind-profile-P67FEHOY.js → mind-profile-WPG42U5Y.js} +2 -2
- package/dist/mind-service-VIKZJK2M.js +38 -0
- package/dist/{mind-sleep-WW2IX7JT.js → mind-sleep-XDISJY74.js} +6 -6
- package/dist/{mind-status-L3EFFRPR.js → mind-status-7FTZWPZF.js} +4 -4
- package/dist/{mind-wake-VSSGW465.js → mind-wake-KIIKEI3A.js} +6 -6
- package/dist/{package-U3VFO273.js → package-V2WHWVG6.js} +8 -5
- package/dist/{read-EBY56C33.js → read-H5C26YO7.js} +20 -10
- package/dist/{read-stdin-HQJ7774D.js → read-stdin-PIRM6A2Y.js} +1 -1
- package/dist/{register-HD74C4TT.js → register-J27WP33N.js} +6 -6
- package/dist/{registry-PJ4S5PHQ.js → registry-UYV5S6QT.js} +3 -3
- package/dist/{reject-UJKFBHRO.js → reject-OEANJYIA.js} +6 -6
- package/dist/{restart-3UCMRUVC.js → restart-V5EGYBJG.js} +4 -4
- package/dist/{sandbox-GJOK4QLQ.js → sandbox-SI5HMBP3.js} +5 -5
- package/dist/scheduler-AGG3L2FO.js +32 -0
- package/dist/{schema-PA3M5ZKH.js → schema-ETMABTW4.js} +4 -2
- package/dist/{seed-QDYVLG74.js → seed-WNGI6PNW.js} +2 -2
- package/dist/{seed-check-S2IX25RL.js → seed-check-PXTH7YXS.js} +2 -2
- package/dist/{seed-cmd-DKOUFEAU.js → seed-cmd-VENFTGS3.js} +4 -4
- package/dist/{seed-create-4XBBOLRH.js → seed-create-663ALOKH.js} +6 -6
- package/dist/{seed-sprout-GQEIIQRT.js → seed-sprout-EH3AGKAI.js} +12 -12
- package/dist/{send-QIV2INHB.js → send-7FUUUZZH.js} +23 -10
- package/dist/{setup-TISPCO22.js → setup-GGMKENLN.js} +4 -4
- package/dist/{setup-XMCBE3LF.js → setup-Z3DEVWV7.js} +11 -11
- package/dist/{skill-PSQGRRJX.js → skill-DKNYJS4P.js} +14 -10
- package/dist/skills/plan-coordinator/SKILL.md +60 -0
- package/dist/skills/volute-mind/SKILL.md +7 -221
- package/dist/skills/volute-mind/references/extensions.md +37 -0
- package/dist/skills/volute-mind/references/integrations.md +48 -0
- package/dist/skills/volute-mind/references/routing.md +86 -0
- package/dist/skills/volute-mind/references/sleep.md +33 -0
- package/dist/skills/volute-mind/references/variants.md +31 -0
- package/dist/{skills-7FV7EJTE.js → skills-Q6VZ2UGD.js} +11 -7
- package/dist/sleep-manager-BJK2ROPX.js +36 -0
- package/dist/spirit-4JP4TY4C.js +23 -0
- package/dist/{split-STOROBYJ.js → split-3YPMS2CL.js} +3 -3
- package/dist/{sprout-WKLZXUIQ.js → sprout-E3HJIV2Z.js} +2 -2
- package/dist/{start-K2NCUUCG.js → start-W3TPKX4D.js} +4 -4
- package/dist/{status-3JBTFSMI.js → status-4OVFXFEJ.js} +7 -7
- package/dist/{stop-H26JZDXF.js → stop-GTT6YWYO.js} +4 -4
- package/dist/system-channel-DXD2JBOU.js +36 -0
- package/dist/system-chat-TYLOL7SX.js +36 -0
- package/dist/{systems-XRI52VCH.js → systems-AYLO727G.js} +7 -7
- package/dist/{tailscale-XHQBZROW.js → tailscale-ZEUK7GKZ.js} +3 -3
- package/dist/{template-hash-A6VVKOXJ.js → template-hash-EJRTKE36.js} +1 -1
- package/dist/up-PA7F2CXE.js +18 -0
- package/dist/{update-UD543CXX.js → update-HG4LCUSG.js} +7 -7
- package/dist/{update-check-ZD6OOIYQ.js → update-check-X3YG4WVP.js} +4 -4
- package/dist/{upgrade-O4Q7WJM3.js → upgrade-YGNIDICG.js} +3 -3
- package/dist/{variant-7TGZHOU3.js → variant-MZUMRTQO.js} +1 -1
- package/dist/{version-notify-NBI2MTJO.js → version-notify-YCH4UVQ2.js} +19 -19
- package/dist/{volute-config-HD7WWUQC.js → volute-config-WBKYJGYQ.js} +1 -1
- package/dist/web-assets/assets/index-DiiwC-CZ.css +1 -0
- package/dist/web-assets/assets/index-d6y5b9Ij.js +75 -0
- package/dist/web-assets/ext-theme.css +48 -9
- package/dist/web-assets/index.html +2 -2
- package/drizzle/0005_meta_summaries.sql +15 -0
- package/drizzle/meta/0005_snapshot.json +7 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +7 -4
- package/packages/extensions/plan/dist/ui/assets/index-CJj2gZnZ.css +1 -0
- package/packages/extensions/plan/dist/ui/assets/index-FMEJmvQz.js +61 -0
- package/packages/extensions/plan/dist/ui/index.html +14 -0
- package/packages/extensions/plan/skills/plan/SKILL.md +43 -0
- package/packages/extensions/plan/skills/plan/scripts/plan-hook.sh +37 -0
- package/templates/_base/home/VOLUTE.md +12 -19
- package/templates/_base/src/lib/context-breakdown.ts +450 -0
- package/templates/_base/src/lib/format-prefix.ts +17 -0
- package/templates/_base/src/lib/hook-loader.ts +8 -2
- package/templates/_base/src/lib/router.ts +75 -33
- package/templates/_base/src/lib/routing.ts +4 -1
- package/templates/_base/src/lib/startup.ts +16 -8
- package/templates/_base/src/lib/types.ts +2 -1
- package/templates/_base/src/lib/volute-server.ts +69 -8
- package/templates/claude/.init/CLAUDE.md +4 -10
- package/templates/claude/package.json.tmpl +1 -0
- package/templates/claude/src/agent.ts +100 -32
- package/templates/claude/src/lib/hooks/reply-instructions.ts +27 -7
- package/templates/claude/src/lib/stream-consumer.ts +2 -2
- package/templates/claude/src/server.ts +1 -0
- package/templates/codex/package.json.tmpl +1 -0
- package/templates/codex/src/agent.ts +80 -8
- package/templates/codex/src/server.ts +1 -4
- package/templates/pi/package.json.tmpl +1 -0
- package/templates/pi/src/agent.ts +115 -36
- package/templates/pi/src/lib/event-handler.ts +22 -7
- package/templates/pi/src/lib/reply-instructions-extension.ts +23 -4
- package/templates/pi/src/lib/subagents.ts +20 -17
- package/templates/pi/src/server.ts +2 -5
- package/dist/chunk-K3NQKI34.js +0 -10
- package/dist/daemon-client-6QXHZ7US.js +0 -12
- package/dist/db-F34YLV7D.js +0 -9
- package/dist/delivery-manager-PFAKEJTC.js +0 -32
- package/dist/down-FWWTEKXM.js +0 -15
- package/dist/extension-OBTGKQQD.js +0 -175
- package/dist/extensions-KYNTVTMO.js +0 -30
- package/dist/history-DKCDI3JO.js +0 -128
- package/dist/message-delivery-DFF5SJRM.js +0 -42
- package/dist/mind-manager-NBJF5D26.js +0 -32
- package/dist/mind-service-2MQ6UK5N.js +0 -38
- package/dist/scheduler-ZZ7XGQG6.js +0 -32
- package/dist/sleep-manager-JTXSN7NV.js +0 -36
- package/dist/spirit-VRONKFMF.js +0 -23
- package/dist/system-chat-JAPOJ3KE.js +0 -36
- package/dist/up-M5AS6SBV.js +0 -18
- package/dist/web-assets/assets/index-CWJrVveV.css +0 -1
- package/dist/web-assets/assets/index-DJt14FRI.js +0 -75
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
hashSkillDir,
|
|
4
|
+
importSkillFromDir,
|
|
5
|
+
removeSharedSkill,
|
|
6
|
+
sharedSkillsDir
|
|
7
|
+
} from "./chunk-N3DNFPVA.js";
|
|
2
8
|
import {
|
|
3
9
|
getAllSites,
|
|
4
10
|
getPublishedPages,
|
|
@@ -9,27 +15,26 @@ import {
|
|
|
9
15
|
import {
|
|
10
16
|
getUser,
|
|
11
17
|
getUserByUsername
|
|
12
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-BM474GX6.js";
|
|
13
19
|
import {
|
|
14
20
|
publish
|
|
15
|
-
} from "./chunk-
|
|
16
|
-
import {
|
|
17
|
-
hashSkillDir,
|
|
18
|
-
importSkillFromDir,
|
|
19
|
-
sharedSkillsDir
|
|
20
|
-
} from "./chunk-C7I35G4R.js";
|
|
21
|
+
} from "./chunk-XWXBJQBE.js";
|
|
21
22
|
import {
|
|
22
23
|
logger_default
|
|
23
24
|
} from "./chunk-YUIHSKR6.js";
|
|
25
|
+
import {
|
|
26
|
+
readGlobalConfig,
|
|
27
|
+
writeGlobalConfig
|
|
28
|
+
} from "./chunk-6OWJXUAR.js";
|
|
24
29
|
import {
|
|
25
30
|
mindDir,
|
|
26
31
|
voluteHome,
|
|
27
32
|
voluteSystemDir
|
|
28
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-N7BLAHNE.js";
|
|
29
34
|
|
|
30
35
|
// src/lib/extensions.ts
|
|
31
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
32
|
-
import { dirname, resolve as
|
|
36
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
37
|
+
import { dirname, resolve as resolve7 } from "path";
|
|
33
38
|
|
|
34
39
|
// packages/extensions/notes/src/index.ts
|
|
35
40
|
import { resolve } from "path";
|
|
@@ -745,7 +750,7 @@ Warning: remote publish failed: ${err.message}`;
|
|
|
745
750
|
const allFlag = args.includes("--all");
|
|
746
751
|
const port = process.env.VOLUTE_DAEMON_PORT || "1618";
|
|
747
752
|
if (allFlag) {
|
|
748
|
-
const { getAllSites: getAllSites2 } = await import("./db-
|
|
753
|
+
const { getAllSites: getAllSites2 } = await import("./db-PLEDCBHZ.js");
|
|
749
754
|
const sites = getAllSites2(db);
|
|
750
755
|
const lines2 = [];
|
|
751
756
|
for (const site of sites) {
|
|
@@ -942,7 +947,7 @@ function createRoutes2(ctx) {
|
|
|
942
947
|
var _voluteHome = null;
|
|
943
948
|
async function getVoluteHome() {
|
|
944
949
|
if (_voluteHome) return _voluteHome();
|
|
945
|
-
const mod = await import("./registry-
|
|
950
|
+
const mod = await import("./registry-UYV5S6QT.js");
|
|
946
951
|
_voluteHome = mod.voluteHome;
|
|
947
952
|
return _voluteHome();
|
|
948
953
|
}
|
|
@@ -1021,12 +1026,438 @@ var src_default2 = createExtension({
|
|
|
1021
1026
|
}
|
|
1022
1027
|
});
|
|
1023
1028
|
|
|
1029
|
+
// packages/extensions/plan/src/index.ts
|
|
1030
|
+
import { resolve as resolve5 } from "path";
|
|
1031
|
+
|
|
1032
|
+
// packages/extensions/plan/src/plans.ts
|
|
1033
|
+
async function startPlan(db, getUser2, userId, title, description) {
|
|
1034
|
+
db.prepare(
|
|
1035
|
+
"UPDATE plans SET status = 'archived', completed_at = datetime('now') WHERE status = 'active'"
|
|
1036
|
+
).run();
|
|
1037
|
+
const row = db.prepare(
|
|
1038
|
+
`INSERT INTO plans (title, description, set_by)
|
|
1039
|
+
VALUES (?, ?, ?)
|
|
1040
|
+
RETURNING *`
|
|
1041
|
+
).get(title, description, userId);
|
|
1042
|
+
const user = await getUser2(userId);
|
|
1043
|
+
return {
|
|
1044
|
+
...row,
|
|
1045
|
+
set_by_username: user?.username ?? "unknown",
|
|
1046
|
+
set_by_display_name: user?.display_name ?? null
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
async function getActivePlan(db, getUser2) {
|
|
1050
|
+
const row = db.prepare("SELECT * FROM plans WHERE status = 'active' LIMIT 1").get();
|
|
1051
|
+
if (!row) return null;
|
|
1052
|
+
const user = await getUser2(row.set_by);
|
|
1053
|
+
const logs = db.prepare("SELECT * FROM plan_logs WHERE plan_id = ? ORDER BY created_at DESC LIMIT 20").all(row.id);
|
|
1054
|
+
const messages = db.prepare("SELECT * FROM plan_messages WHERE plan_id = ? ORDER BY id DESC LIMIT 10").all(row.id);
|
|
1055
|
+
const latestMessage = messages.length > 0 ? messages[0].content : null;
|
|
1056
|
+
return {
|
|
1057
|
+
...row,
|
|
1058
|
+
set_by_username: user?.username ?? "unknown",
|
|
1059
|
+
set_by_display_name: user?.display_name ?? null,
|
|
1060
|
+
logs,
|
|
1061
|
+
messages,
|
|
1062
|
+
latestMessage
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
function logProgress(db, planId, mindName, content) {
|
|
1066
|
+
return db.prepare(
|
|
1067
|
+
`INSERT INTO plan_logs (plan_id, mind_name, content)
|
|
1068
|
+
VALUES (?, ?, ?)
|
|
1069
|
+
RETURNING *`
|
|
1070
|
+
).get(planId, mindName, content);
|
|
1071
|
+
}
|
|
1072
|
+
function addPlanMessage(db, planId, content) {
|
|
1073
|
+
return db.prepare(
|
|
1074
|
+
`INSERT INTO plan_messages (plan_id, content)
|
|
1075
|
+
VALUES (?, ?)
|
|
1076
|
+
RETURNING *`
|
|
1077
|
+
).get(planId, content);
|
|
1078
|
+
}
|
|
1079
|
+
function finishPlan(db, planId, message) {
|
|
1080
|
+
const result = db.prepare(
|
|
1081
|
+
"UPDATE plans SET status = 'completed', completed_at = datetime('now'), finish_message = ? WHERE id = ? AND status = 'active'"
|
|
1082
|
+
).run(message ?? null, planId);
|
|
1083
|
+
return result.changes > 0;
|
|
1084
|
+
}
|
|
1085
|
+
async function listPlans(db, getUser2, opts) {
|
|
1086
|
+
const limit = opts?.limit ?? 20;
|
|
1087
|
+
const offset = opts?.offset ?? 0;
|
|
1088
|
+
const rows = opts?.status ? db.prepare("SELECT * FROM plans WHERE status = ? ORDER BY created_at DESC LIMIT ? OFFSET ?").all(opts.status, limit, offset) : db.prepare("SELECT * FROM plans ORDER BY created_at DESC LIMIT ? OFFSET ?").all(limit, offset);
|
|
1089
|
+
const userCache = /* @__PURE__ */ new Map();
|
|
1090
|
+
const result = [];
|
|
1091
|
+
for (const row of rows) {
|
|
1092
|
+
if (!userCache.has(row.set_by)) {
|
|
1093
|
+
const u = await getUser2(row.set_by);
|
|
1094
|
+
userCache.set(row.set_by, {
|
|
1095
|
+
username: u?.username ?? "unknown",
|
|
1096
|
+
display_name: u?.display_name ?? null
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
const userInfo = userCache.get(row.set_by);
|
|
1100
|
+
result.push({
|
|
1101
|
+
...row,
|
|
1102
|
+
set_by_username: userInfo.username,
|
|
1103
|
+
set_by_display_name: userInfo.display_name
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
return result;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// packages/extensions/plan/src/commands.ts
|
|
1110
|
+
function getFlag2(args, flag) {
|
|
1111
|
+
const idx = args.indexOf(flag);
|
|
1112
|
+
if (idx !== -1 && args[idx + 1]) return args[idx + 1];
|
|
1113
|
+
return void 0;
|
|
1114
|
+
}
|
|
1115
|
+
var _announce = null;
|
|
1116
|
+
async function announceToSystem(text) {
|
|
1117
|
+
if (!_announce) {
|
|
1118
|
+
try {
|
|
1119
|
+
const mod = await import("./system-channel-DXD2JBOU.js");
|
|
1120
|
+
_announce = mod.announceToSystem;
|
|
1121
|
+
} catch {
|
|
1122
|
+
return false;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
try {
|
|
1126
|
+
await _announce(text);
|
|
1127
|
+
return true;
|
|
1128
|
+
} catch (err) {
|
|
1129
|
+
console.error("[plan] Failed to announce to system channel:", err);
|
|
1130
|
+
return false;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
function createCommands3() {
|
|
1134
|
+
return {
|
|
1135
|
+
start: {
|
|
1136
|
+
description: "Start a new system plan",
|
|
1137
|
+
usage: 'volute plan start "title" "description" (description can be piped via stdin)',
|
|
1138
|
+
handler: async (args, ctx) => {
|
|
1139
|
+
if (!ctx.db) return { error: "Plan extension requires a database" };
|
|
1140
|
+
const mindName = ctx.mindName;
|
|
1141
|
+
if (!mindName) return { error: "No mind specified (use --mind or VOLUTE_MIND)" };
|
|
1142
|
+
const user = await ctx.getUserByUsername(mindName);
|
|
1143
|
+
if (!user) return { error: `Unknown mind: ${mindName}` };
|
|
1144
|
+
const title = args[0];
|
|
1145
|
+
const description = args[1] ?? ctx.stdin ?? "";
|
|
1146
|
+
if (!title) return { error: 'Usage: volute plan start "title" "description"' };
|
|
1147
|
+
const plan = await startPlan(ctx.db, ctx.getUser, user.id, title, description);
|
|
1148
|
+
ctx.publishActivity({
|
|
1149
|
+
type: "plan_started",
|
|
1150
|
+
mind: user.username,
|
|
1151
|
+
summary: `${user.username} started plan: "${title}"`,
|
|
1152
|
+
metadata: { planId: plan.id, title }
|
|
1153
|
+
});
|
|
1154
|
+
return { output: `Plan started: ${plan.title}` };
|
|
1155
|
+
}
|
|
1156
|
+
},
|
|
1157
|
+
message: {
|
|
1158
|
+
description: "Post a message about the current plan (sent to #system)",
|
|
1159
|
+
usage: `volute plan message "today's focus: ..." (content can be piped via stdin)`,
|
|
1160
|
+
handler: async (args, ctx) => {
|
|
1161
|
+
if (!ctx.db) return { error: "Plan extension requires a database" };
|
|
1162
|
+
const mindName = ctx.mindName;
|
|
1163
|
+
if (!mindName) return { error: "No mind specified (use --mind or VOLUTE_MIND)" };
|
|
1164
|
+
const content = args[0] ?? ctx.stdin;
|
|
1165
|
+
if (!content) return { error: 'Usage: volute plan message "your message"' };
|
|
1166
|
+
const plan = await getActivePlan(ctx.db, ctx.getUser);
|
|
1167
|
+
if (!plan) return { error: "No active plan" };
|
|
1168
|
+
const msg = addPlanMessage(ctx.db, plan.id, content);
|
|
1169
|
+
ctx.publishActivity({
|
|
1170
|
+
type: "plan_message",
|
|
1171
|
+
mind: mindName,
|
|
1172
|
+
summary: `Plan message: "${content.slice(0, 100)}"`,
|
|
1173
|
+
metadata: { planId: plan.id, messageId: msg.id }
|
|
1174
|
+
});
|
|
1175
|
+
const announced = await announceToSystem(`[Plan: ${plan.title}] ${content}`);
|
|
1176
|
+
return {
|
|
1177
|
+
output: announced ? "Message posted to #system." : "Message logged (system channel unavailable)."
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
},
|
|
1181
|
+
log: {
|
|
1182
|
+
description: "Log progress on the current plan",
|
|
1183
|
+
usage: 'volute plan log "progress update" (content can be piped via stdin)',
|
|
1184
|
+
handler: async (args, ctx) => {
|
|
1185
|
+
if (!ctx.db) return { error: "Plan extension requires a database" };
|
|
1186
|
+
const mindName = ctx.mindName;
|
|
1187
|
+
if (!mindName) return { error: "No mind specified (use --mind or VOLUTE_MIND)" };
|
|
1188
|
+
const content = args[0] ?? ctx.stdin;
|
|
1189
|
+
if (!content) return { error: 'Usage: volute plan log "progress update"' };
|
|
1190
|
+
const plan = await getActivePlan(ctx.db, ctx.getUser);
|
|
1191
|
+
if (!plan) return { error: "No active plan" };
|
|
1192
|
+
const log = logProgress(ctx.db, plan.id, mindName, content);
|
|
1193
|
+
ctx.publishActivity({
|
|
1194
|
+
type: "plan_progress",
|
|
1195
|
+
mind: mindName,
|
|
1196
|
+
summary: `${mindName} logged progress: "${content.slice(0, 100)}"`,
|
|
1197
|
+
metadata: { planId: plan.id, logId: log.id }
|
|
1198
|
+
});
|
|
1199
|
+
return { output: "Progress logged." };
|
|
1200
|
+
}
|
|
1201
|
+
},
|
|
1202
|
+
current: {
|
|
1203
|
+
description: "Show the current active plan",
|
|
1204
|
+
usage: "volute plan current",
|
|
1205
|
+
handler: async (_args, ctx) => {
|
|
1206
|
+
if (!ctx.db) return { error: "Plan extension requires a database" };
|
|
1207
|
+
const plan = await getActivePlan(ctx.db, ctx.getUser);
|
|
1208
|
+
if (!plan) return { output: "No active plan." };
|
|
1209
|
+
const lines = [
|
|
1210
|
+
`# ${plan.title}`,
|
|
1211
|
+
"",
|
|
1212
|
+
`Set by ${plan.set_by_username} \u2014 ${new Date(plan.created_at).toLocaleString()}`
|
|
1213
|
+
];
|
|
1214
|
+
if (plan.description) {
|
|
1215
|
+
lines.push("", plan.description);
|
|
1216
|
+
}
|
|
1217
|
+
if (plan.latestMessage) {
|
|
1218
|
+
lines.push("", `## Latest message`, "", plan.latestMessage);
|
|
1219
|
+
}
|
|
1220
|
+
if (plan.logs.length > 0) {
|
|
1221
|
+
lines.push("", "## Progress");
|
|
1222
|
+
for (const log of plan.logs) {
|
|
1223
|
+
const date = new Date(log.created_at).toLocaleString();
|
|
1224
|
+
lines.push(` ${log.mind_name} (${date}): ${log.content}`);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
return { output: lines.join("\n") };
|
|
1228
|
+
}
|
|
1229
|
+
},
|
|
1230
|
+
history: {
|
|
1231
|
+
description: "List past plans",
|
|
1232
|
+
usage: "volute plan history [--limit N]",
|
|
1233
|
+
handler: async (args, ctx) => {
|
|
1234
|
+
if (!ctx.db) return { error: "Plan extension requires a database" };
|
|
1235
|
+
const limit = parseInt(getFlag2(args, "--limit") ?? "10", 10);
|
|
1236
|
+
const plans = await listPlans(ctx.db, ctx.getUser, { limit });
|
|
1237
|
+
if (plans.length === 0) return { output: "No plans found." };
|
|
1238
|
+
const lines = plans.map((p) => {
|
|
1239
|
+
const date = new Date(p.created_at).toLocaleDateString();
|
|
1240
|
+
const status = p.status === "active" ? " [active]" : "";
|
|
1241
|
+
return ` ${p.title} (${date}, by ${p.set_by_username})${status}`;
|
|
1242
|
+
});
|
|
1243
|
+
return { output: lines.join("\n") };
|
|
1244
|
+
}
|
|
1245
|
+
},
|
|
1246
|
+
finish: {
|
|
1247
|
+
description: "Finish the current plan with a closing message",
|
|
1248
|
+
usage: 'volute plan finish "closing message" (message can be piped via stdin)',
|
|
1249
|
+
handler: async (args, ctx) => {
|
|
1250
|
+
if (!ctx.db) return { error: "Plan extension requires a database" };
|
|
1251
|
+
const mindName = ctx.mindName;
|
|
1252
|
+
if (!mindName) return { error: "No mind specified (use --mind or VOLUTE_MIND)" };
|
|
1253
|
+
const plan = await getActivePlan(ctx.db, ctx.getUser);
|
|
1254
|
+
if (!plan) return { error: "No active plan" };
|
|
1255
|
+
const message = args[0] ?? ctx.stdin ?? "";
|
|
1256
|
+
finishPlan(ctx.db, plan.id, message);
|
|
1257
|
+
ctx.publishActivity({
|
|
1258
|
+
type: "plan_finished",
|
|
1259
|
+
mind: mindName,
|
|
1260
|
+
summary: `${mindName} finished plan: "${plan.title}"`,
|
|
1261
|
+
metadata: { planId: plan.id }
|
|
1262
|
+
});
|
|
1263
|
+
const announcement = message ? `[Plan finished: ${plan.title}] ${message}` : `[Plan finished: ${plan.title}]`;
|
|
1264
|
+
const announced = await announceToSystem(announcement);
|
|
1265
|
+
const suffix = announced ? "" : " (system channel unavailable)";
|
|
1266
|
+
return { output: `Finished: ${plan.title}${suffix}` };
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
// packages/extensions/plan/src/db.ts
|
|
1273
|
+
function initDb3(db) {
|
|
1274
|
+
db.exec(`
|
|
1275
|
+
CREATE TABLE IF NOT EXISTS plans (
|
|
1276
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1277
|
+
title TEXT NOT NULL,
|
|
1278
|
+
description TEXT NOT NULL DEFAULT '',
|
|
1279
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
1280
|
+
set_by INTEGER NOT NULL,
|
|
1281
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1282
|
+
completed_at TEXT,
|
|
1283
|
+
finish_message TEXT
|
|
1284
|
+
);
|
|
1285
|
+
CREATE INDEX IF NOT EXISTS idx_plans_status ON plans(status);
|
|
1286
|
+
CREATE INDEX IF NOT EXISTS idx_plans_created_at ON plans(created_at);
|
|
1287
|
+
|
|
1288
|
+
CREATE TABLE IF NOT EXISTS plan_logs (
|
|
1289
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1290
|
+
plan_id INTEGER NOT NULL REFERENCES plans(id) ON DELETE CASCADE,
|
|
1291
|
+
mind_name TEXT NOT NULL,
|
|
1292
|
+
content TEXT NOT NULL,
|
|
1293
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1294
|
+
);
|
|
1295
|
+
CREATE INDEX IF NOT EXISTS idx_plan_logs_plan_id ON plan_logs(plan_id);
|
|
1296
|
+
|
|
1297
|
+
CREATE TABLE IF NOT EXISTS plan_messages (
|
|
1298
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1299
|
+
plan_id INTEGER NOT NULL REFERENCES plans(id) ON DELETE CASCADE,
|
|
1300
|
+
content TEXT NOT NULL,
|
|
1301
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1302
|
+
);
|
|
1303
|
+
CREATE INDEX IF NOT EXISTS idx_plan_messages_plan_id ON plan_messages(plan_id);
|
|
1304
|
+
`);
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
// packages/extensions/plan/src/routes.ts
|
|
1308
|
+
import { Hono as Hono3 } from "hono";
|
|
1309
|
+
function resolveUserId2(c) {
|
|
1310
|
+
const user = c.get("user");
|
|
1311
|
+
if (!user || user.id === 0) return null;
|
|
1312
|
+
return user;
|
|
1313
|
+
}
|
|
1314
|
+
async function parseJson2(c) {
|
|
1315
|
+
try {
|
|
1316
|
+
return await c.req.json();
|
|
1317
|
+
} catch {
|
|
1318
|
+
return null;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
function createRoutes3(ctx) {
|
|
1322
|
+
if (!ctx.db) throw new Error("Plan extension requires a database");
|
|
1323
|
+
const db = ctx.db;
|
|
1324
|
+
const { getUser: getUser2 } = ctx;
|
|
1325
|
+
const app = new Hono3().get("/current", async (c) => {
|
|
1326
|
+
const plan = await getActivePlan(db, getUser2);
|
|
1327
|
+
if (!plan) return c.json(null);
|
|
1328
|
+
return c.json(plan);
|
|
1329
|
+
}).get("/", async (c) => {
|
|
1330
|
+
const status = c.req.query("status");
|
|
1331
|
+
const rawLimit = c.req.query("limit");
|
|
1332
|
+
const rawOffset = c.req.query("offset");
|
|
1333
|
+
const limit = rawLimit ? parseInt(rawLimit, 10) : void 0;
|
|
1334
|
+
const offset = rawOffset ? parseInt(rawOffset, 10) : void 0;
|
|
1335
|
+
if (limit !== void 0 && Number.isNaN(limit) || offset !== void 0 && Number.isNaN(offset)) {
|
|
1336
|
+
return c.json({ error: "Invalid limit or offset parameter" }, 400);
|
|
1337
|
+
}
|
|
1338
|
+
const plans = await listPlans(db, getUser2, { status: status ?? void 0, limit, offset });
|
|
1339
|
+
return c.json(plans);
|
|
1340
|
+
}).post("/", async (c) => {
|
|
1341
|
+
const actor = resolveUserId2(c);
|
|
1342
|
+
if (!actor) return c.json({ error: "Unauthorized" }, 401);
|
|
1343
|
+
if (actor.role !== "admin" && actor.user_type !== "mind") {
|
|
1344
|
+
return c.json({ error: "Only spirit or admin can start plans" }, 403);
|
|
1345
|
+
}
|
|
1346
|
+
const body = await parseJson2(c);
|
|
1347
|
+
if (!body) return c.json({ error: "Invalid JSON body" }, 400);
|
|
1348
|
+
if (!body.title) return c.json({ error: "title is required" }, 400);
|
|
1349
|
+
const plan = await startPlan(db, getUser2, actor.id, body.title, body.description ?? "");
|
|
1350
|
+
ctx.publishActivity({
|
|
1351
|
+
type: "plan_started",
|
|
1352
|
+
mind: actor.username,
|
|
1353
|
+
summary: `${actor.username} started plan: "${body.title}"`,
|
|
1354
|
+
metadata: { planId: plan.id, title: body.title }
|
|
1355
|
+
});
|
|
1356
|
+
return c.json(plan, 201);
|
|
1357
|
+
}).post("/:id{[0-9]+}/message", async (c) => {
|
|
1358
|
+
const actor = resolveUserId2(c);
|
|
1359
|
+
if (!actor) return c.json({ error: "Unauthorized" }, 401);
|
|
1360
|
+
const planId = parseInt(c.req.param("id"), 10);
|
|
1361
|
+
if (Number.isNaN(planId)) return c.json({ error: "Invalid plan ID" }, 400);
|
|
1362
|
+
const plan = db.prepare("SELECT id FROM plans WHERE id = ? AND status = 'active'").get(planId);
|
|
1363
|
+
if (!plan) return c.json({ error: "Active plan not found" }, 404);
|
|
1364
|
+
const body = await parseJson2(c);
|
|
1365
|
+
if (!body) return c.json({ error: "Invalid JSON body" }, 400);
|
|
1366
|
+
if (!body.content) return c.json({ error: "content is required" }, 400);
|
|
1367
|
+
const msg = addPlanMessage(db, planId, body.content);
|
|
1368
|
+
ctx.publishActivity({
|
|
1369
|
+
type: "plan_message",
|
|
1370
|
+
mind: actor.username,
|
|
1371
|
+
summary: `Plan message: "${body.content.slice(0, 100)}"`,
|
|
1372
|
+
metadata: { planId, messageId: msg.id }
|
|
1373
|
+
});
|
|
1374
|
+
return c.json(msg, 201);
|
|
1375
|
+
}).post("/:id{[0-9]+}/log", async (c) => {
|
|
1376
|
+
const actor = resolveUserId2(c);
|
|
1377
|
+
if (!actor) return c.json({ error: "Unauthorized" }, 401);
|
|
1378
|
+
const planId = parseInt(c.req.param("id"), 10);
|
|
1379
|
+
if (Number.isNaN(planId)) return c.json({ error: "Invalid plan ID" }, 400);
|
|
1380
|
+
const plan = db.prepare("SELECT id FROM plans WHERE id = ? AND status = 'active'").get(planId);
|
|
1381
|
+
if (!plan) return c.json({ error: "Active plan not found" }, 404);
|
|
1382
|
+
const body = await parseJson2(c);
|
|
1383
|
+
if (!body) return c.json({ error: "Invalid JSON body" }, 400);
|
|
1384
|
+
if (!body.content) return c.json({ error: "content is required" }, 400);
|
|
1385
|
+
const log = logProgress(db, planId, actor.username, body.content);
|
|
1386
|
+
ctx.publishActivity({
|
|
1387
|
+
type: "plan_progress",
|
|
1388
|
+
mind: actor.username,
|
|
1389
|
+
summary: `${actor.username} logged progress: "${body.content.slice(0, 100)}"`,
|
|
1390
|
+
metadata: { planId, logId: log.id }
|
|
1391
|
+
});
|
|
1392
|
+
return c.json(log, 201);
|
|
1393
|
+
}).patch("/:id{[0-9]+}/finish", async (c) => {
|
|
1394
|
+
const actor = resolveUserId2(c);
|
|
1395
|
+
if (!actor) return c.json({ error: "Unauthorized" }, 401);
|
|
1396
|
+
if (actor.role !== "admin" && actor.user_type !== "mind") {
|
|
1397
|
+
return c.json({ error: "Only spirit or admin can finish plans" }, 403);
|
|
1398
|
+
}
|
|
1399
|
+
const planId = parseInt(c.req.param("id"), 10);
|
|
1400
|
+
if (Number.isNaN(planId)) return c.json({ error: "Invalid plan ID" }, 400);
|
|
1401
|
+
const body = await parseJson2(c);
|
|
1402
|
+
const message = body?.message;
|
|
1403
|
+
const ok = finishPlan(db, planId, message);
|
|
1404
|
+
if (!ok) return c.json({ error: "Plan not found" }, 404);
|
|
1405
|
+
ctx.publishActivity({
|
|
1406
|
+
type: "plan_finished",
|
|
1407
|
+
mind: actor.username,
|
|
1408
|
+
summary: `${actor.username} finished a plan`,
|
|
1409
|
+
metadata: { planId }
|
|
1410
|
+
});
|
|
1411
|
+
return c.json({ ok: true });
|
|
1412
|
+
}).get("/feed", async (c) => {
|
|
1413
|
+
const rawLimit = c.req.query("limit");
|
|
1414
|
+
const limit = rawLimit ? parseInt(rawLimit, 10) : 5;
|
|
1415
|
+
if (Number.isNaN(limit)) return c.json({ error: "Invalid limit parameter" }, 400);
|
|
1416
|
+
const plans = await listPlans(db, getUser2, { limit });
|
|
1417
|
+
return c.json(
|
|
1418
|
+
plans.map((p) => ({
|
|
1419
|
+
id: `plan-${p.id}`,
|
|
1420
|
+
title: p.title,
|
|
1421
|
+
url: `/plan`,
|
|
1422
|
+
date: p.created_at,
|
|
1423
|
+
author: p.set_by_username,
|
|
1424
|
+
bodyHtml: p.description || `<em>${p.status}</em>`,
|
|
1425
|
+
icon: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="6"/><path d="M8 4v4l2.5 2.5"/></svg>',
|
|
1426
|
+
color: "blue"
|
|
1427
|
+
}))
|
|
1428
|
+
);
|
|
1429
|
+
});
|
|
1430
|
+
return app;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
// packages/extensions/plan/src/index.ts
|
|
1434
|
+
var assetsDir3 = resolve5(import.meta.dirname, "../dist/ui");
|
|
1435
|
+
var skillsDir3 = resolve5(import.meta.dirname, "../skills");
|
|
1436
|
+
var src_default3 = createExtension({
|
|
1437
|
+
id: "plan",
|
|
1438
|
+
name: "Plan",
|
|
1439
|
+
version: "0.1.0",
|
|
1440
|
+
description: "System-wide plans for coordinated mind activity",
|
|
1441
|
+
icon: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="6"/><path d="M8 4v4l2.5 2.5"/></svg>',
|
|
1442
|
+
color: "blue",
|
|
1443
|
+
routes: (ctx) => createRoutes3(ctx),
|
|
1444
|
+
commands: createCommands3(),
|
|
1445
|
+
initDb: initDb3,
|
|
1446
|
+
skillsDir: skillsDir3,
|
|
1447
|
+
standardSkill: true,
|
|
1448
|
+
ui: {
|
|
1449
|
+
assetsDir: assetsDir3,
|
|
1450
|
+
systemSection: { id: "plan", label: "Plan", urlPatterns: ["/plan"] },
|
|
1451
|
+
feedSource: { endpoint: "/api/ext/plan/feed" }
|
|
1452
|
+
}
|
|
1453
|
+
});
|
|
1454
|
+
|
|
1024
1455
|
// src/lib/systems-config.ts
|
|
1025
1456
|
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
|
|
1026
|
-
import { resolve as
|
|
1457
|
+
import { resolve as resolve6 } from "path";
|
|
1027
1458
|
var DEFAULT_API_URL = "https://volute.systems";
|
|
1028
1459
|
function configPath() {
|
|
1029
|
-
return
|
|
1460
|
+
return resolve6(voluteSystemDir(), "systems.json");
|
|
1030
1461
|
}
|
|
1031
1462
|
function readSystemsConfig() {
|
|
1032
1463
|
const path = configPath();
|
|
@@ -1066,14 +1497,15 @@ function deleteSystemsConfig() {
|
|
|
1066
1497
|
// src/lib/extensions.ts
|
|
1067
1498
|
var VALID_EXTENSION_ID2 = /^[a-z0-9][a-z0-9_-]*$/;
|
|
1068
1499
|
var loaded = [];
|
|
1500
|
+
var discovered = [];
|
|
1069
1501
|
function extensionsBaseDir() {
|
|
1070
|
-
return
|
|
1502
|
+
return resolve7(voluteHome(), "extensions");
|
|
1071
1503
|
}
|
|
1072
1504
|
function extensionDataDir(id) {
|
|
1073
|
-
return
|
|
1505
|
+
return resolve7(voluteSystemDir(), "extension-data", id);
|
|
1074
1506
|
}
|
|
1075
1507
|
function extensionsConfigPath() {
|
|
1076
|
-
return
|
|
1508
|
+
return resolve7(voluteHome(), "system", "extensions.json");
|
|
1077
1509
|
}
|
|
1078
1510
|
function readExtensionsConfig() {
|
|
1079
1511
|
const configPath2 = extensionsConfigPath();
|
|
@@ -1097,7 +1529,7 @@ async function getLibsqlDatabase() {
|
|
|
1097
1529
|
return _LibsqlDatabase;
|
|
1098
1530
|
}
|
|
1099
1531
|
async function openExtensionDb(_id, dataDir) {
|
|
1100
|
-
const dbPath =
|
|
1532
|
+
const dbPath = resolve7(dataDir, "data.db");
|
|
1101
1533
|
const Database = await getLibsqlDatabase();
|
|
1102
1534
|
return new Database(dbPath);
|
|
1103
1535
|
}
|
|
@@ -1223,7 +1655,7 @@ async function loadExtension(manifest, app, authMw) {
|
|
|
1223
1655
|
if (resolvedAssetsDir && !existsSync3(resolvedAssetsDir)) {
|
|
1224
1656
|
let searchDir = dirname(new URL(import.meta.url).pathname);
|
|
1225
1657
|
for (let i = 0; i < 5; i++) {
|
|
1226
|
-
const candidate =
|
|
1658
|
+
const candidate = resolve7(searchDir, "packages", "extensions", manifest.id, "dist", "ui");
|
|
1227
1659
|
if (existsSync3(candidate)) {
|
|
1228
1660
|
resolvedAssetsDir = candidate;
|
|
1229
1661
|
break;
|
|
@@ -1232,7 +1664,7 @@ async function loadExtension(manifest, app, authMw) {
|
|
|
1232
1664
|
}
|
|
1233
1665
|
}
|
|
1234
1666
|
if (resolvedAssetsDir && existsSync3(resolvedAssetsDir)) {
|
|
1235
|
-
const
|
|
1667
|
+
const assetsDir4 = resolvedAssetsDir;
|
|
1236
1668
|
const { readFile: readFile2, stat: fsStat } = await import("fs/promises");
|
|
1237
1669
|
const { extname: ext } = await import("path");
|
|
1238
1670
|
const mimeTypes = {
|
|
@@ -1248,12 +1680,12 @@ async function loadExtension(manifest, app, authMw) {
|
|
|
1248
1680
|
".woff2": "font/woff2"
|
|
1249
1681
|
};
|
|
1250
1682
|
const prefix = `/ext/${manifest.id}`;
|
|
1251
|
-
const indexPath =
|
|
1683
|
+
const indexPath = resolve7(assetsDir4, "index.html");
|
|
1252
1684
|
const serveExtAssets = async (c) => {
|
|
1253
1685
|
const urlPath = new URL(c.req.url).pathname;
|
|
1254
1686
|
const relativePath = urlPath.slice(prefix.length).replace(/^\//, "") || "index.html";
|
|
1255
|
-
const filePath =
|
|
1256
|
-
if (filePath !==
|
|
1687
|
+
const filePath = resolve7(assetsDir4, relativePath);
|
|
1688
|
+
if (filePath !== assetsDir4 && !filePath.startsWith(assetsDir4 + "/"))
|
|
1257
1689
|
return c.text("Forbidden", 403);
|
|
1258
1690
|
const s = await fsStat(filePath).catch(() => null);
|
|
1259
1691
|
if (s?.isFile()) {
|
|
@@ -1270,11 +1702,11 @@ async function loadExtension(manifest, app, authMw) {
|
|
|
1270
1702
|
app.get(`${prefix}/*`, serveExtAssets);
|
|
1271
1703
|
app.get(prefix, serveExtAssets);
|
|
1272
1704
|
}
|
|
1273
|
-
const
|
|
1274
|
-
if (
|
|
1705
|
+
const skillsDir4 = resolveSkillsDir(manifest);
|
|
1706
|
+
if (skillsDir4) {
|
|
1275
1707
|
let entries;
|
|
1276
1708
|
try {
|
|
1277
|
-
entries = readdirSync2(
|
|
1709
|
+
entries = readdirSync2(skillsDir4, { withFileTypes: true });
|
|
1278
1710
|
} catch (err) {
|
|
1279
1711
|
logger_default.error(`failed to read skills dir for extension ${manifest.id}`, logger_default.errorData(err));
|
|
1280
1712
|
entries = [];
|
|
@@ -1282,9 +1714,9 @@ async function loadExtension(manifest, app, authMw) {
|
|
|
1282
1714
|
for (const entry of entries) {
|
|
1283
1715
|
if (!entry.isDirectory()) continue;
|
|
1284
1716
|
try {
|
|
1285
|
-
const skillPath =
|
|
1717
|
+
const skillPath = resolve7(skillsDir4, entry.name);
|
|
1286
1718
|
const sourceHash = hashSkillDir(skillPath);
|
|
1287
|
-
const destDir =
|
|
1719
|
+
const destDir = resolve7(sharedSkillsDir(), entry.name);
|
|
1288
1720
|
if (existsSync3(destDir)) {
|
|
1289
1721
|
const destHash = hashSkillDir(destDir);
|
|
1290
1722
|
if (sourceHash === destHash) continue;
|
|
@@ -1309,7 +1741,7 @@ function resolveSkillsDir(manifest) {
|
|
|
1309
1741
|
if (!manifest.skillsDir) return null;
|
|
1310
1742
|
let searchDir = dirname(new URL(import.meta.url).pathname);
|
|
1311
1743
|
for (let i = 0; i < 5; i++) {
|
|
1312
|
-
const candidate =
|
|
1744
|
+
const candidate = resolve7(searchDir, "packages", "extensions", manifest.id, "skills");
|
|
1313
1745
|
if (existsSync3(candidate)) return candidate;
|
|
1314
1746
|
searchDir = dirname(searchDir);
|
|
1315
1747
|
}
|
|
@@ -1318,30 +1750,30 @@ function resolveSkillsDir(manifest) {
|
|
|
1318
1750
|
return null;
|
|
1319
1751
|
}
|
|
1320
1752
|
function discoverBuiltinExtensions() {
|
|
1321
|
-
return [src_default, src_default2];
|
|
1753
|
+
return [src_default, src_default2, src_default3];
|
|
1322
1754
|
}
|
|
1323
1755
|
async function discoverInstalledExtensions() {
|
|
1324
|
-
const
|
|
1756
|
+
const results = [];
|
|
1325
1757
|
const packages = readExtensionsConfig();
|
|
1326
|
-
const npmDir =
|
|
1758
|
+
const npmDir = resolve7(voluteHome(), "extensions", "_npm");
|
|
1327
1759
|
const { createRequire } = await import("module");
|
|
1328
1760
|
for (const pkg of packages) {
|
|
1329
1761
|
try {
|
|
1330
1762
|
let resolved = pkg;
|
|
1331
|
-
const npmPkgDir =
|
|
1763
|
+
const npmPkgDir = resolve7(npmDir, "node_modules", pkg);
|
|
1332
1764
|
if (existsSync3(npmPkgDir)) {
|
|
1333
|
-
const require2 = createRequire(
|
|
1765
|
+
const require2 = createRequire(resolve7(npmDir, "noop.js"));
|
|
1334
1766
|
resolved = require2.resolve(pkg);
|
|
1335
1767
|
}
|
|
1336
1768
|
const mod = await import(resolved);
|
|
1337
1769
|
const manifest = mod.default ?? mod.extension ?? mod;
|
|
1338
1770
|
if (!validateManifest(manifest, `package ${pkg}`)) continue;
|
|
1339
|
-
|
|
1771
|
+
results.push({ manifest, package: pkg });
|
|
1340
1772
|
} catch (err) {
|
|
1341
1773
|
logger_default.error(`failed to load extension package: ${pkg}`, logger_default.errorData(err));
|
|
1342
1774
|
}
|
|
1343
1775
|
}
|
|
1344
|
-
return
|
|
1776
|
+
return results;
|
|
1345
1777
|
}
|
|
1346
1778
|
function validateManifest(manifest, source) {
|
|
1347
1779
|
if (!manifest || typeof manifest !== "object") {
|
|
@@ -1383,8 +1815,8 @@ async function discoverLocalExtensions() {
|
|
|
1383
1815
|
return [];
|
|
1384
1816
|
}
|
|
1385
1817
|
for (const dir of entries) {
|
|
1386
|
-
const extDir =
|
|
1387
|
-
const candidates = [
|
|
1818
|
+
const extDir = resolve7(baseDir, dir);
|
|
1819
|
+
const candidates = [resolve7(extDir, "src", "index.js"), resolve7(extDir, "index.js")];
|
|
1388
1820
|
const entryPoint = candidates.find((p) => existsSync3(p));
|
|
1389
1821
|
if (!entryPoint) continue;
|
|
1390
1822
|
try {
|
|
@@ -1403,14 +1835,25 @@ async function loadAllExtensions(app, authMw) {
|
|
|
1403
1835
|
const builtins = discoverBuiltinExtensions();
|
|
1404
1836
|
const installed = await discoverInstalledExtensions();
|
|
1405
1837
|
const local = await discoverLocalExtensions();
|
|
1406
|
-
const
|
|
1838
|
+
const disabledIds = new Set(readGlobalConfig().disabledExtensions ?? []);
|
|
1839
|
+
const all = [
|
|
1840
|
+
...builtins.map((m) => ({ manifest: m, source: "builtin" })),
|
|
1841
|
+
...installed.map((i) => ({ manifest: i.manifest, source: "npm", package: i.package })),
|
|
1842
|
+
...local.map((m) => ({ manifest: m, source: "local" }))
|
|
1843
|
+
];
|
|
1407
1844
|
const seen = /* @__PURE__ */ new Set();
|
|
1408
|
-
for (const
|
|
1845
|
+
for (const entry of all) {
|
|
1846
|
+
const { manifest } = entry;
|
|
1409
1847
|
if (seen.has(manifest.id)) {
|
|
1410
1848
|
logger_default.warn(`duplicate extension ID: ${manifest.id}, skipping`);
|
|
1411
1849
|
continue;
|
|
1412
1850
|
}
|
|
1413
1851
|
seen.add(manifest.id);
|
|
1852
|
+
discovered.push(entry);
|
|
1853
|
+
if (disabledIds.has(manifest.id)) {
|
|
1854
|
+
logger_default.info(`extension disabled, skipping: ${manifest.id}`);
|
|
1855
|
+
continue;
|
|
1856
|
+
}
|
|
1414
1857
|
try {
|
|
1415
1858
|
await loadExtension(manifest, app, authMw);
|
|
1416
1859
|
} catch (err) {
|
|
@@ -1455,6 +1898,114 @@ function getLoadedExtensions() {
|
|
|
1455
1898
|
};
|
|
1456
1899
|
});
|
|
1457
1900
|
}
|
|
1901
|
+
function getAllDiscoveredExtensions() {
|
|
1902
|
+
const disabledIds = new Set(readGlobalConfig().disabledExtensions ?? []);
|
|
1903
|
+
return discovered.map((d) => ({
|
|
1904
|
+
id: d.manifest.id,
|
|
1905
|
+
name: d.manifest.name,
|
|
1906
|
+
version: d.manifest.version,
|
|
1907
|
+
description: d.manifest.description,
|
|
1908
|
+
icon: d.manifest.icon,
|
|
1909
|
+
source: d.source,
|
|
1910
|
+
enabled: !disabledIds.has(d.manifest.id),
|
|
1911
|
+
package: d.package
|
|
1912
|
+
}));
|
|
1913
|
+
}
|
|
1914
|
+
function setExtensionEnabled(id, enabled) {
|
|
1915
|
+
if (!discovered.find((d) => d.manifest.id === id)) {
|
|
1916
|
+
throw new Error(`Extension "${id}" not found`);
|
|
1917
|
+
}
|
|
1918
|
+
const config = readGlobalConfig();
|
|
1919
|
+
const disabled = new Set(config.disabledExtensions ?? []);
|
|
1920
|
+
if (enabled) {
|
|
1921
|
+
disabled.delete(id);
|
|
1922
|
+
} else {
|
|
1923
|
+
disabled.add(id);
|
|
1924
|
+
}
|
|
1925
|
+
config.disabledExtensions = disabled.size > 0 ? [...disabled] : void 0;
|
|
1926
|
+
writeGlobalConfig(config);
|
|
1927
|
+
}
|
|
1928
|
+
function extensionsNpmDir() {
|
|
1929
|
+
return resolve7(voluteHome(), "extensions", "_npm");
|
|
1930
|
+
}
|
|
1931
|
+
function ensureExtensionsNpmDir() {
|
|
1932
|
+
const dir = extensionsNpmDir();
|
|
1933
|
+
mkdirSync2(dir, { recursive: true });
|
|
1934
|
+
const pkgPath = resolve7(dir, "package.json");
|
|
1935
|
+
if (!existsSync3(pkgPath)) {
|
|
1936
|
+
writeFileSync2(pkgPath, '{"private":true,"dependencies":{}}\n');
|
|
1937
|
+
}
|
|
1938
|
+
return dir;
|
|
1939
|
+
}
|
|
1940
|
+
function writeExtensionsConfig(packages) {
|
|
1941
|
+
const configPath2 = extensionsConfigPath();
|
|
1942
|
+
mkdirSync2(resolve7(configPath2, ".."), { recursive: true });
|
|
1943
|
+
writeFileSync2(configPath2, `${JSON.stringify(packages, null, 2)}
|
|
1944
|
+
`);
|
|
1945
|
+
}
|
|
1946
|
+
var VALID_NPM_PACKAGE = /^(@[a-z0-9-~][a-z0-9._-~]*\/)?[a-z0-9-~][a-z0-9._-~]*(@[^\s]+)?$/;
|
|
1947
|
+
async function installNpmExtension(pkg) {
|
|
1948
|
+
if (!VALID_NPM_PACKAGE.test(pkg)) {
|
|
1949
|
+
throw new Error(`Invalid package name: "${pkg}"`);
|
|
1950
|
+
}
|
|
1951
|
+
const packages = readExtensionsConfig();
|
|
1952
|
+
if (packages.includes(pkg)) {
|
|
1953
|
+
throw new Error(`Extension "${pkg}" is already installed`);
|
|
1954
|
+
}
|
|
1955
|
+
const dir = ensureExtensionsNpmDir();
|
|
1956
|
+
const { exec } = await import("./exec-DVLXKRIO.js");
|
|
1957
|
+
try {
|
|
1958
|
+
await exec("npm", ["install", pkg], { cwd: dir });
|
|
1959
|
+
} catch (err) {
|
|
1960
|
+
logger_default.error(`npm install failed for "${pkg}"`, logger_default.errorData(err));
|
|
1961
|
+
throw new Error(`Failed to install "${pkg}". Check daemon logs for details.`);
|
|
1962
|
+
}
|
|
1963
|
+
packages.push(pkg);
|
|
1964
|
+
writeExtensionsConfig(packages);
|
|
1965
|
+
logger_default.info(`installed extension package: ${pkg}`);
|
|
1966
|
+
}
|
|
1967
|
+
async function uninstallNpmExtension(pkg) {
|
|
1968
|
+
const packages = readExtensionsConfig();
|
|
1969
|
+
const idx = packages.indexOf(pkg);
|
|
1970
|
+
if (idx === -1) {
|
|
1971
|
+
throw new Error(`Extension "${pkg}" is not installed`);
|
|
1972
|
+
}
|
|
1973
|
+
await cleanupExtensionSkills(pkg);
|
|
1974
|
+
packages.splice(idx, 1);
|
|
1975
|
+
writeExtensionsConfig(packages);
|
|
1976
|
+
try {
|
|
1977
|
+
const { exec } = await import("./exec-DVLXKRIO.js");
|
|
1978
|
+
await exec("npm", ["uninstall", pkg], { cwd: extensionsNpmDir() });
|
|
1979
|
+
} catch (err) {
|
|
1980
|
+
logger_default.warn(
|
|
1981
|
+
`npm uninstall failed for "${pkg}" (may have been manually removed)`,
|
|
1982
|
+
logger_default.errorData(err)
|
|
1983
|
+
);
|
|
1984
|
+
}
|
|
1985
|
+
logger_default.info(`uninstalled extension package: ${pkg}`);
|
|
1986
|
+
}
|
|
1987
|
+
async function cleanupExtensionSkills(pkg) {
|
|
1988
|
+
try {
|
|
1989
|
+
const pkgDir = resolve7(extensionsNpmDir(), "node_modules", pkg);
|
|
1990
|
+
if (!existsSync3(pkgDir)) return;
|
|
1991
|
+
const { createRequire } = await import("module");
|
|
1992
|
+
const require2 = createRequire(resolve7(extensionsNpmDir(), "noop.js"));
|
|
1993
|
+
const mod = require2(pkg);
|
|
1994
|
+
const manifest = mod.default ?? mod.extension ?? mod;
|
|
1995
|
+
if (!manifest?.skillsDir || !existsSync3(manifest.skillsDir)) return;
|
|
1996
|
+
const skillDirs = readdirSync2(manifest.skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
1997
|
+
for (const skillId of skillDirs) {
|
|
1998
|
+
try {
|
|
1999
|
+
await removeSharedSkill(skillId);
|
|
2000
|
+
logger_default.info(`removed skill "${skillId}" from extension ${pkg}`);
|
|
2001
|
+
} catch (err) {
|
|
2002
|
+
logger_default.warn(`failed to remove skill "${skillId}" for extension ${pkg}`, logger_default.errorData(err));
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
} catch (err) {
|
|
2006
|
+
logger_default.warn(`could not clean up skills for "${pkg}"`, logger_default.errorData(err));
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
1458
2009
|
function getExtensionStandardSkills() {
|
|
1459
2010
|
const skills = [];
|
|
1460
2011
|
for (const { manifest } of loaded) {
|
|
@@ -1494,6 +2045,7 @@ function notifyExtensionsDaemonStop() {
|
|
|
1494
2045
|
}
|
|
1495
2046
|
}
|
|
1496
2047
|
loaded.length = 0;
|
|
2048
|
+
discovered.length = 0;
|
|
1497
2049
|
}
|
|
1498
2050
|
function notifyExtensionsMindStart(mindName) {
|
|
1499
2051
|
for (const { manifest } of loaded) {
|
|
@@ -1520,6 +2072,10 @@ export {
|
|
|
1520
2072
|
deleteSystemsConfig,
|
|
1521
2073
|
loadAllExtensions,
|
|
1522
2074
|
getLoadedExtensions,
|
|
2075
|
+
getAllDiscoveredExtensions,
|
|
2076
|
+
setExtensionEnabled,
|
|
2077
|
+
installNpmExtension,
|
|
2078
|
+
uninstallNpmExtension,
|
|
1523
2079
|
getExtensionStandardSkills,
|
|
1524
2080
|
notifyExtensionsDaemonStart,
|
|
1525
2081
|
notifyExtensionsDaemonStop,
|