svamp-cli 0.2.119 → 0.2.120

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,7 +1,7 @@
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, 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 as dirname$1, basename as basename$1, resolve } 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, 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';
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';
@@ -2676,7 +2676,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
2676
2676
  const tunnels = handlers.tunnels;
2677
2677
  if (!tunnels) throw new Error("Tunnel management not available");
2678
2678
  if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
2679
- const { FrpcTunnel } = await import('./frpc-CdcXdQde.mjs');
2679
+ const { FrpcTunnel } = await import('./frpc-DlsBjcRf.mjs');
2680
2680
  const tunnel = new FrpcTunnel({
2681
2681
  name: params.name,
2682
2682
  ports: params.ports,
@@ -2937,7 +2937,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
2937
2937
  }
2938
2938
  const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
2939
2939
  const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
2940
- const { toolsForRole } = await import('./sideband-BHWq1P8E.mjs');
2940
+ const { toolsForRole } = await import('./sideband-Wfli3n7U.mjs');
2941
2941
  const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
2942
2942
  return fmt(r2);
2943
2943
  }
@@ -3036,7 +3036,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
3036
3036
  if (r.error || !r.sender) return { error: r.error || "unauthorized" };
3037
3037
  const callId = "call_" + Math.random().toString(16).slice(2, 12);
3038
3038
  const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
3039
- const { queryCore } = await import('./commands-B5rek8XG.mjs');
3039
+ const { queryCore } = await import('./commands-CuY9G_88.mjs');
3040
3040
  const timeout = c.reply?.timeout_sec || 120;
3041
3041
  let result;
3042
3042
  try {
@@ -3389,6 +3389,7 @@ function formatInboxMessageXml(msg) {
3389
3389
  if (msg.from) attrs.push(`from="${escapeXml(msg.from)}"`);
3390
3390
  if (msg.channel) attrs.push(`channel="${escapeXml(msg.channel)}"`);
3391
3391
  if (msg.verified !== void 0) attrs.push(`verified="${msg.verified === true}"`);
3392
+ if (msg.channelId && msg.correlationId) attrs.push(`awaiting-reply="true"`);
3392
3393
  if (msg.to) attrs.push(`to="${escapeXml(msg.to)}"`);
3393
3394
  if (msg.subject) attrs.push(`subject="${escapeXml(msg.subject)}"`);
3394
3395
  if (msg.urgency) attrs.push(`urgency="${msg.urgency}"`);
@@ -3479,6 +3480,48 @@ function appendMessage(messagesDir, sessionId, msg) {
3479
3480
  console.error(`[HYPHA SESSION ${sessionId}] Failed to persist message: ${err?.message ?? err}`);
3480
3481
  }
3481
3482
  }
3483
+ function editableInfo(msg) {
3484
+ const c = msg?.content;
3485
+ if (!c) return null;
3486
+ if (c.role === "user" && c.content?.type === "text") {
3487
+ return { role: "user", text: String(c.content.text ?? "") };
3488
+ }
3489
+ if (c.role === "agent" && c.content?.type === "output") {
3490
+ const data = c.content.data;
3491
+ if (data?.type === "assistant" && Array.isArray(data?.message?.content)) {
3492
+ const textBlocks = data.message.content.filter((b) => b && b.type === "text" && typeof b.text === "string");
3493
+ if (textBlocks.length >= 1) {
3494
+ return { role: "assistant", text: textBlocks.map((b) => b.text).join("\n") };
3495
+ }
3496
+ }
3497
+ if (data?.type === "user" && typeof data?.message?.content === "string") {
3498
+ return { role: "user", text: data.message.content };
3499
+ }
3500
+ }
3501
+ return null;
3502
+ }
3503
+ function patchStoredText(msg, newText) {
3504
+ const c = msg?.content;
3505
+ if (c?.role === "user" && c.content?.type === "text") {
3506
+ c.content.text = newText;
3507
+ return true;
3508
+ }
3509
+ if (c?.role === "agent" && c.content?.type === "output") {
3510
+ const data = c.content.data;
3511
+ if (data?.type === "user" && typeof data?.message?.content === "string") {
3512
+ data.message.content = newText;
3513
+ return true;
3514
+ }
3515
+ if (data?.type === "assistant" && Array.isArray(data?.message?.content)) {
3516
+ const textBlocks = data.message.content.filter((b) => b && b.type === "text" && typeof b.text === "string");
3517
+ if (textBlocks.length === 1) {
3518
+ textBlocks[0].text = newText;
3519
+ return true;
3520
+ }
3521
+ }
3522
+ }
3523
+ return false;
3524
+ }
3482
3525
  function createSessionStore(server, sessionId, initialMetadata, initialAgentState, callbacks, options) {
3483
3526
  const messages = options?.messagesDir ? loadMessages(options.messagesDir) : [];
3484
3527
  let nextSeq = messages.length > 0 ? messages[messages.length - 1].seq + 1 : 1;
@@ -3624,6 +3667,100 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
3624
3667
  notifyListeners({ type: "update-session", sessionId, metadata: { value: metadata, version: metadataVersion } });
3625
3668
  callbacks.onMetadataUpdate?.(metadata);
3626
3669
  };
3670
+ const forkClaudePrint = async (prompt, claudeSessionId, cwd, maxTurns = 6) => {
3671
+ const { spawn } = await import('child_process');
3672
+ return new Promise((resolve) => {
3673
+ const child = spawn("claude", [
3674
+ "--print",
3675
+ prompt,
3676
+ "--resume",
3677
+ claudeSessionId,
3678
+ "--fork-session",
3679
+ "--no-session-persistence",
3680
+ "--permission-mode",
3681
+ "bypassPermissions",
3682
+ "--output-format",
3683
+ "json",
3684
+ "--max-turns",
3685
+ String(maxTurns)
3686
+ ], {
3687
+ cwd,
3688
+ timeout: 6e4,
3689
+ stdio: ["ignore", "pipe", "pipe"],
3690
+ env: { ...process.env, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1" }
3691
+ });
3692
+ let stdout = "";
3693
+ let stderr = "";
3694
+ child.stdout?.on("data", (d) => {
3695
+ stdout += d.toString();
3696
+ });
3697
+ child.stderr?.on("data", (d) => {
3698
+ stderr += d.toString();
3699
+ });
3700
+ child.on("close", (code) => {
3701
+ if (code !== 0 && !stdout) {
3702
+ resolve({ success: false, error: stderr || `claude exited with code ${code}` });
3703
+ return;
3704
+ }
3705
+ try {
3706
+ const result = JSON.parse(stdout);
3707
+ resolve({ success: true, text: result.result || result.text || stdout });
3708
+ } catch {
3709
+ resolve({ success: true, text: stdout.trim() });
3710
+ }
3711
+ });
3712
+ child.on("error", (err) => resolve({ success: false, error: err.message }));
3713
+ });
3714
+ };
3715
+ const performEdit = async (target, newText) => {
3716
+ if (!callbacks.onEditTranscript) return { success: false, message: "Editing history is not supported for this session." };
3717
+ const info = editableInfo(target);
3718
+ if (!info) return { success: false, message: "This message cannot be edited." };
3719
+ const text = typeof newText === "string" ? newText : "";
3720
+ if (!text.trim()) return { success: false, message: "New text is empty." };
3721
+ let fromEnd = 0;
3722
+ for (const m of messages) {
3723
+ if (m.seq <= target.seq) continue;
3724
+ const i2 = editableInfo(m);
3725
+ if (i2 && i2.role === info.role) fromEnd++;
3726
+ }
3727
+ const anchorSeq = target.seq;
3728
+ const applyStoreA = () => {
3729
+ for (let i = messages.length - 1; i >= 0; i--) {
3730
+ if (messages[i].seq > anchorSeq) messages.splice(i, 1);
3731
+ }
3732
+ const tgt = messages.find((m) => m.seq === anchorSeq);
3733
+ if (tgt) {
3734
+ patchStoredText(tgt, text);
3735
+ tgt.updatedAt = Date.now();
3736
+ }
3737
+ nextSeq = anchorSeq + 1;
3738
+ if (options?.messagesDir) {
3739
+ const filePath = join(options.messagesDir, "messages.jsonl");
3740
+ try {
3741
+ const lines = existsSync(filePath) ? readFileSync(filePath, "utf-8").split("\n").filter((l) => l.trim()) : [];
3742
+ const kept = [];
3743
+ for (const line of lines) {
3744
+ let m;
3745
+ try {
3746
+ m = JSON.parse(line);
3747
+ } catch {
3748
+ continue;
3749
+ }
3750
+ if (typeof m.seq === "number" && m.seq > anchorSeq) continue;
3751
+ kept.push(m.seq === anchorSeq && tgt ? JSON.stringify(tgt) : line);
3752
+ }
3753
+ const tmp = `${filePath}.tmp-${process.pid}`;
3754
+ writeFileSync(tmp, kept.length ? kept.join("\n") + "\n" : "");
3755
+ renameSync(tmp, filePath);
3756
+ } catch (err) {
3757
+ console.error(`[HYPHA SESSION ${sessionId}] Store A rewrite failed: ${err?.message ?? err}`);
3758
+ }
3759
+ }
3760
+ notifyListeners({ type: "messages-edited", sessionId, latestSeq: nextSeq - 1 });
3761
+ };
3762
+ return callbacks.onEditTranscript({ role: info.role, fromEnd, oldText: info.text, newText: text, applyStoreA });
3763
+ };
3627
3764
  const rpcHandlers = {
3628
3765
  // ── Messages ──
3629
3766
  getMessages: async (afterSeq, limit, context) => {
@@ -4303,59 +4440,72 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
4303
4440
  if (!claudeSessionId) {
4304
4441
  return { success: false, error: "No active Claude session to query" };
4305
4442
  }
4306
- const { spawn } = await import('child_process');
4307
- const cwd = metadata.path || process.cwd();
4308
- return new Promise((resolve) => {
4309
- const args = [
4310
- "--print",
4311
- question,
4312
- "--resume",
4313
- claudeSessionId,
4314
- "--fork-session",
4315
- "--no-session-persistence",
4316
- "--permission-mode",
4317
- "bypassPermissions",
4318
- "--output-format",
4319
- "json",
4320
- // Allow a few turns so questions that need a look-up (read a file,
4321
- // check state) can make the tool call AND then answer. With 1 turn,
4322
- // any tool-using question died instantly with error_max_turns
4323
- // ("couldn't answer quickly"). The 60s subprocess timeout still bounds it.
4324
- "--max-turns",
4325
- "6"
4326
- ];
4327
- const child = spawn("claude", args, {
4328
- cwd,
4329
- timeout: 6e4,
4330
- // Ignore stdin: --print otherwise waits ~3s for piped input ("no stdin
4331
- // data received in 3s, proceeding without it") — pure latency per /btw.
4332
- stdio: ["ignore", "pipe", "pipe"],
4333
- env: { ...process.env, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1" }
4334
- });
4335
- let stdout = "";
4336
- let stderr = "";
4337
- child.stdout?.on("data", (d) => {
4338
- stdout += d.toString();
4339
- });
4340
- child.stderr?.on("data", (d) => {
4341
- stderr += d.toString();
4342
- });
4343
- child.on("close", (code) => {
4344
- if (code !== 0 && !stdout) {
4345
- resolve({ success: false, error: stderr || `claude exited with code ${code}` });
4346
- return;
4347
- }
4348
- try {
4349
- const result = JSON.parse(stdout);
4350
- resolve({ success: true, answer: result.result || result.text || stdout });
4351
- } catch {
4352
- resolve({ success: true, answer: stdout.trim() });
4353
- }
4354
- });
4355
- child.on("error", (err) => {
4356
- resolve({ success: false, error: err.message });
4357
- });
4358
- });
4443
+ const r = await forkClaudePrint(question, claudeSessionId, metadata.path || process.cwd());
4444
+ return r.success ? { success: true, answer: r.text } : { success: false, error: r.error };
4445
+ },
4446
+ editMessage: async (messageId, newText, context) => {
4447
+ authorizeRequest(context, metadata.sharing, "admin");
4448
+ if (!messageId) return { success: false, message: "messageId is required." };
4449
+ const target = messages.find((m) => m.id === messageId);
4450
+ if (!target) {
4451
+ return { success: false, message: "Message not found in the active window \u2014 only recent messages can be edited." };
4452
+ }
4453
+ return performEdit(target, newText);
4454
+ },
4455
+ refineLastReply: async (instruction, context) => {
4456
+ authorizeRequest(context, metadata.sharing, "admin");
4457
+ if (!instruction || typeof instruction !== "string") {
4458
+ return { success: false, message: "An instruction is required." };
4459
+ }
4460
+ const claudeSessionId = metadata.claudeSessionId;
4461
+ if (!claudeSessionId) return { success: false, message: "No active Claude session to refine." };
4462
+ let last;
4463
+ for (let i = messages.length - 1; i >= 0; i--) {
4464
+ const info = editableInfo(messages[i]);
4465
+ if (info && info.role === "assistant") {
4466
+ last = messages[i];
4467
+ break;
4468
+ }
4469
+ }
4470
+ if (!last) return { success: false, message: "No assistant reply to refine." };
4471
+ const oldText = editableInfo(last).text;
4472
+ const prompt = `You previously wrote this reply:
4473
+
4474
+ <previous_reply>
4475
+ ${oldText}
4476
+ </previous_reply>
4477
+
4478
+ Revise it according to this instruction:
4479
+ <instruction>
4480
+ ${instruction}
4481
+ </instruction>
4482
+
4483
+ Output ONLY the full revised reply text \u2014 no preamble, no commentary, no surrounding quotes or code fences.`;
4484
+ const revised = await forkClaudePrint(prompt, claudeSessionId, metadata.path || process.cwd());
4485
+ if (!revised.success || !revised.text?.trim()) {
4486
+ return { success: false, message: revised.error || "Failed to generate a revised reply." };
4487
+ }
4488
+ return performEdit(last, revised.text.trim());
4489
+ },
4490
+ undoLastEdit: async (context) => {
4491
+ authorizeRequest(context, metadata.sharing, "admin");
4492
+ if (!callbacks.onUndoEdit) return { success: false, message: "Undo is not supported for this session." };
4493
+ const restoreStoreA = () => {
4494
+ if (!options?.messagesDir) return;
4495
+ try {
4496
+ const undoBackup = join(options.messagesDir, "undo", "messages.jsonl");
4497
+ const target = join(options.messagesDir, "messages.jsonl");
4498
+ if (existsSync(undoBackup)) writeFileSync(target, readFileSync(undoBackup));
4499
+ const reloaded = loadMessages(options.messagesDir, sessionId);
4500
+ messages.length = 0;
4501
+ messages.push(...reloaded);
4502
+ nextSeq = messages.length ? messages[messages.length - 1].seq + 1 : 1;
4503
+ notifyListeners({ type: "messages-edited", sessionId, latestSeq: nextSeq - 1 });
4504
+ } catch (err) {
4505
+ console.error(`[HYPHA SESSION ${sessionId}] Undo Store A restore failed: ${err?.message ?? err}`);
4506
+ }
4507
+ };
4508
+ return callbacks.onUndoEdit({ restoreStoreA });
4359
4509
  }
4360
4510
  };
4361
4511
  const store = {
@@ -4447,6 +4597,298 @@ function shortId(length = 10) {
4447
4597
  return out;
4448
4598
  }
4449
4599
 
4600
+ const ADJECTIVES = [
4601
+ "able",
4602
+ "amber",
4603
+ "amused",
4604
+ "ancient",
4605
+ "arctic",
4606
+ "autumn",
4607
+ "azure",
4608
+ "blithe",
4609
+ "bold",
4610
+ "brave",
4611
+ "breezy",
4612
+ "bright",
4613
+ "brisk",
4614
+ "calm",
4615
+ "candid",
4616
+ "cheery",
4617
+ "chill",
4618
+ "clever",
4619
+ "cobalt",
4620
+ "cosmic",
4621
+ "cozy",
4622
+ "crimson",
4623
+ "crisp",
4624
+ "curious",
4625
+ "dapper",
4626
+ "daring",
4627
+ "dawn",
4628
+ "deft",
4629
+ "dewy",
4630
+ "eager",
4631
+ "early",
4632
+ "easy",
4633
+ "electric",
4634
+ "fancy",
4635
+ "feisty",
4636
+ "fleet",
4637
+ "fond",
4638
+ "frosty",
4639
+ "gallant",
4640
+ "gentle",
4641
+ "giddy",
4642
+ "glad",
4643
+ "gleaming",
4644
+ "golden",
4645
+ "graceful",
4646
+ "grand",
4647
+ "hardy",
4648
+ "hazel",
4649
+ "hearty",
4650
+ "honest",
4651
+ "humble",
4652
+ "jolly",
4653
+ "jovial",
4654
+ "keen",
4655
+ "kind",
4656
+ "lively",
4657
+ "loyal",
4658
+ "lucky",
4659
+ "lunar",
4660
+ "mellow",
4661
+ "merry",
4662
+ "mighty",
4663
+ "mint",
4664
+ "misty",
4665
+ "nimble",
4666
+ "noble",
4667
+ "opal",
4668
+ "patient",
4669
+ "peppy",
4670
+ "placid",
4671
+ "plucky",
4672
+ "polar",
4673
+ "prim",
4674
+ "proud",
4675
+ "quick",
4676
+ "quiet",
4677
+ "quirky",
4678
+ "radiant",
4679
+ "rapid",
4680
+ "ready",
4681
+ "regal",
4682
+ "rosy",
4683
+ "royal",
4684
+ "rugged",
4685
+ "sage",
4686
+ "sandy",
4687
+ "scarlet",
4688
+ "serene",
4689
+ "sharp",
4690
+ "shiny",
4691
+ "silent",
4692
+ "silver",
4693
+ "sleek",
4694
+ "smooth",
4695
+ "snappy",
4696
+ "snowy",
4697
+ "solar",
4698
+ "spry",
4699
+ "stellar",
4700
+ "sturdy",
4701
+ "sunny",
4702
+ "swift",
4703
+ "tender",
4704
+ "tidal",
4705
+ "tidy",
4706
+ "tranquil",
4707
+ "trusty",
4708
+ "upbeat",
4709
+ "valiant",
4710
+ "vivid",
4711
+ "warm",
4712
+ "whimsical",
4713
+ "wise",
4714
+ "witty",
4715
+ "zesty",
4716
+ "zippy"
4717
+ ];
4718
+ const ANIMALS = [
4719
+ "ant",
4720
+ "badger",
4721
+ "bat",
4722
+ "bear",
4723
+ "beaver",
4724
+ "bee",
4725
+ "bison",
4726
+ "boar",
4727
+ "bobcat",
4728
+ "buffalo",
4729
+ "camel",
4730
+ "caribou",
4731
+ "cat",
4732
+ "cheetah",
4733
+ "cobra",
4734
+ "condor",
4735
+ "cougar",
4736
+ "coyote",
4737
+ "crab",
4738
+ "crane",
4739
+ "cricket",
4740
+ "crow",
4741
+ "deer",
4742
+ "dingo",
4743
+ "dolphin",
4744
+ "donkey",
4745
+ "dove",
4746
+ "dragon",
4747
+ "duck",
4748
+ "eagle",
4749
+ "eel",
4750
+ "egret",
4751
+ "elk",
4752
+ "falcon",
4753
+ "ferret",
4754
+ "finch",
4755
+ "fox",
4756
+ "frog",
4757
+ "gecko",
4758
+ "gibbon",
4759
+ "goat",
4760
+ "goose",
4761
+ "gopher",
4762
+ "hare",
4763
+ "hawk",
4764
+ "hedgehog",
4765
+ "heron",
4766
+ "hippo",
4767
+ "horse",
4768
+ "ibex",
4769
+ "ibis",
4770
+ "iguana",
4771
+ "jackal",
4772
+ "jaguar",
4773
+ "jay",
4774
+ "kestrel",
4775
+ "koala",
4776
+ "krill",
4777
+ "lark",
4778
+ "lemur",
4779
+ "leopard",
4780
+ "lion",
4781
+ "llama",
4782
+ "lynx",
4783
+ "macaw",
4784
+ "magpie",
4785
+ "mantis",
4786
+ "marmot",
4787
+ "marten",
4788
+ "meerkat",
4789
+ "mink",
4790
+ "mole",
4791
+ "moose",
4792
+ "moth",
4793
+ "mouse",
4794
+ "newt",
4795
+ "ocelot",
4796
+ "octopus",
4797
+ "orca",
4798
+ "osprey",
4799
+ "otter",
4800
+ "owl",
4801
+ "ox",
4802
+ "panda",
4803
+ "panther",
4804
+ "parrot",
4805
+ "pelican",
4806
+ "penguin",
4807
+ "pheasant",
4808
+ "pigeon",
4809
+ "puffin",
4810
+ "puma",
4811
+ "quail",
4812
+ "rabbit",
4813
+ "raccoon",
4814
+ "ram",
4815
+ "raven",
4816
+ "robin",
4817
+ "salmon",
4818
+ "seal",
4819
+ "shark",
4820
+ "sheep",
4821
+ "shrew",
4822
+ "skunk",
4823
+ "sloth",
4824
+ "snail",
4825
+ "sparrow",
4826
+ "spider",
4827
+ "squid",
4828
+ "stag",
4829
+ "stoat",
4830
+ "stork",
4831
+ "swan",
4832
+ "tapir",
4833
+ "tiger",
4834
+ "toad",
4835
+ "trout",
4836
+ "turtle",
4837
+ "viper",
4838
+ "vole",
4839
+ "walrus",
4840
+ "weasel",
4841
+ "whale",
4842
+ "wolf",
4843
+ "wombat",
4844
+ "wren",
4845
+ "yak",
4846
+ "zebra"
4847
+ ];
4848
+ function pick(arr) {
4849
+ const n = arr.length;
4850
+ const max = Math.floor(256 / n) * n;
4851
+ let b;
4852
+ do {
4853
+ b = randomBytes(1)[0];
4854
+ } while (b >= max);
4855
+ return arr[b % n];
4856
+ }
4857
+ function generateFriendlyName(taken = []) {
4858
+ const used = taken instanceof Set ? taken : new Set(taken);
4859
+ for (let i = 0; i < 1e3; i++) {
4860
+ const name = `${pick(ADJECTIVES)}-${pick(ANIMALS)}`;
4861
+ if (!used.has(name)) return name;
4862
+ }
4863
+ const base = `${pick(ADJECTIVES)}-${pick(ANIMALS)}`;
4864
+ let n = 2;
4865
+ while (used.has(`${base}-${n}`)) n++;
4866
+ return `${base}-${n}`;
4867
+ }
4868
+ function sanitizeSegment(s) {
4869
+ return String(s || "").toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
4870
+ }
4871
+ function formatHandle(projectName, friendlyName) {
4872
+ if (!friendlyName) return void 0;
4873
+ const proj = sanitizeSegment(projectName || "");
4874
+ return proj ? `${proj}:${friendlyName}` : friendlyName;
4875
+ }
4876
+ function parseHandle(input) {
4877
+ const str = String(input || "").trim().replace(/^@/, "");
4878
+ if (!str) return null;
4879
+ const i = str.indexOf(":");
4880
+ if (i === -1) return { name: str.toLowerCase() };
4881
+ return { project: sanitizeSegment(str.slice(0, i)), name: str.slice(i + 1).toLowerCase() };
4882
+ }
4883
+ function handleMatchesMetadata(parsed, metadata) {
4884
+ const fn = String(metadata?.friendlyName || "").toLowerCase();
4885
+ if (!fn || fn !== parsed.name) return false;
4886
+ if (parsed.project) {
4887
+ return sanitizeSegment(metadata?.projectName || "") === parsed.project;
4888
+ }
4889
+ return true;
4890
+ }
4891
+
4450
4892
  const SVAMP_HOME$2 = process.env.SVAMP_HOME || join(os.homedir(), ".svamp");
4451
4893
  const num = (key, def) => {
4452
4894
  const v = Number(process.env[key]);
@@ -7189,6 +7631,171 @@ var GeminiTransport$1 = /*#__PURE__*/Object.freeze({
7189
7631
  GeminiTransport: GeminiTransport
7190
7632
  });
7191
7633
 
7634
+ function resolveTranscriptPath(cwd, claudeSessionId) {
7635
+ let real;
7636
+ try {
7637
+ real = realpathSync(cwd);
7638
+ } catch {
7639
+ real = resolve(cwd);
7640
+ }
7641
+ const projectId = real.replace(/[^a-zA-Z0-9-]/g, "-");
7642
+ const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR || join$1(os$1.homedir(), ".claude");
7643
+ return join$1(claudeConfigDir, "projects", projectId, `${claudeSessionId}.jsonl`);
7644
+ }
7645
+ function normalizeText(t) {
7646
+ return (t || "").replace(/\s+/g, " ").trim();
7647
+ }
7648
+ function parseTranscript(file) {
7649
+ const content = readFileSync$1(file, "utf-8");
7650
+ return content.split("\n").filter((l) => l.trim().length > 0).map((raw) => {
7651
+ let obj = null;
7652
+ try {
7653
+ obj = JSON.parse(raw);
7654
+ } catch {
7655
+ }
7656
+ return { raw, obj };
7657
+ });
7658
+ }
7659
+ function assistantText(obj) {
7660
+ const content = obj?.message?.content;
7661
+ if (typeof content === "string") return content;
7662
+ if (!Array.isArray(content)) return "";
7663
+ return content.filter((b) => b && b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n");
7664
+ }
7665
+ function isRealUserRecord(obj) {
7666
+ return !!obj && obj.type === "user" && !obj.isSidechain && typeof obj?.message?.content === "string";
7667
+ }
7668
+ function isAgentTextRecord(obj) {
7669
+ if (!obj || obj.type !== "assistant" || obj.isSidechain) return false;
7670
+ const content = obj?.message?.content;
7671
+ if (!Array.isArray(content)) return false;
7672
+ return content.some((b) => b && b.type === "text" && typeof b.text === "string");
7673
+ }
7674
+ function isRoleTextRecord(obj, role) {
7675
+ return role === "user" ? isRealUserRecord(obj) : isAgentTextRecord(obj);
7676
+ }
7677
+ function recordText(obj, role) {
7678
+ return role === "user" ? String(obj?.message?.content ?? "") : assistantText(obj);
7679
+ }
7680
+ function findAnchorIndex(lines, anchor) {
7681
+ const roleIdx = [];
7682
+ for (let i = 0; i < lines.length; i++) {
7683
+ if (isRoleTextRecord(lines[i].obj, anchor.role)) roleIdx.push(i);
7684
+ }
7685
+ if (roleIdx.length === 0) {
7686
+ return { ok: false, reason: `no ${anchor.role} text records in transcript` };
7687
+ }
7688
+ const pos = roleIdx.length - 1 - anchor.fromEnd;
7689
+ if (pos < 0 || pos >= roleIdx.length) {
7690
+ return { ok: false, reason: `anchor position out of range (fromEnd=${anchor.fromEnd}, have ${roleIdx.length})` };
7691
+ }
7692
+ const index = roleIdx[pos];
7693
+ const got = normalizeText(recordText(lines[index].obj, anchor.role));
7694
+ const want = normalizeText(anchor.oldText);
7695
+ if (got !== want) {
7696
+ if (!(want.length > 0 && got.startsWith(want))) {
7697
+ return { ok: false, reason: "anchor text mismatch \u2014 stores out of sync, refusing to edit" };
7698
+ }
7699
+ }
7700
+ return { ok: true, index };
7701
+ }
7702
+ function orphanCheckObjs(objs) {
7703
+ const toolUseIds = /* @__PURE__ */ new Set();
7704
+ const toolResultIds = /* @__PURE__ */ new Set();
7705
+ for (const obj of objs) {
7706
+ const content = obj?.message?.content;
7707
+ if (!Array.isArray(content)) continue;
7708
+ for (const b of content) {
7709
+ if (b?.type === "tool_use" && b.id) toolUseIds.add(b.id);
7710
+ if (b?.type === "tool_result" && b.tool_use_id) toolResultIds.add(b.tool_use_id);
7711
+ }
7712
+ }
7713
+ for (const id of toolUseIds) {
7714
+ if (!toolResultIds.has(id)) return true;
7715
+ }
7716
+ return false;
7717
+ }
7718
+ function applyTranscriptEdit(file, index, newText) {
7719
+ if (!existsSync$1(file)) return { ok: false, reason: `transcript not found: ${file}` };
7720
+ const lines = parseTranscript(file);
7721
+ if (index < 0 || index >= lines.length) return { ok: false, reason: "index out of range" };
7722
+ const target = lines[index].obj;
7723
+ if (!target) return { ok: false, reason: "target line not parseable" };
7724
+ if (target.type === "assistant") {
7725
+ const content = target?.message?.content;
7726
+ if (!Array.isArray(content)) return { ok: false, reason: "assistant content is not a block array" };
7727
+ const textBlocks = content.filter((b) => b && b.type === "text" && typeof b.text === "string");
7728
+ if (textBlocks.length !== 1) {
7729
+ return { ok: false, reason: `expected exactly 1 text block, found ${textBlocks.length}` };
7730
+ }
7731
+ textBlocks[0].text = newText;
7732
+ } else if (target.type === "user") {
7733
+ if (typeof target?.message?.content !== "string") {
7734
+ return { ok: false, reason: "user content is not a string" };
7735
+ }
7736
+ target.message.content = newText;
7737
+ } else {
7738
+ return { ok: false, reason: `unsupported record type: ${target.type}` };
7739
+ }
7740
+ const keptObjs = [];
7741
+ const kept = [];
7742
+ for (let i = 0; i <= index; i++) {
7743
+ kept.push(i === index ? JSON.stringify(target) : lines[i].raw);
7744
+ keptObjs.push(lines[i].obj);
7745
+ }
7746
+ if (orphanCheckObjs(keptObjs)) {
7747
+ return { ok: false, reason: "edit would orphan a tool_use (truncation drops its tool_result)" };
7748
+ }
7749
+ const out = kept.join("\n") + "\n";
7750
+ const tmp = `${file}.tmp-${process.pid}`;
7751
+ try {
7752
+ writeFileSync$1(tmp, out);
7753
+ renameSync$1(tmp, file);
7754
+ } catch (err) {
7755
+ return { ok: false, reason: `write failed: ${err?.message ?? err}` };
7756
+ }
7757
+ return { ok: true, truncatedAfter: index, totalBefore: lines.length };
7758
+ }
7759
+ function saveUndoSnapshot(undoDir, transcriptFile, messagesFile, meta) {
7760
+ mkdirSync$1(undoDir, { recursive: true });
7761
+ if (existsSync$1(transcriptFile)) copyFileSync(transcriptFile, join$1(undoDir, "transcript.jsonl"));
7762
+ if (existsSync$1(messagesFile)) copyFileSync(messagesFile, join$1(undoDir, "messages.jsonl"));
7763
+ writeFileSync$1(join$1(undoDir, "meta.json"), JSON.stringify(meta));
7764
+ }
7765
+ function readUndoMeta(undoDir) {
7766
+ const p = join$1(undoDir, "meta.json");
7767
+ if (!existsSync$1(p)) return null;
7768
+ try {
7769
+ return JSON.parse(readFileSync$1(p, "utf-8"));
7770
+ } catch {
7771
+ return null;
7772
+ }
7773
+ }
7774
+ function restoreTranscriptFromUndo(undoDir, targetFile) {
7775
+ const backup = join$1(undoDir, "transcript.jsonl");
7776
+ if (!existsSync$1(backup)) return false;
7777
+ const tmp = `${targetFile}.tmp-${process.pid}`;
7778
+ writeFileSync$1(tmp, readFileSync$1(backup));
7779
+ renameSync$1(tmp, targetFile);
7780
+ return true;
7781
+ }
7782
+ function clearUndoSnapshot(undoDir) {
7783
+ try {
7784
+ rmSync$1(undoDir, { recursive: true, force: true });
7785
+ } catch {
7786
+ }
7787
+ }
7788
+ function transcriptSessionIdMatches(file, expectedSessionId) {
7789
+ if (!existsSync$1(file)) return false;
7790
+ const lines = parseTranscript(file);
7791
+ for (const { obj } of lines) {
7792
+ if (obj && typeof obj.sessionId === "string") {
7793
+ return obj.sessionId === expectedSessionId;
7794
+ }
7795
+ }
7796
+ return false;
7797
+ }
7798
+
7192
7799
  const execFileAsync = promisify$1(execFile$1);
7193
7800
  const SVAMP_TOOLS_DIR = join(homedir(), ".svamp", "tools");
7194
7801
  const SVAMP_BIN_DIR = join(SVAMP_TOOLS_DIR, "bin");
@@ -9162,10 +9769,10 @@ async function checkAndRefreshOAuthToken(force = false, logger) {
9162
9769
  }
9163
9770
  }
9164
9771
 
9165
- function buildBaselineSystemPrompt(sessionId) {
9772
+ function buildBaselineSystemPrompt(sessionId, handle) {
9166
9773
  return `# Svamp Session
9167
9774
 
9168
- 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.
9775
+ You are running inside a Svamp session (id: ${sessionId}${handle ? `, handle: ${handle}` : ""}) on Hypha Cloud.${handle ? ` Other agents and the user refer to you as \`${handle}\` (a \`project:name\` handle) \u2014 use it when introducing yourself, and refer to peers by their handles.` : ""} 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.
9169
9776
 
9170
9777
  **Session state:**
9171
9778
  - \`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).
@@ -9178,7 +9785,7 @@ You are running inside a Svamp session (id: ${sessionId}) on Hypha Cloud. Use th
9178
9785
 
9179
9786
  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.
9180
9787
 
9181
- **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.
9788
+ **Inbox messages from other agents** arrive wrapped as \`<svamp-message message-id="\u2026" from="<sender-handle>" \u2026>BODY</svamp-message>\` (a plain user turn has no wrapper); \`from\` is the sender's \`project:name\` handle. 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. **If a message carries \`awaiting-reply="true"\`, the sender is actively polling for your answer through a channel \u2014 you MUST respond with \`svamp session inbox reply <message-id> "<body>"\`; a normal chat reply will NOT reach them.**
9182
9789
  `;
9183
9790
  }
9184
9791
 
@@ -10097,6 +10704,8 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
10097
10704
  messageQueue: [...existingQueue, {
10098
10705
  id: randomUUID$1(),
10099
10706
  text: kickoff,
10707
+ // Compact chat badge; full task lives in LOOP.md. Show an ellipsis
10708
+ // when truncated so it's clear the preview is shortened, not the task.
10100
10709
  displayText: `\u{1F501} Loop started: ${lp.task.trim().slice(0, 100)}${lp.task.trim().length > 100 ? "\u2026" : ""}`,
10101
10710
  createdAt: Date.now()
10102
10711
  }]
@@ -10596,7 +11205,7 @@ async function startDaemon(options) {
10596
11205
  const list = loadExposedTunnels().filter((t) => t.name !== name);
10597
11206
  saveExposedTunnels(list);
10598
11207
  }
10599
- const { ServeManager } = await import('./serveManager-XsXnI804.mjs');
11208
+ const { ServeManager } = await import('./serveManager-QZooKtI4.mjs');
10600
11209
  const serveManager = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
10601
11210
  ensureAutoInstalledSkills(logger).catch(() => {
10602
11211
  });
@@ -10622,6 +11231,16 @@ async function startDaemon(options) {
10622
11231
  clientId: hyphaClientId || machineId
10623
11232
  });
10624
11233
  logger.log(`Connected to Hypha (workspace: ${server.config.workspace})`);
11234
+ writeDaemonStateFile({
11235
+ pid: process.pid,
11236
+ startTime: (/* @__PURE__ */ new Date()).toISOString(),
11237
+ version: DAEMON_VERSION,
11238
+ hyphaServerUrl,
11239
+ workspace: server.config.workspace,
11240
+ machineId,
11241
+ hyphaClientId: server.config.client_id,
11242
+ supervised: process.env.SVAMP_SUPERVISED === "1"
11243
+ });
10625
11244
  server.on("disconnected", (reason) => {
10626
11245
  logger.log(`Hypha connection permanently lost: ${reason}. Daemon continues running \u2014 restart manually to reconnect.`);
10627
11246
  });
@@ -10668,6 +11287,8 @@ ${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
10668
11287
  metadata = {
10669
11288
  flavor: m.flavor,
10670
11289
  name: m.name,
11290
+ friendlyName: m.friendlyName,
11291
+ projectName: m.projectName,
10671
11292
  path: m.path,
10672
11293
  host: m.host,
10673
11294
  lifecycleState: m.lifecycleState,
@@ -10689,6 +11310,24 @@ ${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
10689
11310
  };
10690
11311
  });
10691
11312
  };
11313
+ const collectKnownFriendlyNames = () => {
11314
+ const taken = /* @__PURE__ */ new Set();
11315
+ for (const s of pidToTrackedSession.values()) {
11316
+ try {
11317
+ const fn = s.hyphaService?.getMetadata?.()?.friendlyName;
11318
+ if (fn) taken.add(fn);
11319
+ } catch {
11320
+ }
11321
+ }
11322
+ try {
11323
+ for (const p of loadPersistedSessions()) {
11324
+ const fn = p.metadata?.friendlyName;
11325
+ if (fn) taken.add(fn);
11326
+ }
11327
+ } catch {
11328
+ }
11329
+ return taken;
11330
+ };
10692
11331
  let machineServiceRef = null;
10693
11332
  const spawnSession = async (options2) => {
10694
11333
  logger.log("Spawning session:", JSON.stringify(options2));
@@ -10807,6 +11446,8 @@ ${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
10807
11446
  var parseBashPermission = parseBashPermission2, shouldAutoAllow = shouldAutoAllow2, killAndWaitForExit = killAndWaitForExit2, buildIsolationConfig = buildIsolationConfig2;
10808
11447
  let sessionMetadata = {
10809
11448
  path: directory,
11449
+ // Project namespace for the friendly handle (folder/repo basename).
11450
+ projectName: projectName(directory),
10810
11451
  host: os$1.hostname(),
10811
11452
  version: "0.1.0",
10812
11453
  machineId,
@@ -10833,6 +11474,10 @@ ${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
10833
11474
  const allPersisted = loadPersistedSessions();
10834
11475
  const persisted = allPersisted.find((p) => p.sessionId === sessionId) || (resumeSessionId ? allPersisted.find((p) => p.claudeResumeId === resumeSessionId) : void 0);
10835
11476
  let claudeResumeId = persisted?.claudeResumeId || (resumeSessionId || void 0);
11477
+ sessionMetadata = {
11478
+ ...sessionMetadata,
11479
+ friendlyName: persisted?.metadata?.friendlyName || generateFriendlyName(collectKnownFriendlyNames())
11480
+ };
10836
11481
  let currentPermissionMode = options2.permissionMode || persisted?.permissionMode || "bypassPermissions";
10837
11482
  const sessionCreatedAt = persisted?.createdAt || Date.now();
10838
11483
  let lastSpawnMeta = persisted?.spawnMeta || {};
@@ -11117,7 +11762,7 @@ ${parts.join("\n")}`);
11117
11762
  const permissionMode = toClaudePermissionMode(rawPermissionMode);
11118
11763
  currentPermissionMode = permissionMode;
11119
11764
  const model = effectiveMeta.model || agentConfig.default_model || void 0;
11120
- const appendSystemPrompt = effectiveMeta.appendSystemPrompt || agentConfig.append_system_prompt || buildBaselineSystemPrompt(sessionId);
11765
+ const appendSystemPrompt = effectiveMeta.appendSystemPrompt || agentConfig.append_system_prompt || buildBaselineSystemPrompt(sessionId, formatHandle(sessionMetadata.projectName, sessionMetadata.friendlyName));
11121
11766
  const args = [
11122
11767
  "--output-format",
11123
11768
  "stream-json",
@@ -11179,6 +11824,10 @@ ${parts.join("\n")}`);
11179
11824
  let spawnEnv = { ...process.env, ...extraEnv };
11180
11825
  delete spawnEnv.CLAUDECODE;
11181
11826
  spawnEnv.SVAMP_SESSION_ID = sessionId;
11827
+ {
11828
+ const handle = formatHandle(sessionMetadata.projectName, sessionMetadata.friendlyName);
11829
+ if (handle) spawnEnv.SVAMP_SESSION_HANDLE = handle;
11830
+ }
11182
11831
  delete spawnEnv.SVAMP_SANDBOXED;
11183
11832
  const proxyDesc = applyClaudeProxyEnv(spawnEnv);
11184
11833
  if (proxyDesc) {
@@ -11673,12 +12322,13 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11673
12322
  }
11674
12323
  return child;
11675
12324
  };
11676
- const restartClaudeHandler = async () => {
12325
+ const restartClaudeHandler = async (opts) => {
11677
12326
  logger.log(`[Session ${sessionId}] Restart Claude requested`);
11678
12327
  if (isRestartingClaude || isSwitchingMode) {
11679
12328
  return { success: false, message: "Restart already in progress." };
11680
12329
  }
11681
12330
  isRestartingClaude = true;
12331
+ let beforeRespawnError;
11682
12332
  try {
11683
12333
  if (claudeProcess && claudeProcess.exitCode === null) {
11684
12334
  isKillingClaude = true;
@@ -11690,6 +12340,14 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11690
12340
  if (trackedSession?.stopped) {
11691
12341
  return { success: false, message: "Session was stopped during restart." };
11692
12342
  }
12343
+ if (opts?.beforeRespawn) {
12344
+ try {
12345
+ await opts.beforeRespawn();
12346
+ } catch (hookErr) {
12347
+ beforeRespawnError = hookErr?.message ?? String(hookErr);
12348
+ logger.log(`[Session ${sessionId}] beforeRespawn hook failed: ${beforeRespawnError}`);
12349
+ }
12350
+ }
11693
12351
  if (claudeResumeId) {
11694
12352
  if (!stagedCredentials && shouldIsolateSession()) {
11695
12353
  try {
@@ -11702,6 +12360,9 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11702
12360
  spawnClaude(void 0, { permissionMode: currentPermissionMode });
11703
12361
  sessionService.updateMetadata(sessionMetadata);
11704
12362
  logger.log(`[Session ${sessionId}] Claude respawned with --resume ${claudeResumeId}`);
12363
+ if (beforeRespawnError) {
12364
+ return { success: false, message: `Edit failed (session restored): ${beforeRespawnError}` };
12365
+ }
11705
12366
  return { success: true, message: "Claude process restarted successfully." };
11706
12367
  } else {
11707
12368
  logger.log(`[Session ${sessionId}] No resume ID \u2014 cannot restart`);
@@ -11715,6 +12376,26 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11715
12376
  isRestartingClaude = false;
11716
12377
  }
11717
12378
  };
12379
+ const editHistoryIdleGuard = () => {
12380
+ const lifecycle = sessionMetadata.lifecycleState;
12381
+ if (lifecycle === "running" || lifecycle === "restarting") {
12382
+ return { success: false, message: "Cannot edit while the agent is working \u2014 wait until it is idle." };
12383
+ }
12384
+ if (isRestartingClaude || isSwitchingMode || isKillingClaude) {
12385
+ return { success: false, message: "Cannot edit during a restart/mode switch \u2014 try again in a moment." };
12386
+ }
12387
+ const queueLen = sessionMetadata.messageQueue?.length ?? 0;
12388
+ if (queueLen > 0) {
12389
+ return { success: false, message: "Cannot edit while messages are queued." };
12390
+ }
12391
+ if (isLoopActiveForSession(directory, sessionId)) {
12392
+ return { success: false, message: "Cannot edit history while a loop is active." };
12393
+ }
12394
+ if (!claudeResumeId) {
12395
+ return { success: false, message: "No Claude transcript to edit yet." };
12396
+ }
12397
+ return null;
12398
+ };
11718
12399
  if (shouldIsolateSession()) {
11719
12400
  try {
11720
12401
  stagedCredentials = await stageCredentialsForSharing(sessionId);
@@ -11735,6 +12416,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11735
12416
  logger.log(`[Session ${sessionId}] User message received`);
11736
12417
  userMessagePending = true;
11737
12418
  turnInitiatedByUser = true;
12419
+ clearUndoSnapshot(join$1(getSessionDir(directory, sessionId), "undo"));
11738
12420
  let text;
11739
12421
  let msgMeta = meta;
11740
12422
  try {
@@ -11954,6 +12636,86 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11954
12636
  }
11955
12637
  },
11956
12638
  onRestartClaude: restartClaudeHandler,
12639
+ onEditTranscript: async ({ role, fromEnd, oldText, newText, applyStoreA }) => {
12640
+ const gate = editHistoryIdleGuard();
12641
+ if (gate) return gate;
12642
+ logger.log(`[Session ${sessionId}] Edit history: role=${role} fromEnd=${fromEnd} \u2192 kill + rewrite + resume`);
12643
+ const result = await restartClaudeHandler({
12644
+ beforeRespawn: async () => {
12645
+ const file = resolveTranscriptPath(directory, claudeResumeId);
12646
+ if (!transcriptSessionIdMatches(file, claudeResumeId)) {
12647
+ throw new Error("transcript file id mismatch (session rotated) \u2014 aborting edit");
12648
+ }
12649
+ const lines = parseTranscript(file);
12650
+ const anchor = findAnchorIndex(lines, { role, fromEnd, oldText });
12651
+ if (!anchor.ok) throw new Error(anchor.reason);
12652
+ const sessDir = getSessionDir(directory, sessionId);
12653
+ saveUndoSnapshot(join$1(sessDir, "undo"), file, join$1(sessDir, "messages.jsonl"), {
12654
+ preEditClaudeSessionId: claudeResumeId,
12655
+ createdAt: Date.now(),
12656
+ label: "edit"
12657
+ });
12658
+ const edit = applyTranscriptEdit(file, anchor.index, newText);
12659
+ if (!edit.ok) throw new Error(edit.reason);
12660
+ applyStoreA();
12661
+ logger.log(`[Session ${sessionId}] Edit applied \u2014 transcript truncated after line ${anchor.index}`);
12662
+ }
12663
+ });
12664
+ if (result.success && claudeResumeId && !trackedSession.stopped) {
12665
+ saveSession({
12666
+ sessionId,
12667
+ directory,
12668
+ claudeResumeId,
12669
+ permissionMode: currentPermissionMode,
12670
+ spawnMeta: lastSpawnMeta,
12671
+ metadata: sessionMetadata,
12672
+ createdAt: sessionCreatedAt,
12673
+ machineId,
12674
+ wasProcessing: false
12675
+ });
12676
+ artifactSync.syncSession(sessionId, getSessionDir(directory, sessionId), sessionMetadata, machineId).catch(() => {
12677
+ });
12678
+ }
12679
+ return result;
12680
+ },
12681
+ onUndoEdit: async ({ restoreStoreA }) => {
12682
+ const gate = editHistoryIdleGuard();
12683
+ if (gate) return gate;
12684
+ const undoDir = join$1(getSessionDir(directory, sessionId), "undo");
12685
+ const meta = readUndoMeta(undoDir);
12686
+ if (!meta) return { success: false, message: "Nothing to undo." };
12687
+ logger.log(`[Session ${sessionId}] Undo edit \u2192 restore pre-edit transcript ${meta.preEditClaudeSessionId} + messages, resume`);
12688
+ const result = await restartClaudeHandler({
12689
+ beforeRespawn: async () => {
12690
+ const target = resolveTranscriptPath(directory, meta.preEditClaudeSessionId);
12691
+ if (!restoreTranscriptFromUndo(undoDir, target)) {
12692
+ throw new Error("undo snapshot missing transcript backup");
12693
+ }
12694
+ restoreStoreA();
12695
+ claudeResumeId = meta.preEditClaudeSessionId;
12696
+ sessionMetadata = { ...sessionMetadata, claudeSessionId: claudeResumeId };
12697
+ }
12698
+ });
12699
+ if (result.success) {
12700
+ clearUndoSnapshot(undoDir);
12701
+ if (claudeResumeId && !trackedSession.stopped) {
12702
+ saveSession({
12703
+ sessionId,
12704
+ directory,
12705
+ claudeResumeId,
12706
+ permissionMode: currentPermissionMode,
12707
+ spawnMeta: lastSpawnMeta,
12708
+ metadata: sessionMetadata,
12709
+ createdAt: sessionCreatedAt,
12710
+ machineId,
12711
+ wasProcessing: false
12712
+ });
12713
+ artifactSync.syncSession(sessionId, getSessionDir(directory, sessionId), sessionMetadata, machineId).catch(() => {
12714
+ });
12715
+ }
12716
+ }
12717
+ return result;
12718
+ },
11957
12719
  onUpdateSecurityContext: async (newSecurityContext) => {
11958
12720
  logger.log(`[Session ${sessionId}] Security context update requested \u2014 restarting agent`);
11959
12721
  sessionMetadata = { ...sessionMetadata, securityContext: newSecurityContext };
@@ -12333,8 +13095,12 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
12333
13095
  return false;
12334
13096
  };
12335
13097
  var parseBashPermission = parseBashPermission2, shouldAutoAllow = shouldAutoAllow2;
13098
+ const acpPersistedName = loadPersistedSessions().find((p) => p.sessionId === sessionId)?.metadata?.friendlyName;
12336
13099
  let sessionMetadata = {
12337
13100
  path: directory,
13101
+ // Project namespace for the friendly handle (folder/repo basename).
13102
+ projectName: projectName(directory),
13103
+ friendlyName: acpPersistedName || generateFriendlyName(collectKnownFriendlyNames()),
12338
13104
  host: os$1.hostname(),
12339
13105
  version: "0.1.0",
12340
13106
  machineId,
@@ -13108,7 +13874,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
13108
13874
  const specs = loadExposedTunnels();
13109
13875
  if (specs.length === 0) return;
13110
13876
  logger.log(`[exposed-tunnels] Restoring ${specs.length} tunnel(s) from ${EXPOSED_TUNNELS_FILE}`);
13111
- const { FrpcTunnel } = await import('./frpc-CdcXdQde.mjs');
13877
+ const { FrpcTunnel } = await import('./frpc-DlsBjcRf.mjs');
13112
13878
  for (const spec of specs) {
13113
13879
  if (tunnels.has(spec.name)) continue;
13114
13880
  try {
@@ -13884,4 +14650,4 @@ var run = /*#__PURE__*/Object.freeze({
13884
14650
  writeStopMarker: writeStopMarker
13885
14651
  });
13886
14652
 
13887
- 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 };
14653
+ export { GeminiTransport$1 as $, READ_ONLY_TOOLS as A, loadMachineContext as B, buildMachineInstructions as C, machineToolsForRole as D, buildMachineTools as E, resolveModel as F, formatHandle as G, normalizeAllowedUser as H, loadSecurityContextConfig as I, resolveSecurityContext as J, buildSecurityContextFromFlags as K, mergeSecurityContexts as L, buildSessionShareUrl as M, computeOutboundHop as N, buildMachineShareUrl as O, parseHandle as P, handleMatchesMetadata as Q, RoutineStore as R, ServeAuth as S, describeMisconfiguration as T, buildMachineDeps as U, generateHookSettings as V, projectInfo as W, DefaultTransport$1 as X, acpBackend as Y, acpAgentConfig as Z, codexMcpBackend as _, createSessionStore as a, claudeAuth as a0, instanceConfig as a1, api as a2, run as a3, 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 };