svamp-cli 0.2.194 → 0.2.196

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 (26) hide show
  1. package/bin/skills/crew/SKILL.md +24 -1
  2. package/dist/{agentCommands-u3lACoAL.mjs → agentCommands-Epy2LP4k.mjs} +5 -5
  3. package/dist/{auth-CA8v11FJ.mjs → auth-BVAW-c8L.mjs} +1 -1
  4. package/dist/cli.mjs +60 -60
  5. package/dist/{commands-DRYKMdYK.mjs → commands-D3KJdH3r.mjs} +5 -3
  6. package/dist/{commands-CZD9sTwX.mjs → commands-D8ad_1Aa.mjs} +1 -1
  7. package/dist/{commands-Cj1ipwaC.mjs → commands-DYxBgrYh.mjs} +2 -2
  8. package/dist/{commands-DlKKUqVK.mjs → commands-DsYzp1Pc.mjs} +1 -1
  9. package/dist/{commands-CnOAurhc.mjs → commands-Dx6gz9Dr.mjs} +5 -5
  10. package/dist/{commands-CqnF0mFt.mjs → commands-i3J0d8xS.mjs} +137 -49
  11. package/dist/{commands-UDDf7PQt.mjs → commands-yNPBtne1.mjs} +1 -1
  12. package/dist/{fleet-CPdmiNDX.mjs → fleet-DwAy8i7Z.mjs} +1 -1
  13. package/dist/{frpc-CBDwOpZZ.mjs → frpc-DeD80HI1.mjs} +1 -1
  14. package/dist/{headlessCli-CFj2Gxe0.mjs → headlessCli-Cw8ZR1H7.mjs} +2 -2
  15. package/dist/{httpServer-Cf54pQ77.mjs → httpServer-B1KVQJfm.mjs} +38 -0
  16. package/dist/index.mjs +1 -1
  17. package/dist/{package-C9H0l19h.mjs → package-CpNcSvfm.mjs} +2 -2
  18. package/dist/{rpc-6ftZ30co.mjs → rpc-Blk-caU2.mjs} +1 -1
  19. package/dist/{rpc-BHrZL52o.mjs → rpc-CRpXmlRy.mjs} +6 -2
  20. package/dist/{run-6hsamvmy.mjs → run-CBWkZyBD.mjs} +1 -1
  21. package/dist/{run-Dn-FEr9q.mjs → run-Cxq7C5mA.mjs} +329 -39
  22. package/dist/{scheduler-BtltL3kB.mjs → scheduler-ro49WXPg.mjs} +1 -1
  23. package/dist/{serveCommands-DEGssQo6.mjs → serveCommands-5tBepki5.mjs} +5 -5
  24. package/dist/{serveManager-DwO36v9t.mjs → serveManager-D0edqFb9.mjs} +2 -2
  25. package/dist/{sideband-CjLQLnk1.mjs → sideband-p3YKDEQ0.mjs} +1 -1
  26. package/package.json +2 -2
@@ -1,15 +1,15 @@
1
1
  import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import os$1, { homedir as homedir$1 } from 'os';
2
2
  import fs, { mkdir as mkdir$1, readdir as readdir$1, readFile, writeFile as writeFile$1, rename, unlink } from 'fs/promises';
3
- import { readFileSync as readFileSync$1, mkdirSync as mkdirSync$1, writeFileSync as writeFileSync$1, renameSync as renameSync$1, existsSync as existsSync$1, realpathSync, rmSync as rmSync$1, copyFileSync, unlinkSync as unlinkSync$1, readdirSync as readdirSync$1, watch, rmdirSync } from 'fs';
4
- import path__default, { join as join$1, resolve, dirname as dirname$1, basename as basename$1 } from 'path';
3
+ import { readFileSync as readFileSync$1, mkdirSync as mkdirSync$1, writeFileSync as writeFileSync$1, renameSync as renameSync$1, existsSync as existsSync$1, realpathSync as realpathSync$1, rmSync as rmSync$1, copyFileSync, unlinkSync as unlinkSync$1, readdirSync as readdirSync$1, watch, rmdirSync } from 'fs';
4
+ import path__default, { join as join$1, resolve as resolve$1, dirname as dirname$1, basename as basename$1 } from 'path';
5
5
  import { fileURLToPath } from 'url';
6
6
  import { execFile, spawn as spawn$1, execSync as execSync$1, spawnSync } from 'child_process';
7
7
  import { randomUUID as randomUUID$1 } from 'crypto';
8
8
  import { randomBytes, createHash, randomUUID } from 'node:crypto';
9
- import { existsSync, readFileSync, mkdirSync, readdirSync, writeFileSync, renameSync, rmSync, appendFileSync, unlinkSync } from 'node:fs';
9
+ import { existsSync, readFileSync, realpathSync, readdirSync, statSync, mkdirSync, writeFileSync, renameSync, rmSync, appendFileSync, unlinkSync } from 'node:fs';
10
10
  import { exec, execSync, spawn, execFile as execFile$1, execFileSync } from 'node:child_process';
11
11
  import { promisify } from 'util';
12
- import { join, basename, dirname } from 'node:path';
12
+ import { extname, resolve, join, sep, basename, dirname } from 'node:path';
13
13
  import { EventEmitter } from 'node:events';
14
14
  import os, { homedir, platform } from 'node:os';
15
15
  import { ndJsonStream, ClientSideConnection } from '@agentclientprotocol/sdk';
@@ -1233,6 +1233,155 @@ function renderTemplate(template, ctx) {
1233
1233
  });
1234
1234
  }
1235
1235
 
1236
+ const HARD_MAX_BYTES = 25 * 1024 * 1024;
1237
+ const DEFAULT_MAX_BYTES = 5 * 1024 * 1024;
1238
+ const DEFAULT_MAX_COUNT = 100;
1239
+ const DEFAULT_ALLOWED_EXT = [
1240
+ ".png",
1241
+ ".jpg",
1242
+ ".jpeg",
1243
+ ".gif",
1244
+ ".webp",
1245
+ ".bmp",
1246
+ ".svg",
1247
+ ".pdf",
1248
+ ".txt",
1249
+ ".md",
1250
+ ".csv",
1251
+ ".json",
1252
+ ".log",
1253
+ ".yaml",
1254
+ ".yml"
1255
+ ];
1256
+ function uploadEnabled(c) {
1257
+ return !!(c && c.upload && c.upload.enabled === true);
1258
+ }
1259
+ function effectiveUploadConfig(c) {
1260
+ const u = c.upload || {};
1261
+ const maxBytes = Math.min(
1262
+ typeof u.maxBytes === "number" && u.maxBytes > 0 ? u.maxBytes : DEFAULT_MAX_BYTES,
1263
+ HARD_MAX_BYTES
1264
+ );
1265
+ const maxCount = typeof u.maxCount === "number" && u.maxCount > 0 ? Math.floor(u.maxCount) : DEFAULT_MAX_COUNT;
1266
+ const allowedExt = Array.isArray(u.allowedExt) ? u.allowedExt.map((e) => String(e).toLowerCase().trim()).filter(Boolean) : DEFAULT_ALLOWED_EXT;
1267
+ return { ...u, maxBytes, maxCount, allowedExt };
1268
+ }
1269
+ function validateUploadConfig(c) {
1270
+ const u = c.upload;
1271
+ if (u == null) return [];
1272
+ if (typeof u !== "object") return ["upload must be an object"];
1273
+ const errs = [];
1274
+ if (u.enabled != null && typeof u.enabled !== "boolean") errs.push("upload.enabled must be a boolean");
1275
+ if (u.maxBytes != null && (typeof u.maxBytes !== "number" || u.maxBytes <= 0)) errs.push("upload.maxBytes must be a positive number");
1276
+ if (u.maxCount != null && (typeof u.maxCount !== "number" || u.maxCount <= 0)) errs.push("upload.maxCount must be a positive number");
1277
+ if (u.allowedExt != null && !Array.isArray(u.allowedExt)) errs.push("upload.allowedExt must be an array of extensions");
1278
+ if (u.allowedMime != null && !Array.isArray(u.allowedMime)) errs.push("upload.allowedMime must be an array of mime types");
1279
+ if (u.dir != null) {
1280
+ if (typeof u.dir !== "string") errs.push("upload.dir must be a string");
1281
+ else if (u.dir.startsWith("/") || u.dir.startsWith("\\") || /(^|[\\/])\.\.([\\/]|$)/.test(u.dir) || u.dir.includes("\0"))
1282
+ errs.push('upload.dir must be a project-relative path without ".." traversal');
1283
+ }
1284
+ return errs;
1285
+ }
1286
+ const CONTROL_CHARS = /[\x00-\x1f\x7f]/;
1287
+ function sanitizeFilename(raw) {
1288
+ const input = String(raw ?? "").trim();
1289
+ if (!input) return { error: "filename required" };
1290
+ if (input.length > 200) return { error: "filename too long (max 200 chars)" };
1291
+ if (CONTROL_CHARS.test(input)) return { error: "filename contains control characters" };
1292
+ if (input.includes("\0")) return { error: "filename contains null byte" };
1293
+ if (input.includes("/") || input.includes("\\")) return { error: "filename must not contain path separators" };
1294
+ if (input.includes("..")) return { error: 'filename must not contain ".."' };
1295
+ const name = basename(input);
1296
+ if (!name || name === "." || name === "..") return { error: "invalid filename" };
1297
+ if (name.startsWith(".")) return { error: "filename must not start with a dot" };
1298
+ if (!/^[A-Za-z0-9 ._-]+$/.test(name)) return { error: "filename has disallowed characters (use letters, digits, space, . _ -)" };
1299
+ return { name };
1300
+ }
1301
+ function validateUpload(input) {
1302
+ const { projectDir, channel, bytes, contentType } = input;
1303
+ if (!uploadEnabled(channel)) return { error: "uploads are not enabled on this channel" };
1304
+ const cfg = effectiveUploadConfig(channel);
1305
+ const fn = sanitizeFilename(input.filename);
1306
+ if (fn.error || !fn.name) return { error: fn.error || "invalid filename" };
1307
+ const name = fn.name;
1308
+ if (!Number.isFinite(bytes) || bytes < 0) return { error: "invalid file size" };
1309
+ if (bytes === 0) return { error: "empty file" };
1310
+ if (bytes > cfg.maxBytes) return { error: `file too large (${bytes} bytes > ${cfg.maxBytes} limit)` };
1311
+ const ext = extname(name).toLowerCase();
1312
+ if (!ext) return { error: "file has no extension" };
1313
+ if (!cfg.allowedExt.includes(ext)) return { error: `file type not allowed: ${ext} (allowed: ${cfg.allowedExt.join(", ") || "none"})` };
1314
+ if (Array.isArray(cfg.allowedMime) && cfg.allowedMime.length) {
1315
+ const ct = String(contentType || "").toLowerCase().split(";")[0].trim();
1316
+ if (!ct) return { error: "content-type required (channel restricts MIME types)" };
1317
+ if (!cfg.allowedMime.map((m) => String(m).toLowerCase().trim()).includes(ct))
1318
+ return { error: `mime type not allowed: ${ct}` };
1319
+ }
1320
+ let projectReal;
1321
+ try {
1322
+ projectReal = realpathSync(projectDir);
1323
+ } catch {
1324
+ projectReal = resolve(projectDir);
1325
+ }
1326
+ const relDir = cfg.dir && String(cfg.dir).trim() ? String(cfg.dir).trim() : join(".svamp", "channels", "uploads", String(channel.id || "unknown"));
1327
+ const destDir = resolve(projectReal, relDir);
1328
+ if (destDir !== projectReal && !destDir.startsWith(projectReal + sep))
1329
+ return { error: "upload destination escapes the project folder" };
1330
+ const fullPath = join(destDir, name);
1331
+ if (fullPath !== destDir && !fullPath.startsWith(destDir + sep))
1332
+ return { error: "upload path escapes the destination directory" };
1333
+ try {
1334
+ if (existsSync(destDir)) {
1335
+ const count = readdirSync(destDir).filter((f) => {
1336
+ try {
1337
+ return statSync(join(destDir, f)).isFile();
1338
+ } catch {
1339
+ return false;
1340
+ }
1341
+ }).length;
1342
+ if (count >= cfg.maxCount) return { error: `upload limit reached (${cfg.maxCount} files)` };
1343
+ }
1344
+ } catch {
1345
+ }
1346
+ return { target: { destDir, name, fullPath } };
1347
+ }
1348
+ function writeUpload(projectDir, target, data) {
1349
+ mkdirSync(target.destDir, { recursive: true });
1350
+ let finalName = target.name;
1351
+ let finalPath = target.fullPath;
1352
+ if (existsSync(finalPath)) {
1353
+ const ext = extname(target.name);
1354
+ const stem = target.name.slice(0, target.name.length - ext.length);
1355
+ finalName = `${stem}-${randomBytes(3).toString("hex")}${ext}`;
1356
+ finalPath = join(target.destDir, finalName);
1357
+ }
1358
+ const tmp = finalPath + ".tmp-" + randomBytes(4).toString("hex");
1359
+ writeFileSync(tmp, data);
1360
+ renameSync(tmp, finalPath);
1361
+ let projectReal;
1362
+ try {
1363
+ projectReal = realpathSync(projectDir);
1364
+ } catch {
1365
+ projectReal = resolve(projectDir);
1366
+ }
1367
+ const relPath = finalPath.startsWith(projectReal + sep) ? finalPath.slice(projectReal.length + 1) : finalPath;
1368
+ return { name: finalName, relPath, fullPath: finalPath, bytes: data.length };
1369
+ }
1370
+ function decodeBase64Capped(b64, maxBytes) {
1371
+ const s = String(b64 ?? "");
1372
+ if (!s) return { error: "empty file content" };
1373
+ if (s.length > Math.ceil(maxBytes / 3) * 4 + 16) return { error: `file too large (exceeds ${maxBytes} byte limit)` };
1374
+ let data;
1375
+ try {
1376
+ data = Buffer.from(s, "base64");
1377
+ } catch {
1378
+ return { error: "invalid base64 content" };
1379
+ }
1380
+ if (!data.length) return { error: "invalid or empty base64 content" };
1381
+ if (data.length > maxBytes) return { error: `file too large (${data.length} bytes > ${maxBytes} limit)` };
1382
+ return { data };
1383
+ }
1384
+
1236
1385
  const genId = () => "c_" + randomBytes(5).toString("hex");
1237
1386
  const genKey = () => "ck_" + randomBytes(18).toString("base64url");
1238
1387
  const DEFAULT_TEMPLATE = `<inbound-message from="\${sender.name}" sender-type="\${sender.kind}" verified="\${sender.verified}" channel="\${channel.name}" call-id="\${call.id}" at="\${now}">
@@ -1268,6 +1417,7 @@ function validateChannel(c) {
1268
1417
  if (c.skill?.description && unsafe.test(c.skill.description)) errs.push(`skill.description must be single-line and not contain < > " ' &`);
1269
1418
  if (m === "fixed" && c.identity.fixed?.name && unsafe.test(c.identity.fixed.name)) errs.push(`identity.fixed.name must not contain < > " ' & or newlines`);
1270
1419
  for (const cl of c.identity?.callers || []) if (unsafe.test(cl.name || "")) errs.push(`caller name "${cl.name}" must not contain < > " ' & or newlines`);
1420
+ errs.push(...validateUploadConfig(c));
1271
1421
  return errs;
1272
1422
  }
1273
1423
  function normalizeBind(c) {
@@ -1377,6 +1527,7 @@ function generateSkillBody(channel, ctx) {
1377
1527
  const gw = ctx?.channelsServiceId ? gatewayBase(svc, base) : `${base}/<workspace>/services/<machine>:channels`;
1378
1528
  const skillUrl = `${gw}/skill?channel=${channel.id}`;
1379
1529
  const sendUrl = `${gw}/send`;
1530
+ const uploadUrl = `${gw}/upload`;
1380
1531
  const key = ctx?.key || "<your-key>";
1381
1532
  const isAgent = channel.action?.kind === "agent";
1382
1533
  const isQueue = channel.reply?.mode === "queue";
@@ -1422,6 +1573,47 @@ to \`send()\`. The reply is then delivered straight to **your session's inbox**
1422
1573
  channel's outbox and you must poll \`receive()\` (above) for it \u2014 it will **not** appear
1423
1574
  in your inbox.` : "";
1424
1575
  const rpcAuthNote = hyphaOpen ? `Your verified Hypha identity is accepted \u2014 no key needed.` : `Uses your verified Hypha identity.`;
1576
+ const uploadSection = uploadEnabled(channel) ? (() => {
1577
+ const u = effectiveUploadConfig(channel);
1578
+ const mb = (u.maxBytes / (1024 * 1024)).toFixed(u.maxBytes % (1024 * 1024) === 0 ? 0 : 1);
1579
+ const exts = u.allowedExt.join(", ") || "(none)";
1580
+ const mimeLine = Array.isArray(u.allowedMime) && u.allowedMime.length ? `
1581
+ - **Allowed MIME types:** ${u.allowedMime.join(", ")}` : "";
1582
+ return `
1583
+
1584
+ ## Uploading a file
1585
+ This channel **accepts file uploads** (server-validated, deny-by-default elsewhere).
1586
+ Send the file base64-encoded; the server validates it and saves it into the session's
1587
+ project folder, returning the saved relative path. Reference that path in a follow-up
1588
+ \`send()\` so the agent knows what you uploaded.
1589
+
1590
+ **Limits (enforced server-side):**
1591
+ - **Max size:** ${mb} MiB per file
1592
+ - **Max files retained:** ${u.maxCount}
1593
+ - **Allowed extensions:** ${exts}${mimeLine}
1594
+ - Filenames are sanitized to a safe basename \u2014 no paths, no \`..\` traversal.
1595
+
1596
+ ### Hypha RPC
1597
+ \`\`\`js
1598
+ const res = await get_service("${svc}").upload({
1599
+ channel: "${channel.id}",
1600
+ filename: "report.pdf",
1601
+ content_base64: "<base64 of the file bytes>",
1602
+ content_type: "application/pdf",
1603
+ from: "your-name"${rSession ? `,
1604
+ session: "${rSession}"` : ""}${hyphaOpen ? "" : `,
1605
+ key: "${key}"`}
1606
+ });
1607
+ // \u2192 { ok: true, name, path, bytes }
1608
+ \`\`\`
1609
+
1610
+ ### HTTP
1611
+ \`\`\`bash
1612
+ curl -X POST "${uploadUrl}" \\
1613
+ -H 'Content-Type: application/json' \\
1614
+ -d '{"kwargs": {"channel": "${channel.id}", "filename": "report.pdf", "content_base64": "<base64>", "content_type": "application/pdf", "from": "your-name", "key": "${key}"}}'
1615
+ \`\`\``;
1616
+ })() : "";
1425
1617
  return `---
1426
1618
  name: ${name}
1427
1619
  description: ${desc}
@@ -1454,7 +1646,7 @@ curl -X POST "${sendUrl}" \\
1454
1646
  -d '{"kwargs": {"channel": "${channel.id}", "message": "your message here", "from": "your-name", "key": "${key}"${sessionKv}}}'
1455
1647
  \`\`\`
1456
1648
 
1457
- Always-current copy of this skill (all ids filled in): ${skillUrl}${queueSection}`;
1649
+ Always-current copy of this skill (all ids filled in): ${skillUrl}${uploadSection}${queueSection}`;
1458
1650
  }
1459
1651
 
1460
1652
  function resolveSender(channel, input = {}) {
@@ -2720,7 +2912,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
2720
2912
  const tunnels = handlers.tunnels;
2721
2913
  if (!tunnels) throw new Error("Tunnel management not available");
2722
2914
  if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
2723
- const { FrpcTunnel } = await import('./frpc-CBDwOpZZ.mjs');
2915
+ const { FrpcTunnel } = await import('./frpc-DeD80HI1.mjs');
2724
2916
  const tunnel = new FrpcTunnel({
2725
2917
  name: params.name,
2726
2918
  ports: params.ports,
@@ -3167,7 +3359,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
3167
3359
  }
3168
3360
  const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
3169
3361
  const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
3170
- const { toolsForRole } = await import('./sideband-CjLQLnk1.mjs');
3362
+ const { toolsForRole } = await import('./sideband-p3YKDEQ0.mjs');
3171
3363
  const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
3172
3364
  return fmt(r2);
3173
3365
  }
@@ -3266,7 +3458,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
3266
3458
  if (r.error || !r.sender) return { error: r.error || "unauthorized" };
3267
3459
  const callId = "call_" + Math.random().toString(16).slice(2, 12);
3268
3460
  const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
3269
- const { queryCore } = await import('./commands-DlKKUqVK.mjs');
3461
+ const { queryCore } = await import('./commands-DsYzp1Pc.mjs');
3270
3462
  const timeout = c.reply?.timeout_sec || 120;
3271
3463
  let result;
3272
3464
  try {
@@ -3371,6 +3563,44 @@ ${d?.error || "not found"}`;
3371
3563
  }
3372
3564
  await new Promise((resolve) => setTimeout(resolve, 500));
3373
3565
  }
3566
+ },
3567
+ // Safety-restricted file upload (#0093). Deny-by-default: only channels whose
3568
+ // config sets upload.enabled accept files. The upload is a pure, validated file
3569
+ // write into the channel's project folder — no agent run — so we do NOT spawn a
3570
+ // stateless one-shot; we resolve the channel + dir from disk and write directly,
3571
+ // which works whether or not a live session hosts the channel. All safety checks
3572
+ // (filename/size/extension/MIME/count/path-confinement) live in channel/upload.ts.
3573
+ upload: async (kwargs = {}, context) => {
3574
+ trackInbound();
3575
+ if (!kwargs.channel) return { error: MISSING_CHANNEL_HINT };
3576
+ const found = readChannelFromDisk(kwargs.channel);
3577
+ if (!found) return { error: "channel not found" };
3578
+ const { c, dir } = found;
3579
+ if (!uploadEnabled(c)) return { error: "uploads are not enabled on this channel" };
3580
+ const u = context?.user;
3581
+ const r = resolveSender(c, {
3582
+ key: kwargs.key,
3583
+ from: kwargs.from,
3584
+ hyphaUser: u && u.is_anonymous !== true ? u.email || u.id : void 0,
3585
+ hyphaAnonymous: u?.is_anonymous === true,
3586
+ hyphaWorkspace: u?.scope?.current_workspace
3587
+ });
3588
+ if (r.error || !r.sender) return { error: r.error || "unauthorized" };
3589
+ const cfg = effectiveUploadConfig(c);
3590
+ const dec = decodeBase64Capped(kwargs.content_base64, cfg.maxBytes);
3591
+ if (dec.error || !dec.data) return { error: dec.error || "invalid content" };
3592
+ const v = validateUpload({ projectDir: dir, channel: c, filename: kwargs.filename, bytes: dec.data.length, contentType: kwargs.content_type });
3593
+ if (v.error || !v.target) return { error: v.error || "upload rejected" };
3594
+ try {
3595
+ const saved = writeUpload(dir, v.target, dec.data);
3596
+ try {
3597
+ new ChannelStore(dir).recordCall(c.id, { sender: r.sender.name, verified: r.sender.verified, callId: "up_" + Math.random().toString(16).slice(2, 10), outcome: "uploaded" });
3598
+ } catch {
3599
+ }
3600
+ return { ok: true, name: saved.name, path: saved.relPath, bytes: saved.bytes };
3601
+ } catch (e) {
3602
+ return { ok: false, error: e?.message || String(e) };
3603
+ }
3374
3604
  }
3375
3605
  },
3376
3606
  { overwrite: true }
@@ -3760,7 +3990,9 @@ var claudeAuth = /*#__PURE__*/Object.freeze({
3760
3990
 
3761
3991
  const PARTICIPANTS_CHANNEL_ID = "sys-participants";
3762
3992
  function channelPublicView(c) {
3763
- return { id: c.id, name: c.name, description: c.description, identity: { mode: c.identity?.mode }, action: c.action?.kind, bind: c.bind };
3993
+ const u = c.upload;
3994
+ const upload = u && u.enabled === true ? { enabled: true, ...typeof u.maxBytes === "number" ? { maxBytes: u.maxBytes } : {}, ...Array.isArray(u.allowedExt) ? { allowedExt: u.allowedExt } : {} } : void 0;
3995
+ return { id: c.id, name: c.name, description: c.description, identity: { mode: c.identity?.mode }, action: c.action?.kind, bind: c.bind, ...upload ? { upload } : {} };
3764
3996
  }
3765
3997
  function isStructuredMessage(msg) {
3766
3998
  return !!(msg.from || msg.fromSession || msg.subject || msg.replyTo || msg.threadId || msg.channel);
@@ -3782,7 +4014,6 @@ function formatInboxMessageXml(msg) {
3782
4014
  if (msg.subject) attrs.push(`subject="${escapeXml(msg.subject)}"`);
3783
4015
  if (msg.urgency) attrs.push(`urgency="${msg.urgency}"`);
3784
4016
  if (msg.replyTo) attrs.push(`reply-to="${escapeXml(msg.replyTo)}"`);
3785
- if (msg.cc && msg.cc.length > 0) attrs.push(`cc="${msg.cc.map(escapeXml).join(",")}"`);
3786
4017
  if (msg.threadId) attrs.push(`thread-id="${escapeXml(msg.threadId)}"`);
3787
4018
  attrs.push(`timestamp="${msg.timestamp}"`);
3788
4019
  return `<svamp-message ${attrs.join(" ")}>
@@ -3942,13 +4173,15 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
3942
4173
  body: m.body,
3943
4174
  timestamp: m.timestamp,
3944
4175
  read: m.read,
4176
+ // `handled` = agent consumed it; drives the "pending" (unread) badge
4177
+ // together with `read` so handled messages don't linger as phantom-unread.
4178
+ ...m.handled ? { handled: true } : {},
3945
4179
  from: m.from,
3946
4180
  fromSession: m.fromSession,
3947
4181
  to: m.to,
3948
4182
  subject: m.subject,
3949
4183
  urgency: m.urgency,
3950
4184
  replyTo: m.replyTo,
3951
- cc: m.cc,
3952
4185
  threadId: m.threadId,
3953
4186
  // Channel provenance so the inbox UI can show who/verified.
3954
4187
  ...m.verified !== void 0 ? { verified: m.verified } : {},
@@ -4553,6 +4786,40 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
4553
4786
  return { ok: false, call_id: callId, status: "error", error: e?.message || String(e) };
4554
4787
  }
4555
4788
  },
4789
+ // Safety-restricted file upload (#0093). Deny-by-default: a channel accepts
4790
+ // uploads ONLY when its config sets upload.enabled. Auth = the channel's OWN
4791
+ // identity policy (resolveSender), same as channelSend. All validation is
4792
+ // server-side (channel/upload.ts): filename sanitization (no traversal),
4793
+ // size cap, extension/MIME allowlist, file-count cap, path confinement.
4794
+ channelUpload: async (params, context) => {
4795
+ const c = channelStore.get(params.channel);
4796
+ if (!c || c.enabled === false) return { error: "channel not found" };
4797
+ if (!uploadEnabled(c)) return { error: "uploads are not enabled on this channel" };
4798
+ const u = context?.user;
4799
+ const r = resolveSender(c, {
4800
+ key: params.key,
4801
+ from: params.from,
4802
+ hyphaUser: u && u.is_anonymous !== true ? u.email || u.id : void 0,
4803
+ hyphaAnonymous: u?.is_anonymous === true,
4804
+ hyphaWorkspace: u?.scope?.current_workspace
4805
+ });
4806
+ if (r.error || !r.sender) return { error: r.error || "unauthorized" };
4807
+ const cfg2 = effectiveUploadConfig(c);
4808
+ const dec = decodeBase64Capped(params.content_base64, cfg2.maxBytes);
4809
+ if (dec.error || !dec.data) return { error: dec.error || "invalid content" };
4810
+ const v = validateUpload({ projectDir: metadata.path, channel: c, filename: params.filename, bytes: dec.data.length, contentType: params.content_type });
4811
+ if (v.error || !v.target) return { error: v.error || "upload rejected" };
4812
+ const callId = "call_" + randomUUID().slice(0, 10);
4813
+ try {
4814
+ const saved = writeUpload(metadata.path, v.target, dec.data);
4815
+ channelStore.recordCall(c.id, { sender: r.sender.name, verified: r.sender.verified, callId, outcome: "uploaded" });
4816
+ syncChannelsToMetadata();
4817
+ return { ok: true, name: saved.name, path: saved.relPath, bytes: saved.bytes };
4818
+ } catch (e) {
4819
+ channelStore.recordCall(c.id, { sender: r.sender.name, verified: r.sender.verified, callId, outcome: "error" });
4820
+ return { ok: false, error: e?.message || String(e) };
4821
+ }
4822
+ },
4556
4823
  // Agent/owner answers a queued (async) channel message → channel outbox, addressed
4557
4824
  // to the original external caller. Routed here by `inbox reply` for channel-origin
4558
4825
  // inbox messages (which carry channelId + correlationId + from). Owner-gated since
@@ -5063,6 +5330,13 @@ Output ONLY the full revised reply text \u2014 no preamble, no commentary, no su
5063
5330
  syncInboxToMetadata();
5064
5331
  }
5065
5332
  },
5333
+ markInboxHandled: (messageId) => {
5334
+ const msg = inbox.find((m) => m.messageId === messageId);
5335
+ if (msg && !msg.handled) {
5336
+ msg.handled = true;
5337
+ syncInboxToMetadata();
5338
+ }
5339
+ },
5066
5340
  disconnect: async () => {
5067
5341
  const toRemove = [...listeners];
5068
5342
  for (const listener of toRemove) {
@@ -8039,9 +8313,9 @@ var GeminiTransport$1 = /*#__PURE__*/Object.freeze({
8039
8313
  function resolveTranscriptPath(cwd, claudeSessionId) {
8040
8314
  let real;
8041
8315
  try {
8042
- real = realpathSync(cwd);
8316
+ real = realpathSync$1(cwd);
8043
8317
  } catch {
8044
- real = resolve(cwd);
8318
+ real = resolve$1(cwd);
8045
8319
  }
8046
8320
  const projectId = real.replace(/[^a-zA-Z0-9-]/g, "-");
8047
8321
  const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR || join$1(os$1.homedir(), ".claude");
@@ -9913,7 +10187,7 @@ function normalizeStatus(s) {
9913
10187
  if (s === "done") return "archived";
9914
10188
  return s === "ready" || s === "in_progress" || s === "archived" ? s : "ready";
9915
10189
  }
9916
- const FIELD_ORDER = ["id", "title", "status", "scope", "labels", "verify", "disposition", "triaged", "branch", "session", "original", "created", "closed"];
10190
+ const FIELD_ORDER = ["id", "title", "status", "scope", "labels", "verify", "disposition", "triaged", "branch", "session", "owner", "original", "created", "closed"];
9917
10191
  function resolveProjectRoot(start = process.cwd()) {
9918
10192
  let dir = start;
9919
10193
  for (let i = 0; i < 40; i++) {
@@ -9970,6 +10244,7 @@ function parseIssue(content) {
9970
10244
  triaged: fm.triaged === true,
9971
10245
  branch: fm.branch ?? null,
9972
10246
  session: fm.session ?? null,
10247
+ owner: fm.owner ?? null,
9973
10248
  original: fm.original ?? null,
9974
10249
  created: String(fm.created ?? (/* @__PURE__ */ new Date()).toISOString()),
9975
10250
  closed: fm.closed ?? null,
@@ -9997,6 +10272,13 @@ function listIssues(projectRoot, opts = {}) {
9997
10272
  if (opts.scope) items = items.filter((i) => i.scope === opts.scope);
9998
10273
  return items.sort((a, b) => Number(a.id) - Number(b.id));
9999
10274
  }
10275
+ function isVisibleTo(issue, session) {
10276
+ if (issue.scope === "session") return issue.session === session || issue.owner === session;
10277
+ if (issue.owner === session) return true;
10278
+ if (issue.session === session) return true;
10279
+ if (!issue.owner && !issue.session) return true;
10280
+ return false;
10281
+ }
10000
10282
  function getIssue(projectRoot, id) {
10001
10283
  const padded = /^\d+$/.test(id) ? id.padStart(4, "0") : id;
10002
10284
  for (const p of [issuePath(projectRoot, padded), issuePath(projectRoot, padded, true)]) {
@@ -10033,6 +10315,7 @@ function addIssue(projectRoot, fields) {
10033
10315
  triaged: fields.triaged ?? false,
10034
10316
  branch: null,
10035
10317
  session: fields.session ?? null,
10318
+ owner: fields.owner ?? null,
10036
10319
  original: fields.original ?? null,
10037
10320
  created: (/* @__PURE__ */ new Date()).toISOString(),
10038
10321
  closed: null,
@@ -11614,7 +11897,7 @@ async function startDaemon(options) {
11614
11897
  saveExposedTunnels(list);
11615
11898
  }
11616
11899
  async function createExposedTunnel(spec) {
11617
- const { FrpcTunnel } = await import('./frpc-CBDwOpZZ.mjs');
11900
+ const { FrpcTunnel } = await import('./frpc-DeD80HI1.mjs');
11618
11901
  const tunnel = new FrpcTunnel({
11619
11902
  name: spec.name,
11620
11903
  ports: spec.ports,
@@ -11634,7 +11917,7 @@ async function startDaemon(options) {
11634
11917
  return tunnel;
11635
11918
  }
11636
11919
  const tunnelRecreateState = /* @__PURE__ */ new Map();
11637
- const { ServeManager } = await import('./serveManager-DwO36v9t.mjs');
11920
+ const { ServeManager } = await import('./serveManager-D0edqFb9.mjs');
11638
11921
  const serveManager = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
11639
11922
  ensureAutoInstalledSkills(logger).catch(() => {
11640
11923
  });
@@ -13416,6 +13699,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
13416
13699
  }
13417
13700
  if (decision.action === "wake") {
13418
13701
  logger.log(`[Session ${sessionId}] Delivering inbox message to agent`);
13702
+ sessionService.markInboxHandled(message.messageId);
13419
13703
  writeInboundContext(sessionId, { hopCount: message.hopCount ?? 0, threadId: message.threadId || message.messageId });
13420
13704
  sessionService.pushMessage(formatted, "user");
13421
13705
  if (!claudeProcess || claudeProcess.exitCode !== null) {
@@ -13498,11 +13782,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
13498
13782
  });
13499
13783
  },
13500
13784
  onIssue: async (params) => {
13501
- const { issueRpc } = await import('./rpc-BHrZL52o.mjs');
13785
+ const { issueRpc } = await import('./rpc-CRpXmlRy.mjs');
13502
13786
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner });
13503
13787
  },
13504
13788
  onWorkflow: async (params) => {
13505
- const { workflowRpc } = await import('./rpc-6ftZ30co.mjs');
13789
+ const { workflowRpc } = await import('./rpc-Blk-caU2.mjs');
13506
13790
  return workflowRpc(params?.cwd || directory, params || {});
13507
13791
  },
13508
13792
  onRipgrep: async (args, cwd) => {
@@ -13519,23 +13803,23 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
13519
13803
  });
13520
13804
  },
13521
13805
  onReadFile: async (path) => {
13522
- const resolvedPath = resolve(directory, path);
13523
- if (sessionMetadata.securityContext && resolvedPath !== resolve(directory) && !resolvedPath.startsWith(resolve(directory) + "/")) {
13806
+ const resolvedPath = resolve$1(directory, path);
13807
+ if (sessionMetadata.securityContext && resolvedPath !== resolve$1(directory) && !resolvedPath.startsWith(resolve$1(directory) + "/")) {
13524
13808
  throw new Error("Path outside working directory");
13525
13809
  }
13526
13810
  return await readSessionFileBase64(resolvedPath);
13527
13811
  },
13528
13812
  onWriteFile: async (path, content) => {
13529
- const resolvedPath = resolve(directory, path);
13530
- if (sessionMetadata.securityContext && resolvedPath !== resolve(directory) && !resolvedPath.startsWith(resolve(directory) + "/")) {
13813
+ const resolvedPath = resolve$1(directory, path);
13814
+ if (sessionMetadata.securityContext && resolvedPath !== resolve$1(directory) && !resolvedPath.startsWith(resolve$1(directory) + "/")) {
13531
13815
  throw new Error("Path outside working directory");
13532
13816
  }
13533
13817
  await fs.mkdir(dirname$1(resolvedPath), { recursive: true });
13534
13818
  await fs.writeFile(resolvedPath, Buffer.from(content, "base64"));
13535
13819
  },
13536
13820
  onListDirectory: async (path) => {
13537
- const resolvedDir = resolve(directory, path || ".");
13538
- if (sessionMetadata.securityContext && resolvedDir !== resolve(directory) && !resolvedDir.startsWith(resolve(directory) + "/")) {
13821
+ const resolvedDir = resolve$1(directory, path || ".");
13822
+ if (sessionMetadata.securityContext && resolvedDir !== resolve$1(directory) && !resolvedDir.startsWith(resolve$1(directory) + "/")) {
13539
13823
  throw new Error("Path outside working directory");
13540
13824
  }
13541
13825
  const entries = await fs.readdir(resolvedDir, { withFileTypes: true });
@@ -13573,8 +13857,8 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
13573
13857
  return null;
13574
13858
  }
13575
13859
  }
13576
- const resolvedPath = resolve(directory, treePath);
13577
- if (sessionMetadata.securityContext && resolvedPath !== resolve(directory) && !resolvedPath.startsWith(resolve(directory) + "/")) {
13860
+ const resolvedPath = resolve$1(directory, treePath);
13861
+ if (sessionMetadata.securityContext && resolvedPath !== resolve$1(directory) && !resolvedPath.startsWith(resolve$1(directory) + "/")) {
13578
13862
  throw new Error("Path outside working directory");
13579
13863
  }
13580
13864
  const tree = await buildTree(resolvedPath, basename$1(resolvedPath), 0);
@@ -13623,6 +13907,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
13623
13907
  sessionService.updateMetadata(sessionMetadata);
13624
13908
  if (typeof next.inboxHop === "number") {
13625
13909
  writeInboundContext(sessionId, { hopCount: next.inboxHop, threadId: next.inboxThreadId });
13910
+ sessionService.markInboxHandled(next.id);
13626
13911
  }
13627
13912
  logger.log(`[Session ${sessionId}] Processing queued message: "${next.text.slice(0, 50)}..."`);
13628
13913
  sessionWasProcessing = true;
@@ -13898,6 +14183,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
13898
14183
  const wake = decision.action === "wake" && acpBackendReady;
13899
14184
  if (wake) {
13900
14185
  logger.log(`[${agentName} Session ${sessionId}] Delivering inbox message to agent`);
14186
+ sessionService.markInboxHandled(message.messageId);
13901
14187
  writeInboundContext(sessionId, { hopCount: message.hopCount ?? 0, threadId: message.threadId || message.messageId });
13902
14188
  sessionService.pushMessage(formatted, "user");
13903
14189
  sessionMetadata = { ...sessionMetadata, lifecycleState: "running" };
@@ -13928,6 +14214,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
13928
14214
  sessionService.updateMetadata(sessionMetadata);
13929
14215
  if (typeof next.inboxHop === "number") {
13930
14216
  writeInboundContext(sessionId, { hopCount: next.inboxHop, threadId: next.inboxThreadId });
14217
+ sessionService.markInboxHandled(next.id);
13931
14218
  }
13932
14219
  sessionService.pushMessage(next.text, "user");
13933
14220
  sessionService.sendKeepAlive(true);
@@ -13979,6 +14266,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
13979
14266
  const remaining = queue.slice(1);
13980
14267
  sessionMetadata = { ...sessionMetadata, messageQueue: remaining.length > 0 ? remaining : void 0, lifecycleState: "running" };
13981
14268
  sessionService.updateMetadata(sessionMetadata);
14269
+ sessionService.markInboxHandled(next.id);
13982
14270
  logger.log(`[Session ${sessionId}] Processing queued message from metadata update: "${next.text.slice(0, 50)}..."`);
13983
14271
  sessionService.sendKeepAlive(true);
13984
14272
  agentBackend.sendPrompt(sessionId, next.text).catch((err) => {
@@ -14008,11 +14296,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
14008
14296
  });
14009
14297
  },
14010
14298
  onIssue: async (params) => {
14011
- const { issueRpc } = await import('./rpc-BHrZL52o.mjs');
14299
+ const { issueRpc } = await import('./rpc-CRpXmlRy.mjs');
14012
14300
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner });
14013
14301
  },
14014
14302
  onWorkflow: async (params) => {
14015
- const { workflowRpc } = await import('./rpc-6ftZ30co.mjs');
14303
+ const { workflowRpc } = await import('./rpc-Blk-caU2.mjs');
14016
14304
  return workflowRpc(params?.cwd || directory, params || {});
14017
14305
  },
14018
14306
  onRipgrep: async (args, cwd) => {
@@ -14029,23 +14317,23 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
14029
14317
  });
14030
14318
  },
14031
14319
  onReadFile: async (path) => {
14032
- const resolvedPath = resolve(directory, path);
14033
- if (sessionMetadata.securityContext && resolvedPath !== resolve(directory) && !resolvedPath.startsWith(resolve(directory) + "/")) {
14320
+ const resolvedPath = resolve$1(directory, path);
14321
+ if (sessionMetadata.securityContext && resolvedPath !== resolve$1(directory) && !resolvedPath.startsWith(resolve$1(directory) + "/")) {
14034
14322
  throw new Error("Path outside working directory");
14035
14323
  }
14036
14324
  return await readSessionFileBase64(resolvedPath);
14037
14325
  },
14038
14326
  onWriteFile: async (path, content) => {
14039
- const resolvedPath = resolve(directory, path);
14040
- if (sessionMetadata.securityContext && resolvedPath !== resolve(directory) && !resolvedPath.startsWith(resolve(directory) + "/")) {
14327
+ const resolvedPath = resolve$1(directory, path);
14328
+ if (sessionMetadata.securityContext && resolvedPath !== resolve$1(directory) && !resolvedPath.startsWith(resolve$1(directory) + "/")) {
14041
14329
  throw new Error("Path outside working directory");
14042
14330
  }
14043
14331
  await fs.mkdir(dirname$1(resolvedPath), { recursive: true });
14044
14332
  await fs.writeFile(resolvedPath, Buffer.from(content, "base64"));
14045
14333
  },
14046
14334
  onListDirectory: async (path) => {
14047
- const resolvedDir = resolve(directory, path || ".");
14048
- if (sessionMetadata.securityContext && resolvedDir !== resolve(directory) && !resolvedDir.startsWith(resolve(directory) + "/")) {
14335
+ const resolvedDir = resolve$1(directory, path || ".");
14336
+ if (sessionMetadata.securityContext && resolvedDir !== resolve$1(directory) && !resolvedDir.startsWith(resolve$1(directory) + "/")) {
14049
14337
  throw new Error("Path outside working directory");
14050
14338
  }
14051
14339
  const entries = await fs.readdir(resolvedDir, { withFileTypes: true });
@@ -14083,8 +14371,8 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
14083
14371
  return null;
14084
14372
  }
14085
14373
  }
14086
- const resolvedPath = resolve(directory, treePath);
14087
- if (sessionMetadata.securityContext && resolvedPath !== resolve(directory) && !resolvedPath.startsWith(resolve(directory) + "/")) {
14374
+ const resolvedPath = resolve$1(directory, treePath);
14375
+ if (sessionMetadata.securityContext && resolvedPath !== resolve$1(directory) && !resolvedPath.startsWith(resolve$1(directory) + "/")) {
14088
14376
  throw new Error("Path outside working directory");
14089
14377
  }
14090
14378
  const tree = await buildTree(resolvedPath, basename$1(resolvedPath), 0);
@@ -14113,6 +14401,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
14113
14401
  const remaining = queue.slice(1);
14114
14402
  sessionMetadata = { ...sessionMetadata, messageQueue: remaining.length > 0 ? remaining : void 0, lifecycleState: "running" };
14115
14403
  sessionService.updateMetadata(sessionMetadata);
14404
+ sessionService.markInboxHandled(next.id);
14116
14405
  logger.log(`[Session ${sessionId}] Processing queued message (ACP loop activation): "${next.text.slice(0, 50)}..."`);
14117
14406
  sessionService.sendKeepAlive(true);
14118
14407
  agentBackend.sendPrompt(sessionId, next.text).catch((err) => {
@@ -14203,6 +14492,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
14203
14492
  sessionMetadata = { ...sessionMetadata, messageQueue: remaining.length > 0 ? remaining : void 0, lifecycleState: "running" };
14204
14493
  sessionService.updateMetadata(sessionMetadata);
14205
14494
  sessionService.sendKeepAlive(true);
14495
+ sessionService.markInboxHandled(next.id);
14206
14496
  logger.log(`[Session ${sessionId}] Processing queued message: "${next.text.slice(0, 50)}..."`);
14207
14497
  sessionService.pushMessage(next.displayText || next.text, "user");
14208
14498
  agentBackend.sendPrompt(sessionId, next.text).catch((err) => {
@@ -14500,7 +14790,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
14500
14790
  const channelHttpPort = Number(process.env.SVAMP_CHANNEL_HTTP_PORT) || 0;
14501
14791
  if (channelHttpPort > 0) {
14502
14792
  try {
14503
- const { createChannelHttpServer } = await import('./httpServer-Cf54pQ77.mjs');
14793
+ const { createChannelHttpServer } = await import('./httpServer-B1KVQJfm.mjs');
14504
14794
  const channelHttpServer = createChannelHttpServer({
14505
14795
  getSessionIds: () => {
14506
14796
  const ids = [];
@@ -14765,7 +15055,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
14765
15055
  const PING_TIMEOUT_MS = 15e3;
14766
15056
  const POST_RECONNECT_GRACE_MS = 2e4;
14767
15057
  const RECONNECT_JITTER_MS = 2500;
14768
- const { WorkflowScheduler } = await import('./scheduler-BtltL3kB.mjs');
15058
+ const { WorkflowScheduler } = await import('./scheduler-ro49WXPg.mjs');
14769
15059
  const workflowScheduler = new WorkflowScheduler({
14770
15060
  projectRoots: () => {
14771
15061
  const dirs = /* @__PURE__ */ new Set();
@@ -15344,4 +15634,4 @@ var run = /*#__PURE__*/Object.freeze({
15344
15634
  writeStopMarker: writeStopMarker
15345
15635
  });
15346
15636
 
15347
- export { buildMachineDeps as $, machineToolsForRole as A, buildMachineTools as B, parseFrontmatter as C, getSkillsServer as D, getSkillsWorkspaceName as E, getSkillsCollectionName as F, fetchWithTimeout as G, searchSkills as H, SKILLS_DIR as I, getSkillInfo as J, downloadSkillFile as K, listSkillFiles as L, resolveModel as M, formatHandle as N, normalizeAllowedUser as O, loadSecurityContextConfig as P, resolveSecurityContext as Q, READ_ONLY_TOOLS as R, ServeAuth as S, buildSecurityContextFromFlags as T, mergeSecurityContexts as U, buildSessionShareUrl as V, computeOutboundHop as W, buildMachineShareUrl as X, parseHandle as Y, handleMatchesMetadata as Z, describeMisconfiguration as _, createSessionStore as a, applyClaudeProxyEnv as a0, composeSessionId as a1, generateFriendlyName as a2, generateHookSettings as a3, claudeAuth as a4, projectInfo as a5, DefaultTransport$1 as a6, acpBackend as a7, acpAgentConfig as a8, codexMcpBackend as a9, GeminiTransport$1 as aa, instanceConfig as ab, api as ac, run as ad, stopDaemon as b, connectToHypha as c, daemonStatus as d, clearStopMarker as e, stopMarkerExists as f, getHyphaServerUrl$1 as g, getFrpsSubdomainHost as h, getFrpsServerPort as i, getFrpsServerAddr as j, getHyphaServerUrl as k, hasCookieToken as l, resolveProjectRoot as m, getIssue as n, addComment as o, addIssue as p, listIssues as q, registerMachineService as r, startDaemon as s, searchIssues as t, updateIssue as u, cronMatches as v, summarize as w, shortId as x, loadMachineContext as y, buildMachineInstructions as z };
15637
+ export { describeMisconfiguration as $, buildMachineInstructions as A, machineToolsForRole as B, buildMachineTools as C, parseFrontmatter as D, getSkillsServer as E, getSkillsWorkspaceName as F, getSkillsCollectionName as G, fetchWithTimeout as H, searchSkills as I, SKILLS_DIR as J, getSkillInfo as K, downloadSkillFile as L, listSkillFiles as M, resolveModel as N, formatHandle as O, normalizeAllowedUser as P, loadSecurityContextConfig as Q, READ_ONLY_TOOLS as R, ServeAuth as S, resolveSecurityContext as T, buildSecurityContextFromFlags as U, mergeSecurityContexts as V, buildSessionShareUrl as W, computeOutboundHop as X, buildMachineShareUrl as Y, parseHandle as Z, handleMatchesMetadata as _, createSessionStore as a, buildMachineDeps as a0, applyClaudeProxyEnv as a1, composeSessionId as a2, generateFriendlyName as a3, generateHookSettings as a4, claudeAuth as a5, projectInfo as a6, DefaultTransport$1 as a7, acpBackend as a8, acpAgentConfig as a9, codexMcpBackend as aa, GeminiTransport$1 as ab, instanceConfig as ac, api as ad, run as ae, stopDaemon as b, connectToHypha as c, daemonStatus as d, clearStopMarker as e, stopMarkerExists as f, getHyphaServerUrl$1 as g, getFrpsSubdomainHost as h, getFrpsServerPort as i, getFrpsServerAddr as j, getHyphaServerUrl as k, hasCookieToken as l, resolveProjectRoot as m, getIssue as n, addComment as o, addIssue as p, listIssues as q, registerMachineService as r, startDaemon as s, searchIssues as t, updateIssue as u, isVisibleTo as v, cronMatches as w, summarize as x, shortId as y, loadMachineContext as z };