svamp-cli 0.2.108 → 0.2.110

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.
@@ -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
3
  import { readFileSync as readFileSync$1, mkdirSync as mkdirSync$1, writeFileSync as writeFileSync$1, renameSync as renameSync$1, existsSync as existsSync$1, rmSync as rmSync$1, unlinkSync as unlinkSync$1, copyFileSync, readdirSync as readdirSync$1, watch, rmdirSync } from 'fs';
4
- import path__default, { join as join$1, dirname, basename, resolve } from 'path';
4
+ import path__default, { join as join$1, dirname as dirname$1, basename as basename$1, resolve } 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
+ import { randomBytes, randomUUID, createHash } from 'node:crypto';
8
9
  import { existsSync, readFileSync, mkdirSync, readdirSync, writeFileSync, renameSync, rmSync, appendFileSync, unlinkSync } from 'node:fs';
9
- import { exec, spawn, execSync, execFile as execFile$1, execFileSync } from 'node:child_process';
10
+ import { exec, execSync, spawn, execFile as execFile$1, execFileSync } from 'node:child_process';
10
11
  import { promisify } from 'util';
11
- import { join } from 'node:path';
12
- import { randomBytes, randomUUID, createHash } from 'node:crypto';
12
+ import { join, basename, dirname } from 'node:path';
13
13
  import os, { homedir, platform } from 'node:os';
14
14
  import { EventEmitter } from 'node:events';
15
15
  import { ndJsonStream, ClientSideConnection } from '@agentclientprotocol/sdk';
@@ -2527,7 +2527,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
2527
2527
  const tunnels = handlers.tunnels;
2528
2528
  if (!tunnels) throw new Error("Tunnel management not available");
2529
2529
  if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
2530
- const { FrpcTunnel } = await import('./frpc-C5Bhpsdw.mjs');
2530
+ const { FrpcTunnel } = await import('./frpc-BoGKNxdH.mjs');
2531
2531
  const tunnel = new FrpcTunnel({
2532
2532
  name: params.name,
2533
2533
  ports: params.ports,
@@ -2788,7 +2788,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
2788
2788
  }
2789
2789
  const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
2790
2790
  const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
2791
- const { toolsForRole } = await import('./sideband-CgiHKPJo.mjs');
2791
+ const { toolsForRole } = await import('./sideband-HcIQ3hOP.mjs');
2792
2792
  const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
2793
2793
  return fmt(r2);
2794
2794
  }
@@ -2887,7 +2887,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
2887
2887
  if (r.error || !r.sender) return { error: r.error || "unauthorized" };
2888
2888
  const callId = "call_" + Math.random().toString(16).slice(2, 12);
2889
2889
  const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
2890
- const { queryCore } = await import('./commands-DOsK9QRw.mjs');
2890
+ const { queryCore } = await import('./commands-Bg0_Ljvw.mjs');
2891
2891
  const timeout = c.reply?.timeout_sec || 120;
2892
2892
  let result;
2893
2893
  try {
@@ -3349,7 +3349,6 @@ function formatInboxMessageXml(msg) {
3349
3349
  if (msg.from) attrs.push(`from="${escapeXml(msg.from)}"`);
3350
3350
  if (msg.channel) attrs.push(`channel="${escapeXml(msg.channel)}"`);
3351
3351
  if (msg.verified !== void 0) attrs.push(`verified="${msg.verified === true}"`);
3352
- if (msg.fromSession) attrs.push(`from-session="${escapeXml(msg.fromSession)}"`);
3353
3352
  if (msg.to) attrs.push(`to="${escapeXml(msg.to)}"`);
3354
3353
  if (msg.subject) attrs.push(`subject="${escapeXml(msg.subject)}"`);
3355
3354
  if (msg.urgency) attrs.push(`urgency="${msg.urgency}"`);
@@ -3989,6 +3988,16 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
3989
3988
  callbacks.onArchiveSession();
3990
3989
  return { success: true };
3991
3990
  },
3991
+ // Manual ✨ trigger from the UI: regenerate the session topic + shared project
3992
+ // description via a forked btw (Claude sessions only).
3993
+ regenerateSummary: async (context) => {
3994
+ authorizeRequest(context, metadata.sharing, "interact");
3995
+ if (!callbacks.onRegenerateSummary) {
3996
+ return { success: false, error: "Summary regeneration is not supported for this agent." };
3997
+ }
3998
+ const result = await callbacks.onRegenerateSummary();
3999
+ return { success: true, ...result };
4000
+ },
3992
4001
  // ── Activity ──
3993
4002
  keepAlive: async (thinking, mode, context) => {
3994
4003
  authorizeRequest(context, metadata.sharing, "interact");
@@ -4384,6 +4393,20 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
4384
4393
  return { store, rpcHandlers };
4385
4394
  }
4386
4395
 
4396
+ const ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
4397
+ const MAX_UNBIASED = Math.floor(256 / ALPHABET.length) * ALPHABET.length;
4398
+ function shortId(length = 10) {
4399
+ let out = "";
4400
+ while (out.length < length) {
4401
+ const buf = randomBytes(length - out.length);
4402
+ for (let i = 0; i < buf.length && out.length < length; i++) {
4403
+ const b = buf[i];
4404
+ if (b < MAX_UNBIASED) out += ALPHABET[b % ALPHABET.length];
4405
+ }
4406
+ }
4407
+ return out;
4408
+ }
4409
+
4387
4410
  const SVAMP_HOME$2 = process.env.SVAMP_HOME || join(os.homedir(), ".svamp");
4388
4411
  const num = (key, def) => {
4389
4412
  const v = Number(process.env[key]);
@@ -4503,6 +4526,97 @@ function classifyInbound(message, now = Date.now()) {
4503
4526
  return { action: "wake", reason: "urgent", breakerTripped: tripped };
4504
4527
  }
4505
4528
 
4529
+ function resolveProjectRoot(directory) {
4530
+ try {
4531
+ const top = execSync("git rev-parse --show-toplevel", {
4532
+ cwd: directory,
4533
+ stdio: ["ignore", "pipe", "ignore"]
4534
+ }).toString().trim();
4535
+ if (top) return top;
4536
+ } catch {
4537
+ }
4538
+ return directory.replace(/[/\\]+$/, "") || directory;
4539
+ }
4540
+ function projectInfoPath(directory) {
4541
+ return join(resolveProjectRoot(directory), ".svamp", "project.json");
4542
+ }
4543
+ function projectName(directory) {
4544
+ return basename(resolveProjectRoot(directory).replace(/[/\\]+$/, "")) || "project";
4545
+ }
4546
+ function readProjectInfo(directory) {
4547
+ try {
4548
+ const p = projectInfoPath(directory);
4549
+ if (!existsSync(p)) return null;
4550
+ const info = JSON.parse(readFileSync(p, "utf-8"));
4551
+ if (info && typeof info === "object") return info;
4552
+ } catch {
4553
+ }
4554
+ return null;
4555
+ }
4556
+ function writeProjectInfo(directory, info) {
4557
+ const p = projectInfoPath(directory);
4558
+ mkdirSync(dirname(p), { recursive: true });
4559
+ const tmp = p + ".tmp";
4560
+ writeFileSync(tmp, JSON.stringify(info, null, 2));
4561
+ renameSync(tmp, p);
4562
+ }
4563
+ function sanitizeDescription(raw, maxLen = 240) {
4564
+ if (!raw) return null;
4565
+ let t = String(raw).replace(/\s+/g, " ").trim();
4566
+ t = t.replace(/^["'`*\s]+|["'`*\s]+$/g, "").trim();
4567
+ if (t.length > maxLen) t = t.slice(0, maxLen - 1).trimEnd() + "\u2026";
4568
+ return t || null;
4569
+ }
4570
+ function extractDescriptionFromDocs(directory) {
4571
+ const root = resolveProjectRoot(directory);
4572
+ const candidates = [
4573
+ [join(root, "CLAUDE.md"), "claude.md"],
4574
+ [join(root, "README.md"), "readme"],
4575
+ [join(root, "readme.md"), "readme"]
4576
+ ];
4577
+ for (const [file, source] of candidates) {
4578
+ try {
4579
+ if (!existsSync(file)) continue;
4580
+ const desc = sanitizeDescription(leadParagraph(readFileSync(file, "utf-8")));
4581
+ if (desc) return { description: desc, source };
4582
+ } catch {
4583
+ }
4584
+ }
4585
+ return null;
4586
+ }
4587
+ function leadParagraph(md) {
4588
+ const buf = [];
4589
+ for (const raw of md.split(/\r?\n/)) {
4590
+ const line = raw.trim();
4591
+ if (!line) {
4592
+ if (buf.length) break;
4593
+ else continue;
4594
+ }
4595
+ if (/^#{1,6}\s/.test(line)) {
4596
+ if (buf.length) break;
4597
+ else continue;
4598
+ }
4599
+ if (/^([![]|<|\||>)/.test(line)) continue;
4600
+ if (/^[-*_=]{3,}$/.test(line)) continue;
4601
+ buf.push(line);
4602
+ if (buf.join(" ").length > 240) break;
4603
+ }
4604
+ const d = buf.join(" ").replace(/\s+/g, " ").trim();
4605
+ return d || null;
4606
+ }
4607
+
4608
+ var projectInfo = /*#__PURE__*/Object.freeze({
4609
+ __proto__: null,
4610
+ extractDescriptionFromDocs: extractDescriptionFromDocs,
4611
+ leadParagraph: leadParagraph,
4612
+ projectInfoPath: projectInfoPath,
4613
+ projectName: projectName,
4614
+ readProjectInfo: readProjectInfo,
4615
+ resolveProjectRoot: resolveProjectRoot,
4616
+ sanitizeDescription: sanitizeDescription,
4617
+ writeProjectInfo: writeProjectInfo
4618
+ });
4619
+
4506
4620
  async function registerDebugService(server, machineId, deps) {
4507
4621
  const serviceInfo = await server.registerService(
4508
4622
  {
@@ -9014,7 +9128,7 @@ function buildBaselineSystemPrompt(sessionId) {
9014
9128
  You are running inside a Svamp session (id: ${sessionId}) on Hypha Cloud. Use the \`svamp\` CLI to manage session state (title, link, notify) and the \`hypha\` CLI for artifacts, tokens, and RPC services. Installed skills live in \`~/.claude/skills/\` \u2014 read them for full references.
9015
9129
 
9016
9130
  **Session state:**
9017
- - \`svamp session set-title "<title>"\` \u2014 set a concise 3-8 word title after the first response, and update it whenever the topic shifts. This is how the user and other agents recognize you in lists \u2014 keep it current.
9131
+ - \`svamp session set-title "<topic>"\` \u2014 your session topic: a short sentence describing what you're working on. It's the index the user and other agents see when listing sessions, so set it after your first reply and update it as focus shifts (else it's auto-summarized for you).
9018
9132
  - \`svamp session set-link "<url>" "<label>"\` \u2014 surface any viewable artifact as a button
9019
9133
  - \`svamp session notify "<msg>" [--level info|warning|error]\` \u2014 send a user notification
9020
9134
 
@@ -9024,7 +9138,7 @@ You are running inside a Svamp session (id: ${sessionId}) on Hypha Cloud. Use th
9024
9138
 
9025
9139
  You share this machine and project folder with other agent sessions (same user, possibly other machines too). When collaboration, coordination, or avoiding edit conflicts becomes relevant, run \`svamp session whoami\` \u2014 it shows who's around and how to reach them. Keep cross-agent contact purposeful: only initiate with a concrete reason, avoid ping-pong loops.
9026
9140
 
9027
- **Inbox messages from other agents** arrive wrapped as \`<svamp-message message-id="\u2026" from="agent:\u2026" from-session="\u2026" \u2026>BODY</svamp-message>\` (a plain user turn has no wrapper). Reply only when useful: \`svamp session inbox reply <message-id> "<body>"\`.
9141
+ **Inbox messages from other agents** arrive wrapped as \`<svamp-message message-id="\u2026" from="agent:\u2026" \u2026>BODY</svamp-message>\` (a plain user turn has no wrapper). Reply only when useful: \`svamp session inbox reply <message-id> "<body>"\` \u2014 the message-id is all you need; routing back to the sender is automatic.
9028
9142
  `;
9029
9143
  }
9030
9144
 
@@ -9312,7 +9426,7 @@ async function readSessionFileBase64(resolvedPath) {
9312
9426
  }
9313
9427
 
9314
9428
  const __filename$1 = fileURLToPath(import.meta.url);
9315
- const __dirname$1 = dirname(__filename$1);
9429
+ const __dirname$1 = dirname$1(__filename$1);
9316
9430
  const CLAUDE_SKILLS_DIR = join$1(os$1.homedir(), ".claude", "skills");
9317
9431
  function looksLikeClaudeError(line) {
9318
9432
  const l = line.toLowerCase();
@@ -9389,7 +9503,7 @@ async function installSkillFromEndpoint(name, baseUrl) {
9389
9503
  const content = await fileResp.text();
9390
9504
  const localPath = join$1(targetDir, filePath);
9391
9505
  if (!localPath.startsWith(targetDir + "/")) continue;
9392
- mkdirSync$1(dirname(localPath), { recursive: true });
9506
+ mkdirSync$1(dirname$1(localPath), { recursive: true });
9393
9507
  writeFileSync$1(localPath, content, "utf-8");
9394
9508
  }
9395
9509
  }
@@ -9423,7 +9537,7 @@ async function installSkillFromMarketplace(name) {
9423
9537
  const content = await resp.text();
9424
9538
  const localPath = join$1(targetDir, filePath);
9425
9539
  if (!localPath.startsWith(targetDir + "/")) continue;
9426
- mkdirSync$1(dirname(localPath), { recursive: true });
9540
+ mkdirSync$1(dirname$1(localPath), { recursive: true });
9427
9541
  writeFileSync$1(localPath, content, "utf-8");
9428
9542
  }
9429
9543
  }
@@ -9431,9 +9545,9 @@ function getBundledSkillsDir() {
9431
9545
  try {
9432
9546
  const here = fileURLToPath(import.meta.url);
9433
9547
  const candidates = [
9434
- join$1(dirname(here), "..", "bin", "skills"),
9548
+ join$1(dirname$1(here), "..", "bin", "skills"),
9435
9549
  // built dist/ layout
9436
- join$1(dirname(here), "..", "..", "bin", "skills")
9550
+ join$1(dirname$1(here), "..", "..", "bin", "skills")
9437
9551
  // src/daemon → bin layout via tsx
9438
9552
  ];
9439
9553
  for (const c of candidates) {
@@ -9691,7 +9805,7 @@ function readSvampConfig(configPath) {
9691
9805
  return {};
9692
9806
  }
9693
9807
  function writeSvampConfig(configPath, config) {
9694
- mkdirSync$1(dirname(configPath), { recursive: true });
9808
+ mkdirSync$1(dirname$1(configPath), { recursive: true });
9695
9809
  const content = JSON.stringify(config, null, 2);
9696
9810
  const tmpPath = configPath + ".tmp";
9697
9811
  writeFileSync$1(tmpPath, content);
@@ -9862,7 +9976,7 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
9862
9976
  messageQueue: [...existingQueue, {
9863
9977
  id: randomUUID$1(),
9864
9978
  text: kickoff,
9865
- displayText: `\u{1F501} Loop started: ${lp.task.trim().slice(0, 100)}`,
9979
+ displayText: `\u{1F501} Loop started: ${lp.task.trim().slice(0, 100)}${lp.task.trim().length > 100 ? "\u2026" : ""}`,
9866
9980
  createdAt: Date.now()
9867
9981
  }]
9868
9982
  }));
@@ -9899,7 +10013,7 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
9899
10013
  };
9900
10014
  let watcher = null;
9901
10015
  try {
9902
- const configDir = dirname(configPath);
10016
+ const configDir = dirname$1(configPath);
9903
10017
  mkdirSync$1(configDir, { recursive: true });
9904
10018
  watcher = watch(configDir, (eventType, filename) => {
9905
10019
  if (filename === "config.json") configChecker();
@@ -10314,7 +10428,7 @@ async function startDaemon(options) {
10314
10428
  const list = loadExposedTunnels().filter((t) => t.name !== name);
10315
10429
  saveExposedTunnels(list);
10316
10430
  }
10317
- const { ServeManager } = await import('./serveManager-QZxNxQq0.mjs');
10431
+ const { ServeManager } = await import('./serveManager-DW3iB_DY.mjs');
10318
10432
  const serveManager = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
10319
10433
  ensureAutoInstalledSkills(logger).catch(() => {
10320
10434
  });
@@ -10406,7 +10520,7 @@ async function startDaemon(options) {
10406
10520
  return { type: "error", errorMessage: `Failed to create directory: ${err.message}` };
10407
10521
  }
10408
10522
  }
10409
- const sessionId = options2.sessionId || randomUUID$1();
10523
+ const sessionId = options2.sessionId || shortId();
10410
10524
  const agentName = options2.agent || agentConfig.agent_type || "claude";
10411
10525
  if (agentName !== "claude" && (KNOWN_ACP_AGENTS[agentName] || KNOWN_MCP_AGENTS[agentName])) {
10412
10526
  return await spawnAgentSession(sessionId, directory, agentName, options2, resumeSessionId);
@@ -10531,6 +10645,135 @@ async function startDaemon(options) {
10531
10645
  let lastSpawnMeta = persisted?.spawnMeta || {};
10532
10646
  let sessionWasProcessing = !!options2.wasProcessing;
10533
10647
  let lastMainModel;
10648
+ const AUTO_TOPIC_DISABLED = process.env.SVAMP_AUTO_TOPIC === "0";
10649
+ const projName = projectName(directory);
10650
+ let bootstrapAttempted = false;
10651
+ let topicBtwInFlight = false;
10652
+ const sanitizeTopic = (raw) => sanitizeDescription(raw == null ? null : String(raw).split(/\r?\n/).find((l) => l.trim()) || "", 140);
10653
+ const applyTopic = (topic) => {
10654
+ sessionMetadata = { ...sessionMetadata, summary: { text: topic, updatedAt: Date.now() } };
10655
+ sessionService.updateMetadata(sessionMetadata);
10656
+ sessionService.pushMessage({ type: "summary", summary: topic }, "session");
10657
+ };
10658
+ const applyProjectToMeta = (name, description) => {
10659
+ if (sessionMetadata.projectName === name && sessionMetadata.projectDescription === description) return;
10660
+ sessionMetadata = { ...sessionMetadata, projectName: name, projectDescription: description };
10661
+ sessionService.updateMetadata(sessionMetadata);
10662
+ };
10663
+ const forkBtw = (question) => new Promise((resolve2) => {
10664
+ const claudeSessionId = sessionMetadata.claudeSessionId;
10665
+ if (!claudeSessionId) {
10666
+ resolve2(null);
10667
+ return;
10668
+ }
10669
+ const cwd = sessionMetadata.path || directory;
10670
+ const model = process.env.SVAMP_TOPIC_MODEL;
10671
+ try {
10672
+ const child = spawn$1("claude", [
10673
+ "--print",
10674
+ question,
10675
+ "--resume",
10676
+ claudeSessionId,
10677
+ "--fork-session",
10678
+ "--no-session-persistence",
10679
+ "--permission-mode",
10680
+ "bypassPermissions",
10681
+ ...model ? ["--model", model] : [],
10682
+ "--output-format",
10683
+ "json",
10684
+ "--max-turns",
10685
+ "6"
10686
+ ], { cwd, timeout: 6e4, stdio: ["ignore", "pipe", "pipe"], env: { ...process.env, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1" } });
10687
+ let stdout = "";
10688
+ child.stdout?.on("data", (d) => {
10689
+ stdout += d.toString();
10690
+ });
10691
+ child.on("close", () => {
10692
+ try {
10693
+ const r = JSON.parse(stdout);
10694
+ resolve2(r.result || r.text || null);
10695
+ } catch {
10696
+ resolve2(null);
10697
+ }
10698
+ });
10699
+ child.on("error", () => resolve2(null));
10700
+ } catch {
10701
+ resolve2(null);
10702
+ }
10703
+ });
10704
+ const parseLabeled = (raw, label) => {
10705
+ if (!raw) return null;
10706
+ const m = raw.match(new RegExp(`${label}\\s*:\\s*(.+)`, "i"));
10707
+ return m ? m[1].trim() : null;
10708
+ };
10709
+ const generateSummary = async (force) => {
10710
+ if (topicBtwInFlight) return {};
10711
+ const existing = readProjectInfo(directory);
10712
+ applyProjectToMeta(existing?.name || projName, existing?.description);
10713
+ let projectDescription = existing?.description;
10714
+ let haveProject = !!projectDescription;
10715
+ if (force || !haveProject) {
10716
+ const docs = extractDescriptionFromDocs(directory);
10717
+ if (docs) {
10718
+ writeProjectInfo(directory, { name: projName, description: docs.description, source: docs.source, updatedAt: Date.now() });
10719
+ applyProjectToMeta(projName, docs.description);
10720
+ projectDescription = docs.description;
10721
+ haveProject = true;
10722
+ }
10723
+ }
10724
+ const needTopic = force || !sessionMetadata.summary?.text?.trim();
10725
+ const needProjectBtw = !haveProject;
10726
+ let topic = sessionMetadata.summary?.text;
10727
+ if (!needTopic && !needProjectBtw || trackedSession?.stopped) return { topic, projectDescription };
10728
+ topicBtwInFlight = true;
10729
+ try {
10730
+ const parts = [];
10731
+ if (needTopic) parts.push("TOPIC: <one sentence (10-18 words) on what THIS session is currently working on>");
10732
+ if (needProjectBtw) parts.push("PROJECT: <one sentence on what this project/folder is about overall \u2014 its purpose, not this session's task>");
10733
+ const raw = await forkBtw(`Based only on the conversation you can already see (do NOT use any tools), output EXACTLY the following line(s), nothing else:
10734
+ ${parts.join("\n")}`);
10735
+ if (needTopic) {
10736
+ const t = sanitizeTopic(needProjectBtw ? parseLabeled(raw, "TOPIC") : parseLabeled(raw, "TOPIC") || raw);
10737
+ if (t) {
10738
+ applyTopic(t);
10739
+ topic = t;
10740
+ logger.log(`[Session ${sessionId}] ${force ? "Regenerated" : "Auto"}-topic \u2192 "${t}"`);
10741
+ } else if (!sessionMetadata.summary?.text?.trim()) {
10742
+ const b = basename$1(directory.replace(/[/\\]+$/, "")) || "session";
10743
+ applyTopic(b);
10744
+ topic = b;
10745
+ }
10746
+ }
10747
+ if (needProjectBtw) {
10748
+ const d = sanitizeDescription(parseLabeled(raw, "PROJECT") || (!needTopic ? raw : null));
10749
+ if (d) {
10750
+ writeProjectInfo(directory, { name: projName, description: d, source: "btw", updatedAt: Date.now() });
10751
+ applyProjectToMeta(projName, d);
10752
+ projectDescription = d;
10753
+ logger.log(`[Session ${sessionId}] Project description (btw) \u2192 "${d}"`);
10754
+ }
10755
+ }
10756
+ } catch {
10757
+ } finally {
10758
+ topicBtwInFlight = false;
10759
+ }
10760
+ return { topic, projectDescription };
10761
+ };
10762
+ const maybeBootstrap = () => {
10763
+ try {
10764
+ const existing = readProjectInfo(directory);
10765
+ applyProjectToMeta(existing?.name || projName, existing?.description);
10766
+ if (bootstrapAttempted || topicBtwInFlight) return;
10767
+ if (AUTO_TOPIC_DISABLED || trackedSession?.stopped || isLoopActive(directory)) return;
10768
+ bootstrapAttempted = true;
10769
+ void generateSummary(false);
10770
+ } catch {
10771
+ }
10772
+ };
10773
+ const regenerateSummaryNow = async () => {
10774
+ bootstrapAttempted = true;
10775
+ return await generateSummary(true);
10776
+ };
10534
10777
  let spawnHasReceivedInit = false;
10535
10778
  let startupFailureRetryPending = false;
10536
10779
  let startupRetryMessage;
@@ -11012,17 +11255,7 @@ async function startDaemon(options) {
11012
11255
  }
11013
11256
  checkSvampConfig?.();
11014
11257
  clearInboundContext(sessionId);
11015
- try {
11016
- const hasTitle = !!sessionMetadata.summary?.text?.trim() || !!sessionMetadata.customTitle?.toString().trim();
11017
- if (!hasTitle) {
11018
- const base = basename(directory.replace(/[/\\]+$/, "")) || "session";
11019
- sessionMetadata = { ...sessionMetadata, summary: { text: base, updatedAt: Date.now() } };
11020
- sessionService.updateMetadata(sessionMetadata);
11021
- sessionService.pushMessage({ type: "summary", summary: base }, "session");
11022
- logger.log(`[Session ${sessionId}] Auto-title fallback \u2192 "${base}"`);
11023
- }
11024
- } catch {
11025
- }
11258
+ maybeBootstrap();
11026
11259
  if (backgroundTaskCount > 0) {
11027
11260
  const taskInfo = `Background tasks still running (${backgroundTaskCount}): ${backgroundTaskNames.join(", ")}`;
11028
11261
  logger.log(`[Session ${sessionId}] ${taskInfo}`);
@@ -11589,6 +11822,10 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11589
11822
  logger.log(`[Session ${sessionId}] Archive session requested`);
11590
11823
  archiveSession(sessionId);
11591
11824
  },
11825
+ onRegenerateSummary: async () => {
11826
+ logger.log(`[Session ${sessionId}] Manual summary regeneration requested`);
11827
+ return await regenerateSummaryNow();
11828
+ },
11592
11829
  onInboxMessage: (message) => {
11593
11830
  if (trackedSession?.stopped) return;
11594
11831
  const decision = classifyInbound(message);
@@ -11719,7 +11956,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11719
11956
  if (sessionMetadata.securityContext && resolvedPath !== resolve(directory) && !resolvedPath.startsWith(resolve(directory) + "/")) {
11720
11957
  throw new Error("Path outside working directory");
11721
11958
  }
11722
- await fs.mkdir(dirname(resolvedPath), { recursive: true });
11959
+ await fs.mkdir(dirname$1(resolvedPath), { recursive: true });
11723
11960
  await fs.writeFile(resolvedPath, Buffer.from(content, "base64"));
11724
11961
  },
11725
11962
  onListDirectory: async (path) => {
@@ -11766,7 +12003,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11766
12003
  if (sessionMetadata.securityContext && resolvedPath !== resolve(directory) && !resolvedPath.startsWith(resolve(directory) + "/")) {
11767
12004
  throw new Error("Path outside working directory");
11768
12005
  }
11769
- const tree = await buildTree(resolvedPath, basename(resolvedPath), 0);
12006
+ const tree = await buildTree(resolvedPath, basename$1(resolvedPath), 0);
11770
12007
  return { success: !!tree, tree };
11771
12008
  }
11772
12009
  },
@@ -12197,7 +12434,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
12197
12434
  if (sessionMetadata.securityContext && resolvedPath !== resolve(directory) && !resolvedPath.startsWith(resolve(directory) + "/")) {
12198
12435
  throw new Error("Path outside working directory");
12199
12436
  }
12200
- await fs.mkdir(dirname(resolvedPath), { recursive: true });
12437
+ await fs.mkdir(dirname$1(resolvedPath), { recursive: true });
12201
12438
  await fs.writeFile(resolvedPath, Buffer.from(content, "base64"));
12202
12439
  },
12203
12440
  onListDirectory: async (path) => {
@@ -12244,7 +12481,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
12244
12481
  if (sessionMetadata.securityContext && resolvedPath !== resolve(directory) && !resolvedPath.startsWith(resolve(directory) + "/")) {
12245
12482
  throw new Error("Path outside working directory");
12246
12483
  }
12247
- const tree = await buildTree(resolvedPath, basename(resolvedPath), 0);
12484
+ const tree = await buildTree(resolvedPath, basename$1(resolvedPath), 0);
12248
12485
  return { success: !!tree, tree };
12249
12486
  }
12250
12487
  },
@@ -12344,7 +12581,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
12344
12581
  try {
12345
12582
  const hasTitle = !!sessionMetadata.summary?.text?.trim() || !!sessionMetadata.customTitle?.toString().trim();
12346
12583
  if (!hasTitle) {
12347
- const base = basename(directory.replace(/[/\\]+$/, "")) || "session";
12584
+ const base = basename$1(directory.replace(/[/\\]+$/, "")) || "session";
12348
12585
  sessionMetadata = { ...sessionMetadata, summary: { text: base, updatedAt: Date.now() } };
12349
12586
  sessionService.updateMetadata(sessionMetadata);
12350
12587
  sessionService.pushMessage({ type: "summary", summary: base }, "session");
@@ -12676,7 +12913,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
12676
12913
  const specs = loadExposedTunnels();
12677
12914
  if (specs.length === 0) return;
12678
12915
  logger.log(`[exposed-tunnels] Restoring ${specs.length} tunnel(s) from ${EXPOSED_TUNNELS_FILE}`);
12679
- const { FrpcTunnel } = await import('./frpc-C5Bhpsdw.mjs');
12916
+ const { FrpcTunnel } = await import('./frpc-BoGKNxdH.mjs');
12680
12917
  for (const spec of specs) {
12681
12918
  if (tunnels.has(spec.name)) continue;
12682
12919
  try {
@@ -12919,6 +13156,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
12919
13156
  const HEARTBEAT_INTERVAL_MS = 1e4;
12920
13157
  const PING_TIMEOUT_MS = 15e3;
12921
13158
  const POST_RECONNECT_GRACE_MS = 2e4;
13159
+ const RECONNECT_JITTER_MS = 2500;
12922
13160
  let heartbeatRunning = false;
12923
13161
  let lastReconnectAt = 0;
12924
13162
  let heartbeatCycle = 0;
@@ -12998,6 +13236,8 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
12998
13236
  logger.log(`Connection down for ${consecutiveHeartbeatFailures * HEARTBEAT_INTERVAL_MS / 1e3}s (${consecutiveHeartbeatFailures} failures, retrying indefinitely)`);
12999
13237
  }
13000
13238
  if (consecutiveHeartbeatFailures === 2 || consecutiveHeartbeatFailures % 3 === 0) {
13239
+ const jitterMs = Math.floor(Math.random() * RECONNECT_JITTER_MS);
13240
+ if (jitterMs > 0) await new Promise((r) => setTimeout(r, jitterMs));
13001
13241
  const conn = server.rpc?._connection;
13002
13242
  const ws = conn?._websocket;
13003
13243
  if (ws?.readyState === 1) {
@@ -13449,4 +13689,4 @@ var run = /*#__PURE__*/Object.freeze({
13449
13689
  writeStopMarker: writeStopMarker
13450
13690
  });
13451
13691
 
13452
- export { loadMachineContext as A, buildMachineInstructions as B, machineToolsForRole as C, buildMachineTools as D, resolveModel as E, normalizeAllowedUser as F, loadSecurityContextConfig as G, resolveSecurityContext as H, buildSecurityContextFromFlags as I, mergeSecurityContexts as J, buildSessionShareUrl as K, computeOutboundHop as L, buildMachineShareUrl as M, describeMisconfiguration as N, buildMachineDeps as O, generateHookSettings as P, DefaultTransport$1 as Q, RoutineStore as R, ServeAuth as S, acpBackend as T, acpAgentConfig as U, codexMcpBackend as V, GeminiTransport$1 as W, claudeAuth as X, instanceConfig as Y, api as Z, run as _, createSessionStore as a, 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, RoutineRunner as m, getSkillsServer as n, getSkillsWorkspaceName as o, parseFrontmatter as p, getSkillsCollectionName as q, registerMachineService as r, startDaemon as s, fetchWithTimeout as t, searchSkills as u, SKILLS_DIR as v, getSkillInfo as w, downloadSkillFile as x, listSkillFiles as y, READ_ONLY_TOOLS as z };
13692
+ export { api as $, READ_ONLY_TOOLS as A, loadMachineContext as B, buildMachineInstructions as C, machineToolsForRole as D, buildMachineTools as E, resolveModel as F, normalizeAllowedUser as G, loadSecurityContextConfig as H, resolveSecurityContext as I, buildSecurityContextFromFlags as J, mergeSecurityContexts as K, buildSessionShareUrl as L, computeOutboundHop as M, buildMachineShareUrl as N, describeMisconfiguration as O, buildMachineDeps as P, generateHookSettings as Q, RoutineStore as R, ServeAuth as S, projectInfo as T, DefaultTransport$1 as U, acpBackend as V, acpAgentConfig as W, codexMcpBackend as X, GeminiTransport$1 as Y, claudeAuth as Z, instanceConfig as _, createSessionStore as a, run as a0, 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, RoutineRunner as m, shortId as n, getSkillsServer as o, parseFrontmatter as p, getSkillsWorkspaceName as q, registerMachineService as r, startDaemon as s, getSkillsCollectionName as t, fetchWithTimeout as u, searchSkills as v, SKILLS_DIR as w, getSkillInfo as x, downloadSkillFile as y, listSkillFiles as z };
@@ -1,8 +1,7 @@
1
- import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import { randomUUID } from 'node:crypto';
1
+ import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import { n as shortId, c as connectToHypha, a as createSessionStore, r as registerMachineService, Q as generateHookSettings } from './run-C3kuv0DL.mjs';
2
2
  import os from 'node:os';
3
3
  import { resolve, join } from 'node:path';
4
4
  import { existsSync, readFileSync, watch } from 'node:fs';
5
- import { c as connectToHypha, a as createSessionStore, r as registerMachineService, P as generateHookSettings } from './run-LyzVTe3J.mjs';
6
5
  import { createServer } from 'node:http';
7
6
  import { spawn } from 'node:child_process';
8
7
  import { createInterface } from 'node:readline';
@@ -13,6 +12,7 @@ import 'path';
13
12
  import 'url';
14
13
  import 'child_process';
15
14
  import 'crypto';
15
+ import 'node:crypto';
16
16
  import 'util';
17
17
  import 'node:events';
18
18
  import '@agentclientprotocol/sdk';
@@ -605,7 +605,7 @@ const log = (...args) => {
605
605
  };
606
606
  async function runInteractive(options) {
607
607
  const cwd = options.directory;
608
- const sessionId = randomUUID();
608
+ const sessionId = shortId();
609
609
  const permissionMode = options.permissionMode || "default";
610
610
  log(`Starting interactive session: ${sessionId}`);
611
611
  log(`Directory: ${cwd}`);
@@ -54,7 +54,7 @@ async function handleServeCommand() {
54
54
  }
55
55
  }
56
56
  async function serveAdd(args, machineId) {
57
- const { connectAndGetMachine } = await import('./commands-DOsK9QRw.mjs');
57
+ const { connectAndGetMachine } = await import('./commands-Bg0_Ljvw.mjs');
58
58
  const pos = positionalArgs(args);
59
59
  const name = pos[0];
60
60
  if (!name) {
@@ -93,7 +93,7 @@ async function serveAdd(args, machineId) {
93
93
  }
94
94
  }
95
95
  async function serveApply(args, machineId) {
96
- const { connectAndGetMachine } = await import('./commands-DOsK9QRw.mjs');
96
+ const { connectAndGetMachine } = await import('./commands-Bg0_Ljvw.mjs');
97
97
  const fs = await import('fs');
98
98
  const yaml = await import('yaml');
99
99
  const file = positionalArgs(args)[0];
@@ -182,7 +182,7 @@ async function serveApply(args, machineId) {
182
182
  }
183
183
  }
184
184
  async function serveRemove(args, machineId) {
185
- const { connectAndGetMachine } = await import('./commands-DOsK9QRw.mjs');
185
+ const { connectAndGetMachine } = await import('./commands-Bg0_Ljvw.mjs');
186
186
  const pos = positionalArgs(args);
187
187
  const name = pos[0];
188
188
  if (!name) {
@@ -202,7 +202,7 @@ async function serveRemove(args, machineId) {
202
202
  }
203
203
  }
204
204
  async function serveList(args, machineId) {
205
- const { connectAndGetMachine } = await import('./commands-DOsK9QRw.mjs');
205
+ const { connectAndGetMachine } = await import('./commands-Bg0_Ljvw.mjs');
206
206
  const all = hasFlag(args, "--all", "-a");
207
207
  const json = hasFlag(args, "--json");
208
208
  const sessionId = getFlag(args, "--session");
@@ -235,7 +235,7 @@ async function serveList(args, machineId) {
235
235
  }
236
236
  }
237
237
  async function serveInfo(machineId) {
238
- const { connectAndGetMachine } = await import('./commands-DOsK9QRw.mjs');
238
+ const { connectAndGetMachine } = await import('./commands-Bg0_Ljvw.mjs');
239
239
  const { machine, server } = await connectAndGetMachine(machineId);
240
240
  try {
241
241
  const info = await machine.serveInfo();
@@ -4,15 +4,15 @@ import * as fs from 'fs';
4
4
  import * as http from 'http';
5
5
  import * as net from 'net';
6
6
  import * as path from 'path';
7
- import { k as getHyphaServerUrl, S as ServeAuth, l as hasCookieToken } from './run-LyzVTe3J.mjs';
7
+ import { k as getHyphaServerUrl, S as ServeAuth, l as hasCookieToken } from './run-C3kuv0DL.mjs';
8
8
  import 'os';
9
9
  import 'fs/promises';
10
10
  import 'url';
11
+ import 'node:crypto';
11
12
  import 'node:fs';
12
13
  import 'node:child_process';
13
14
  import 'util';
14
15
  import 'node:path';
15
- import 'node:crypto';
16
16
  import 'node:os';
17
17
  import 'node:events';
18
18
  import '@agentclientprotocol/sdk';
@@ -713,7 +713,7 @@ class ServeManager {
713
713
  const mount = this.mounts.get(mountName);
714
714
  const subdomainOverride = mount?.access === "link" && mount.linkToken ? /* @__PURE__ */ new Map([[this.port, `static-${subdomainSafe}-${mount.linkToken}`]]) : void 0;
715
715
  try {
716
- const { FrpcTunnel } = await import('./frpc-C5Bhpsdw.mjs');
716
+ const { FrpcTunnel } = await import('./frpc-BoGKNxdH.mjs');
717
717
  let tunnel;
718
718
  tunnel = new FrpcTunnel({
719
719
  name: tunnelName,
@@ -1,4 +1,4 @@
1
- import { z as READ_ONLY_TOOLS, A as loadMachineContext, B as buildMachineInstructions, C as machineToolsForRole, D as buildMachineTools } from './run-LyzVTe3J.mjs';
1
+ import { A as READ_ONLY_TOOLS, B as loadMachineContext, C as buildMachineInstructions, D as machineToolsForRole, E as buildMachineTools } from './run-C3kuv0DL.mjs';
2
2
  import 'node:child_process';
3
3
  import 'os';
4
4
  import 'fs/promises';
@@ -7,10 +7,10 @@ import 'path';
7
7
  import 'url';
8
8
  import 'child_process';
9
9
  import 'crypto';
10
+ import 'node:crypto';
10
11
  import 'node:fs';
11
12
  import 'util';
12
13
  import 'node:path';
13
- import 'node:crypto';
14
14
  import 'node:os';
15
15
  import 'node:events';
16
16
  import '@agentclientprotocol/sdk';