volute 0.42.1 → 0.43.1

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 (101) hide show
  1. package/dist/{activity-events-KFKVRZE3.js → activity-events-UISXY4Y5.js} +3 -3
  2. package/dist/{ai-service-3GBOJGUA.js → ai-service-FBCCEYF5.js} +8 -4
  3. package/dist/{archive-4PPYF7LL.js → archive-I32WHCJB.js} +3 -3
  4. package/dist/{auth-EAASQPTN.js → auth-3RIG6GKN.js} +4 -4
  5. package/dist/{chat-LYL5CWJV.js → chat-SDSX5TVV.js} +2 -2
  6. package/dist/{chunk-M2YMNTHK.js → chunk-2HIVYZDB.js} +1 -1
  7. package/dist/{chunk-6G7R2LPI.js → chunk-4SVONUTC.js} +569 -379
  8. package/dist/{chunk-QPTQKGGF.js → chunk-57LBQ7JX.js} +1 -1
  9. package/dist/{chunk-K547QOCM.js → chunk-7G2EEYXX.js} +2 -2
  10. package/dist/{chunk-E7XWFDZL.js → chunk-AD46AYZQ.js} +1 -1
  11. package/dist/{chunk-P6DPB33N.js → chunk-ATEX3ZPG.js} +2 -2
  12. package/dist/{chunk-WR7CSHQ4.js → chunk-C6ESRQZ7.js} +1 -1
  13. package/dist/{chunk-JU5GWO77.js → chunk-DE4EKQFH.js} +3 -3
  14. package/dist/{chunk-JPDN6ZRJ.js → chunk-DMUPZQWP.js} +8 -8
  15. package/dist/{chunk-BR6KFHVT.js → chunk-EE7SZJON.js} +2 -2
  16. package/dist/{chunk-5QG36RV7.js → chunk-EFL5UOY4.js} +3 -3
  17. package/dist/{chunk-OEJFZHZZ.js → chunk-GZJFC76M.js} +1 -1
  18. package/dist/{chunk-KUJVCDWT.js → chunk-JVK6QQIC.js} +2 -2
  19. package/dist/{chunk-YLTIEEVL.js → chunk-K4EHBHPE.js} +3 -3
  20. package/dist/{chunk-M3T3Y6FT.js → chunk-KINGVQSR.js} +5 -5
  21. package/dist/{chunk-MHFAWJCC.js → chunk-L3IV23ZG.js} +12 -1
  22. package/dist/{chunk-VBKRZCR7.js → chunk-M3PGGSBH.js} +1 -1
  23. package/dist/{chunk-NSGIVS5T.js → chunk-O3DXAR44.js} +2 -2
  24. package/dist/{chunk-KZAL7HEQ.js → chunk-ONM2AZFL.js} +1 -1
  25. package/dist/chunk-PGPLCJPK.js +272 -0
  26. package/dist/{chunk-43K5UWF7.js → chunk-QQ2MVQU3.js} +13 -12
  27. package/dist/{chunk-62ICA2FF.js → chunk-SPLPKBES.js} +17 -1
  28. package/dist/{chunk-BLOAJLGI.js → chunk-XDTKJNW5.js} +1 -1
  29. package/dist/{chunk-BWZHX46O.js → chunk-Y6O35D7K.js} +21 -6
  30. package/dist/{chunk-IUZ3EV2D.js → chunk-YAMO2U3N.js} +1 -1
  31. package/dist/{chunk-4ZSUUBG5.js → chunk-YSV3VJQK.js} +55 -2
  32. package/dist/cli.js +15 -15
  33. package/dist/{cloud-sync-E3ZL5SZ4.js → cloud-sync-KOEBUFW5.js} +16 -17
  34. package/dist/{conversations-FSZCUUYO.js → conversations-67WWW25C.js} +4 -4
  35. package/dist/{create-ERNQYOCK.js → create-7W3LV3J7.js} +1 -1
  36. package/dist/{daemon-restart-QBZOMILO.js → daemon-restart-EZXB73W7.js} +7 -8
  37. package/dist/daemon.js +524 -433
  38. package/dist/{db-ZJJJYJBU.js → db-CZV62BOQ.js} +2 -2
  39. package/dist/delivery-manager-OMCCJEVP.js +32 -0
  40. package/dist/{delivery-router-FDBZJQOY.js → delivery-router-HXBHYHJM.js} +7 -5
  41. package/dist/down-RRNYIGCU.js +16 -0
  42. package/dist/echo-text-KFSUCZXL.js +32 -0
  43. package/dist/{exec-ZDD7NEB4.js → exec-SJK7VUTC.js} +3 -4
  44. package/dist/{export-WYI3E3TB.js → export-7LPCIB5J.js} +3 -3
  45. package/dist/{extensions-X6MKBJLB.js → extensions-PQPZT6LS.js} +16 -17
  46. package/dist/{files-COTCVBGO.js → files-LV7VAKPC.js} +3 -3
  47. package/dist/{import-EPK6WCYS.js → import-XPECCRQ7.js} +6 -6
  48. package/dist/{isolation-F7ABZMDA.js → isolation-5AYEY55P.js} +5 -3
  49. package/dist/{login-GPBXOU4D.js → login-KJSN4JDA.js} +2 -2
  50. package/dist/{logout-DNNTIYE2.js → logout-TMJGUQV2.js} +2 -2
  51. package/dist/message-delivery-HRGFNWO7.js +34 -0
  52. package/dist/{mind-K5PZHPBR.js → mind-G3WEWDM4.js} +5 -5
  53. package/dist/{mind-activity-tracker-HWVFE533.js → mind-activity-tracker-JHAT6456.js} +4 -4
  54. package/dist/mind-service-2C7AINBO.js +36 -0
  55. package/dist/{package-G4AM2IDZ.js → package-24TL6DTF.js} +1 -1
  56. package/dist/{registry-MDLNPTTI.js → registry-7LQHTRUX.js} +2 -2
  57. package/dist/{sandbox-E7RWZDVG.js → sandbox-SJH3XOPZ.js} +4 -4
  58. package/dist/scheduler-VON2TB52.js +30 -0
  59. package/dist/{schema-HHYJDG7B.js → schema-LRUTFOER.js} +1 -1
  60. package/dist/{seed-SKVSWF7S.js → seed-HHSJTM6O.js} +1 -1
  61. package/dist/{seed-cmd-D2H5VLB4.js → seed-cmd-UPQY35RG.js} +2 -2
  62. package/dist/{seed-create-BUSKQ5II.js → seed-create-MDRMXMSB.js} +3 -3
  63. package/dist/{seed-sprout-R2IZPITD.js → seed-sprout-4M7IQWMH.js} +7 -8
  64. package/dist/{send-SLDFRSTL.js → send-ZBZWIH5V.js} +3 -3
  65. package/dist/{service-ZDOSEQCE.js → service-4EVZKGSA.js} +4 -5
  66. package/dist/{service-install-2HZPDHEY.js → service-install-SHRM6NHK.js} +5 -6
  67. package/dist/{setup-IYOUPCIK.js → setup-25MMJ3J4.js} +3 -3
  68. package/dist/{setup-IU3ID7II.js → setup-ZD2LMBR3.js} +8 -10
  69. package/dist/{skills-P6RGGASV.js → skills-4KQ377GN.js} +5 -6
  70. package/dist/sleep-manager-AG5SQDDI.js +34 -0
  71. package/dist/{spirit-XLD6QXSS.js → spirit-53DQPRNR.js} +7 -8
  72. package/dist/{sprout-QV2RJM5W.js → sprout-YKM2M6D6.js} +1 -1
  73. package/dist/{src-TJ7RBU5H.js → src-3EGHHKZK.js} +8 -8
  74. package/dist/{status-QAKAPDYW.js → status-WTU2WHXY.js} +5 -6
  75. package/dist/system-chat-54RN3K67.js +34 -0
  76. package/dist/{tailscale-TIRTGM6E.js → tailscale-Q7ZJL7S7.js} +2 -2
  77. package/dist/{turn-tracker-Z5UJC4BY.js → turn-tracker-JZYERJ76.js} +7 -5
  78. package/dist/up-LFZA4GI5.js +18 -0
  79. package/dist/{update-NY3XBVAJ.js → update-EJ7Q77ZX.js} +5 -6
  80. package/dist/{update-check-OG27AL4Z.js → update-check-ACYYNKTW.js} +3 -3
  81. package/dist/{version-notify-P7UAR7HZ.js → version-notify-SRQJCQEK.js} +17 -18
  82. package/dist/web-assets/assets/{index-CrFAsfiE.js → index-DTDmEfPU.js} +2 -2
  83. package/dist/web-assets/index.html +1 -1
  84. package/drizzle/0010_delivery_queue_redrive.sql +10 -0
  85. package/drizzle/meta/_journal.json +7 -0
  86. package/package.json +1 -1
  87. package/templates/claude/src/agent.ts +8 -1
  88. package/templates/claude/src/lib/message-channel.ts +28 -6
  89. package/templates/codex/src/server.ts +2 -8
  90. package/dist/chunk-GN52E2DP.js +0 -59
  91. package/dist/chunk-TFBQESH7.js +0 -179
  92. package/dist/delivery-manager-J3BRHXUF.js +0 -33
  93. package/dist/down-DSRWSCXY.js +0 -17
  94. package/dist/echo-text-3HBQUWPX.js +0 -33
  95. package/dist/message-delivery-4ZWFDBAY.js +0 -41
  96. package/dist/mind-manager-6DSY5IJW.js +0 -33
  97. package/dist/mind-service-R2FM53KD.js +0 -37
  98. package/dist/scheduler-2FKQCRCM.js +0 -31
  99. package/dist/sleep-manager-F5KWIFOX.js +0 -35
  100. package/dist/system-chat-EQIH6JVL.js +0 -35
  101. package/dist/up-ZTPGC7HB.js +0 -19
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-T2TP6ZC6.js";
5
5
  import {
6
6
  voluteSystemDir
7
- } from "./chunk-IUZ3EV2D.js";
7
+ } from "./chunk-YAMO2U3N.js";
8
8
 
9
9
  // packages/daemon/src/lib/bridges/bridges.ts
10
10
  import { existsSync, readFileSync, writeFileSync } from "fs";
@@ -4,10 +4,10 @@ import {
4
4
  } from "./chunk-T2TP6ZC6.js";
5
5
  import {
6
6
  getDb
7
- } from "./chunk-IUZ3EV2D.js";
7
+ } from "./chunk-YAMO2U3N.js";
8
8
  import {
9
9
  activity
10
- } from "./chunk-MHFAWJCC.js";
10
+ } from "./chunk-L3IV23ZG.js";
11
11
 
12
12
  // packages/daemon/src/lib/events/activity-events.ts
13
13
  var subscribers = /* @__PURE__ */ new Set();
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  voluteSystemDir
4
- } from "./chunk-IUZ3EV2D.js";
4
+ } from "./chunk-YAMO2U3N.js";
5
5
 
6
6
  // packages/daemon/src/lib/update-check.ts
7
7
  import { execFile as execFileCb, execFileSync } from "child_process";
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  exec,
4
4
  execInherit
5
- } from "./chunk-GN52E2DP.js";
5
+ } from "./chunk-PGPLCJPK.js";
6
6
  import {
7
7
  voluteSystemDir
8
- } from "./chunk-IUZ3EV2D.js";
8
+ } from "./chunk-YAMO2U3N.js";
9
9
 
10
10
  // packages/daemon/src/lib/config/service-mode.ts
11
11
  import { execFileSync } from "child_process";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  readGlobalConfig
4
- } from "./chunk-KZAL7HEQ.js";
4
+ } from "./chunk-ONM2AZFL.js";
5
5
  import {
6
6
  logger_default
7
7
  } from "./chunk-T2TP6ZC6.js";
@@ -7,14 +7,14 @@ import {
7
7
  modeLabel,
8
8
  pollHealth,
9
9
  startService
10
- } from "./chunk-P6DPB33N.js";
10
+ } from "./chunk-ATEX3ZPG.js";
11
11
  import {
12
12
  readGlobalConfig
13
- } from "./chunk-KZAL7HEQ.js";
13
+ } from "./chunk-ONM2AZFL.js";
14
14
  import {
15
15
  voluteHome,
16
16
  voluteSystemDir
17
- } from "./chunk-IUZ3EV2D.js";
17
+ } from "./chunk-YAMO2U3N.js";
18
18
 
19
19
  // src/commands/up.ts
20
20
  import { spawn } from "child_process";
@@ -2,14 +2,14 @@
2
2
  import {
3
3
  findBridgeForChannel,
4
4
  getBridgeConfig
5
- } from "./chunk-QPTQKGGF.js";
5
+ } from "./chunk-57LBQ7JX.js";
6
6
  import {
7
7
  recordOutbound
8
- } from "./chunk-6G7R2LPI.js";
8
+ } from "./chunk-4SVONUTC.js";
9
9
  import {
10
10
  readEnv,
11
11
  sharedEnvPath
12
- } from "./chunk-BLOAJLGI.js";
12
+ } from "./chunk-XDTKJNW5.js";
13
13
  import {
14
14
  addMessage,
15
15
  findDMConversation,
@@ -17,21 +17,21 @@ import {
17
17
  getChannelName,
18
18
  getConversation,
19
19
  getParticipants
20
- } from "./chunk-YLTIEEVL.js";
20
+ } from "./chunk-K4EHBHPE.js";
21
21
  import {
22
22
  readVoluteConfig
23
23
  } from "./chunk-CU6OFXMM.js";
24
24
  import {
25
25
  publish
26
- } from "./chunk-BWZHX46O.js";
26
+ } from "./chunk-Y6O35D7K.js";
27
27
  import {
28
28
  daemonConfigPath,
29
29
  readDaemonToken
30
- } from "./chunk-P6DPB33N.js";
30
+ } from "./chunk-ATEX3ZPG.js";
31
31
  import {
32
32
  getOrCreateMindUser,
33
33
  getUserByUsername
34
- } from "./chunk-5QG36RV7.js";
34
+ } from "./chunk-EFL5UOY4.js";
35
35
  import {
36
36
  logger_default
37
37
  } from "./chunk-T2TP6ZC6.js";
@@ -40,7 +40,7 @@ import {
40
40
  } from "./chunk-LGNUFVMR.js";
41
41
  import {
42
42
  mindDir
43
- } from "./chunk-IUZ3EV2D.js";
43
+ } from "./chunk-YAMO2U3N.js";
44
44
  import {
45
45
  __export
46
46
  } from "./chunk-K3NQKI34.js";
@@ -3,10 +3,10 @@ import {
3
3
  LAUNCHD_PLIST_LABEL,
4
4
  LAUNCHD_PLIST_PATH,
5
5
  USER_SYSTEMD_UNIT
6
- } from "./chunk-P6DPB33N.js";
6
+ } from "./chunk-ATEX3ZPG.js";
7
7
  import {
8
8
  resolveVoluteBin
9
- } from "./chunk-GN52E2DP.js";
9
+ } from "./chunk-PGPLCJPK.js";
10
10
 
11
11
  // packages/daemon/src/lib/config/service-install.ts
12
12
  import { execFile } from "child_process";
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  broadcast
4
- } from "./chunk-K547QOCM.js";
4
+ } from "./chunk-7G2EEYXX.js";
5
5
  import {
6
6
  getDb
7
- } from "./chunk-IUZ3EV2D.js";
7
+ } from "./chunk-YAMO2U3N.js";
8
8
  import {
9
9
  users
10
- } from "./chunk-MHFAWJCC.js";
10
+ } from "./chunk-L3IV23ZG.js";
11
11
 
12
12
  // packages/daemon/src/lib/auth.ts
13
13
  import { compareSync, hashSync } from "bcryptjs";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  mindDir,
4
4
  stateDir
5
- } from "./chunk-IUZ3EV2D.js";
5
+ } from "./chunk-YAMO2U3N.js";
6
6
 
7
7
  // packages/daemon/src/lib/mind/archive.ts
8
8
  import { execFileSync } from "child_process";
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  setBridgeConfig
4
- } from "./chunk-QPTQKGGF.js";
4
+ } from "./chunk-57LBQ7JX.js";
5
5
  import {
6
6
  readEnv,
7
7
  sharedEnvPath,
8
8
  writeEnv
9
- } from "./chunk-BLOAJLGI.js";
9
+ } from "./chunk-XDTKJNW5.js";
10
10
 
11
11
  // packages/daemon/src/lib/template/import-utils.ts
12
12
  import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "fs";
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  subscribe
4
- } from "./chunk-K547QOCM.js";
4
+ } from "./chunk-7G2EEYXX.js";
5
5
  import {
6
6
  logger_default
7
7
  } from "./chunk-T2TP6ZC6.js";
8
8
  import {
9
9
  getDb
10
- } from "./chunk-IUZ3EV2D.js";
10
+ } from "./chunk-YAMO2U3N.js";
11
11
  import {
12
12
  channels,
13
13
  conversationParticipants,
@@ -15,7 +15,7 @@ import {
15
15
  conversations,
16
16
  messages,
17
17
  users
18
- } from "./chunk-MHFAWJCC.js";
18
+ } from "./chunk-L3IV23ZG.js";
19
19
 
20
20
  // packages/daemon/src/lib/events/conversations.ts
21
21
  import { randomUUID } from "crypto";
@@ -2,11 +2,11 @@
2
2
  import {
3
3
  exec,
4
4
  gitExec
5
- } from "./chunk-GN52E2DP.js";
5
+ } from "./chunk-PGPLCJPK.js";
6
6
  import {
7
7
  readGlobalConfig,
8
8
  writeGlobalConfig
9
- } from "./chunk-KZAL7HEQ.js";
9
+ } from "./chunk-ONM2AZFL.js";
10
10
  import {
11
11
  logger_default
12
12
  } from "./chunk-T2TP6ZC6.js";
@@ -15,10 +15,10 @@ import {
15
15
  mindDir,
16
16
  readRegistry,
17
17
  voluteHome
18
- } from "./chunk-IUZ3EV2D.js";
18
+ } from "./chunk-YAMO2U3N.js";
19
19
  import {
20
20
  sharedSkills
21
- } from "./chunk-MHFAWJCC.js";
21
+ } from "./chunk-L3IV23ZG.js";
22
22
 
23
23
  // packages/daemon/src/lib/skills.ts
24
24
  import { createHash } from "crypto";
@@ -46,7 +46,7 @@ async function initDefaultSkills() {
46
46
  const config = readGlobalConfig();
47
47
  let extensionSkills = [];
48
48
  try {
49
- const { getExtensionStandardSkills } = await import("./extensions-X6MKBJLB.js");
49
+ const { getExtensionStandardSkills } = await import("./extensions-PQPZT6LS.js");
50
50
  extensionSkills = getExtensionStandardSkills();
51
51
  } catch (err) {
52
52
  logger_default.warn("failed to load extension standard skills during init", logger_default.errorData(err));
@@ -150,18 +150,29 @@ var deliveryQueue = sqliteTable(
150
150
  "delivery_queue",
151
151
  {
152
152
  id: integer("id").primaryKey({ autoIncrement: true }),
153
+ // Keying/cleanup/dedup key: always the base (parent) name, so an insert under a
154
+ // variant name and the id-scoped cleanup share a key.
153
155
  mind: text("mind").notNull(),
156
+ // Original delivery target — may be a variant name. Used to resolve the port on
157
+ // redrive so a variant's stranded message is re-delivered to the variant, not the
158
+ // parent. Null on legacy rows → callers fall back to `mind`.
159
+ target_mind: text("target_mind"),
154
160
  session: text("session").notNull(),
155
161
  channel: text("channel"),
156
162
  sender: text("sender"),
157
163
  status: text("status").notNull().default("pending"),
158
164
  payload: text("payload").notNull(),
165
+ // Redrive bookkeeping: how many delivery attempts have been made, and the
166
+ // earliest time the row is eligible for a retry (null = deliver ASAP).
167
+ attempts: integer("attempts").notNull().default(0),
168
+ next_attempt_at: text("next_attempt_at"),
159
169
  created_at: text("created_at").notNull().default(sql`(datetime('now'))`)
160
170
  },
161
171
  (table) => [
162
172
  index("idx_delivery_queue_mind_session").on(table.mind, table.session),
163
173
  index("idx_delivery_queue_mind_status").on(table.mind, table.status),
164
- index("idx_delivery_queue_status").on(table.status)
174
+ index("idx_delivery_queue_status").on(table.status),
175
+ index("idx_delivery_queue_status_next").on(table.status, table.next_attempt_at)
165
176
  ]
166
177
  );
167
178
  var activity = sqliteTable(
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  publish
4
- } from "./chunk-K547QOCM.js";
4
+ } from "./chunk-7G2EEYXX.js";
5
5
  import {
6
6
  logger_default
7
7
  } from "./chunk-T2TP6ZC6.js";
@@ -8,10 +8,10 @@ import {
8
8
  pollHealthDown,
9
9
  readDaemonConfig,
10
10
  stopService
11
- } from "./chunk-P6DPB33N.js";
11
+ } from "./chunk-ATEX3ZPG.js";
12
12
  import {
13
13
  voluteSystemDir
14
- } from "./chunk-IUZ3EV2D.js";
14
+ } from "./chunk-YAMO2U3N.js";
15
15
 
16
16
  // src/commands/down.ts
17
17
  import { existsSync, readFileSync, unlinkSync } from "fs";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  voluteSystemDir
4
- } from "./chunk-IUZ3EV2D.js";
4
+ } from "./chunk-YAMO2U3N.js";
5
5
 
6
6
  // packages/daemon/src/lib/config/setup.ts
7
7
  import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
@@ -0,0 +1,272 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ getBaseName,
4
+ validateMindName
5
+ } from "./chunk-YAMO2U3N.js";
6
+
7
+ // packages/daemon/src/lib/mind/isolation.ts
8
+ import { execFileSync as execFileSync2 } from "child_process";
9
+ import { existsSync, readdirSync, statSync } from "fs";
10
+ import { resolve } from "path";
11
+
12
+ // packages/daemon/src/lib/util/exec.ts
13
+ import { execFile as execFileCb, execFileSync, spawn } from "child_process";
14
+ async function exec(cmd, args, options) {
15
+ const [wrappedCmd, wrappedArgs] = options?.mindName ? await wrapForIsolation(cmd, args, options.mindName) : [cmd, args];
16
+ return new Promise((resolve2, reject) => {
17
+ execFileCb(
18
+ wrappedCmd,
19
+ wrappedArgs,
20
+ { cwd: options?.cwd, env: options?.env },
21
+ (err, stdout, stderr) => {
22
+ if (err) {
23
+ err.stderr = stderr;
24
+ err.stdout = stdout;
25
+ reject(err);
26
+ } else {
27
+ resolve2(stdout);
28
+ }
29
+ }
30
+ );
31
+ });
32
+ }
33
+ function gitExec(args, options) {
34
+ const fullArgs = process.env.VOLUTE_ISOLATION === "user" ? ["-c", "safe.directory=*", ...args] : args;
35
+ return exec("git", fullArgs, options);
36
+ }
37
+ function resolveVoluteBin() {
38
+ try {
39
+ return execFileSync("which", ["volute"], { encoding: "utf-8" }).trim();
40
+ } catch {
41
+ throw new Error("Could not find volute binary on PATH");
42
+ }
43
+ }
44
+ async function execInherit(cmd, args, options) {
45
+ const [wrappedCmd, wrappedArgs] = options?.mindName ? await wrapForIsolation(cmd, args, options.mindName) : [cmd, args];
46
+ return new Promise((resolve2, reject) => {
47
+ const child = spawn(wrappedCmd, wrappedArgs, {
48
+ cwd: options?.cwd,
49
+ env: options?.env,
50
+ stdio: "inherit"
51
+ });
52
+ child.on("error", reject);
53
+ child.on("close", (code) => {
54
+ if (code === 0) resolve2();
55
+ else reject(new Error(`${cmd} ${args.join(" ")} exited with code ${code}`));
56
+ });
57
+ });
58
+ }
59
+
60
+ // packages/daemon/src/lib/mind/isolation.ts
61
+ function isIsolationEnabled() {
62
+ return process.env.VOLUTE_ISOLATION === "user";
63
+ }
64
+ function mindUserName(mindName) {
65
+ const err = validateMindName(mindName);
66
+ if (err && mindName !== "volute") {
67
+ throw new Error(`Invalid mind name for isolation: ${err}`);
68
+ }
69
+ const prefix = process.env.VOLUTE_USER_PREFIX ?? "mind-";
70
+ return `${prefix}${mindName}`;
71
+ }
72
+ function findNextMacId(type) {
73
+ const idField = type === "Users" ? "UniqueID" : "PrimaryGroupID";
74
+ let output;
75
+ try {
76
+ output = execFileSync2("dscl", [".", "-list", `/${type}`, idField], { encoding: "utf-8" });
77
+ } catch (err) {
78
+ throw new Error(
79
+ `Failed to query ${type} via dscl: ${err instanceof Error ? err.message : err}`
80
+ );
81
+ }
82
+ const ids = /* @__PURE__ */ new Set();
83
+ for (const line of output.split("\n")) {
84
+ const parts = line.trim().split(/\s+/);
85
+ const id = parseInt(parts[parts.length - 1], 10);
86
+ if (!Number.isNaN(id)) ids.add(id);
87
+ }
88
+ let next = 401;
89
+ while (ids.has(next)) next++;
90
+ return next;
91
+ }
92
+ function getVoluteGroupGid() {
93
+ if (process.platform === "darwin") {
94
+ const output2 = execFileSync2("dscl", [".", "-read", "/Groups/volute", "PrimaryGroupID"], {
95
+ encoding: "utf-8"
96
+ });
97
+ const match = output2.match(/PrimaryGroupID:\s*(\d+)/);
98
+ if (!match) throw new Error("Could not read volute group GID");
99
+ return parseInt(match[1], 10);
100
+ }
101
+ const output = execFileSync2("getent", ["group", "volute"], { encoding: "utf-8" });
102
+ const gid = parseInt(output.split(":")[2], 10);
103
+ if (Number.isNaN(gid)) throw new Error("Could not read volute group GID");
104
+ return gid;
105
+ }
106
+ function ensureVoluteGroup(opts) {
107
+ if (!opts?.force && !isIsolationEnabled()) return;
108
+ if (process.platform === "darwin") {
109
+ try {
110
+ execFileSync2("dscl", [".", "-read", "/Groups/volute"], { stdio: "ignore" });
111
+ return;
112
+ } catch {
113
+ }
114
+ const gid = findNextMacId("Groups");
115
+ try {
116
+ execFileSync2("dscl", [".", "-create", "/Groups/volute"]);
117
+ execFileSync2("dscl", [".", "-create", "/Groups/volute", "PrimaryGroupID", String(gid)]);
118
+ execFileSync2("dscl", [".", "-create", "/Groups/volute", "Password", "*"]);
119
+ } catch (err) {
120
+ const msg = err instanceof Error ? err.message : String(err);
121
+ throw new Error(`Failed to create volute group on macOS: ${msg}`);
122
+ }
123
+ return;
124
+ }
125
+ try {
126
+ execFileSync2("getent", ["group", "volute"], { stdio: "ignore" });
127
+ } catch {
128
+ try {
129
+ execFileSync2("groupadd", ["volute"], { stdio: ["ignore", "ignore", "pipe"] });
130
+ } catch (err) {
131
+ const stderr = err?.stderr?.toString().trim();
132
+ throw new Error(`Failed to create volute group${stderr ? `: ${stderr}` : ""}`);
133
+ }
134
+ }
135
+ }
136
+ function createMindUser(name, homeDir) {
137
+ if (!isIsolationEnabled()) return;
138
+ const user = mindUserName(name);
139
+ try {
140
+ execFileSync2("id", [user], { stdio: "ignore" });
141
+ return;
142
+ } catch {
143
+ }
144
+ if (process.platform === "darwin") {
145
+ const uid = findNextMacId("Users");
146
+ const gid = getVoluteGroupGid();
147
+ const home = homeDir ?? "/var/empty";
148
+ try {
149
+ execFileSync2("dscl", [".", "-create", `/Users/${user}`]);
150
+ execFileSync2("dscl", [".", "-create", `/Users/${user}`, "UniqueID", String(uid)]);
151
+ execFileSync2("dscl", [".", "-create", `/Users/${user}`, "PrimaryGroupID", String(gid)]);
152
+ execFileSync2("dscl", [".", "-create", `/Users/${user}`, "UserShell", "/usr/bin/false"]);
153
+ execFileSync2("dscl", [".", "-create", `/Users/${user}`, "NFSHomeDirectory", home]);
154
+ execFileSync2("dscl", [".", "-create", `/Users/${user}`, "RealName", `Volute Mind: ${name}`]);
155
+ execFileSync2("dscl", [".", "-create", `/Users/${user}`, "IsHidden", "1"]);
156
+ execFileSync2("dscl", [".", "-append", "/Groups/volute", "GroupMembership", user]);
157
+ } catch (err) {
158
+ const msg = err instanceof Error ? err.message : String(err);
159
+ throw new Error(`Failed to create user ${user} on macOS: ${msg}`);
160
+ }
161
+ return;
162
+ }
163
+ try {
164
+ const args = ["-r", "-M", "-G", "volute", "-s", "/usr/sbin/nologin"];
165
+ if (homeDir) args.push("-d", homeDir);
166
+ args.push(user);
167
+ execFileSync2("useradd", args, {
168
+ stdio: ["ignore", "ignore", "pipe"]
169
+ });
170
+ } catch (err) {
171
+ const stderr = err?.stderr?.toString().trim();
172
+ throw new Error(`Failed to create user ${user}${stderr ? `: ${stderr}` : ""}`);
173
+ }
174
+ }
175
+ function deleteMindUser(name) {
176
+ if (!isIsolationEnabled()) return;
177
+ const user = mindUserName(name);
178
+ if (process.platform === "darwin") {
179
+ try {
180
+ execFileSync2("dscl", [".", "-delete", `/Users/${user}`], { stdio: "ignore" });
181
+ } catch {
182
+ }
183
+ try {
184
+ execFileSync2("dscl", [".", "-delete", "/Groups/volute", "GroupMembership", user], {
185
+ stdio: "ignore"
186
+ });
187
+ } catch {
188
+ }
189
+ return;
190
+ }
191
+ try {
192
+ execFileSync2("userdel", [user], { stdio: "ignore" });
193
+ } catch {
194
+ }
195
+ }
196
+ async function wrapForIsolation(cmd, args, mindName) {
197
+ if (!isIsolationEnabled()) return [cmd, args];
198
+ const baseName = await getBaseName(mindName);
199
+ const user = mindUserName(baseName);
200
+ if (process.platform === "darwin") {
201
+ return ["sudo", ["-u", user, "--", cmd, ...args]];
202
+ }
203
+ return ["runuser", ["-u", user, "--", cmd, ...args]];
204
+ }
205
+ async function userUid(user) {
206
+ try {
207
+ const uid = parseInt((await exec("id", ["-u", user])).trim(), 10);
208
+ return Number.isNaN(uid) ? null : uid;
209
+ } catch {
210
+ return null;
211
+ }
212
+ }
213
+ function ownedBy(path, uid) {
214
+ try {
215
+ return statSync(path).uid === uid;
216
+ } catch {
217
+ return false;
218
+ }
219
+ }
220
+ async function chownTargets(dir, user) {
221
+ const nodeModules = resolve(dir, "node_modules");
222
+ const home = resolve(dir, "home");
223
+ if (existsSync(nodeModules) && existsSync(home)) {
224
+ const uid = await userUid(user);
225
+ if (uid !== null && ownedBy(nodeModules, uid)) {
226
+ return readdirSync(dir).filter((entry) => entry !== "node_modules").map((entry) => resolve(dir, entry));
227
+ }
228
+ }
229
+ return [dir];
230
+ }
231
+ async function chownMindDir(dir, name) {
232
+ if (!isIsolationEnabled()) return;
233
+ const user = mindUserName(name);
234
+ const group = process.platform === "darwin" ? "volute" : user;
235
+ for (const target of await chownTargets(dir, user)) {
236
+ try {
237
+ await exec("chown", ["-R", `${user}:${group}`, target]);
238
+ } catch (err) {
239
+ const stderr = String(err?.stderr ?? "").trim();
240
+ throw new Error(
241
+ `Failed to chown ${target} to ${user}:${group}${stderr ? `: ${stderr}` : ""}`
242
+ );
243
+ }
244
+ }
245
+ try {
246
+ await exec("chown", [`${user}:${group}`, dir]);
247
+ } catch (err) {
248
+ const stderr = String(err?.stderr ?? "").trim();
249
+ throw new Error(`Failed to chown ${dir} to ${user}:${group}${stderr ? `: ${stderr}` : ""}`);
250
+ }
251
+ try {
252
+ await exec("chmod", ["700", dir]);
253
+ } catch (err) {
254
+ const stderr = String(err?.stderr ?? "").trim();
255
+ throw new Error(`Failed to chmod ${dir}${stderr ? `: ${stderr}` : ""}`);
256
+ }
257
+ }
258
+
259
+ export {
260
+ isIsolationEnabled,
261
+ mindUserName,
262
+ ensureVoluteGroup,
263
+ createMindUser,
264
+ deleteMindUser,
265
+ wrapForIsolation,
266
+ chownTargets,
267
+ chownMindDir,
268
+ exec,
269
+ gitExec,
270
+ resolveVoluteBin,
271
+ execInherit
272
+ };
@@ -10,21 +10,22 @@ import {
10
10
  getSharedSkill,
11
11
  installSkill,
12
12
  mindSkillsDir
13
- } from "./chunk-M3T3Y6FT.js";
13
+ } from "./chunk-KINGVQSR.js";
14
14
  import {
15
15
  readVoluteConfig,
16
16
  writeVoluteConfig
17
17
  } from "./chunk-CU6OFXMM.js";
18
18
  import {
19
19
  exec
20
- } from "./chunk-GN52E2DP.js";
20
+ } from "./chunk-PGPLCJPK.js";
21
21
  import {
22
22
  qualifyModelId,
23
- resolveTemplate
24
- } from "./chunk-4ZSUUBG5.js";
23
+ resolveTemplate,
24
+ unqualifyModelId
25
+ } from "./chunk-YSV3VJQK.js";
25
26
  import {
26
27
  readGlobalConfig
27
- } from "./chunk-KZAL7HEQ.js";
28
+ } from "./chunk-ONM2AZFL.js";
28
29
  import {
29
30
  logger_default
30
31
  } from "./chunk-T2TP6ZC6.js";
@@ -33,7 +34,7 @@ import {
33
34
  findMind,
34
35
  nextPort,
35
36
  voluteSystemDir
36
- } from "./chunk-IUZ3EV2D.js";
37
+ } from "./chunk-YAMO2U3N.js";
37
38
 
38
39
  // packages/daemon/src/lib/mind/spirit.ts
39
40
  import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
@@ -99,7 +100,7 @@ async function ensureSpiritProject() {
99
100
  writeFileSync(routesPath, `${JSON.stringify(routesContent, null, 2)}
100
101
  `);
101
102
  if (spiritModel) {
102
- const modelForConfig = template === "pi" ? qualifyModelId(spiritModel) : spiritModel;
103
+ const modelForConfig = template === "pi" ? qualifyModelId(spiritModel) : unqualifyModelId(spiritModel);
103
104
  const configPath = resolve(dir, "home/.config/config.json");
104
105
  const mindConfig = existsSync(configPath) ? JSON.parse(readFileSync(configPath, "utf-8")) : {};
105
106
  mindConfig.model = modelForConfig;
@@ -129,10 +130,10 @@ async function ensureSpiritProject() {
129
130
  } catch (err) {
130
131
  slog.warn("failed to add tending schedule to spirit config", logger_default.errorData(err));
131
132
  }
132
- const { createMindUser, chownMindDir, ensureVoluteGroup } = await import("./isolation-F7ABZMDA.js");
133
+ const { createMindUser, chownMindDir, ensureVoluteGroup } = await import("./isolation-5AYEY55P.js");
133
134
  ensureVoluteGroup();
134
135
  createMindUser("volute", resolve(dir, "home"));
135
- chownMindDir(dir, "volute");
136
+ await chownMindDir(dir, "volute");
136
137
  const port = await nextPort();
137
138
  await addSpirit("volute", port, template, dir);
138
139
  slog.info("spirit project created");
@@ -163,8 +164,8 @@ async function syncSpiritTemplate() {
163
164
  cpSync(newPkg, resolve(dir, "package.json"));
164
165
  await exec("npm", ["install"], { cwd: dir, env: npmEnv() });
165
166
  }
166
- const db = await (await import("./db-ZJJJYJBU.js")).getDb();
167
- const { minds } = await import("./schema-HHYJDG7B.js");
167
+ const db = await (await import("./db-CZV62BOQ.js")).getDb();
168
+ const { minds } = await import("./schema-LRUTFOER.js");
168
169
  const { eq } = await import("drizzle-orm");
169
170
  await db.update(minds).set({ template: expectedTemplate }).where(eq(minds.name, "volute"));
170
171
  }
@@ -190,7 +191,7 @@ async function syncSpiritTemplate() {
190
191
  writeFileSync(resolve(dir, "home/SOUL.md"), getSpiritSoul(systemName, config.description));
191
192
  const spiritModel = config.spiritModel;
192
193
  if (spiritModel) {
193
- const modelForConfig = template === "pi" ? qualifyModelId(spiritModel) : spiritModel;
194
+ const modelForConfig = template === "pi" ? qualifyModelId(spiritModel) : unqualifyModelId(spiritModel);
194
195
  const mindConfigPath = resolve(dir, "home/.config/config.json");
195
196
  const mindConfig = existsSync(mindConfigPath) ? JSON.parse(readFileSync(mindConfigPath, "utf-8")) : {};
196
197
  if (mindConfig.model !== modelForConfig) {