volute 0.32.0 → 0.33.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.
Files changed (97) hide show
  1. package/README.md +16 -0
  2. package/dist/{activity-events-HETAODOK.js → activity-events-XJO3P4RR.js} +1 -1
  3. package/dist/{ai-service-ZIPCV3MX.js → ai-service-SBY2WG7O.js} +2 -2
  4. package/dist/api.d.ts +666 -848
  5. package/dist/{auth-6DMGES3I.js → auth-GKCDSO4T.js} +2 -2
  6. package/dist/{chat-XT4OBJBU.js → chat-U5ZOME3O.js} +8 -8
  7. package/dist/{chunk-QBQ424EM.js → chunk-3Z2DPESO.js} +457 -203
  8. package/dist/chunk-6LXAAQ43.js +22 -0
  9. package/dist/{spirit-N4W4UQRH.js → chunk-7J3HEVR7.js} +12 -9
  10. package/dist/{chunk-WKF5FEFK.js → chunk-A2A4KLFE.js} +54 -155
  11. package/dist/{chunk-D5G5YOPL.js → chunk-C7I35G4R.js} +3 -3
  12. package/dist/{chunk-SX5TKJBZ.js → chunk-GY5HBI7A.js} +1 -1
  13. package/dist/{chunk-2FLJ63GU.js → chunk-JUKK7FPS.js} +1 -1
  14. package/dist/{chunk-TDRYEPH4.js → chunk-JYVGHWEJ.js} +2 -2
  15. package/dist/chunk-KIEPMIM5.js +59 -0
  16. package/dist/{chunk-QZANELPX.js → chunk-KVK2DLWI.js} +1 -0
  17. package/dist/{chunk-R7E6CRVQ.js → chunk-LOEJ4HPQ.js} +1 -1
  18. package/dist/{chunk-TSXLLQZW.js → chunk-N432I7QH.js} +9 -0
  19. package/dist/{chunk-LSGWR54X.js → chunk-NNB4WIG7.js} +1 -1
  20. package/dist/{chunk-JJ7W6WSB.js → chunk-NPKSDYA2.js} +2 -2
  21. package/dist/chunk-OYAKCAVY.js +29 -0
  22. package/dist/{chunk-IYDIE3HG.js → chunk-QTUVYI7W.js} +1 -1
  23. package/dist/{chunk-S6NFERDC.js → chunk-RVGLDGMI.js} +1 -1
  24. package/dist/{chunk-LGB6JBHI.js → chunk-VH33ZWMW.js} +4 -54
  25. package/dist/cli.js +26 -18
  26. package/dist/{clock-2UOZ6JPU.js → clock-BVH3V6E3.js} +5 -5
  27. package/dist/{cloud-sync-JN3NWKEM.js → cloud-sync-4NWLMFVH.js} +15 -11
  28. package/dist/{conversations-3O5O6AS3.js → conversations-AWI5SZW2.js} +2 -2
  29. package/dist/{create-WBBYI6V7.js → create-2FK7Z46Y.js} +1 -1
  30. package/dist/{create-RNLNCORE.js → create-YWD2TIP4.js} +4 -4
  31. package/dist/{daemon-restart-NGFHFAUF.js → daemon-restart-GOBUKLX7.js} +6 -5
  32. package/dist/daemon.js +1182 -1031
  33. package/dist/delivery-manager-PFAKEJTC.js +32 -0
  34. package/dist/{down-TB3ESMNP.js → down-FWWTEKXM.js} +4 -3
  35. package/dist/{extension-FQ5D3NCC.js → extension-OBTGKQQD.js} +2 -1
  36. package/dist/{extensions-GDYWQXC4.js → extensions-KYNTVTMO.js} +7 -6
  37. package/dist/isolation-LLAYQYDY.js +22 -0
  38. package/dist/message-delivery-DFF5SJRM.js +42 -0
  39. package/dist/{mind-2B6M7Y25.js → mind-IOJFLEM5.js} +13 -7
  40. package/dist/{mind-activity-tracker-NZZT2NTT.js → mind-activity-tracker-F6O4Q2SL.js} +2 -2
  41. package/dist/mind-manager-NBJF5D26.js +32 -0
  42. package/dist/mind-profile-P67FEHOY.js +47 -0
  43. package/dist/mind-service-2MQ6UK5N.js +38 -0
  44. package/dist/{package-PK6JUFL3.js → package-U3VFO273.js} +2 -1
  45. package/dist/read-stdin-HQJ7774D.js +8 -0
  46. package/dist/{sandbox-JANNTX6U.js → sandbox-GJOK4QLQ.js} +2 -2
  47. package/dist/scheduler-ZZ7XGQG6.js +32 -0
  48. package/dist/seed-QDYVLG74.js +11 -0
  49. package/dist/seed-check-S2IX25RL.js +32 -0
  50. package/dist/seed-cmd-DKOUFEAU.js +36 -0
  51. package/dist/{seed-ALUQ55FF.js → seed-create-4XBBOLRH.js} +5 -5
  52. package/dist/{sprout-L2GFOVF7.js → seed-sprout-GQEIIQRT.js} +19 -6
  53. package/dist/{send-3MI36LEF.js → send-QIV2INHB.js} +51 -49
  54. package/dist/{setup-SZIARWI6.js → setup-TISPCO22.js} +3 -1
  55. package/dist/{setup-WENLVPVP.js → setup-XMCBE3LF.js} +7 -5
  56. package/dist/skills/imagegen/SKILL.md +11 -7
  57. package/dist/skills/imagegen/scripts/imagegen.ts +146 -25
  58. package/dist/skills/orientation/SKILL.md +9 -2
  59. package/dist/skills/seed-nurture/SKILL.md +42 -0
  60. package/dist/skills/volute-mind/SKILL.md +4 -0
  61. package/dist/{skills-XNZK6P4K.js → skills-7FV7EJTE.js} +4 -3
  62. package/dist/sleep-manager-JTXSN7NV.js +36 -0
  63. package/dist/spirit-VRONKFMF.js +23 -0
  64. package/dist/sprout-WKLZXUIQ.js +11 -0
  65. package/dist/{status-TCUMUO6M.js → status-3JBTFSMI.js} +3 -2
  66. package/dist/{system-chat-NPYFYZVI.js → system-chat-JAPOJ3KE.js} +15 -11
  67. package/dist/{systems-DHBKVYEY.js → systems-XRI52VCH.js} +2 -2
  68. package/dist/{up-6I6BHRTO.js → up-M5AS6SBV.js} +5 -4
  69. package/dist/{update-QVPRF6GR.js → update-UD543CXX.js} +3 -2
  70. package/dist/{version-notify-TCKWBZZG.js → version-notify-NBI2MTJO.js} +18 -15
  71. package/dist/volute-config-HD7WWUQC.js +10 -0
  72. package/dist/web-assets/assets/index-CWJrVveV.css +1 -0
  73. package/dist/web-assets/assets/index-DJt14FRI.js +75 -0
  74. package/dist/web-assets/index.html +2 -2
  75. package/package.json +2 -1
  76. package/templates/claude/src/lib/stream-consumer.ts +38 -0
  77. package/templates/codex/src/agent.ts +1 -0
  78. package/dist/delivery-manager-SDVXFD4W.js +0 -28
  79. package/dist/message-delivery-2FIM7QKO.js +0 -32
  80. package/dist/mind-manager-BNCMGYXW.js +0 -28
  81. package/dist/mind-service-AV273WT4.js +0 -34
  82. package/dist/sleep-manager-53DZOWW7.js +0 -32
  83. package/dist/web-assets/assets/index-Bui7U9Uu.css +0 -1
  84. package/dist/web-assets/assets/index-e36DIo1b.js +0 -73
  85. package/dist/{accept-74M7I4RZ.js → accept-D5VBM7JW.js} +3 -3
  86. package/dist/{bridge-BVCBTGPF.js → bridge-TXWWPPOJ.js} +3 -3
  87. package/dist/{env-RLYQBOOP.js → env-JCOF2222.js} +3 -3
  88. package/dist/{files-EAMPO2SJ.js → files-65PMW5IK.js} +3 -3
  89. package/dist/{history-FO5PHBQ5.js → history-DKCDI3JO.js} +3 -3
  90. package/dist/{list-DW2VRTOZ.js → list-JQ463EDA.js} +3 -3
  91. package/dist/{login-7CHPW2PN.js → login-D7ETSU4R.js} +3 -3
  92. package/dist/{mind-sleep-B7BHJLH7.js → mind-sleep-WW2IX7JT.js} +3 -3
  93. package/dist/{mind-wake-GY3RFX7Y.js → mind-wake-VSSGW465.js} +3 -3
  94. package/dist/{read-5AMJRO3D.js → read-EBY56C33.js} +3 -3
  95. package/dist/{register-V2JZZKFK.js → register-HD74C4TT.js} +3 -3
  96. package/dist/{reject-33HEZMZ4.js → reject-UJKFBHRO.js} +3 -3
  97. package/dist/{skill-TUVOTW4Z.js → skill-PSQGRRJX.js} +3 -3
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/read-stdin.ts
4
+ import { isatty } from "tty";
5
+ async function readStdin() {
6
+ if (isatty(0)) return void 0;
7
+ const chunks = [];
8
+ try {
9
+ for await (const chunk of process.stdin) {
10
+ chunks.push(chunk);
11
+ }
12
+ } catch (err) {
13
+ console.error(`Failed to read from stdin: ${err instanceof Error ? err.message : String(err)}`);
14
+ process.exit(1);
15
+ }
16
+ const text = Buffer.concat(chunks).toString().replace(/\r?\n$/, "");
17
+ return text || void 0;
18
+ }
19
+
20
+ export {
21
+ readStdin
22
+ };
@@ -8,28 +8,26 @@ import {
8
8
  import {
9
9
  getSharedSkill,
10
10
  installSkill
11
- } from "./chunk-D5G5YOPL.js";
11
+ } from "./chunk-C7I35G4R.js";
12
12
  import {
13
13
  qualifyModelId,
14
14
  resolveTemplate
15
- } from "./chunk-IYDIE3HG.js";
15
+ } from "./chunk-QTUVYI7W.js";
16
16
  import {
17
17
  logger_default
18
18
  } from "./chunk-YUIHSKR6.js";
19
19
  import {
20
20
  exec
21
- } from "./chunk-LGB6JBHI.js";
21
+ } from "./chunk-KIEPMIM5.js";
22
22
  import {
23
23
  readGlobalConfig
24
- } from "./chunk-TSXLLQZW.js";
24
+ } from "./chunk-N432I7QH.js";
25
25
  import {
26
26
  addSpirit,
27
27
  findMind,
28
28
  nextPort,
29
29
  voluteSystemDir
30
30
  } from "./chunk-LRCG2JLP.js";
31
- import "./chunk-RPZZSXV3.js";
32
- import "./chunk-K3NQKI34.js";
33
31
 
34
32
  // src/lib/spirit.ts
35
33
  import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
@@ -81,7 +79,7 @@ async function ensureSpiritProject() {
81
79
  } catch (err) {
82
80
  slog.warn("git init failed for spirit \u2014 not critical", logger_default.errorData(err));
83
81
  }
84
- const spiritSkills = ["volute-admin", "orientation", "memory"];
82
+ const spiritSkills = ["volute-admin", "orientation", "memory", "seed-nurture"];
85
83
  for (const skillId of spiritSkills) {
86
84
  try {
87
85
  const shared = await getSharedSkill(skillId);
@@ -92,6 +90,10 @@ async function ensureSpiritProject() {
92
90
  slog.warn(`failed to install skill ${skillId} for spirit`, logger_default.errorData(err));
93
91
  }
94
92
  }
93
+ const { createMindUser, chownMindDir, ensureVoluteGroup } = await import("./isolation-LLAYQYDY.js");
94
+ ensureVoluteGroup();
95
+ createMindUser("volute", resolve(dir, "home"));
96
+ chownMindDir(dir, "volute");
95
97
  const port = await nextPort();
96
98
  await addSpirit("volute", port, template, dir);
97
99
  slog.info("spirit project created");
@@ -209,9 +211,10 @@ When helping humans create minds:
209
211
  - You maintain separate context per conversation
210
212
  `;
211
213
  }
214
+
212
215
  export {
213
- ensureSpiritProject,
214
- getSpiritModel,
215
216
  spiritDir,
217
+ getSpiritModel,
218
+ ensureSpiritProject,
216
219
  syncSpiritTemplate
217
220
  };
@@ -9,27 +9,23 @@ import {
9
9
  import {
10
10
  getUser,
11
11
  getUserByUsername
12
- } from "./chunk-TDRYEPH4.js";
12
+ } from "./chunk-JYVGHWEJ.js";
13
13
  import {
14
14
  publish
15
- } from "./chunk-QZANELPX.js";
15
+ } from "./chunk-KVK2DLWI.js";
16
16
  import {
17
17
  hashSkillDir,
18
18
  importSkillFromDir,
19
19
  sharedSkillsDir
20
- } from "./chunk-D5G5YOPL.js";
20
+ } from "./chunk-C7I35G4R.js";
21
21
  import {
22
22
  logger_default
23
23
  } from "./chunk-YUIHSKR6.js";
24
24
  import {
25
- getDb,
26
25
  mindDir,
27
26
  voluteHome,
28
27
  voluteSystemDir
29
28
  } from "./chunk-LRCG2JLP.js";
30
- import {
31
- turns
32
- } from "./chunk-RPZZSXV3.js";
33
29
 
34
30
  // src/lib/extensions.ts
35
31
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
@@ -299,7 +295,7 @@ function createCommands() {
299
295
  return {
300
296
  write: {
301
297
  description: "Write a new note",
302
- usage: 'volute notes write "title" "content" [--reply-to author/slug]',
298
+ usage: 'volute notes write "title" ["content"] [--reply-to author/slug] (content can be piped via stdin)',
303
299
  handler: async (args, ctx) => {
304
300
  if (!ctx.db) return { error: "Notes extension requires a database" };
305
301
  const mindName = ctx.mindName;
@@ -307,7 +303,7 @@ function createCommands() {
307
303
  const user = await ctx.getUserByUsername(mindName);
308
304
  if (!user) return { error: `Unknown mind: ${mindName}` };
309
305
  const title = args[0];
310
- const content = args[1];
306
+ const content = args[1] ?? ctx.stdin;
311
307
  if (!title || !content)
312
308
  return { error: 'Usage: volute notes write "title" "content" [--reply-to author/slug]' };
313
309
  let replyToId;
@@ -381,7 +377,7 @@ Comments (${note.comments.length}):`);
381
377
  },
382
378
  comment: {
383
379
  description: "Comment on a note",
384
- usage: 'volute notes comment <author/slug> "content"',
380
+ usage: 'volute notes comment <author/slug> ["content"] (content can be piped via stdin)',
385
381
  handler: async (args, ctx) => {
386
382
  if (!ctx.db) return { error: "Notes extension requires a database" };
387
383
  const mindName = ctx.mindName;
@@ -389,7 +385,7 @@ Comments (${note.comments.length}):`);
389
385
  const user = await ctx.getUserByUsername(mindName);
390
386
  if (!user) return { error: `Unknown mind: ${mindName}` };
391
387
  const ref = args[0];
392
- const content = args[1];
388
+ const content = args[1] ?? ctx.stdin;
393
389
  if (!ref || !ref.includes("/") || !content) {
394
390
  return { error: 'Usage: volute notes comment <author/slug> "content"' };
395
391
  }
@@ -528,19 +524,16 @@ function createRoutes(ctx) {
528
524
  replyToId = id;
529
525
  }
530
526
  const note = await createNote(db, getUser2, actor.id, body.title, body.content, replyToId);
531
- ctx.publishActivity(
532
- {
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
- },
542
- c
543
- );
527
+ ctx.publishActivity({
528
+ type: "note_created",
529
+ mind: actor.username,
530
+ summary: `${actor.username} wrote "${body.title}"`,
531
+ metadata: {
532
+ author: actor.username,
533
+ slug: note.slug,
534
+ bodyHtml: body.content.slice(0, 500)
535
+ }
536
+ });
544
537
  return c.json(note, 201);
545
538
  }).get("/:author/:slug", async (c) => {
546
539
  const { author, slug } = c.req.param();
@@ -629,6 +622,7 @@ var src_default = createExtension({
629
622
  version: "0.1.0",
630
623
  description: "Public notes for sharing thoughts, reflections, and ideas",
631
624
  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>',
625
+ color: "yellow",
632
626
  routes: (ctx) => createRoutes(ctx),
633
627
  commands: createCommands(),
634
628
  initDb: initDb2,
@@ -1012,6 +1006,7 @@ var src_default2 = createExtension({
1012
1006
  skillsDir: skillsDir2,
1013
1007
  standardSkill: true,
1014
1008
  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>',
1009
+ color: "purple",
1015
1010
  ui: {
1016
1011
  assetsDir: assetsDir2,
1017
1012
  systemSection: {
@@ -1026,115 +1021,6 @@ var src_default2 = createExtension({
1026
1021
  }
1027
1022
  });
1028
1023
 
1029
- // src/lib/daemon/turn-tracker.ts
1030
- import { randomUUID } from "crypto";
1031
- import { eq } from "drizzle-orm";
1032
- var tlog = logger_default.child("turn-tracker");
1033
- var activeTurns = /* @__PURE__ */ new Map();
1034
- function key(mind, session) {
1035
- return `${mind}:${session ?? "*"}`;
1036
- }
1037
- async function createTurn(mind) {
1038
- const k = key(mind);
1039
- const existing = activeTurns.get(k);
1040
- if (existing) return existing.turnId;
1041
- const turnId = randomUUID();
1042
- const entry = { turnId, lastToolUseEventId: void 0 };
1043
- activeTurns.set(k, entry);
1044
- try {
1045
- const db = await getDb();
1046
- await db.insert(turns).values({ id: turnId, mind, status: "active" });
1047
- } catch (err) {
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;
1053
- }
1054
- function getActiveTurnId(mind, session) {
1055
- return (activeTurns.get(key(mind, session)) ?? activeTurns.get(key(mind)))?.turnId;
1056
- }
1057
- function trackToolUse(mind, session, eventId) {
1058
- const entry = activeTurns.get(key(mind, session)) ?? activeTurns.get(key(mind));
1059
- if (entry) entry.lastToolUseEventId = eventId;
1060
- }
1061
- function getLastToolUseEventId(mind, session) {
1062
- return (activeTurns.get(key(mind, session)) ?? activeTurns.get(key(mind)))?.lastToolUseEventId;
1063
- }
1064
- async function assignSession(mind, turnId, session) {
1065
- const wildcardKey = key(mind);
1066
- const entry = activeTurns.get(wildcardKey);
1067
- if (!entry || entry.turnId !== turnId) {
1068
- tlog.warn(`assignSession: no matching turn for ${mind} (turnId=${turnId}, session=${session})`);
1069
- return;
1070
- }
1071
- try {
1072
- const db = await getDb();
1073
- await db.update(turns).set({ session }).where(eq(turns.id, turnId));
1074
- } catch (err) {
1075
- tlog.error(`failed to assign session to turn ${turnId}`, logger_default.errorData(err));
1076
- return;
1077
- }
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
- try {
1087
- const db = await getDb();
1088
- await db.update(turns).set({ status: "complete" }).where(eq(turns.id, entry.turnId));
1089
- } catch (err) {
1090
- tlog.error(`failed to complete turn ${entry.turnId}`, logger_default.errorData(err));
1091
- return void 0;
1092
- }
1093
- activeTurns.delete(k);
1094
- activeTurns.delete(wildcardKey);
1095
- return entry.turnId;
1096
- }
1097
- async function setSummaryEventId(turnId, summaryEventId) {
1098
- try {
1099
- const db = await getDb();
1100
- await db.update(turns).set({ summary_event_id: summaryEventId }).where(eq(turns.id, turnId));
1101
- } catch (err) {
1102
- tlog.error(`failed to set summary event for turn ${turnId}`, logger_default.errorData(err));
1103
- }
1104
- }
1105
- async function completeOrphanedTurns() {
1106
- try {
1107
- const db = await getDb();
1108
- const active = await db.select({ id: turns.id }).from(turns).where(eq(turns.status, "active"));
1109
- if (active.length === 0) return;
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));
1114
- }
1115
- }
1116
- async function clearMind(mind) {
1117
- const toDelete = [];
1118
- const turnIds = [];
1119
- for (const [k, entry] of activeTurns.entries()) {
1120
- if (k.startsWith(`${mind}:`)) {
1121
- turnIds.push(entry.turnId);
1122
- toDelete.push(k);
1123
- }
1124
- }
1125
- for (const k of toDelete) activeTurns.delete(k);
1126
- if (turnIds.length > 0) {
1127
- try {
1128
- const db = await getDb();
1129
- for (const id of turnIds) {
1130
- await db.update(turns).set({ status: "complete" }).where(eq(turns.id, id));
1131
- }
1132
- } catch (err) {
1133
- tlog.error(`failed to complete orphaned turns for ${mind}`, logger_default.errorData(err));
1134
- }
1135
- }
1136
- }
1137
-
1138
1024
  // src/lib/systems-config.ts
1139
1025
  import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
1140
1026
  import { resolve as resolve5 } from "path";
@@ -1237,15 +1123,16 @@ async function buildContext(manifest, dataDir, authMw) {
1237
1123
  },
1238
1124
  getUser: async (id) => getUser(id),
1239
1125
  getUserByUsername: async (username) => getUserByUsername(username),
1240
- publishActivity: (event, sessionOrContext) => {
1241
- const session = typeof sessionOrContext === "string" ? sessionOrContext : sessionOrContext?.get("mindSession");
1242
- const turnId = getActiveTurnId(event.mind, session);
1243
- const sourceEventId = getLastToolUseEventId(event.mind, session);
1244
- publish({
1126
+ publishActivity: (event) => {
1127
+ const enriched = {
1245
1128
  ...event,
1246
- turn_id: turnId,
1247
- source_event_id: sourceEventId
1248
- }).catch(
1129
+ metadata: {
1130
+ ...event.metadata,
1131
+ ...manifest.icon && !event.metadata?.icon ? { icon: manifest.icon } : {},
1132
+ ...manifest.color && !event.metadata?.color ? { color: manifest.color } : {}
1133
+ }
1134
+ };
1135
+ publish(enriched).catch(
1249
1136
  (err) => logger_default.error(`extension ${manifest.id}: failed to publish activity`, logger_default.errorData(err))
1250
1137
  );
1251
1138
  },
@@ -1295,15 +1182,36 @@ async function loadExtension(manifest, app, authMw) {
1295
1182
  const mindName = body.mind || user?.username;
1296
1183
  const session = c.get("mindSession");
1297
1184
  try {
1185
+ const activityPromises = [];
1298
1186
  const result = await cmd.handler(body.args ?? [], {
1299
1187
  ...context,
1300
- // Bind publishActivity to the session so command handlers
1301
- // don't need to pass it explicitly
1302
- publishActivity: (event, sc) => context.publishActivity(event, sc ?? session),
1188
+ publishActivity: (rawEvent) => {
1189
+ const event = {
1190
+ ...rawEvent,
1191
+ metadata: {
1192
+ ...rawEvent.metadata,
1193
+ ...manifest.icon && !rawEvent.metadata?.icon ? { icon: manifest.icon } : {},
1194
+ ...manifest.color && !rawEvent.metadata?.color ? { color: manifest.color } : {}
1195
+ }
1196
+ };
1197
+ activityPromises.push(
1198
+ publish(event).catch((err) => {
1199
+ logger_default.error(
1200
+ `extension ${manifest.id}: failed to publish activity`,
1201
+ logger_default.errorData(err)
1202
+ );
1203
+ return 0;
1204
+ })
1205
+ );
1206
+ },
1303
1207
  mindName,
1304
- session
1208
+ session,
1209
+ stdin: body.stdin
1305
1210
  });
1306
- return c.json(result);
1211
+ const activityIds = (await Promise.all(activityPromises)).filter((id) => id > 0);
1212
+ const markers = activityIds.map((id) => `[volute:activity:${id}]`).join("");
1213
+ const output = result && typeof result === "object" && "output" in result ? { ...result, output: `${result.output}${markers}` } : markers ? { ...result, output: markers } : result;
1214
+ return c.json(output);
1307
1215
  } catch (err) {
1308
1216
  logger_default.error(`extension command ${manifest.id}/${cmdName} failed`, logger_default.errorData(err));
1309
1217
  return c.json({ error: err.message }, 500);
@@ -1607,15 +1515,6 @@ function notifyExtensionsMindStop(mindName) {
1607
1515
  }
1608
1516
 
1609
1517
  export {
1610
- createTurn,
1611
- getActiveTurnId,
1612
- trackToolUse,
1613
- getLastToolUseEventId,
1614
- assignSession,
1615
- completeTurn,
1616
- setSummaryEventId,
1617
- completeOrphanedTurns,
1618
- clearMind,
1619
1518
  readSystemsConfig,
1620
1519
  writeSystemsConfig,
1621
1520
  deleteSystemsConfig,
@@ -5,11 +5,11 @@ import {
5
5
  import {
6
6
  exec,
7
7
  gitExec
8
- } from "./chunk-LGB6JBHI.js";
8
+ } from "./chunk-KIEPMIM5.js";
9
9
  import {
10
10
  readGlobalConfig,
11
11
  writeGlobalConfig
12
- } from "./chunk-TSXLLQZW.js";
12
+ } from "./chunk-N432I7QH.js";
13
13
  import {
14
14
  getDb,
15
15
  voluteHome
@@ -44,7 +44,7 @@ async function initDefaultSkills() {
44
44
  const config = readGlobalConfig();
45
45
  let extensionSkills = [];
46
46
  try {
47
- const { getExtensionStandardSkills } = await import("./extensions-GDYWQXC4.js");
47
+ const { getExtensionStandardSkills } = await import("./extensions-KYNTVTMO.js");
48
48
  extensionSkills = getExtensionStandardSkills();
49
49
  } catch (err) {
50
50
  logger_default.warn("failed to load extension standard skills during init", logger_default.errorData(err));
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-YUIHSKR6.js";
5
5
  import {
6
6
  readGlobalConfig
7
- } from "./chunk-TSXLLQZW.js";
7
+ } from "./chunk-N432I7QH.js";
8
8
  import {
9
9
  getBaseName,
10
10
  readRegistry,
@@ -5,7 +5,7 @@ import {
5
5
  pollHealthDown,
6
6
  readDaemonConfig,
7
7
  stopService
8
- } from "./chunk-LSGWR54X.js";
8
+ } from "./chunk-NNB4WIG7.js";
9
9
  import {
10
10
  voluteSystemDir
11
11
  } from "./chunk-LRCG2JLP.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  broadcast
4
- } from "./chunk-QZANELPX.js";
4
+ } from "./chunk-KVK2DLWI.js";
5
5
  import {
6
6
  getDb
7
7
  } from "./chunk-LRCG2JLP.js";
@@ -99,7 +99,7 @@ async function getOrCreateSystemUser() {
99
99
  password_hash: "!system",
100
100
  role: "system",
101
101
  user_type: "system",
102
- display_name: "Volute"
102
+ display_name: "volute"
103
103
  }).returning(userSelectFields);
104
104
  return result;
105
105
  } catch (err) {
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ wrapForIsolation
4
+ } from "./chunk-VH33ZWMW.js";
5
+
6
+ // src/lib/exec.ts
7
+ import { execFile as execFileCb, execFileSync, spawn } from "child_process";
8
+ async function exec(cmd, args, options) {
9
+ const [wrappedCmd, wrappedArgs] = options?.mindName ? await wrapForIsolation(cmd, args, options.mindName) : [cmd, args];
10
+ return new Promise((resolve, reject) => {
11
+ execFileCb(
12
+ wrappedCmd,
13
+ wrappedArgs,
14
+ { cwd: options?.cwd, env: options?.env },
15
+ (err, stdout, stderr) => {
16
+ if (err) {
17
+ err.stderr = stderr;
18
+ err.stdout = stdout;
19
+ reject(err);
20
+ } else {
21
+ resolve(stdout);
22
+ }
23
+ }
24
+ );
25
+ });
26
+ }
27
+ function gitExec(args, options) {
28
+ const fullArgs = process.env.VOLUTE_ISOLATION === "user" ? ["-c", "safe.directory=*", ...args] : args;
29
+ return exec("git", fullArgs, options);
30
+ }
31
+ function resolveVoluteBin() {
32
+ try {
33
+ return execFileSync("which", ["volute"], { encoding: "utf-8" }).trim();
34
+ } catch {
35
+ throw new Error("Could not find volute binary on PATH");
36
+ }
37
+ }
38
+ async function execInherit(cmd, args, options) {
39
+ const [wrappedCmd, wrappedArgs] = options?.mindName ? await wrapForIsolation(cmd, args, options.mindName) : [cmd, args];
40
+ return new Promise((resolve, reject) => {
41
+ const child = spawn(wrappedCmd, wrappedArgs, {
42
+ cwd: options?.cwd,
43
+ env: options?.env,
44
+ stdio: "inherit"
45
+ });
46
+ child.on("error", reject);
47
+ child.on("close", (code) => {
48
+ if (code === 0) resolve();
49
+ else reject(new Error(`${cmd} ${args.join(" ")} exited with code ${code}`));
50
+ });
51
+ });
52
+ }
53
+
54
+ export {
55
+ exec,
56
+ gitExec,
57
+ resolveVoluteBin,
58
+ execInherit
59
+ };
@@ -37,6 +37,7 @@ async function publish(event) {
37
37
  }
38
38
  const full = { ...event, id, created_at };
39
39
  notify(full);
40
+ return id;
40
41
  }
41
42
  function broadcast(event) {
42
43
  const created_at = event.created_at ?? (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  publish
4
- } from "./chunk-QZANELPX.js";
4
+ } from "./chunk-KVK2DLWI.js";
5
5
  import {
6
6
  logger_default
7
7
  } from "./chunk-YUIHSKR6.js";
@@ -29,6 +29,14 @@ function isSetupComplete() {
29
29
  const config = readGlobalConfig();
30
30
  return config.setupCompleted === true;
31
31
  }
32
+ function isImagegenEnabled() {
33
+ const config = readGlobalConfig();
34
+ const ig = config.imagegen;
35
+ if (!ig) return false;
36
+ if (ig.enabled === true) return true;
37
+ if (ig.providers && Object.keys(ig.providers).length > 0) return true;
38
+ return false;
39
+ }
32
40
  function migrateSetupCompleted() {
33
41
  const config = readGlobalConfig();
34
42
  if (config.setup != null && config.setupCompleted == null) {
@@ -42,5 +50,6 @@ export {
42
50
  readGlobalConfig,
43
51
  writeGlobalConfig,
44
52
  isSetupComplete,
53
+ isImagegenEnabled,
45
54
  migrateSetupCompleted
46
55
  };
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  exec,
4
4
  execInherit
5
- } from "./chunk-LGB6JBHI.js";
5
+ } from "./chunk-KIEPMIM5.js";
6
6
  import {
7
7
  voluteSystemDir
8
8
  } from "./chunk-LRCG2JLP.js";
@@ -4,10 +4,10 @@ import {
4
4
  modeLabel,
5
5
  pollHealth,
6
6
  startService
7
- } from "./chunk-LSGWR54X.js";
7
+ } from "./chunk-NNB4WIG7.js";
8
8
  import {
9
9
  readGlobalConfig
10
- } from "./chunk-TSXLLQZW.js";
10
+ } from "./chunk-N432I7QH.js";
11
11
  import {
12
12
  parseArgs
13
13
  } from "./chunk-D424ZQGI.js";
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/volute-config.ts
4
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
5
+ import { dirname, resolve } from "path";
6
+ function readJson(path) {
7
+ if (!existsSync(path)) return null;
8
+ try {
9
+ return JSON.parse(readFileSync(path, "utf-8"));
10
+ } catch (err) {
11
+ console.error(`[volute-config] failed to parse ${path}: ${err}`);
12
+ return null;
13
+ }
14
+ }
15
+ function readVoluteConfig(mindDir) {
16
+ const path = resolve(mindDir, "home/.config/volute.json");
17
+ return readJson(path);
18
+ }
19
+ function writeVoluteConfig(mindDir, config) {
20
+ const path = resolve(mindDir, "home/.config/volute.json");
21
+ mkdirSync(dirname(path), { recursive: true });
22
+ writeFileSync(path, `${JSON.stringify(config, null, 2)}
23
+ `);
24
+ }
25
+
26
+ export {
27
+ readVoluteConfig,
28
+ writeVoluteConfig
29
+ };
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  readGlobalConfig,
7
7
  writeGlobalConfig
8
- } from "./chunk-TSXLLQZW.js";
8
+ } from "./chunk-N432I7QH.js";
9
9
 
10
10
  // src/lib/ai-service.ts
11
11
  import { complete, getEnvApiKey, getModel, getModels, getProviders } from "@mariozechner/pi-ai";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  subscribe
4
- } from "./chunk-QZANELPX.js";
4
+ } from "./chunk-KVK2DLWI.js";
5
5
  import {
6
6
  logger_default
7
7
  } from "./chunk-YUIHSKR6.js";