volute 0.32.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/README.md +16 -0
- package/dist/{accept-74M7I4RZ.js → accept-TW6V4WI4.js} +4 -4
- package/dist/{activity-events-HETAODOK.js → activity-events-BN7V6KCC.js} +4 -4
- package/dist/{ai-service-ZIPCV3MX.js → ai-service-PSILB5WD.js} +5 -5
- package/dist/{api-client-YPKOZP2O.js → api-client-XUXOB7LI.js} +1 -1
- package/dist/api.d.ts +1198 -957
- package/dist/{archive-INXYFVCW.js → archive-C2VEMQOR.js} +4 -4
- package/dist/{auth-6DMGES3I.js → auth-ZFZXJZDQ.js} +5 -5
- package/dist/{bridge-BVCBTGPF.js → bridge-O753D5F4.js} +4 -4
- package/dist/{chat-XT4OBJBU.js → chat-BHYX7DJ4.js} +9 -9
- package/dist/{chunk-M7UL5S3Q.js → chunk-2IOP6PHB.js} +1 -1
- package/dist/{chunk-JJ7W6WSB.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-6LXAAQ43.js +22 -0
- package/dist/{chunk-TSXLLQZW.js → chunk-6OWJXUAR.js} +10 -1
- package/dist/{chunk-I5KY25PQ.js → chunk-6WAWMWR5.js} +1 -1
- package/dist/{chunk-LSGWR54X.js → chunk-7F2SW2KD.js} +2 -2
- package/dist/chunk-7KJOFUNN.js +22 -0
- package/dist/{spirit-N4W4UQRH.js → chunk-B2BVAIZ4.js} +21 -12
- package/dist/{chunk-LGB6JBHI.js → chunk-BDK73LK6.js} +5 -55
- package/dist/{chunk-IYDIE3HG.js → chunk-BFWHBQK4.js} +1 -1
- package/dist/{chunk-TDRYEPH4.js → chunk-BM474GX6.js} +4 -4
- package/dist/{chunk-R7E6CRVQ.js → chunk-BTWAGDV5.js} +1 -1
- package/dist/{chunk-WKF5FEFK.js → chunk-CVL5IGIR.js} +629 -174
- package/dist/{chunk-S6NFERDC.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-D5G5YOPL.js → chunk-N3DNFPVA.js} +41 -5
- package/dist/{chunk-LRCG2JLP.js → chunk-N7BLAHNE.js} +5 -1
- package/dist/chunk-OYAKCAVY.js +29 -0
- package/dist/{chunk-UKVWJRKN.js → chunk-PLDWHR4D.js} +1 -1
- package/dist/{chunk-QBQ424EM.js → chunk-TAHX36HZ.js} +545 -246
- package/dist/chunk-U5BTYSAL.js +59 -0
- package/dist/{chunk-SX5TKJBZ.js → chunk-V45JXOWY.js} +2 -2
- package/dist/{chunk-2FLJ63GU.js → chunk-V6ZCNULL.js} +2 -2
- package/dist/{chunk-QZANELPX.js → chunk-XWXBJQBE.js} +3 -2
- package/dist/cli.js +32 -24
- package/dist/{clock-2UOZ6JPU.js → clock-3X4DSC2N.js} +38 -23
- package/dist/{cloud-sync-JN3NWKEM.js → cloud-sync-TG3TIX5H.js} +21 -17
- 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-3O5O6AS3.js → conversations-HL2JP5GI.js} +5 -5
- package/dist/{create-RNLNCORE.js → create-3SEKKI6P.js} +5 -5
- package/dist/{create-WBBYI6V7.js → create-UOSOQ2HN.js} +4 -4
- package/dist/daemon-client-WOAQXXBM.js +12 -0
- package/dist/{daemon-restart-NGFHFAUF.js → daemon-restart-5ABHNXJZ.js} +9 -8
- package/dist/daemon.js +2730 -1520
- 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-RLYQBOOP.js → env-R34DT7XL.js} +10 -6
- 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-EAMPO2SJ.js → files-VQV2VZQO.js} +5 -5
- package/dist/{import-DDUFE7AY.js → import-MK2I2T6F.js} +5 -5
- package/dist/isolation-62MKDZN3.js +22 -0
- package/dist/{join-I5QEE3LG.js → join-DGYHTJUH.js} +3 -3
- package/dist/lib-DYEZMGW7.js +6588 -0
- package/dist/{list-DW2VRTOZ.js → list-C644WTHV.js} +16 -8
- package/dist/{login-7CHPW2PN.js → login-IIGEQPHL.js} +4 -4
- 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-2B6M7Y25.js → mind-BI4EPBVZ.js} +25 -19
- package/dist/{mind-activity-tracker-NZZT2NTT.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-WPG42U5Y.js +47 -0
- package/dist/mind-service-VIKZJK2M.js +38 -0
- package/dist/{mind-sleep-B7BHJLH7.js → mind-sleep-XDISJY74.js} +4 -4
- package/dist/{mind-status-L3EFFRPR.js → mind-status-7FTZWPZF.js} +4 -4
- package/dist/{mind-wake-GY3RFX7Y.js → mind-wake-KIIKEI3A.js} +4 -4
- package/dist/{package-PK6JUFL3.js → package-V2WHWVG6.js} +9 -5
- package/dist/{read-5AMJRO3D.js → read-H5C26YO7.js} +18 -8
- package/dist/read-stdin-PIRM6A2Y.js +8 -0
- package/dist/{register-V2JZZKFK.js → register-J27WP33N.js} +4 -4
- package/dist/{registry-PJ4S5PHQ.js → registry-UYV5S6QT.js} +3 -3
- package/dist/{reject-33HEZMZ4.js → reject-OEANJYIA.js} +4 -4
- package/dist/{restart-3UCMRUVC.js → restart-V5EGYBJG.js} +4 -4
- package/dist/{sandbox-JANNTX6U.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-WNGI6PNW.js +11 -0
- package/dist/seed-check-PXTH7YXS.js +32 -0
- package/dist/seed-cmd-VENFTGS3.js +36 -0
- package/dist/{seed-ALUQ55FF.js → seed-create-663ALOKH.js} +8 -8
- package/dist/{sprout-L2GFOVF7.js → seed-sprout-EH3AGKAI.js} +24 -11
- package/dist/{send-3MI36LEF.js → send-7FUUUZZH.js} +66 -51
- package/dist/{setup-SZIARWI6.js → setup-GGMKENLN.js} +6 -4
- package/dist/{setup-WENLVPVP.js → setup-Z3DEVWV7.js} +13 -11
- package/dist/{skill-TUVOTW4Z.js → skill-DKNYJS4P.js} +12 -8
- package/dist/skills/imagegen/SKILL.md +11 -7
- package/dist/skills/imagegen/scripts/imagegen.ts +146 -25
- package/dist/skills/orientation/SKILL.md +9 -2
- package/dist/skills/plan-coordinator/SKILL.md +60 -0
- package/dist/skills/seed-nurture/SKILL.md +42 -0
- package/dist/skills/volute-mind/SKILL.md +11 -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-XNZK6P4K.js → skills-Q6VZ2UGD.js} +11 -6
- 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-E3HJIV2Z.js +11 -0
- package/dist/{start-K2NCUUCG.js → start-W3TPKX4D.js} +4 -4
- package/dist/{status-TCUMUO6M.js → status-4OVFXFEJ.js} +7 -6
- 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-DHBKVYEY.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-QVPRF6GR.js → update-HG4LCUSG.js} +7 -6
- 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-TCKWBZZG.js → version-notify-YCH4UVQ2.js} +23 -20
- package/dist/volute-config-WBKYJGYQ.js +10 -0
- 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 +8 -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 +40 -2
- package/templates/claude/src/server.ts +1 -0
- package/templates/codex/package.json.tmpl +1 -0
- package/templates/codex/src/agent.ts +81 -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-SDVXFD4W.js +0 -28
- package/dist/down-TB3ESMNP.js +0 -14
- package/dist/extension-FQ5D3NCC.js +0 -174
- package/dist/extensions-GDYWQXC4.js +0 -29
- package/dist/history-FO5PHBQ5.js +0 -128
- package/dist/message-delivery-2FIM7QKO.js +0 -32
- package/dist/mind-manager-BNCMGYXW.js +0 -28
- package/dist/mind-service-AV273WT4.js +0 -34
- package/dist/sleep-manager-53DZOWW7.js +0 -32
- package/dist/system-chat-NPYFYZVI.js +0 -32
- package/dist/up-6I6BHRTO.js +0 -17
- package/dist/web-assets/assets/index-Bui7U9Uu.css +0 -1
- package/dist/web-assets/assets/index-e36DIo1b.js +0 -73
|
@@ -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,31 +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-D5G5YOPL.js";
|
|
21
|
+
} from "./chunk-XWXBJQBE.js";
|
|
21
22
|
import {
|
|
22
23
|
logger_default
|
|
23
24
|
} from "./chunk-YUIHSKR6.js";
|
|
24
25
|
import {
|
|
25
|
-
|
|
26
|
+
readGlobalConfig,
|
|
27
|
+
writeGlobalConfig
|
|
28
|
+
} from "./chunk-6OWJXUAR.js";
|
|
29
|
+
import {
|
|
26
30
|
mindDir,
|
|
27
31
|
voluteHome,
|
|
28
32
|
voluteSystemDir
|
|
29
|
-
} from "./chunk-
|
|
30
|
-
import {
|
|
31
|
-
turns
|
|
32
|
-
} from "./chunk-RPZZSXV3.js";
|
|
33
|
+
} from "./chunk-N7BLAHNE.js";
|
|
33
34
|
|
|
34
35
|
// src/lib/extensions.ts
|
|
35
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
36
|
-
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";
|
|
37
38
|
|
|
38
39
|
// packages/extensions/notes/src/index.ts
|
|
39
40
|
import { resolve } from "path";
|
|
@@ -299,7 +300,7 @@ function createCommands() {
|
|
|
299
300
|
return {
|
|
300
301
|
write: {
|
|
301
302
|
description: "Write a new note",
|
|
302
|
-
usage: 'volute notes write "title" "content" [--reply-to author/slug]',
|
|
303
|
+
usage: 'volute notes write "title" ["content"] [--reply-to author/slug] (content can be piped via stdin)',
|
|
303
304
|
handler: async (args, ctx) => {
|
|
304
305
|
if (!ctx.db) return { error: "Notes extension requires a database" };
|
|
305
306
|
const mindName = ctx.mindName;
|
|
@@ -307,7 +308,7 @@ function createCommands() {
|
|
|
307
308
|
const user = await ctx.getUserByUsername(mindName);
|
|
308
309
|
if (!user) return { error: `Unknown mind: ${mindName}` };
|
|
309
310
|
const title = args[0];
|
|
310
|
-
const content = args[1];
|
|
311
|
+
const content = args[1] ?? ctx.stdin;
|
|
311
312
|
if (!title || !content)
|
|
312
313
|
return { error: 'Usage: volute notes write "title" "content" [--reply-to author/slug]' };
|
|
313
314
|
let replyToId;
|
|
@@ -381,7 +382,7 @@ Comments (${note.comments.length}):`);
|
|
|
381
382
|
},
|
|
382
383
|
comment: {
|
|
383
384
|
description: "Comment on a note",
|
|
384
|
-
usage: 'volute notes comment <author/slug> "content"',
|
|
385
|
+
usage: 'volute notes comment <author/slug> ["content"] (content can be piped via stdin)',
|
|
385
386
|
handler: async (args, ctx) => {
|
|
386
387
|
if (!ctx.db) return { error: "Notes extension requires a database" };
|
|
387
388
|
const mindName = ctx.mindName;
|
|
@@ -389,7 +390,7 @@ Comments (${note.comments.length}):`);
|
|
|
389
390
|
const user = await ctx.getUserByUsername(mindName);
|
|
390
391
|
if (!user) return { error: `Unknown mind: ${mindName}` };
|
|
391
392
|
const ref = args[0];
|
|
392
|
-
const content = args[1];
|
|
393
|
+
const content = args[1] ?? ctx.stdin;
|
|
393
394
|
if (!ref || !ref.includes("/") || !content) {
|
|
394
395
|
return { error: 'Usage: volute notes comment <author/slug> "content"' };
|
|
395
396
|
}
|
|
@@ -528,19 +529,16 @@ function createRoutes(ctx) {
|
|
|
528
529
|
replyToId = id;
|
|
529
530
|
}
|
|
530
531
|
const note = await createNote(db, getUser2, actor.id, body.title, body.content, replyToId);
|
|
531
|
-
ctx.publishActivity(
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
},
|
|
542
|
-
c
|
|
543
|
-
);
|
|
532
|
+
ctx.publishActivity({
|
|
533
|
+
type: "note_created",
|
|
534
|
+
mind: actor.username,
|
|
535
|
+
summary: `${actor.username} wrote "${body.title}"`,
|
|
536
|
+
metadata: {
|
|
537
|
+
author: actor.username,
|
|
538
|
+
slug: note.slug,
|
|
539
|
+
bodyHtml: body.content.slice(0, 500)
|
|
540
|
+
}
|
|
541
|
+
});
|
|
544
542
|
return c.json(note, 201);
|
|
545
543
|
}).get("/:author/:slug", async (c) => {
|
|
546
544
|
const { author, slug } = c.req.param();
|
|
@@ -629,6 +627,7 @@ var src_default = createExtension({
|
|
|
629
627
|
version: "0.1.0",
|
|
630
628
|
description: "Public notes for sharing thoughts, reflections, and ideas",
|
|
631
629
|
icon: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 4h10M3 7h8M3 10h6M3 13h9"/></svg>',
|
|
630
|
+
color: "yellow",
|
|
632
631
|
routes: (ctx) => createRoutes(ctx),
|
|
633
632
|
commands: createCommands(),
|
|
634
633
|
initDb: initDb2,
|
|
@@ -751,7 +750,7 @@ Warning: remote publish failed: ${err.message}`;
|
|
|
751
750
|
const allFlag = args.includes("--all");
|
|
752
751
|
const port = process.env.VOLUTE_DAEMON_PORT || "1618";
|
|
753
752
|
if (allFlag) {
|
|
754
|
-
const { getAllSites: getAllSites2 } = await import("./db-
|
|
753
|
+
const { getAllSites: getAllSites2 } = await import("./db-PLEDCBHZ.js");
|
|
755
754
|
const sites = getAllSites2(db);
|
|
756
755
|
const lines2 = [];
|
|
757
756
|
for (const site of sites) {
|
|
@@ -948,7 +947,7 @@ function createRoutes2(ctx) {
|
|
|
948
947
|
var _voluteHome = null;
|
|
949
948
|
async function getVoluteHome() {
|
|
950
949
|
if (_voluteHome) return _voluteHome();
|
|
951
|
-
const mod = await import("./registry-
|
|
950
|
+
const mod = await import("./registry-UYV5S6QT.js");
|
|
952
951
|
_voluteHome = mod.voluteHome;
|
|
953
952
|
return _voluteHome();
|
|
954
953
|
}
|
|
@@ -1012,6 +1011,7 @@ var src_default2 = createExtension({
|
|
|
1012
1011
|
skillsDir: skillsDir2,
|
|
1013
1012
|
standardSkill: true,
|
|
1014
1013
|
icon: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="2" width="14" height="12" rx="1.5"/><path d="M1 5h14"/><circle cx="3" cy="3.5" r="0.5" fill="currentColor" stroke="none"/><circle cx="5" cy="3.5" r="0.5" fill="currentColor" stroke="none"/></svg>',
|
|
1014
|
+
color: "purple",
|
|
1015
1015
|
ui: {
|
|
1016
1016
|
assetsDir: assetsDir2,
|
|
1017
1017
|
systemSection: {
|
|
@@ -1026,121 +1026,438 @@ var src_default2 = createExtension({
|
|
|
1026
1026
|
}
|
|
1027
1027
|
});
|
|
1028
1028
|
|
|
1029
|
-
//
|
|
1030
|
-
import {
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
const
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
}
|
|
1048
|
-
tlog.error(`failed to create turn for ${mind}`, logger_default.errorData(err));
|
|
1049
|
-
if (activeTurns.get(k) === entry) activeTurns.delete(k);
|
|
1050
|
-
return void 0;
|
|
1051
|
-
}
|
|
1052
|
-
return turnId;
|
|
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
|
+
};
|
|
1053
1048
|
}
|
|
1054
|
-
function
|
|
1055
|
-
|
|
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
|
+
};
|
|
1056
1064
|
}
|
|
1057
|
-
function
|
|
1058
|
-
|
|
1059
|
-
|
|
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);
|
|
1060
1071
|
}
|
|
1061
|
-
function
|
|
1062
|
-
return
|
|
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);
|
|
1063
1078
|
}
|
|
1064
|
-
|
|
1065
|
-
const
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
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
|
+
});
|
|
1070
1105
|
}
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
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
|
+
}
|
|
1077
1124
|
}
|
|
1078
|
-
activeTurns.delete(wildcardKey);
|
|
1079
|
-
activeTurns.set(key(mind, session), entry);
|
|
1080
|
-
}
|
|
1081
|
-
async function completeTurn(mind, session) {
|
|
1082
|
-
const k = key(mind, session);
|
|
1083
|
-
const wildcardKey = key(mind);
|
|
1084
|
-
const entry = activeTurns.get(k) ?? activeTurns.get(wildcardKey);
|
|
1085
|
-
if (!entry) return void 0;
|
|
1086
1125
|
try {
|
|
1087
|
-
|
|
1088
|
-
|
|
1126
|
+
await _announce(text);
|
|
1127
|
+
return true;
|
|
1089
1128
|
} catch (err) {
|
|
1090
|
-
|
|
1091
|
-
return
|
|
1129
|
+
console.error("[plan] Failed to announce to system channel:", err);
|
|
1130
|
+
return false;
|
|
1092
1131
|
}
|
|
1093
|
-
activeTurns.delete(k);
|
|
1094
|
-
activeTurns.delete(wildcardKey);
|
|
1095
|
-
return entry.turnId;
|
|
1096
1132
|
}
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
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
|
+
};
|
|
1104
1270
|
}
|
|
1105
|
-
|
|
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) {
|
|
1106
1315
|
try {
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
await db.update(turns).set({ status: "complete" }).where(eq(turns.status, "active"));
|
|
1111
|
-
tlog.info(`completed ${active.length} orphaned active turn(s) from previous daemon session`);
|
|
1112
|
-
} catch (err) {
|
|
1113
|
-
tlog.error("failed to complete orphaned turns on startup", logger_default.errorData(err));
|
|
1316
|
+
return await c.req.json();
|
|
1317
|
+
} catch {
|
|
1318
|
+
return null;
|
|
1114
1319
|
}
|
|
1115
1320
|
}
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
const
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
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);
|
|
1123
1337
|
}
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
}
|
|
1132
|
-
} catch (err) {
|
|
1133
|
-
tlog.error(`failed to complete orphaned turns for ${mind}`, logger_default.errorData(err));
|
|
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);
|
|
1134
1345
|
}
|
|
1135
|
-
|
|
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;
|
|
1136
1431
|
}
|
|
1137
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
|
+
|
|
1138
1455
|
// src/lib/systems-config.ts
|
|
1139
1456
|
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
|
|
1140
|
-
import { resolve as
|
|
1457
|
+
import { resolve as resolve6 } from "path";
|
|
1141
1458
|
var DEFAULT_API_URL = "https://volute.systems";
|
|
1142
1459
|
function configPath() {
|
|
1143
|
-
return
|
|
1460
|
+
return resolve6(voluteSystemDir(), "systems.json");
|
|
1144
1461
|
}
|
|
1145
1462
|
function readSystemsConfig() {
|
|
1146
1463
|
const path = configPath();
|
|
@@ -1180,14 +1497,15 @@ function deleteSystemsConfig() {
|
|
|
1180
1497
|
// src/lib/extensions.ts
|
|
1181
1498
|
var VALID_EXTENSION_ID2 = /^[a-z0-9][a-z0-9_-]*$/;
|
|
1182
1499
|
var loaded = [];
|
|
1500
|
+
var discovered = [];
|
|
1183
1501
|
function extensionsBaseDir() {
|
|
1184
|
-
return
|
|
1502
|
+
return resolve7(voluteHome(), "extensions");
|
|
1185
1503
|
}
|
|
1186
1504
|
function extensionDataDir(id) {
|
|
1187
|
-
return
|
|
1505
|
+
return resolve7(voluteSystemDir(), "extension-data", id);
|
|
1188
1506
|
}
|
|
1189
1507
|
function extensionsConfigPath() {
|
|
1190
|
-
return
|
|
1508
|
+
return resolve7(voluteHome(), "system", "extensions.json");
|
|
1191
1509
|
}
|
|
1192
1510
|
function readExtensionsConfig() {
|
|
1193
1511
|
const configPath2 = extensionsConfigPath();
|
|
@@ -1211,7 +1529,7 @@ async function getLibsqlDatabase() {
|
|
|
1211
1529
|
return _LibsqlDatabase;
|
|
1212
1530
|
}
|
|
1213
1531
|
async function openExtensionDb(_id, dataDir) {
|
|
1214
|
-
const dbPath =
|
|
1532
|
+
const dbPath = resolve7(dataDir, "data.db");
|
|
1215
1533
|
const Database = await getLibsqlDatabase();
|
|
1216
1534
|
return new Database(dbPath);
|
|
1217
1535
|
}
|
|
@@ -1237,15 +1555,16 @@ async function buildContext(manifest, dataDir, authMw) {
|
|
|
1237
1555
|
},
|
|
1238
1556
|
getUser: async (id) => getUser(id),
|
|
1239
1557
|
getUserByUsername: async (username) => getUserByUsername(username),
|
|
1240
|
-
publishActivity: (event
|
|
1241
|
-
const
|
|
1242
|
-
const turnId = getActiveTurnId(event.mind, session);
|
|
1243
|
-
const sourceEventId = getLastToolUseEventId(event.mind, session);
|
|
1244
|
-
publish({
|
|
1558
|
+
publishActivity: (event) => {
|
|
1559
|
+
const enriched = {
|
|
1245
1560
|
...event,
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1561
|
+
metadata: {
|
|
1562
|
+
...event.metadata,
|
|
1563
|
+
...manifest.icon && !event.metadata?.icon ? { icon: manifest.icon } : {},
|
|
1564
|
+
...manifest.color && !event.metadata?.color ? { color: manifest.color } : {}
|
|
1565
|
+
}
|
|
1566
|
+
};
|
|
1567
|
+
publish(enriched).catch(
|
|
1249
1568
|
(err) => logger_default.error(`extension ${manifest.id}: failed to publish activity`, logger_default.errorData(err))
|
|
1250
1569
|
);
|
|
1251
1570
|
},
|
|
@@ -1295,15 +1614,36 @@ async function loadExtension(manifest, app, authMw) {
|
|
|
1295
1614
|
const mindName = body.mind || user?.username;
|
|
1296
1615
|
const session = c.get("mindSession");
|
|
1297
1616
|
try {
|
|
1617
|
+
const activityPromises = [];
|
|
1298
1618
|
const result = await cmd.handler(body.args ?? [], {
|
|
1299
1619
|
...context,
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1620
|
+
publishActivity: (rawEvent) => {
|
|
1621
|
+
const event = {
|
|
1622
|
+
...rawEvent,
|
|
1623
|
+
metadata: {
|
|
1624
|
+
...rawEvent.metadata,
|
|
1625
|
+
...manifest.icon && !rawEvent.metadata?.icon ? { icon: manifest.icon } : {},
|
|
1626
|
+
...manifest.color && !rawEvent.metadata?.color ? { color: manifest.color } : {}
|
|
1627
|
+
}
|
|
1628
|
+
};
|
|
1629
|
+
activityPromises.push(
|
|
1630
|
+
publish(event).catch((err) => {
|
|
1631
|
+
logger_default.error(
|
|
1632
|
+
`extension ${manifest.id}: failed to publish activity`,
|
|
1633
|
+
logger_default.errorData(err)
|
|
1634
|
+
);
|
|
1635
|
+
return 0;
|
|
1636
|
+
})
|
|
1637
|
+
);
|
|
1638
|
+
},
|
|
1303
1639
|
mindName,
|
|
1304
|
-
session
|
|
1640
|
+
session,
|
|
1641
|
+
stdin: body.stdin
|
|
1305
1642
|
});
|
|
1306
|
-
|
|
1643
|
+
const activityIds = (await Promise.all(activityPromises)).filter((id) => id > 0);
|
|
1644
|
+
const markers = activityIds.map((id) => `[volute:activity:${id}]`).join("");
|
|
1645
|
+
const output = result && typeof result === "object" && "output" in result ? { ...result, output: `${result.output}${markers}` } : markers ? { ...result, output: markers } : result;
|
|
1646
|
+
return c.json(output);
|
|
1307
1647
|
} catch (err) {
|
|
1308
1648
|
logger_default.error(`extension command ${manifest.id}/${cmdName} failed`, logger_default.errorData(err));
|
|
1309
1649
|
return c.json({ error: err.message }, 500);
|
|
@@ -1315,7 +1655,7 @@ async function loadExtension(manifest, app, authMw) {
|
|
|
1315
1655
|
if (resolvedAssetsDir && !existsSync3(resolvedAssetsDir)) {
|
|
1316
1656
|
let searchDir = dirname(new URL(import.meta.url).pathname);
|
|
1317
1657
|
for (let i = 0; i < 5; i++) {
|
|
1318
|
-
const candidate =
|
|
1658
|
+
const candidate = resolve7(searchDir, "packages", "extensions", manifest.id, "dist", "ui");
|
|
1319
1659
|
if (existsSync3(candidate)) {
|
|
1320
1660
|
resolvedAssetsDir = candidate;
|
|
1321
1661
|
break;
|
|
@@ -1324,7 +1664,7 @@ async function loadExtension(manifest, app, authMw) {
|
|
|
1324
1664
|
}
|
|
1325
1665
|
}
|
|
1326
1666
|
if (resolvedAssetsDir && existsSync3(resolvedAssetsDir)) {
|
|
1327
|
-
const
|
|
1667
|
+
const assetsDir4 = resolvedAssetsDir;
|
|
1328
1668
|
const { readFile: readFile2, stat: fsStat } = await import("fs/promises");
|
|
1329
1669
|
const { extname: ext } = await import("path");
|
|
1330
1670
|
const mimeTypes = {
|
|
@@ -1340,12 +1680,12 @@ async function loadExtension(manifest, app, authMw) {
|
|
|
1340
1680
|
".woff2": "font/woff2"
|
|
1341
1681
|
};
|
|
1342
1682
|
const prefix = `/ext/${manifest.id}`;
|
|
1343
|
-
const indexPath =
|
|
1683
|
+
const indexPath = resolve7(assetsDir4, "index.html");
|
|
1344
1684
|
const serveExtAssets = async (c) => {
|
|
1345
1685
|
const urlPath = new URL(c.req.url).pathname;
|
|
1346
1686
|
const relativePath = urlPath.slice(prefix.length).replace(/^\//, "") || "index.html";
|
|
1347
|
-
const filePath =
|
|
1348
|
-
if (filePath !==
|
|
1687
|
+
const filePath = resolve7(assetsDir4, relativePath);
|
|
1688
|
+
if (filePath !== assetsDir4 && !filePath.startsWith(assetsDir4 + "/"))
|
|
1349
1689
|
return c.text("Forbidden", 403);
|
|
1350
1690
|
const s = await fsStat(filePath).catch(() => null);
|
|
1351
1691
|
if (s?.isFile()) {
|
|
@@ -1362,11 +1702,11 @@ async function loadExtension(manifest, app, authMw) {
|
|
|
1362
1702
|
app.get(`${prefix}/*`, serveExtAssets);
|
|
1363
1703
|
app.get(prefix, serveExtAssets);
|
|
1364
1704
|
}
|
|
1365
|
-
const
|
|
1366
|
-
if (
|
|
1705
|
+
const skillsDir4 = resolveSkillsDir(manifest);
|
|
1706
|
+
if (skillsDir4) {
|
|
1367
1707
|
let entries;
|
|
1368
1708
|
try {
|
|
1369
|
-
entries = readdirSync2(
|
|
1709
|
+
entries = readdirSync2(skillsDir4, { withFileTypes: true });
|
|
1370
1710
|
} catch (err) {
|
|
1371
1711
|
logger_default.error(`failed to read skills dir for extension ${manifest.id}`, logger_default.errorData(err));
|
|
1372
1712
|
entries = [];
|
|
@@ -1374,9 +1714,9 @@ async function loadExtension(manifest, app, authMw) {
|
|
|
1374
1714
|
for (const entry of entries) {
|
|
1375
1715
|
if (!entry.isDirectory()) continue;
|
|
1376
1716
|
try {
|
|
1377
|
-
const skillPath =
|
|
1717
|
+
const skillPath = resolve7(skillsDir4, entry.name);
|
|
1378
1718
|
const sourceHash = hashSkillDir(skillPath);
|
|
1379
|
-
const destDir =
|
|
1719
|
+
const destDir = resolve7(sharedSkillsDir(), entry.name);
|
|
1380
1720
|
if (existsSync3(destDir)) {
|
|
1381
1721
|
const destHash = hashSkillDir(destDir);
|
|
1382
1722
|
if (sourceHash === destHash) continue;
|
|
@@ -1401,7 +1741,7 @@ function resolveSkillsDir(manifest) {
|
|
|
1401
1741
|
if (!manifest.skillsDir) return null;
|
|
1402
1742
|
let searchDir = dirname(new URL(import.meta.url).pathname);
|
|
1403
1743
|
for (let i = 0; i < 5; i++) {
|
|
1404
|
-
const candidate =
|
|
1744
|
+
const candidate = resolve7(searchDir, "packages", "extensions", manifest.id, "skills");
|
|
1405
1745
|
if (existsSync3(candidate)) return candidate;
|
|
1406
1746
|
searchDir = dirname(searchDir);
|
|
1407
1747
|
}
|
|
@@ -1410,30 +1750,30 @@ function resolveSkillsDir(manifest) {
|
|
|
1410
1750
|
return null;
|
|
1411
1751
|
}
|
|
1412
1752
|
function discoverBuiltinExtensions() {
|
|
1413
|
-
return [src_default, src_default2];
|
|
1753
|
+
return [src_default, src_default2, src_default3];
|
|
1414
1754
|
}
|
|
1415
1755
|
async function discoverInstalledExtensions() {
|
|
1416
|
-
const
|
|
1756
|
+
const results = [];
|
|
1417
1757
|
const packages = readExtensionsConfig();
|
|
1418
|
-
const npmDir =
|
|
1758
|
+
const npmDir = resolve7(voluteHome(), "extensions", "_npm");
|
|
1419
1759
|
const { createRequire } = await import("module");
|
|
1420
1760
|
for (const pkg of packages) {
|
|
1421
1761
|
try {
|
|
1422
1762
|
let resolved = pkg;
|
|
1423
|
-
const npmPkgDir =
|
|
1763
|
+
const npmPkgDir = resolve7(npmDir, "node_modules", pkg);
|
|
1424
1764
|
if (existsSync3(npmPkgDir)) {
|
|
1425
|
-
const require2 = createRequire(
|
|
1765
|
+
const require2 = createRequire(resolve7(npmDir, "noop.js"));
|
|
1426
1766
|
resolved = require2.resolve(pkg);
|
|
1427
1767
|
}
|
|
1428
1768
|
const mod = await import(resolved);
|
|
1429
1769
|
const manifest = mod.default ?? mod.extension ?? mod;
|
|
1430
1770
|
if (!validateManifest(manifest, `package ${pkg}`)) continue;
|
|
1431
|
-
|
|
1771
|
+
results.push({ manifest, package: pkg });
|
|
1432
1772
|
} catch (err) {
|
|
1433
1773
|
logger_default.error(`failed to load extension package: ${pkg}`, logger_default.errorData(err));
|
|
1434
1774
|
}
|
|
1435
1775
|
}
|
|
1436
|
-
return
|
|
1776
|
+
return results;
|
|
1437
1777
|
}
|
|
1438
1778
|
function validateManifest(manifest, source) {
|
|
1439
1779
|
if (!manifest || typeof manifest !== "object") {
|
|
@@ -1475,8 +1815,8 @@ async function discoverLocalExtensions() {
|
|
|
1475
1815
|
return [];
|
|
1476
1816
|
}
|
|
1477
1817
|
for (const dir of entries) {
|
|
1478
|
-
const extDir =
|
|
1479
|
-
const candidates = [
|
|
1818
|
+
const extDir = resolve7(baseDir, dir);
|
|
1819
|
+
const candidates = [resolve7(extDir, "src", "index.js"), resolve7(extDir, "index.js")];
|
|
1480
1820
|
const entryPoint = candidates.find((p) => existsSync3(p));
|
|
1481
1821
|
if (!entryPoint) continue;
|
|
1482
1822
|
try {
|
|
@@ -1495,14 +1835,25 @@ async function loadAllExtensions(app, authMw) {
|
|
|
1495
1835
|
const builtins = discoverBuiltinExtensions();
|
|
1496
1836
|
const installed = await discoverInstalledExtensions();
|
|
1497
1837
|
const local = await discoverLocalExtensions();
|
|
1498
|
-
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
|
+
];
|
|
1499
1844
|
const seen = /* @__PURE__ */ new Set();
|
|
1500
|
-
for (const
|
|
1845
|
+
for (const entry of all) {
|
|
1846
|
+
const { manifest } = entry;
|
|
1501
1847
|
if (seen.has(manifest.id)) {
|
|
1502
1848
|
logger_default.warn(`duplicate extension ID: ${manifest.id}, skipping`);
|
|
1503
1849
|
continue;
|
|
1504
1850
|
}
|
|
1505
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
|
+
}
|
|
1506
1857
|
try {
|
|
1507
1858
|
await loadExtension(manifest, app, authMw);
|
|
1508
1859
|
} catch (err) {
|
|
@@ -1547,6 +1898,114 @@ function getLoadedExtensions() {
|
|
|
1547
1898
|
};
|
|
1548
1899
|
});
|
|
1549
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
|
+
}
|
|
1550
2009
|
function getExtensionStandardSkills() {
|
|
1551
2010
|
const skills = [];
|
|
1552
2011
|
for (const { manifest } of loaded) {
|
|
@@ -1586,6 +2045,7 @@ function notifyExtensionsDaemonStop() {
|
|
|
1586
2045
|
}
|
|
1587
2046
|
}
|
|
1588
2047
|
loaded.length = 0;
|
|
2048
|
+
discovered.length = 0;
|
|
1589
2049
|
}
|
|
1590
2050
|
function notifyExtensionsMindStart(mindName) {
|
|
1591
2051
|
for (const { manifest } of loaded) {
|
|
@@ -1607,20 +2067,15 @@ function notifyExtensionsMindStop(mindName) {
|
|
|
1607
2067
|
}
|
|
1608
2068
|
|
|
1609
2069
|
export {
|
|
1610
|
-
createTurn,
|
|
1611
|
-
getActiveTurnId,
|
|
1612
|
-
trackToolUse,
|
|
1613
|
-
getLastToolUseEventId,
|
|
1614
|
-
assignSession,
|
|
1615
|
-
completeTurn,
|
|
1616
|
-
setSummaryEventId,
|
|
1617
|
-
completeOrphanedTurns,
|
|
1618
|
-
clearMind,
|
|
1619
2070
|
readSystemsConfig,
|
|
1620
2071
|
writeSystemsConfig,
|
|
1621
2072
|
deleteSystemsConfig,
|
|
1622
2073
|
loadAllExtensions,
|
|
1623
2074
|
getLoadedExtensions,
|
|
2075
|
+
getAllDiscoveredExtensions,
|
|
2076
|
+
setExtensionEnabled,
|
|
2077
|
+
installNpmExtension,
|
|
2078
|
+
uninstallNpmExtension,
|
|
1624
2079
|
getExtensionStandardSkills,
|
|
1625
2080
|
notifyExtensionsDaemonStart,
|
|
1626
2081
|
notifyExtensionsDaemonStop,
|