@geminixiang/mama 0.2.0-beta.4 → 0.2.0-beta.5

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 (122) hide show
  1. package/README.md +100 -421
  2. package/dist/adapter.d.ts +9 -0
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/adapter.js.map +1 -1
  5. package/dist/adapters/discord/bot.d.ts +1 -0
  6. package/dist/adapters/discord/bot.d.ts.map +1 -1
  7. package/dist/adapters/discord/bot.js +4 -1
  8. package/dist/adapters/discord/bot.js.map +1 -1
  9. package/dist/adapters/discord/context.d.ts.map +1 -1
  10. package/dist/adapters/discord/context.js +9 -2
  11. package/dist/adapters/discord/context.js.map +1 -1
  12. package/dist/adapters/slack/bot.d.ts +1 -0
  13. package/dist/adapters/slack/bot.d.ts.map +1 -1
  14. package/dist/adapters/slack/bot.js +9 -3
  15. package/dist/adapters/slack/bot.js.map +1 -1
  16. package/dist/adapters/slack/context.d.ts.map +1 -1
  17. package/dist/adapters/slack/context.js +13 -3
  18. package/dist/adapters/slack/context.js.map +1 -1
  19. package/dist/adapters/telegram/context.d.ts.map +1 -1
  20. package/dist/adapters/telegram/context.js +9 -2
  21. package/dist/adapters/telegram/context.js.map +1 -1
  22. package/dist/agent.d.ts.map +1 -1
  23. package/dist/agent.js +13 -5
  24. package/dist/agent.js.map +1 -1
  25. package/dist/bindings.d.ts +2 -1
  26. package/dist/bindings.d.ts.map +1 -1
  27. package/dist/bindings.js.map +1 -1
  28. package/dist/commands/index.d.ts +5 -0
  29. package/dist/commands/index.d.ts.map +1 -0
  30. package/dist/commands/index.js +8 -0
  31. package/dist/commands/index.js.map +1 -0
  32. package/dist/commands/login.d.ts +5 -0
  33. package/dist/commands/login.d.ts.map +1 -0
  34. package/dist/commands/login.js +37 -0
  35. package/dist/commands/login.js.map +1 -0
  36. package/dist/commands/registry.d.ts +7 -0
  37. package/dist/commands/registry.d.ts.map +1 -0
  38. package/dist/commands/registry.js +14 -0
  39. package/dist/commands/registry.js.map +1 -0
  40. package/dist/commands/session-view.d.ts +5 -0
  41. package/dist/commands/session-view.d.ts.map +1 -0
  42. package/dist/commands/session-view.js +38 -0
  43. package/dist/commands/session-view.js.map +1 -0
  44. package/dist/commands/types.d.ts +41 -0
  45. package/dist/commands/types.d.ts.map +1 -0
  46. package/dist/commands/types.js +2 -0
  47. package/dist/commands/types.js.map +1 -0
  48. package/dist/commands/utils.d.ts +5 -0
  49. package/dist/commands/utils.d.ts.map +1 -0
  50. package/dist/commands/utils.js +9 -0
  51. package/dist/commands/utils.js.map +1 -0
  52. package/dist/config.d.ts +4 -4
  53. package/dist/config.d.ts.map +1 -1
  54. package/dist/config.js +34 -40
  55. package/dist/config.js.map +1 -1
  56. package/dist/context.d.ts.map +1 -1
  57. package/dist/context.js +74 -68
  58. package/dist/context.js.map +1 -1
  59. package/dist/execution-resolver.d.ts +6 -3
  60. package/dist/execution-resolver.d.ts.map +1 -1
  61. package/dist/execution-resolver.js +47 -14
  62. package/dist/execution-resolver.js.map +1 -1
  63. package/dist/index.d.ts +7 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +4 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/instrument.d.ts.map +1 -1
  68. package/dist/instrument.js +2 -3
  69. package/dist/instrument.js.map +1 -1
  70. package/dist/login/index.d.ts.map +1 -1
  71. package/dist/login/index.js +19 -8
  72. package/dist/login/index.js.map +1 -1
  73. package/dist/login/portal.d.ts.map +1 -1
  74. package/dist/login/portal.js +7 -7
  75. package/dist/login/portal.js.map +1 -1
  76. package/dist/login/session.d.ts +3 -2
  77. package/dist/login/session.d.ts.map +1 -1
  78. package/dist/login/session.js.map +1 -1
  79. package/dist/main.d.ts.map +1 -1
  80. package/dist/main.js +62 -386
  81. package/dist/main.js.map +1 -1
  82. package/dist/provisioner.d.ts +11 -9
  83. package/dist/provisioner.d.ts.map +1 -1
  84. package/dist/provisioner.js +125 -87
  85. package/dist/provisioner.js.map +1 -1
  86. package/dist/runtime/index.d.ts +2 -0
  87. package/dist/runtime/index.d.ts.map +1 -0
  88. package/dist/runtime/index.js +2 -0
  89. package/dist/runtime/index.js.map +1 -0
  90. package/dist/runtime/session-runtime.d.ts +26 -0
  91. package/dist/runtime/session-runtime.d.ts.map +1 -0
  92. package/dist/runtime/session-runtime.js +285 -0
  93. package/dist/runtime/session-runtime.js.map +1 -0
  94. package/dist/sandbox/cloudflare.d.ts +14 -0
  95. package/dist/sandbox/cloudflare.d.ts.map +1 -0
  96. package/dist/sandbox/cloudflare.js +131 -0
  97. package/dist/sandbox/cloudflare.js.map +1 -0
  98. package/dist/sandbox/index.d.ts +6 -4
  99. package/dist/sandbox/index.d.ts.map +1 -1
  100. package/dist/sandbox/index.js +6 -3
  101. package/dist/sandbox/index.js.map +1 -1
  102. package/dist/sandbox/types.d.ts +5 -1
  103. package/dist/sandbox/types.d.ts.map +1 -1
  104. package/dist/sandbox/types.js.map +1 -1
  105. package/dist/session-view/portal.d.ts.map +1 -1
  106. package/dist/session-view/portal.js +10 -1
  107. package/dist/session-view/portal.js.map +1 -1
  108. package/dist/session-view/service.d.ts.map +1 -1
  109. package/dist/session-view/service.js +36 -26
  110. package/dist/session-view/service.js.map +1 -1
  111. package/dist/session-view/store.d.ts +3 -2
  112. package/dist/session-view/store.d.ts.map +1 -1
  113. package/dist/session-view/store.js.map +1 -1
  114. package/dist/vault-routing.d.ts +3 -5
  115. package/dist/vault-routing.d.ts.map +1 -1
  116. package/dist/vault-routing.js +8 -20
  117. package/dist/vault-routing.js.map +1 -1
  118. package/dist/vault.d.ts +7 -5
  119. package/dist/vault.d.ts.map +1 -1
  120. package/dist/vault.js +101 -50
  121. package/dist/vault.js.map +1 -1
  122. package/package.json +1 -2
package/dist/vault.js CHANGED
@@ -1,7 +1,13 @@
1
- import { chmodSync, existsSync, mkdirSync, readFileSync } from "fs";
1
+ import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync } from "fs";
2
2
  import { dirname, isAbsolute, join, normalize, sep } from "path";
3
3
  import { atomicWritePrivateFile } from "./fs-atomic.js";
4
4
  const PRIVATE_DIR_MODE = 0o700;
5
+ function sanitizeCloudflareSandboxId(value) {
6
+ return (value
7
+ .toLowerCase()
8
+ .replace(/[^a-z0-9-]+/g, "-")
9
+ .replace(/^-+|-+$/g, "") || "unknown");
10
+ }
5
11
  // ── parseEnvFile ───────────────────────────────────────────────────────────────
6
12
  /**
7
13
  * Parse a KEY=VALUE env file. Supports:
@@ -73,35 +79,52 @@ export class FileVaultManager {
73
79
  for (const [key, entry] of Object.entries(this.config.vaults)) {
74
80
  if (entry.sandbox?.type === "host") {
75
81
  console.error(`vault: "${key}" uses sandbox.type=host, which is blocked for credential isolation. ` +
76
- "Use sandbox.type=image or sandbox.type=firecracker.");
82
+ "Use sandbox.type=image, sandbox.type=firecracker, or sandbox.type=cloudflare.");
77
83
  }
78
84
  if (entry.sandbox?.type === "container" || entry.sandbox?.type === "docker") {
79
85
  console.error(`vault: "${key}" uses sandbox.type=${entry.sandbox.type}, which is blocked for credential isolation. ` +
80
- "Use sandbox.type=image for per-user containers or sandbox.type=firecracker.");
86
+ "Use sandbox.type=image for per-user containers, sandbox.type=firecracker, or sandbox.type=cloudflare.");
81
87
  }
82
88
  }
83
89
  }
84
90
  isEnabled() {
85
- return this.config !== null;
91
+ return this.config !== null || existsSync(this.vaultsDir);
86
92
  }
87
93
  hasEntry(key) {
88
- return !!this.config?.vaults[key];
94
+ return !!this.config?.vaults[key] || existsSync(join(this.vaultsDir, key));
89
95
  }
90
96
  resolve(userId) {
91
97
  const entry = this.config?.vaults[userId];
92
- if (!entry)
98
+ const dir = join(this.vaultsDir, userId);
99
+ if (!entry && !existsSync(dir))
93
100
  return undefined;
94
101
  return this.buildResolved(userId, entry);
95
102
  }
96
103
  getSandboxConfig(userId, baseConfig) {
97
104
  const vault = this.resolve(userId);
98
- if (!vault?.sandboxOverride)
105
+ if (!vault?.sandboxOverride) {
106
+ if (baseConfig.type === "cloudflare") {
107
+ return {
108
+ type: "cloudflare",
109
+ sandboxId: `${baseConfig.sandboxId}-${sanitizeCloudflareSandboxId(userId)}`,
110
+ };
111
+ }
99
112
  return baseConfig;
113
+ }
100
114
  const override = vault.sandboxOverride;
101
115
  if (override.type === "image") {
102
116
  if (baseConfig.type !== "image") {
117
+ if (!override.container) {
118
+ if (baseConfig.type === "cloudflare") {
119
+ return {
120
+ type: "cloudflare",
121
+ sandboxId: `${baseConfig.sandboxId}-${sanitizeCloudflareSandboxId(userId)}`,
122
+ };
123
+ }
124
+ return baseConfig;
125
+ }
103
126
  throw new Error(`vault "${userId}" sets sandbox.type=image, but base sandbox is "${baseConfig.type}". ` +
104
- "Use --sandbox=image:<image> to enable per-user managed containers.");
127
+ "Use --sandbox=image:<image> to enable managed image containers for this vault.");
105
128
  }
106
129
  const container = override.container || `mama-sandbox-${userId}`;
107
130
  return { type: "container", container };
@@ -121,25 +144,41 @@ export class FileVaultManager {
121
144
  sshPort: override.sshPort,
122
145
  };
123
146
  }
147
+ if (override.type === "cloudflare") {
148
+ if (!override.sandboxId)
149
+ return baseConfig;
150
+ if (baseConfig.type !== "cloudflare") {
151
+ throw new Error(`vault "${userId}" sets sandbox.type=cloudflare, but base sandbox is "${baseConfig.type}". ` +
152
+ "Use --sandbox=cloudflare:<sandbox-id> to enable Cloudflare remote execution.");
153
+ }
154
+ return {
155
+ type: "cloudflare",
156
+ sandboxId: override.sandboxId,
157
+ };
158
+ }
124
159
  if (override.type === "host") {
125
160
  throw new Error(`vault "${userId}" uses sandbox.type=host, which is blocked for credential isolation. ` +
126
- "Use sandbox.type=image or sandbox.type=firecracker.");
161
+ "Use sandbox.type=image, sandbox.type=firecracker, or sandbox.type=cloudflare.");
127
162
  }
128
163
  if (override.type === "container" || override.type === "docker") {
129
164
  throw new Error(`vault "${userId}" uses sandbox.type=${override.type}, which is blocked for credential isolation. ` +
130
- "Use sandbox.type=image for per-user containers or sandbox.type=firecracker.");
165
+ "Use sandbox.type=image for per-user containers, sandbox.type=firecracker, or sandbox.type=cloudflare.");
131
166
  }
132
167
  // No type override — return base config unchanged
133
168
  return baseConfig;
134
169
  }
135
170
  list() {
136
- if (!this.config)
137
- return [];
138
- const results = [];
139
- for (const [key, entry] of Object.entries(this.config.vaults)) {
140
- results.push(this.buildResolved(key, entry));
171
+ const keys = new Set();
172
+ for (const key of Object.keys(this.config?.vaults ?? {})) {
173
+ keys.add(key);
174
+ }
175
+ if (existsSync(this.vaultsDir)) {
176
+ for (const entry of readdirSync(this.vaultsDir, { withFileTypes: true })) {
177
+ if (entry.isDirectory())
178
+ keys.add(entry.name);
179
+ }
141
180
  }
142
- return results;
181
+ return Array.from(keys, (key) => this.buildResolved(key, this.config?.vaults[key]));
143
182
  }
144
183
  addEntry(key, entry) {
145
184
  const cfg = (this.config ??= { vaults: {} });
@@ -171,15 +210,6 @@ export class FileVaultManager {
171
210
  nextEntry = { ...nextEntry, sandbox: entry.sandbox };
172
211
  changed = true;
173
212
  }
174
- else if (existingSandbox.type === "image" &&
175
- !existingSandbox.container &&
176
- entry.sandbox.container) {
177
- nextEntry = {
178
- ...nextEntry,
179
- sandbox: { ...existingSandbox, container: entry.sandbox.container },
180
- };
181
- changed = true;
182
- }
183
213
  if (!changed)
184
214
  return;
185
215
  cfg.vaults[key] = nextEntry;
@@ -202,8 +232,7 @@ export class FileVaultManager {
202
232
  }
203
233
  upsertFile(key, relativePath, content, targetPath) {
204
234
  const normalizedPath = normalizeVaultRelativePath(relativePath);
205
- const normalizedTarget = normalizeVaultTargetPath(targetPath);
206
- if (!normalizedPath || (targetPath !== undefined && !normalizedTarget)) {
235
+ if (!normalizedPath || (targetPath !== undefined && !normalizeVaultTargetPath(targetPath))) {
207
236
  throw new Error(`vault: invalid relative secret file path for "${key}": ${relativePath}`);
208
237
  }
209
238
  const dir = join(this.vaultsDir, key);
@@ -214,7 +243,6 @@ export class FileVaultManager {
214
243
  if (parentDir !== dir)
215
244
  ensurePrivateDir(parentDir);
216
245
  atomicWritePrivateFile(filePath, content);
217
- this.ensureMountEntry(key, normalizedPath, normalizedTarget);
218
246
  }
219
247
  // ── private ────────────────────────────────────────────────────────────────
220
248
  persistConfig() {
@@ -250,31 +278,20 @@ export class FileVaultManager {
250
278
  return null;
251
279
  }
252
280
  }
253
- ensureMountEntry(key, relativePath, targetPath) {
254
- if (!this.config?.vaults[key]) {
255
- throw new Error(`vault: cannot add mount "${relativePath}" for missing entry "${key}"`);
256
- }
257
- const existing = this.config.vaults[key];
258
- const mounts = existing.mounts ?? [];
259
- if (mounts.some((mount) => typeof mount === "string"
260
- ? mount === relativePath && !targetPath
261
- : mount.source === relativePath && mount.target === targetPath)) {
262
- return;
263
- }
264
- this.config.vaults[key] = {
265
- ...existing,
266
- mounts: [...mounts, targetPath ? { source: relativePath, target: targetPath } : relativePath],
267
- };
268
- this.persistConfig();
269
- }
270
281
  buildResolved(key, entry) {
271
282
  const dir = join(this.vaultsDir, key);
272
- const mounts = (entry.mounts ?? [])
283
+ const inferredMounts = this.inferMountsFromDir(dir);
284
+ const configuredMounts = (entry?.mounts ?? [])
273
285
  .map((mount) => this.resolveMountEntry(dir, mount))
274
286
  .filter((mount) => mount !== undefined);
287
+ const mountsByTarget = new Map();
288
+ for (const mount of inferredMounts)
289
+ mountsByTarget.set(mount.target, mount);
290
+ for (const mount of configuredMounts)
291
+ mountsByTarget.set(mount.target, mount);
275
292
  let env = {};
276
293
  const envPath = join(dir, "env");
277
- if (entry.envFile !== false && existsSync(envPath)) {
294
+ if ((entry?.envFile ?? true) && existsSync(envPath)) {
278
295
  try {
279
296
  env = parseEnvFile(readFileSync(envPath, "utf-8"));
280
297
  }
@@ -284,13 +301,29 @@ export class FileVaultManager {
284
301
  }
285
302
  return {
286
303
  userId: key,
287
- displayName: entry.displayName,
304
+ displayName: entry?.displayName ?? key,
288
305
  dir,
289
- mounts,
306
+ mounts: [...mountsByTarget.values()],
290
307
  env,
291
- sandboxOverride: entry.sandbox,
308
+ sandboxOverride: entry?.sandbox,
292
309
  };
293
310
  }
311
+ inferMountsFromDir(dir) {
312
+ if (!existsSync(dir)) {
313
+ return [];
314
+ }
315
+ const mounts = [];
316
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
317
+ if (entry.name === "env")
318
+ continue;
319
+ const source = join(dir, entry.name);
320
+ const target = inferredVaultTargetPath(entry.name);
321
+ if (!target)
322
+ continue;
323
+ mounts.push({ source, target });
324
+ }
325
+ return mounts;
326
+ }
294
327
  resolveMountEntry(dir, mount) {
295
328
  if (typeof mount === "string") {
296
329
  const normalizedSource = normalizeVaultRelativePath(mount);
@@ -342,4 +375,22 @@ export function defaultVaultTargetPath(relativePath) {
342
375
  const normalized = normalizeVaultRelativePath(relativePath) ?? relativePath.replace(/^\/+/, "");
343
376
  return `/root/${normalized}`;
344
377
  }
378
+ function inferredVaultTargetPath(relativePath) {
379
+ const normalized = normalizeVaultRelativePath(relativePath);
380
+ if (!normalized)
381
+ return undefined;
382
+ if (normalized === "gws.json") {
383
+ return "/root/.config/gws/credentials.json";
384
+ }
385
+ if (normalized === ".ssh" || normalized.startsWith(".ssh/")) {
386
+ return "/root/.ssh";
387
+ }
388
+ if (normalized === ".kube" || normalized.startsWith(".kube/")) {
389
+ return "/root/.kube";
390
+ }
391
+ if (normalized === ".config/gh" || normalized.startsWith(".config/gh/")) {
392
+ return "/root/.config/gh";
393
+ }
394
+ return defaultVaultTargetPath(normalized);
395
+ }
345
396
  //# sourceMappingURL=vault.js.map
package/dist/vault.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"vault.js","sourceRoot":"","sources":["../src/vault.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AACpE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAEjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAExD,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAiF/B,kFAAkF;AAElF;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAE9E,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAElD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,OAAO,KAAK,CAAC,CAAC;YAAE,SAAS;QAE7B,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7C,IAAI,CAAC,GAAG;YAAE,SAAS;QAEnB,IAAI,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;QAEvC,wBAAwB;QACxB,IACE,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YAC9C,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAC9C,CAAC;YACD,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7B,CAAC;QAED,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACnB,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED,kFAAkF;AAElF,MAAM,OAAO,gBAAgB;IAK3B,YAAY,QAAgB;QAJpB,WAAM,GAAuB,IAAI,CAAC;QAKxC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC1C,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;QACrD,IAAI,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC;IAED,MAAM;QACJ,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YACjC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;YACnB,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YACnD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAE/B,IACE,CAAC,MAAM;gBACP,OAAO,MAAM,KAAK,QAAQ;gBAC1B,CAAC,MAAM,CAAC,MAAM;gBACd,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,EACjC,CAAC;gBACD,OAAO,CAAC,KAAK,CAAC,4DAA4D,CAAC,CAAC;gBAC5E,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;gBACnB,OAAO;YACT,CAAC;YAED,IAAI,CAAC,MAAM,GAAG,MAAqB,CAAC;YACpC,IAAI,CAAC,2BAA2B,EAAE,CAAC;QACrC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,yBAAyB,IAAI,CAAC,UAAU,GAAG,EAAE,GAAG,CAAC,CAAC;YAChE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACrB,CAAC;IACH,CAAC;IAED,sFAAsF;IAC9E,2BAA2B;QACjC,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO;QACzB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;YAC9D,IAAI,KAAK,CAAC,OAAO,EAAE,IAAI,KAAK,MAAM,EAAE,CAAC;gBACnC,OAAO,CAAC,KAAK,CACX,WAAW,GAAG,uEAAuE;oBACnF,qDAAqD,CACxD,CAAC;YACJ,CAAC;YACD,IAAI,KAAK,CAAC,OAAO,EAAE,IAAI,KAAK,WAAW,IAAI,KAAK,CAAC,OAAO,EAAE,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC5E,OAAO,CAAC,KAAK,CACX,WAAW,GAAG,uBAAuB,KAAK,CAAC,OAAO,CAAC,IAAI,+CAA+C;oBACpG,6EAA6E,CAChF,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC;IAC9B,CAAC;IAED,QAAQ,CAAC,GAAW;QAClB,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IACpC,CAAC;IAED,OAAO,CAAC,MAAc;QACpB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QAC1C,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAC7B,OAAO,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC3C,CAAC;IAED,gBAAgB,CAAC,MAAc,EAAE,UAAyB;QACxD,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACnC,IAAI,CAAC,KAAK,EAAE,eAAe;YAAE,OAAO,UAAU,CAAC;QAE/C,MAAM,QAAQ,GAAG,KAAK,CAAC,eAAe,CAAC;QAEvC,IAAI,QAAQ,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC9B,IAAI,UAAU,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;gBAChC,MAAM,IAAI,KAAK,CACb,UAAU,MAAM,mDAAmD,UAAU,CAAC,IAAI,KAAK;oBACrF,oEAAoE,CACvE,CAAC;YACJ,CAAC;YACD,MAAM,SAAS,GAAG,QAAQ,CAAC,SAAS,IAAI,gBAAgB,MAAM,EAAE,CAAC;YACjE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC;QAC1C,CAAC;QAED,IAAI,QAAQ,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;YACpC,IAAI,CAAC,QAAQ,CAAC,IAAI;gBAAE,OAAO,UAAU,CAAC;YACtC,IAAI,UAAU,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;gBACtC,MAAM,IAAI,KAAK,CACb,UAAU,MAAM,yDAAyD,UAAU,CAAC,IAAI,KAAK;oBAC3F,iGAAiG,CACpG,CAAC;YACJ,CAAC;YACD,OAAO;gBACL,IAAI,EAAE,aAAa;gBACnB,IAAI,EAAE,QAAQ,CAAC,IAAI;gBACnB,QAAQ,EAAE,UAAU,CAAC,QAAQ;gBAC7B,OAAO,EAAE,QAAQ,CAAC,OAAO;gBACzB,OAAO,EAAE,QAAQ,CAAC,OAAO;aAC1B,CAAC;QACJ,CAAC;QAED,IAAI,QAAQ,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CACb,UAAU,MAAM,uEAAuE;gBACrF,qDAAqD,CACxD,CAAC;QACJ,CAAC;QAED,IAAI,QAAQ,CAAC,IAAI,KAAK,WAAW,IAAI,QAAQ,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAChE,MAAM,IAAI,KAAK,CACb,UAAU,MAAM,uBAAuB,QAAQ,CAAC,IAAI,+CAA+C;gBACjG,6EAA6E,CAChF,CAAC;QACJ,CAAC;QAED,kDAAkD;QAClD,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,IAAI;QACF,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO,EAAE,CAAC;QAE5B,MAAM,OAAO,GAAoB,EAAE,CAAC;QACpC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;YAC9D,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;QAC/C,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,QAAQ,CAAC,GAAW,EAAE,KAAiB;QACrC,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC;QAC7C,qCAAqC;QACrC,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC;YAAE,OAAO;QAC5B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACxB,IAAI,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC;IAED,uBAAuB,CAAC,GAAW,EAAE,KAAiB;QACpD,IAAI,KAAK,CAAC,OAAO,EAAE,IAAI,KAAK,OAAO,EAAE,CAAC;YACpC,MAAM,IAAI,KAAK,CAAC,mEAAmE,GAAG,GAAG,CAAC,CAAC;QAC7F,CAAC;QAED,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC;QAC7C,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YACxB,IAAI,CAAC,aAAa,EAAE,CAAC;YACrB,OAAO;QACT,CAAC;QAED,IAAI,SAAS,GAAG,QAAQ,CAAC;QACzB,IAAI,OAAO,GAAG,KAAK,CAAC;QAEpB,IAAI,CAAC,QAAQ,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACzC,SAAS,GAAG,EAAE,GAAG,SAAS,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC;YACvD,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;QAED,MAAM,eAAe,GAAG,QAAQ,CAAC,OAAO,CAAC;QACzC,IAAI,CAAC,eAAe,EAAE,IAAI,EAAE,CAAC;YAC3B,SAAS,GAAG,EAAE,GAAG,SAAS,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;YACrD,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;aAAM,IACL,eAAe,CAAC,IAAI,KAAK,OAAO;YAChC,CAAC,eAAe,CAAC,SAAS;YAC1B,KAAK,CAAC,OAAO,CAAC,SAAS,EACvB,CAAC;YACD,SAAS,GAAG;gBACV,GAAG,SAAS;gBACZ,OAAO,EAAE,EAAE,GAAG,eAAe,EAAE,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE;aACpE,CAAC;YACF,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;QAED,IAAI,CAAC,OAAO;YAAE,OAAO;QAErB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;QAC5B,IAAI,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC;IAED,SAAS,CAAC,GAAW,EAAE,GAA2B;QAChD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QACtC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACjC,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACjC,gBAAgB,CAAC,GAAG,CAAC,CAAC;QACtB,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC;YAClC,CAAC,CAAC,YAAY,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC9C,CAAC,CAAE,EAA6B,CAAC;QACnC,MAAM,MAAM,GAAG,EAAE,GAAG,QAAQ,EAAE,GAAG,GAAG,EAAE,CAAC;QACvC,MAAM,OAAO,GACX,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;aACnB,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;aACpD,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,CAAC;aAC9C,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;QACvB,sBAAsB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC3C,CAAC;IAED,UAAU,CAAC,GAAW,EAAE,YAAoB,EAAE,OAAe,EAAE,UAAmB;QAChF,MAAM,cAAc,GAAG,0BAA0B,CAAC,YAAY,CAAC,CAAC;QAChE,MAAM,gBAAgB,GAAG,wBAAwB,CAAC,UAAU,CAAC,CAAC;QAC9D,IAAI,CAAC,cAAc,IAAI,CAAC,UAAU,KAAK,SAAS,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACvE,MAAM,IAAI,KAAK,CAAC,iDAAiD,GAAG,MAAM,YAAY,EAAE,CAAC,CAAC;QAC5F,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;QAE3C,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACjC,gBAAgB,CAAC,GAAG,CAAC,CAAC;QACtB,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;QACpC,IAAI,SAAS,KAAK,GAAG;YAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC;QACnD,sBAAsB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC1C,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,cAAc,EAAE,gBAAgB,CAAC,CAAC;IAC/D,CAAC;IAED,8EAA8E;IAEtE,aAAa;QACnB,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAEjC,yEAAyE;QACzE,yEAAyE;QACzE,wEAAwE;QACxE,gEAAgE;QAChE,MAAM,MAAM,GAAG,IAAI,CAAC,kBAAkB,EAAE,CAAC;QACzC,IAAI,MAAM,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAC1B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;gBACzD,IAAI,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;oBACjC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;gBAClC,CAAC;YACH,CAAC;QACH,CAAC;QAED,sBAAsB,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACvF,CAAC;IAEO,kBAAkB;QACxB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC;YAAE,OAAO,IAAI,CAAC;QAC9C,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;YAClE,IACE,CAAC,MAAM;gBACP,OAAO,MAAM,KAAK,QAAQ;gBAC1B,CAAC,MAAM,CAAC,MAAM;gBACd,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,EACjC,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC;YACD,OAAO,MAAqB,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAEO,gBAAgB,CAAC,GAAW,EAAE,YAAoB,EAAE,UAAmB;QAC7E,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,4BAA4B,YAAY,wBAAwB,GAAG,GAAG,CAAC,CAAC;QAC1F,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACzC,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC;QACrC,IACE,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CACpB,OAAO,KAAK,KAAK,QAAQ;YACvB,CAAC,CAAC,KAAK,KAAK,YAAY,IAAI,CAAC,UAAU;YACvC,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,YAAY,IAAI,KAAK,CAAC,MAAM,KAAK,UAAU,CACjE,EACD,CAAC;YACD,OAAO;QACT,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG;YACxB,GAAG,QAAQ;YACX,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC;SAC9F,CAAC;QACF,IAAI,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC;IAEO,aAAa,CAAC,GAAW,EAAE,KAAiB;QAClD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QAEtC,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC;aAChC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;aAClD,MAAM,CAAC,CAAC,KAAK,EAA+B,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC;QAEvE,IAAI,GAAG,GAA2B,EAAE,CAAC;QACrC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACjC,IAAI,KAAK,CAAC,OAAO,KAAK,KAAK,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YACnD,IAAI,CAAC;gBACH,GAAG,GAAG,YAAY,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;YACrD,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CAAC,wCAAwC,GAAG,IAAI,EAAE,GAAG,CAAC,CAAC;YACtE,CAAC;QACH,CAAC;QAED,OAAO;YACL,MAAM,EAAE,GAAG;YACX,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,GAAG;YACH,MAAM;YACN,GAAG;YACH,eAAe,EAAE,KAAK,CAAC,OAAO;SAC/B,CAAC;IACJ,CAAC;IAEO,iBAAiB,CACvB,GAAW,EACX,KAA+B;QAE/B,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,MAAM,gBAAgB,GAAG,0BAA0B,CAAC,KAAK,CAAC,CAAC;YAC3D,IAAI,CAAC,gBAAgB;gBAAE,OAAO,SAAS,CAAC;YACxC,OAAO;gBACL,MAAM,EAAE,IAAI,CAAC,GAAG,EAAE,gBAAgB,CAAC;gBACnC,MAAM,EAAE,sBAAsB,CAAC,gBAAgB,CAAC;aACjD,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QAC1D,MAAM,gBAAgB,GAAG,0BAA0B,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAClE,IAAI,CAAC,gBAAgB;YAAE,OAAO,SAAS,CAAC;QACxC,MAAM,gBAAgB,GAAG,wBAAwB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAChE,OAAO;YACL,MAAM,EAAE,IAAI,CAAC,GAAG,EAAE,gBAAgB,CAAC;YACnC,MAAM,EAAE,gBAAgB,IAAI,sBAAsB,CAAC,gBAAgB,CAAC;SACrE,CAAC;IACJ,CAAC;CACF;AAED,SAAS,gBAAgB,CAAC,IAAY;IACpC,SAAS,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAC;IAC7D,SAAS,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;AACpC,CAAC;AAED,SAAS,0BAA0B,CAAC,YAAoB;IACtD,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,CAAC;IACpC,IAAI,CAAC,OAAO,IAAI,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,SAAS,CAAC;IAEtD,MAAM,UAAU,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3D,IAAI,CAAC,UAAU,IAAI,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,IAAI,IAAI,UAAU,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7F,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,SAAS,wBAAwB,CAAC,UAAmB;IACnD,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC;IAClC,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACzC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,UAAU,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3D,OAAO,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;AAC7D,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,YAAoB;IACzD,MAAM,UAAU,GAAG,0BAA0B,CAAC,YAAY,CAAC,IAAI,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAChG,OAAO,SAAS,UAAU,EAAE,CAAC;AAC/B,CAAC","sourcesContent":["import { chmodSync, existsSync, mkdirSync, readFileSync } from \"fs\";\nimport { dirname, isAbsolute, join, normalize, sep } from \"path\";\nimport type { SandboxConfig } from \"./sandbox.js\";\nimport { atomicWritePrivateFile } from \"./fs-atomic.js\";\n\nconst PRIVATE_DIR_MODE = 0o700;\n\n// ── Types ──────────────────────────────────────────────────────────────────────\n\n/** Shape of workspace/vaults/vault.json */\nexport interface VaultConfig {\n vaults: Record<string, VaultEntry>;\n}\n\n/** Per-user vault mount entry in vault.json */\nexport interface VaultMountEntry {\n source: string;\n target?: string;\n}\n\n/** Per-user vault entry in vault.json */\nexport interface VaultEntry {\n displayName: string;\n platform?: \"slack\" | \"discord\" | \"telegram\";\n /** Subdirs/files in vault dir to mount into sandbox (e.g. [\".gcloud\", \".ssh\", \".kube\"]) */\n mounts?: Array<string | VaultMountEntry>;\n /** Whether to load env file as environment variables (default: true if env file exists) */\n envFile?: boolean;\n /** Per-user sandbox config override */\n sandbox?: {\n type?: \"image\" | \"firecracker\" | \"host\" | \"container\" | \"docker\";\n container?: string;\n image?: string;\n vmId?: string;\n sshUser?: string;\n sshPort?: number;\n };\n}\n\nexport interface ResolvedVaultMount {\n source: string;\n target: string;\n}\n\n/** Resolved vault ready for use at runtime */\nexport interface ResolvedVault {\n userId: string;\n displayName: string;\n /** Absolute path to vault directory */\n dir: string;\n /** Absolute mount specs */\n mounts: ResolvedVaultMount[];\n /** Parsed from env file */\n env: Record<string, string>;\n sandboxOverride?: VaultEntry[\"sandbox\"];\n}\n\nexport interface VaultManager {\n /** Return true when vault.json contains this exact key. */\n hasEntry(key: string): boolean;\n /** Resolve vault for a user; returns undefined when no entry exists. */\n resolve(userId: string): ResolvedVault | undefined;\n /** Get sandbox config with credential injection for a user */\n getSandboxConfig(userId: string, baseConfig: SandboxConfig): SandboxConfig;\n /** List all configured vaults */\n list(): ResolvedVault[];\n /** Re-read vault.json without restart */\n reload(): void;\n /** Check if vault system is enabled (vault.json exists) */\n isEnabled(): boolean;\n /**\n * Add a vault entry and persist to disk.\n * No-op if the key already exists (idempotent).\n */\n addEntry(key: string, entry: VaultEntry): void;\n /**\n * Ensure a vault entry has image sandbox metadata.\n * Creates the entry when missing and upgrades existing entries that lack sandbox.type.\n */\n ensureImageSandboxEntry(key: string, entry: VaultEntry): void;\n /** Merge environment variables into vaults/<key>/env and persist them to disk. */\n upsertEnv(key: string, env: Record<string, string>): void;\n /** Write a private file into vaults/<key>/ and ensure it is mounted into the sandbox. */\n upsertFile(key: string, relativePath: string, content: string, targetPath?: string): void;\n}\n\n// ── parseEnvFile ───────────────────────────────────────────────────────────────\n\n/**\n * Parse a KEY=VALUE env file. Supports:\n * - Lines starting with # are comments\n * - Empty lines are skipped\n * - Values can be quoted with single or double quotes (quotes are stripped)\n * - No variable expansion\n * - The value is everything after the first `=` to end of line (no inline comments)\n */\nexport function parseEnvFile(content: string): Record<string, string> {\n const env: Record<string, string> = {};\n const lines = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\").split(\"\\n\");\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n\n const eqIndex = trimmed.indexOf(\"=\");\n if (eqIndex === -1) continue;\n\n const key = trimmed.slice(0, eqIndex).trim();\n if (!key) continue;\n\n let value = trimmed.slice(eqIndex + 1);\n\n // Strip matching quotes\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1);\n }\n\n env[key] = value;\n }\n\n return env;\n}\n\n// ── FileVaultManager ───────────────────────────────────────────────────────────\n\nexport class FileVaultManager implements VaultManager {\n private config: VaultConfig | null = null;\n private readonly vaultsDir: string;\n private readonly configPath: string;\n\n constructor(stateDir: string) {\n this.vaultsDir = join(stateDir, \"vaults\");\n this.configPath = join(this.vaultsDir, \"vault.json\");\n this.reload();\n }\n\n reload(): void {\n if (!existsSync(this.configPath)) {\n this.config = null;\n return;\n }\n\n try {\n const raw = readFileSync(this.configPath, \"utf-8\");\n const parsed = JSON.parse(raw);\n\n if (\n !parsed ||\n typeof parsed !== \"object\" ||\n !parsed.vaults ||\n typeof parsed.vaults !== \"object\"\n ) {\n console.error(`vault: malformed vault.json — expected { vaults: { ... } }`);\n this.config = null;\n return;\n }\n\n this.config = parsed as VaultConfig;\n this.warnUnsupportedSandboxTypes();\n } catch (err) {\n console.error(`vault: failed to read ${this.configPath}:`, err);\n this.config = null;\n }\n }\n\n /** Warn for legacy or insecure vault sandbox overrides that are no longer allowed. */\n private warnUnsupportedSandboxTypes(): void {\n if (!this.config) return;\n for (const [key, entry] of Object.entries(this.config.vaults)) {\n if (entry.sandbox?.type === \"host\") {\n console.error(\n `vault: \"${key}\" uses sandbox.type=host, which is blocked for credential isolation. ` +\n \"Use sandbox.type=image or sandbox.type=firecracker.\",\n );\n }\n if (entry.sandbox?.type === \"container\" || entry.sandbox?.type === \"docker\") {\n console.error(\n `vault: \"${key}\" uses sandbox.type=${entry.sandbox.type}, which is blocked for credential isolation. ` +\n \"Use sandbox.type=image for per-user containers or sandbox.type=firecracker.\",\n );\n }\n }\n }\n\n isEnabled(): boolean {\n return this.config !== null;\n }\n\n hasEntry(key: string): boolean {\n return !!this.config?.vaults[key];\n }\n\n resolve(userId: string): ResolvedVault | undefined {\n const entry = this.config?.vaults[userId];\n if (!entry) return undefined;\n return this.buildResolved(userId, entry);\n }\n\n getSandboxConfig(userId: string, baseConfig: SandboxConfig): SandboxConfig {\n const vault = this.resolve(userId);\n if (!vault?.sandboxOverride) return baseConfig;\n\n const override = vault.sandboxOverride;\n\n if (override.type === \"image\") {\n if (baseConfig.type !== \"image\") {\n throw new Error(\n `vault \"${userId}\" sets sandbox.type=image, but base sandbox is \"${baseConfig.type}\". ` +\n \"Use --sandbox=image:<image> to enable per-user managed containers.\",\n );\n }\n const container = override.container || `mama-sandbox-${userId}`;\n return { type: \"container\", container };\n }\n\n if (override.type === \"firecracker\") {\n if (!override.vmId) return baseConfig;\n if (baseConfig.type !== \"firecracker\") {\n throw new Error(\n `vault \"${userId}\" sets sandbox.type=firecracker, but base sandbox is \"${baseConfig.type}\". ` +\n \"Use --sandbox=firecracker:<vm-id>:<host-path> so /workspace stays mapped to the real workspace.\",\n );\n }\n return {\n type: \"firecracker\",\n vmId: override.vmId,\n hostPath: baseConfig.hostPath,\n sshUser: override.sshUser,\n sshPort: override.sshPort,\n };\n }\n\n if (override.type === \"host\") {\n throw new Error(\n `vault \"${userId}\" uses sandbox.type=host, which is blocked for credential isolation. ` +\n \"Use sandbox.type=image or sandbox.type=firecracker.\",\n );\n }\n\n if (override.type === \"container\" || override.type === \"docker\") {\n throw new Error(\n `vault \"${userId}\" uses sandbox.type=${override.type}, which is blocked for credential isolation. ` +\n \"Use sandbox.type=image for per-user containers or sandbox.type=firecracker.\",\n );\n }\n\n // No type override — return base config unchanged\n return baseConfig;\n }\n\n list(): ResolvedVault[] {\n if (!this.config) return [];\n\n const results: ResolvedVault[] = [];\n for (const [key, entry] of Object.entries(this.config.vaults)) {\n results.push(this.buildResolved(key, entry));\n }\n return results;\n }\n\n addEntry(key: string, entry: VaultEntry): void {\n const cfg = (this.config ??= { vaults: {} });\n // Idempotent: skip if already exists\n if (cfg.vaults[key]) return;\n cfg.vaults[key] = entry;\n this.persistConfig();\n }\n\n ensureImageSandboxEntry(key: string, entry: VaultEntry): void {\n if (entry.sandbox?.type !== \"image\") {\n throw new Error(`vault: ensureImageSandboxEntry requires sandbox.type=image for \"${key}\"`);\n }\n\n const cfg = (this.config ??= { vaults: {} });\n const existing = cfg.vaults[key];\n if (!existing) {\n cfg.vaults[key] = entry;\n this.persistConfig();\n return;\n }\n\n let nextEntry = existing;\n let changed = false;\n\n if (!existing.platform && entry.platform) {\n nextEntry = { ...nextEntry, platform: entry.platform };\n changed = true;\n }\n\n const existingSandbox = existing.sandbox;\n if (!existingSandbox?.type) {\n nextEntry = { ...nextEntry, sandbox: entry.sandbox };\n changed = true;\n } else if (\n existingSandbox.type === \"image\" &&\n !existingSandbox.container &&\n entry.sandbox.container\n ) {\n nextEntry = {\n ...nextEntry,\n sandbox: { ...existingSandbox, container: entry.sandbox.container },\n };\n changed = true;\n }\n\n if (!changed) return;\n\n cfg.vaults[key] = nextEntry;\n this.persistConfig();\n }\n\n upsertEnv(key: string, env: Record<string, string>): void {\n const dir = join(this.vaultsDir, key);\n const envPath = join(dir, \"env\");\n ensurePrivateDir(this.vaultsDir);\n ensurePrivateDir(dir);\n const existing = existsSync(envPath)\n ? parseEnvFile(readFileSync(envPath, \"utf-8\"))\n : ({} as Record<string, string>);\n const merged = { ...existing, ...env };\n const content =\n Object.entries(merged)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([envKey, value]) => `${envKey}=${value}`)\n .join(\"\\n\") + \"\\n\";\n atomicWritePrivateFile(envPath, content);\n }\n\n upsertFile(key: string, relativePath: string, content: string, targetPath?: string): void {\n const normalizedPath = normalizeVaultRelativePath(relativePath);\n const normalizedTarget = normalizeVaultTargetPath(targetPath);\n if (!normalizedPath || (targetPath !== undefined && !normalizedTarget)) {\n throw new Error(`vault: invalid relative secret file path for \"${key}\": ${relativePath}`);\n }\n\n const dir = join(this.vaultsDir, key);\n const filePath = join(dir, normalizedPath);\n\n ensurePrivateDir(this.vaultsDir);\n ensurePrivateDir(dir);\n const parentDir = dirname(filePath);\n if (parentDir !== dir) ensurePrivateDir(parentDir);\n atomicWritePrivateFile(filePath, content);\n this.ensureMountEntry(key, normalizedPath, normalizedTarget);\n }\n\n // ── private ────────────────────────────────────────────────────────────────\n\n private persistConfig(): void {\n ensurePrivateDir(this.vaultsDir);\n\n // Preserve concurrent external edits: pull in any entries that appear on\n // disk but not in our in-memory view, so a background edit (e.g. another\n // admin adding a user) is not silently dropped by the next upsert here.\n // Individual field edits still follow last-writer-wins per key.\n const onDisk = this.readConfigFromDisk();\n if (onDisk && this.config) {\n for (const [key, entry] of Object.entries(onDisk.vaults)) {\n if (!(key in this.config.vaults)) {\n this.config.vaults[key] = entry;\n }\n }\n }\n\n atomicWritePrivateFile(this.configPath, JSON.stringify(this.config, null, 2) + \"\\n\");\n }\n\n private readConfigFromDisk(): VaultConfig | null {\n if (!existsSync(this.configPath)) return null;\n try {\n const parsed = JSON.parse(readFileSync(this.configPath, \"utf-8\"));\n if (\n !parsed ||\n typeof parsed !== \"object\" ||\n !parsed.vaults ||\n typeof parsed.vaults !== \"object\"\n ) {\n return null;\n }\n return parsed as VaultConfig;\n } catch {\n return null;\n }\n }\n\n private ensureMountEntry(key: string, relativePath: string, targetPath?: string): void {\n if (!this.config?.vaults[key]) {\n throw new Error(`vault: cannot add mount \"${relativePath}\" for missing entry \"${key}\"`);\n }\n\n const existing = this.config.vaults[key];\n const mounts = existing.mounts ?? [];\n if (\n mounts.some((mount) =>\n typeof mount === \"string\"\n ? mount === relativePath && !targetPath\n : mount.source === relativePath && mount.target === targetPath,\n )\n ) {\n return;\n }\n\n this.config.vaults[key] = {\n ...existing,\n mounts: [...mounts, targetPath ? { source: relativePath, target: targetPath } : relativePath],\n };\n this.persistConfig();\n }\n\n private buildResolved(key: string, entry: VaultEntry): ResolvedVault {\n const dir = join(this.vaultsDir, key);\n\n const mounts = (entry.mounts ?? [])\n .map((mount) => this.resolveMountEntry(dir, mount))\n .filter((mount): mount is ResolvedVaultMount => mount !== undefined);\n\n let env: Record<string, string> = {};\n const envPath = join(dir, \"env\");\n if (entry.envFile !== false && existsSync(envPath)) {\n try {\n env = parseEnvFile(readFileSync(envPath, \"utf-8\"));\n } catch (err) {\n console.error(`vault: failed to parse env file for \"${key}\":`, err);\n }\n }\n\n return {\n userId: key,\n displayName: entry.displayName,\n dir,\n mounts,\n env,\n sandboxOverride: entry.sandbox,\n };\n }\n\n private resolveMountEntry(\n dir: string,\n mount: string | VaultMountEntry,\n ): ResolvedVaultMount | undefined {\n if (typeof mount === \"string\") {\n const normalizedSource = normalizeVaultRelativePath(mount);\n if (!normalizedSource) return undefined;\n return {\n source: join(dir, normalizedSource),\n target: defaultVaultTargetPath(normalizedSource),\n };\n }\n\n if (!mount || typeof mount !== \"object\") return undefined;\n const normalizedSource = normalizeVaultRelativePath(mount.source);\n if (!normalizedSource) return undefined;\n const normalizedTarget = normalizeVaultTargetPath(mount.target);\n return {\n source: join(dir, normalizedSource),\n target: normalizedTarget ?? defaultVaultTargetPath(normalizedSource),\n };\n }\n}\n\nfunction ensurePrivateDir(path: string): void {\n mkdirSync(path, { recursive: true, mode: PRIVATE_DIR_MODE });\n chmodSync(path, PRIVATE_DIR_MODE);\n}\n\nfunction normalizeVaultRelativePath(relativePath: string): string | undefined {\n const trimmed = relativePath.trim();\n if (!trimmed || isAbsolute(trimmed)) return undefined;\n\n const normalized = normalize(trimmed).split(sep).join(\"/\");\n if (!normalized || normalized === \".\" || normalized === \"..\" || normalized.startsWith(\"../\")) {\n return undefined;\n }\n return normalized;\n}\n\nfunction normalizeVaultTargetPath(targetPath?: string): string | undefined {\n if (targetPath === undefined) {\n return undefined;\n }\n\n const trimmed = targetPath.trim();\n if (!trimmed || !trimmed.startsWith(\"/\")) {\n return undefined;\n }\n\n const normalized = normalize(trimmed).split(sep).join(\"/\");\n return normalized.startsWith(\"/\") ? normalized : undefined;\n}\n\nexport function defaultVaultTargetPath(relativePath: string): string {\n const normalized = normalizeVaultRelativePath(relativePath) ?? relativePath.replace(/^\\/+/, \"\");\n return `/root/${normalized}`;\n}\n"]}
1
+ {"version":3,"file":"vault.js","sourceRoot":"","sources":["../src/vault.ts"],"names":[],"mappings":"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","sourcesContent":["import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync } from \"fs\";\nimport { dirname, isAbsolute, join, normalize, sep } from \"path\";\nimport type { PlatformName } from \"./adapter.js\";\nimport type { SandboxConfig } from \"./sandbox.js\";\nimport { atomicWritePrivateFile } from \"./fs-atomic.js\";\n\nconst PRIVATE_DIR_MODE = 0o700;\n\nfunction sanitizeCloudflareSandboxId(value: string): string {\n return (\n value\n .toLowerCase()\n .replace(/[^a-z0-9-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\") || \"unknown\"\n );\n}\n\n// ── Types ──────────────────────────────────────────────────────────────────────\n\n/** Shape of workspace/vaults/vault.json */\nexport interface VaultConfig {\n vaults: Record<string, VaultEntry>;\n}\n\n/** Per-user vault mount entry in vault.json */\nexport interface VaultMountEntry {\n source: string;\n target?: string;\n}\n\n/** Per-user vault entry in vault.json */\nexport interface VaultEntry {\n displayName: string;\n platform?: PlatformName;\n /** Subdirs/files in vault dir to mount into sandbox (e.g. [\".gcloud\", \".ssh\", \".kube\"]) */\n mounts?: Array<string | VaultMountEntry>;\n /** Whether to load env file as environment variables (default: true if env file exists) */\n envFile?: boolean;\n /** Per-user sandbox config override */\n sandbox?: {\n type?: \"image\" | \"firecracker\" | \"cloudflare\" | \"host\" | \"container\" | \"docker\";\n container?: string;\n image?: string;\n vmId?: string;\n sandboxId?: string;\n sshUser?: string;\n sshPort?: number;\n };\n}\n\nexport interface ResolvedVaultMount {\n source: string;\n target: string;\n}\n\n/** Resolved vault ready for use at runtime */\nexport interface ResolvedVault {\n userId: string;\n displayName: string;\n /** Absolute path to vault directory */\n dir: string;\n /** Absolute mount specs */\n mounts: ResolvedVaultMount[];\n /** Parsed from env file */\n env: Record<string, string>;\n sandboxOverride?: VaultEntry[\"sandbox\"];\n}\n\nexport interface VaultManager {\n /** Return true when a vault entry or vault directory exists for this exact key. */\n hasEntry(key: string): boolean;\n /** Resolve vault for a user; returns undefined when no entry exists. */\n resolve(userId: string): ResolvedVault | undefined;\n /** Get sandbox config with credential injection for a user */\n getSandboxConfig(userId: string, baseConfig: SandboxConfig): SandboxConfig;\n /** List all configured vaults */\n list(): ResolvedVault[];\n /** Re-read vault.json without restart */\n reload(): void;\n /** Check if vault system metadata is enabled (vault.json exists) */\n isEnabled(): boolean;\n /**\n * Add a vault entry and persist to disk.\n * No-op if the key already exists (idempotent).\n */\n addEntry(key: string, entry: VaultEntry): void;\n /**\n * Ensure a vault entry has image sandbox metadata.\n * Creates the entry when missing and upgrades existing entries that lack sandbox.type.\n */\n ensureImageSandboxEntry(key: string, entry: VaultEntry): void;\n /** Merge environment variables into vaults/<key>/env and persist them to disk. */\n upsertEnv(key: string, env: Record<string, string>): void;\n /** Write a private file into vaults/<key>/ and ensure it is mounted into the sandbox. */\n upsertFile(key: string, relativePath: string, content: string, targetPath?: string): void;\n}\n\n// ── parseEnvFile ───────────────────────────────────────────────────────────────\n\n/**\n * Parse a KEY=VALUE env file. Supports:\n * - Lines starting with # are comments\n * - Empty lines are skipped\n * - Values can be quoted with single or double quotes (quotes are stripped)\n * - No variable expansion\n * - The value is everything after the first `=` to end of line (no inline comments)\n */\nexport function parseEnvFile(content: string): Record<string, string> {\n const env: Record<string, string> = {};\n const lines = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\").split(\"\\n\");\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n\n const eqIndex = trimmed.indexOf(\"=\");\n if (eqIndex === -1) continue;\n\n const key = trimmed.slice(0, eqIndex).trim();\n if (!key) continue;\n\n let value = trimmed.slice(eqIndex + 1);\n\n // Strip matching quotes\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1);\n }\n\n env[key] = value;\n }\n\n return env;\n}\n\n// ── FileVaultManager ───────────────────────────────────────────────────────────\n\nexport class FileVaultManager implements VaultManager {\n private config: VaultConfig | null = null;\n private readonly vaultsDir: string;\n private readonly configPath: string;\n\n constructor(stateDir: string) {\n this.vaultsDir = join(stateDir, \"vaults\");\n this.configPath = join(this.vaultsDir, \"vault.json\");\n this.reload();\n }\n\n reload(): void {\n if (!existsSync(this.configPath)) {\n this.config = null;\n return;\n }\n\n try {\n const raw = readFileSync(this.configPath, \"utf-8\");\n const parsed = JSON.parse(raw);\n\n if (\n !parsed ||\n typeof parsed !== \"object\" ||\n !parsed.vaults ||\n typeof parsed.vaults !== \"object\"\n ) {\n console.error(`vault: malformed vault.json — expected { vaults: { ... } }`);\n this.config = null;\n return;\n }\n\n this.config = parsed as VaultConfig;\n this.warnUnsupportedSandboxTypes();\n } catch (err) {\n console.error(`vault: failed to read ${this.configPath}:`, err);\n this.config = null;\n }\n }\n\n /** Warn for legacy or insecure vault sandbox overrides that are no longer allowed. */\n private warnUnsupportedSandboxTypes(): void {\n if (!this.config) return;\n for (const [key, entry] of Object.entries(this.config.vaults)) {\n if (entry.sandbox?.type === \"host\") {\n console.error(\n `vault: \"${key}\" uses sandbox.type=host, which is blocked for credential isolation. ` +\n \"Use sandbox.type=image, sandbox.type=firecracker, or sandbox.type=cloudflare.\",\n );\n }\n if (entry.sandbox?.type === \"container\" || entry.sandbox?.type === \"docker\") {\n console.error(\n `vault: \"${key}\" uses sandbox.type=${entry.sandbox.type}, which is blocked for credential isolation. ` +\n \"Use sandbox.type=image for per-user containers, sandbox.type=firecracker, or sandbox.type=cloudflare.\",\n );\n }\n }\n }\n\n isEnabled(): boolean {\n return this.config !== null || existsSync(this.vaultsDir);\n }\n\n hasEntry(key: string): boolean {\n return !!this.config?.vaults[key] || existsSync(join(this.vaultsDir, key));\n }\n\n resolve(userId: string): ResolvedVault | undefined {\n const entry = this.config?.vaults[userId];\n const dir = join(this.vaultsDir, userId);\n if (!entry && !existsSync(dir)) return undefined;\n return this.buildResolved(userId, entry);\n }\n\n getSandboxConfig(userId: string, baseConfig: SandboxConfig): SandboxConfig {\n const vault = this.resolve(userId);\n if (!vault?.sandboxOverride) {\n if (baseConfig.type === \"cloudflare\") {\n return {\n type: \"cloudflare\",\n sandboxId: `${baseConfig.sandboxId}-${sanitizeCloudflareSandboxId(userId)}`,\n };\n }\n return baseConfig;\n }\n\n const override = vault.sandboxOverride;\n\n if (override.type === \"image\") {\n if (baseConfig.type !== \"image\") {\n if (!override.container) {\n if (baseConfig.type === \"cloudflare\") {\n return {\n type: \"cloudflare\",\n sandboxId: `${baseConfig.sandboxId}-${sanitizeCloudflareSandboxId(userId)}`,\n };\n }\n return baseConfig;\n }\n throw new Error(\n `vault \"${userId}\" sets sandbox.type=image, but base sandbox is \"${baseConfig.type}\". ` +\n \"Use --sandbox=image:<image> to enable managed image containers for this vault.\",\n );\n }\n const container = override.container || `mama-sandbox-${userId}`;\n return { type: \"container\", container };\n }\n\n if (override.type === \"firecracker\") {\n if (!override.vmId) return baseConfig;\n if (baseConfig.type !== \"firecracker\") {\n throw new Error(\n `vault \"${userId}\" sets sandbox.type=firecracker, but base sandbox is \"${baseConfig.type}\". ` +\n \"Use --sandbox=firecracker:<vm-id>:<host-path> so /workspace stays mapped to the real workspace.\",\n );\n }\n return {\n type: \"firecracker\",\n vmId: override.vmId,\n hostPath: baseConfig.hostPath,\n sshUser: override.sshUser,\n sshPort: override.sshPort,\n };\n }\n\n if (override.type === \"cloudflare\") {\n if (!override.sandboxId) return baseConfig;\n if (baseConfig.type !== \"cloudflare\") {\n throw new Error(\n `vault \"${userId}\" sets sandbox.type=cloudflare, but base sandbox is \"${baseConfig.type}\". ` +\n \"Use --sandbox=cloudflare:<sandbox-id> to enable Cloudflare remote execution.\",\n );\n }\n return {\n type: \"cloudflare\",\n sandboxId: override.sandboxId,\n };\n }\n\n if (override.type === \"host\") {\n throw new Error(\n `vault \"${userId}\" uses sandbox.type=host, which is blocked for credential isolation. ` +\n \"Use sandbox.type=image, sandbox.type=firecracker, or sandbox.type=cloudflare.\",\n );\n }\n\n if (override.type === \"container\" || override.type === \"docker\") {\n throw new Error(\n `vault \"${userId}\" uses sandbox.type=${override.type}, which is blocked for credential isolation. ` +\n \"Use sandbox.type=image for per-user containers, sandbox.type=firecracker, or sandbox.type=cloudflare.\",\n );\n }\n\n // No type override — return base config unchanged\n return baseConfig;\n }\n\n list(): ResolvedVault[] {\n const keys = new Set<string>();\n for (const key of Object.keys(this.config?.vaults ?? {})) {\n keys.add(key);\n }\n if (existsSync(this.vaultsDir)) {\n for (const entry of readdirSync(this.vaultsDir, { withFileTypes: true })) {\n if (entry.isDirectory()) keys.add(entry.name);\n }\n }\n return Array.from(keys, (key) => this.buildResolved(key, this.config?.vaults[key]));\n }\n\n addEntry(key: string, entry: VaultEntry): void {\n const cfg = (this.config ??= { vaults: {} });\n // Idempotent: skip if already exists\n if (cfg.vaults[key]) return;\n cfg.vaults[key] = entry;\n this.persistConfig();\n }\n\n ensureImageSandboxEntry(key: string, entry: VaultEntry): void {\n if (entry.sandbox?.type !== \"image\") {\n throw new Error(`vault: ensureImageSandboxEntry requires sandbox.type=image for \"${key}\"`);\n }\n\n const cfg = (this.config ??= { vaults: {} });\n const existing = cfg.vaults[key];\n if (!existing) {\n cfg.vaults[key] = entry;\n this.persistConfig();\n return;\n }\n\n let nextEntry = existing;\n let changed = false;\n\n if (!existing.platform && entry.platform) {\n nextEntry = { ...nextEntry, platform: entry.platform };\n changed = true;\n }\n\n const existingSandbox = existing.sandbox;\n if (!existingSandbox?.type) {\n nextEntry = { ...nextEntry, sandbox: entry.sandbox };\n changed = true;\n }\n\n if (!changed) return;\n\n cfg.vaults[key] = nextEntry;\n this.persistConfig();\n }\n\n upsertEnv(key: string, env: Record<string, string>): void {\n const dir = join(this.vaultsDir, key);\n const envPath = join(dir, \"env\");\n ensurePrivateDir(this.vaultsDir);\n ensurePrivateDir(dir);\n const existing = existsSync(envPath)\n ? parseEnvFile(readFileSync(envPath, \"utf-8\"))\n : ({} as Record<string, string>);\n const merged = { ...existing, ...env };\n const content =\n Object.entries(merged)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([envKey, value]) => `${envKey}=${value}`)\n .join(\"\\n\") + \"\\n\";\n atomicWritePrivateFile(envPath, content);\n }\n\n upsertFile(key: string, relativePath: string, content: string, targetPath?: string): void {\n const normalizedPath = normalizeVaultRelativePath(relativePath);\n if (!normalizedPath || (targetPath !== undefined && !normalizeVaultTargetPath(targetPath))) {\n throw new Error(`vault: invalid relative secret file path for \"${key}\": ${relativePath}`);\n }\n\n const dir = join(this.vaultsDir, key);\n const filePath = join(dir, normalizedPath);\n\n ensurePrivateDir(this.vaultsDir);\n ensurePrivateDir(dir);\n const parentDir = dirname(filePath);\n if (parentDir !== dir) ensurePrivateDir(parentDir);\n atomicWritePrivateFile(filePath, content);\n }\n\n // ── private ────────────────────────────────────────────────────────────────\n\n private persistConfig(): void {\n ensurePrivateDir(this.vaultsDir);\n\n // Preserve concurrent external edits: pull in any entries that appear on\n // disk but not in our in-memory view, so a background edit (e.g. another\n // admin adding a user) is not silently dropped by the next upsert here.\n // Individual field edits still follow last-writer-wins per key.\n const onDisk = this.readConfigFromDisk();\n if (onDisk && this.config) {\n for (const [key, entry] of Object.entries(onDisk.vaults)) {\n if (!(key in this.config.vaults)) {\n this.config.vaults[key] = entry;\n }\n }\n }\n\n atomicWritePrivateFile(this.configPath, JSON.stringify(this.config, null, 2) + \"\\n\");\n }\n\n private readConfigFromDisk(): VaultConfig | null {\n if (!existsSync(this.configPath)) return null;\n try {\n const parsed = JSON.parse(readFileSync(this.configPath, \"utf-8\"));\n if (\n !parsed ||\n typeof parsed !== \"object\" ||\n !parsed.vaults ||\n typeof parsed.vaults !== \"object\"\n ) {\n return null;\n }\n return parsed as VaultConfig;\n } catch {\n return null;\n }\n }\n\n private buildResolved(key: string, entry?: VaultEntry): ResolvedVault {\n const dir = join(this.vaultsDir, key);\n\n const inferredMounts = this.inferMountsFromDir(dir);\n const configuredMounts = (entry?.mounts ?? [])\n .map((mount) => this.resolveMountEntry(dir, mount))\n .filter((mount): mount is ResolvedVaultMount => mount !== undefined);\n const mountsByTarget = new Map<string, ResolvedVaultMount>();\n for (const mount of inferredMounts) mountsByTarget.set(mount.target, mount);\n for (const mount of configuredMounts) mountsByTarget.set(mount.target, mount);\n\n let env: Record<string, string> = {};\n const envPath = join(dir, \"env\");\n if ((entry?.envFile ?? true) && existsSync(envPath)) {\n try {\n env = parseEnvFile(readFileSync(envPath, \"utf-8\"));\n } catch (err) {\n console.error(`vault: failed to parse env file for \"${key}\":`, err);\n }\n }\n\n return {\n userId: key,\n displayName: entry?.displayName ?? key,\n dir,\n mounts: [...mountsByTarget.values()],\n env,\n sandboxOverride: entry?.sandbox,\n };\n }\n\n private inferMountsFromDir(dir: string): ResolvedVaultMount[] {\n if (!existsSync(dir)) {\n return [];\n }\n\n const mounts: ResolvedVaultMount[] = [];\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.name === \"env\") continue;\n const source = join(dir, entry.name);\n const target = inferredVaultTargetPath(entry.name);\n if (!target) continue;\n mounts.push({ source, target });\n }\n return mounts;\n }\n\n private resolveMountEntry(\n dir: string,\n mount: string | VaultMountEntry,\n ): ResolvedVaultMount | undefined {\n if (typeof mount === \"string\") {\n const normalizedSource = normalizeVaultRelativePath(mount);\n if (!normalizedSource) return undefined;\n return {\n source: join(dir, normalizedSource),\n target: defaultVaultTargetPath(normalizedSource),\n };\n }\n\n if (!mount || typeof mount !== \"object\") return undefined;\n const normalizedSource = normalizeVaultRelativePath(mount.source);\n if (!normalizedSource) return undefined;\n const normalizedTarget = normalizeVaultTargetPath(mount.target);\n return {\n source: join(dir, normalizedSource),\n target: normalizedTarget ?? defaultVaultTargetPath(normalizedSource),\n };\n }\n}\n\nfunction ensurePrivateDir(path: string): void {\n mkdirSync(path, { recursive: true, mode: PRIVATE_DIR_MODE });\n chmodSync(path, PRIVATE_DIR_MODE);\n}\n\nfunction normalizeVaultRelativePath(relativePath: string): string | undefined {\n const trimmed = relativePath.trim();\n if (!trimmed || isAbsolute(trimmed)) return undefined;\n\n const normalized = normalize(trimmed).split(sep).join(\"/\");\n if (!normalized || normalized === \".\" || normalized === \"..\" || normalized.startsWith(\"../\")) {\n return undefined;\n }\n return normalized;\n}\n\nfunction normalizeVaultTargetPath(targetPath?: string): string | undefined {\n if (targetPath === undefined) {\n return undefined;\n }\n\n const trimmed = targetPath.trim();\n if (!trimmed || !trimmed.startsWith(\"/\")) {\n return undefined;\n }\n\n const normalized = normalize(trimmed).split(sep).join(\"/\");\n return normalized.startsWith(\"/\") ? normalized : undefined;\n}\n\nexport function defaultVaultTargetPath(relativePath: string): string {\n const normalized = normalizeVaultRelativePath(relativePath) ?? relativePath.replace(/^\\/+/, \"\");\n return `/root/${normalized}`;\n}\n\nfunction inferredVaultTargetPath(relativePath: string): string | undefined {\n const normalized = normalizeVaultRelativePath(relativePath);\n if (!normalized) return undefined;\n\n if (normalized === \"gws.json\") {\n return \"/root/.config/gws/credentials.json\";\n }\n if (normalized === \".ssh\" || normalized.startsWith(\".ssh/\")) {\n return \"/root/.ssh\";\n }\n if (normalized === \".kube\" || normalized.startsWith(\".kube/\")) {\n return \"/root/.kube\";\n }\n if (normalized === \".config/gh\" || normalized.startsWith(\".config/gh/\")) {\n return \"/root/.config/gh\";\n }\n\n return defaultVaultTargetPath(normalized);\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geminixiang/mama",
3
- "version": "0.2.0-beta.4",
3
+ "version": "0.2.0-beta.5",
4
4
  "description": "Multi-Agent Mischief Assistant for Slack, Telegram, and Discord",
5
5
  "keywords": [
6
6
  "agent",
@@ -39,7 +39,6 @@
39
39
  "prepare": "husky"
40
40
  },
41
41
  "dependencies": {
42
- "@anthropic-ai/sandbox-runtime": "^0.0.49",
43
42
  "@google-cloud/logging": "^11.2.1",
44
43
  "@mariozechner/pi-agent-core": "^0.71.1",
45
44
  "@mariozechner/pi-ai": "^0.71.1",