svamp-cli 0.2.119 → 0.2.121

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-DRWpAeZW.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-DDiW5TJU.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-DgrVRMPf.mjs');
3040
3040
  const timeout = c.reply?.timeout_sec || 120;
3041
3041
  let result;
3042
3042
  try {
@@ -3113,9 +3113,6 @@ ${d?.error || "not found"}`;
3113
3113
  trackInbound();
3114
3114
  const res = resolveChannel(kwargs.channel, kwargs.session);
3115
3115
  if ("error" in res) return { error: res.error };
3116
- if (res.tier === "session" && !("stopped" in res)) {
3117
- return res.rpc.channelReceive({ channel: kwargs.channel, key: kwargs.key, from: kwargs.from, cursor: kwargs.cursor, correlationId: kwargs.correlationId, wait: kwargs.wait }, context);
3118
- }
3119
3116
  const { c, dir } = res;
3120
3117
  if (c.reply?.mode !== "queue") {
3121
3118
  return { error: "this channel has no async replies (not a queue-mode channel)" };
@@ -3130,9 +3127,16 @@ ${d?.error || "not found"}`;
3130
3127
  });
3131
3128
  if (r.error || !r.sender) return { error: r.error || "unauthorized" };
3132
3129
  const cursor = Math.max(0, Number(kwargs.cursor) || 0);
3133
- const outbox = new ChannelOutbox(dir);
3134
- const replies = outbox.since(c.id, cursor, r.sender.name, kwargs.correlationId);
3135
- return { ok: true, replies, cursor: outbox.cursor(c.id) };
3130
+ const waitMs = Math.min(Math.max(0, Number(kwargs.wait ?? 25) * 1e3), 6e4);
3131
+ const deadline = Date.now() + waitMs;
3132
+ for (; ; ) {
3133
+ const outbox = new ChannelOutbox(dir);
3134
+ const replies = outbox.since(c.id, cursor, r.sender.name, kwargs.correlationId);
3135
+ if (replies.length || Date.now() >= deadline) {
3136
+ return { ok: true, replies, cursor: outbox.cursor(c.id) };
3137
+ }
3138
+ await new Promise((resolve) => setTimeout(resolve, 500));
3139
+ }
3136
3140
  }
3137
3141
  },
3138
3142
  { overwrite: true }
@@ -3389,6 +3393,7 @@ function formatInboxMessageXml(msg) {
3389
3393
  if (msg.from) attrs.push(`from="${escapeXml(msg.from)}"`);
3390
3394
  if (msg.channel) attrs.push(`channel="${escapeXml(msg.channel)}"`);
3391
3395
  if (msg.verified !== void 0) attrs.push(`verified="${msg.verified === true}"`);
3396
+ if (msg.channelId && msg.correlationId) attrs.push(`awaiting-reply="true"`);
3392
3397
  if (msg.to) attrs.push(`to="${escapeXml(msg.to)}"`);
3393
3398
  if (msg.subject) attrs.push(`subject="${escapeXml(msg.subject)}"`);
3394
3399
  if (msg.urgency) attrs.push(`urgency="${msg.urgency}"`);
@@ -3479,6 +3484,48 @@ function appendMessage(messagesDir, sessionId, msg) {
3479
3484
  console.error(`[HYPHA SESSION ${sessionId}] Failed to persist message: ${err?.message ?? err}`);
3480
3485
  }
3481
3486
  }
3487
+ function editableInfo(msg) {
3488
+ const c = msg?.content;
3489
+ if (!c) return null;
3490
+ if (c.role === "user" && c.content?.type === "text") {
3491
+ return { role: "user", text: String(c.content.text ?? "") };
3492
+ }
3493
+ if (c.role === "agent" && c.content?.type === "output") {
3494
+ const data = c.content.data;
3495
+ if (data?.type === "assistant" && Array.isArray(data?.message?.content)) {
3496
+ const textBlocks = data.message.content.filter((b) => b && b.type === "text" && typeof b.text === "string");
3497
+ if (textBlocks.length >= 1) {
3498
+ return { role: "assistant", text: textBlocks.map((b) => b.text).join("\n") };
3499
+ }
3500
+ }
3501
+ if (data?.type === "user" && typeof data?.message?.content === "string") {
3502
+ return { role: "user", text: data.message.content };
3503
+ }
3504
+ }
3505
+ return null;
3506
+ }
3507
+ function patchStoredText(msg, newText) {
3508
+ const c = msg?.content;
3509
+ if (c?.role === "user" && c.content?.type === "text") {
3510
+ c.content.text = newText;
3511
+ return true;
3512
+ }
3513
+ if (c?.role === "agent" && c.content?.type === "output") {
3514
+ const data = c.content.data;
3515
+ if (data?.type === "user" && typeof data?.message?.content === "string") {
3516
+ data.message.content = newText;
3517
+ return true;
3518
+ }
3519
+ if (data?.type === "assistant" && Array.isArray(data?.message?.content)) {
3520
+ const textBlocks = data.message.content.filter((b) => b && b.type === "text" && typeof b.text === "string");
3521
+ if (textBlocks.length === 1) {
3522
+ textBlocks[0].text = newText;
3523
+ return true;
3524
+ }
3525
+ }
3526
+ }
3527
+ return false;
3528
+ }
3482
3529
  function createSessionStore(server, sessionId, initialMetadata, initialAgentState, callbacks, options) {
3483
3530
  const messages = options?.messagesDir ? loadMessages(options.messagesDir) : [];
3484
3531
  let nextSeq = messages.length > 0 ? messages[messages.length - 1].seq + 1 : 1;
@@ -3624,6 +3671,100 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
3624
3671
  notifyListeners({ type: "update-session", sessionId, metadata: { value: metadata, version: metadataVersion } });
3625
3672
  callbacks.onMetadataUpdate?.(metadata);
3626
3673
  };
3674
+ const forkClaudePrint = async (prompt, claudeSessionId, cwd, maxTurns = 6) => {
3675
+ const { spawn } = await import('child_process');
3676
+ return new Promise((resolve) => {
3677
+ const child = spawn("claude", [
3678
+ "--print",
3679
+ prompt,
3680
+ "--resume",
3681
+ claudeSessionId,
3682
+ "--fork-session",
3683
+ "--no-session-persistence",
3684
+ "--permission-mode",
3685
+ "bypassPermissions",
3686
+ "--output-format",
3687
+ "json",
3688
+ "--max-turns",
3689
+ String(maxTurns)
3690
+ ], {
3691
+ cwd,
3692
+ timeout: 6e4,
3693
+ stdio: ["ignore", "pipe", "pipe"],
3694
+ env: { ...process.env, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1" }
3695
+ });
3696
+ let stdout = "";
3697
+ let stderr = "";
3698
+ child.stdout?.on("data", (d) => {
3699
+ stdout += d.toString();
3700
+ });
3701
+ child.stderr?.on("data", (d) => {
3702
+ stderr += d.toString();
3703
+ });
3704
+ child.on("close", (code) => {
3705
+ if (code !== 0 && !stdout) {
3706
+ resolve({ success: false, error: stderr || `claude exited with code ${code}` });
3707
+ return;
3708
+ }
3709
+ try {
3710
+ const result = JSON.parse(stdout);
3711
+ resolve({ success: true, text: result.result || result.text || stdout });
3712
+ } catch {
3713
+ resolve({ success: true, text: stdout.trim() });
3714
+ }
3715
+ });
3716
+ child.on("error", (err) => resolve({ success: false, error: err.message }));
3717
+ });
3718
+ };
3719
+ const performEdit = async (target, newText) => {
3720
+ if (!callbacks.onEditTranscript) return { success: false, message: "Editing history is not supported for this session." };
3721
+ const info = editableInfo(target);
3722
+ if (!info) return { success: false, message: "This message cannot be edited." };
3723
+ const text = typeof newText === "string" ? newText : "";
3724
+ if (!text.trim()) return { success: false, message: "New text is empty." };
3725
+ let fromEnd = 0;
3726
+ for (const m of messages) {
3727
+ if (m.seq <= target.seq) continue;
3728
+ const i2 = editableInfo(m);
3729
+ if (i2 && i2.role === info.role) fromEnd++;
3730
+ }
3731
+ const anchorSeq = target.seq;
3732
+ const applyStoreA = () => {
3733
+ for (let i = messages.length - 1; i >= 0; i--) {
3734
+ if (messages[i].seq > anchorSeq) messages.splice(i, 1);
3735
+ }
3736
+ const tgt = messages.find((m) => m.seq === anchorSeq);
3737
+ if (tgt) {
3738
+ patchStoredText(tgt, text);
3739
+ tgt.updatedAt = Date.now();
3740
+ }
3741
+ nextSeq = anchorSeq + 1;
3742
+ if (options?.messagesDir) {
3743
+ const filePath = join(options.messagesDir, "messages.jsonl");
3744
+ try {
3745
+ const lines = existsSync(filePath) ? readFileSync(filePath, "utf-8").split("\n").filter((l) => l.trim()) : [];
3746
+ const kept = [];
3747
+ for (const line of lines) {
3748
+ let m;
3749
+ try {
3750
+ m = JSON.parse(line);
3751
+ } catch {
3752
+ continue;
3753
+ }
3754
+ if (typeof m.seq === "number" && m.seq > anchorSeq) continue;
3755
+ kept.push(m.seq === anchorSeq && tgt ? JSON.stringify(tgt) : line);
3756
+ }
3757
+ const tmp = `${filePath}.tmp-${process.pid}`;
3758
+ writeFileSync(tmp, kept.length ? kept.join("\n") + "\n" : "");
3759
+ renameSync(tmp, filePath);
3760
+ } catch (err) {
3761
+ console.error(`[HYPHA SESSION ${sessionId}] Store A rewrite failed: ${err?.message ?? err}`);
3762
+ }
3763
+ }
3764
+ notifyListeners({ type: "messages-edited", sessionId, latestSeq: nextSeq - 1 });
3765
+ };
3766
+ return callbacks.onEditTranscript({ role: info.role, fromEnd, oldText: info.text, newText: text, applyStoreA });
3767
+ };
3627
3768
  const rpcHandlers = {
3628
3769
  // ── Messages ──
3629
3770
  getMessages: async (afterSeq, limit, context) => {
@@ -4303,59 +4444,72 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
4303
4444
  if (!claudeSessionId) {
4304
4445
  return { success: false, error: "No active Claude session to query" };
4305
4446
  }
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
- });
4447
+ const r = await forkClaudePrint(question, claudeSessionId, metadata.path || process.cwd());
4448
+ return r.success ? { success: true, answer: r.text } : { success: false, error: r.error };
4449
+ },
4450
+ editMessage: async (messageId, newText, context) => {
4451
+ authorizeRequest(context, metadata.sharing, "admin");
4452
+ if (!messageId) return { success: false, message: "messageId is required." };
4453
+ const target = messages.find((m) => m.id === messageId);
4454
+ if (!target) {
4455
+ return { success: false, message: "Message not found in the active window \u2014 only recent messages can be edited." };
4456
+ }
4457
+ return performEdit(target, newText);
4458
+ },
4459
+ refineLastReply: async (instruction, context) => {
4460
+ authorizeRequest(context, metadata.sharing, "admin");
4461
+ if (!instruction || typeof instruction !== "string") {
4462
+ return { success: false, message: "An instruction is required." };
4463
+ }
4464
+ const claudeSessionId = metadata.claudeSessionId;
4465
+ if (!claudeSessionId) return { success: false, message: "No active Claude session to refine." };
4466
+ let last;
4467
+ for (let i = messages.length - 1; i >= 0; i--) {
4468
+ const info = editableInfo(messages[i]);
4469
+ if (info && info.role === "assistant") {
4470
+ last = messages[i];
4471
+ break;
4472
+ }
4473
+ }
4474
+ if (!last) return { success: false, message: "No assistant reply to refine." };
4475
+ const oldText = editableInfo(last).text;
4476
+ const prompt = `You previously wrote this reply:
4477
+
4478
+ <previous_reply>
4479
+ ${oldText}
4480
+ </previous_reply>
4481
+
4482
+ Revise it according to this instruction:
4483
+ <instruction>
4484
+ ${instruction}
4485
+ </instruction>
4486
+
4487
+ Output ONLY the full revised reply text \u2014 no preamble, no commentary, no surrounding quotes or code fences.`;
4488
+ const revised = await forkClaudePrint(prompt, claudeSessionId, metadata.path || process.cwd());
4489
+ if (!revised.success || !revised.text?.trim()) {
4490
+ return { success: false, message: revised.error || "Failed to generate a revised reply." };
4491
+ }
4492
+ return performEdit(last, revised.text.trim());
4493
+ },
4494
+ undoLastEdit: async (context) => {
4495
+ authorizeRequest(context, metadata.sharing, "admin");
4496
+ if (!callbacks.onUndoEdit) return { success: false, message: "Undo is not supported for this session." };
4497
+ const restoreStoreA = () => {
4498
+ if (!options?.messagesDir) return;
4499
+ try {
4500
+ const undoBackup = join(options.messagesDir, "undo", "messages.jsonl");
4501
+ const target = join(options.messagesDir, "messages.jsonl");
4502
+ if (existsSync(undoBackup)) writeFileSync(target, readFileSync(undoBackup));
4503
+ const reloaded = loadMessages(options.messagesDir, sessionId);
4504
+ messages.length = 0;
4505
+ messages.push(...reloaded);
4506
+ nextSeq = messages.length ? messages[messages.length - 1].seq + 1 : 1;
4507
+ notifyListeners({ type: "messages-edited", sessionId, latestSeq: nextSeq - 1 });
4508
+ } catch (err) {
4509
+ console.error(`[HYPHA SESSION ${sessionId}] Undo Store A restore failed: ${err?.message ?? err}`);
4510
+ }
4511
+ };
4512
+ return callbacks.onUndoEdit({ restoreStoreA });
4359
4513
  }
4360
4514
  };
4361
4515
  const store = {
@@ -4447,6 +4601,298 @@ function shortId(length = 10) {
4447
4601
  return out;
4448
4602
  }
4449
4603
 
4604
+ const ADJECTIVES = [
4605
+ "able",
4606
+ "amber",
4607
+ "amused",
4608
+ "ancient",
4609
+ "arctic",
4610
+ "autumn",
4611
+ "azure",
4612
+ "blithe",
4613
+ "bold",
4614
+ "brave",
4615
+ "breezy",
4616
+ "bright",
4617
+ "brisk",
4618
+ "calm",
4619
+ "candid",
4620
+ "cheery",
4621
+ "chill",
4622
+ "clever",
4623
+ "cobalt",
4624
+ "cosmic",
4625
+ "cozy",
4626
+ "crimson",
4627
+ "crisp",
4628
+ "curious",
4629
+ "dapper",
4630
+ "daring",
4631
+ "dawn",
4632
+ "deft",
4633
+ "dewy",
4634
+ "eager",
4635
+ "early",
4636
+ "easy",
4637
+ "electric",
4638
+ "fancy",
4639
+ "feisty",
4640
+ "fleet",
4641
+ "fond",
4642
+ "frosty",
4643
+ "gallant",
4644
+ "gentle",
4645
+ "giddy",
4646
+ "glad",
4647
+ "gleaming",
4648
+ "golden",
4649
+ "graceful",
4650
+ "grand",
4651
+ "hardy",
4652
+ "hazel",
4653
+ "hearty",
4654
+ "honest",
4655
+ "humble",
4656
+ "jolly",
4657
+ "jovial",
4658
+ "keen",
4659
+ "kind",
4660
+ "lively",
4661
+ "loyal",
4662
+ "lucky",
4663
+ "lunar",
4664
+ "mellow",
4665
+ "merry",
4666
+ "mighty",
4667
+ "mint",
4668
+ "misty",
4669
+ "nimble",
4670
+ "noble",
4671
+ "opal",
4672
+ "patient",
4673
+ "peppy",
4674
+ "placid",
4675
+ "plucky",
4676
+ "polar",
4677
+ "prim",
4678
+ "proud",
4679
+ "quick",
4680
+ "quiet",
4681
+ "quirky",
4682
+ "radiant",
4683
+ "rapid",
4684
+ "ready",
4685
+ "regal",
4686
+ "rosy",
4687
+ "royal",
4688
+ "rugged",
4689
+ "sage",
4690
+ "sandy",
4691
+ "scarlet",
4692
+ "serene",
4693
+ "sharp",
4694
+ "shiny",
4695
+ "silent",
4696
+ "silver",
4697
+ "sleek",
4698
+ "smooth",
4699
+ "snappy",
4700
+ "snowy",
4701
+ "solar",
4702
+ "spry",
4703
+ "stellar",
4704
+ "sturdy",
4705
+ "sunny",
4706
+ "swift",
4707
+ "tender",
4708
+ "tidal",
4709
+ "tidy",
4710
+ "tranquil",
4711
+ "trusty",
4712
+ "upbeat",
4713
+ "valiant",
4714
+ "vivid",
4715
+ "warm",
4716
+ "whimsical",
4717
+ "wise",
4718
+ "witty",
4719
+ "zesty",
4720
+ "zippy"
4721
+ ];
4722
+ const ANIMALS = [
4723
+ "ant",
4724
+ "badger",
4725
+ "bat",
4726
+ "bear",
4727
+ "beaver",
4728
+ "bee",
4729
+ "bison",
4730
+ "boar",
4731
+ "bobcat",
4732
+ "buffalo",
4733
+ "camel",
4734
+ "caribou",
4735
+ "cat",
4736
+ "cheetah",
4737
+ "cobra",
4738
+ "condor",
4739
+ "cougar",
4740
+ "coyote",
4741
+ "crab",
4742
+ "crane",
4743
+ "cricket",
4744
+ "crow",
4745
+ "deer",
4746
+ "dingo",
4747
+ "dolphin",
4748
+ "donkey",
4749
+ "dove",
4750
+ "dragon",
4751
+ "duck",
4752
+ "eagle",
4753
+ "eel",
4754
+ "egret",
4755
+ "elk",
4756
+ "falcon",
4757
+ "ferret",
4758
+ "finch",
4759
+ "fox",
4760
+ "frog",
4761
+ "gecko",
4762
+ "gibbon",
4763
+ "goat",
4764
+ "goose",
4765
+ "gopher",
4766
+ "hare",
4767
+ "hawk",
4768
+ "hedgehog",
4769
+ "heron",
4770
+ "hippo",
4771
+ "horse",
4772
+ "ibex",
4773
+ "ibis",
4774
+ "iguana",
4775
+ "jackal",
4776
+ "jaguar",
4777
+ "jay",
4778
+ "kestrel",
4779
+ "koala",
4780
+ "krill",
4781
+ "lark",
4782
+ "lemur",
4783
+ "leopard",
4784
+ "lion",
4785
+ "llama",
4786
+ "lynx",
4787
+ "macaw",
4788
+ "magpie",
4789
+ "mantis",
4790
+ "marmot",
4791
+ "marten",
4792
+ "meerkat",
4793
+ "mink",
4794
+ "mole",
4795
+ "moose",
4796
+ "moth",
4797
+ "mouse",
4798
+ "newt",
4799
+ "ocelot",
4800
+ "octopus",
4801
+ "orca",
4802
+ "osprey",
4803
+ "otter",
4804
+ "owl",
4805
+ "ox",
4806
+ "panda",
4807
+ "panther",
4808
+ "parrot",
4809
+ "pelican",
4810
+ "penguin",
4811
+ "pheasant",
4812
+ "pigeon",
4813
+ "puffin",
4814
+ "puma",
4815
+ "quail",
4816
+ "rabbit",
4817
+ "raccoon",
4818
+ "ram",
4819
+ "raven",
4820
+ "robin",
4821
+ "salmon",
4822
+ "seal",
4823
+ "shark",
4824
+ "sheep",
4825
+ "shrew",
4826
+ "skunk",
4827
+ "sloth",
4828
+ "snail",
4829
+ "sparrow",
4830
+ "spider",
4831
+ "squid",
4832
+ "stag",
4833
+ "stoat",
4834
+ "stork",
4835
+ "swan",
4836
+ "tapir",
4837
+ "tiger",
4838
+ "toad",
4839
+ "trout",
4840
+ "turtle",
4841
+ "viper",
4842
+ "vole",
4843
+ "walrus",
4844
+ "weasel",
4845
+ "whale",
4846
+ "wolf",
4847
+ "wombat",
4848
+ "wren",
4849
+ "yak",
4850
+ "zebra"
4851
+ ];
4852
+ function pick(arr) {
4853
+ const n = arr.length;
4854
+ const max = Math.floor(256 / n) * n;
4855
+ let b;
4856
+ do {
4857
+ b = randomBytes(1)[0];
4858
+ } while (b >= max);
4859
+ return arr[b % n];
4860
+ }
4861
+ function generateFriendlyName(taken = []) {
4862
+ const used = taken instanceof Set ? taken : new Set(taken);
4863
+ for (let i = 0; i < 1e3; i++) {
4864
+ const name = `${pick(ADJECTIVES)}-${pick(ANIMALS)}`;
4865
+ if (!used.has(name)) return name;
4866
+ }
4867
+ const base = `${pick(ADJECTIVES)}-${pick(ANIMALS)}`;
4868
+ let n = 2;
4869
+ while (used.has(`${base}-${n}`)) n++;
4870
+ return `${base}-${n}`;
4871
+ }
4872
+ function sanitizeSegment(s) {
4873
+ return String(s || "").toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
4874
+ }
4875
+ function formatHandle(projectName, friendlyName) {
4876
+ if (!friendlyName) return void 0;
4877
+ const proj = sanitizeSegment(projectName || "");
4878
+ return proj ? `${proj}:${friendlyName}` : friendlyName;
4879
+ }
4880
+ function parseHandle(input) {
4881
+ const str = String(input || "").trim().replace(/^@/, "");
4882
+ if (!str) return null;
4883
+ const i = str.indexOf(":");
4884
+ if (i === -1) return { name: str.toLowerCase() };
4885
+ return { project: sanitizeSegment(str.slice(0, i)), name: str.slice(i + 1).toLowerCase() };
4886
+ }
4887
+ function handleMatchesMetadata(parsed, metadata) {
4888
+ const fn = String(metadata?.friendlyName || "").toLowerCase();
4889
+ if (!fn || fn !== parsed.name) return false;
4890
+ if (parsed.project) {
4891
+ return sanitizeSegment(metadata?.projectName || "") === parsed.project;
4892
+ }
4893
+ return true;
4894
+ }
4895
+
4450
4896
  const SVAMP_HOME$2 = process.env.SVAMP_HOME || join(os.homedir(), ".svamp");
4451
4897
  const num = (key, def) => {
4452
4898
  const v = Number(process.env[key]);
@@ -7189,6 +7635,171 @@ var GeminiTransport$1 = /*#__PURE__*/Object.freeze({
7189
7635
  GeminiTransport: GeminiTransport
7190
7636
  });
7191
7637
 
7638
+ function resolveTranscriptPath(cwd, claudeSessionId) {
7639
+ let real;
7640
+ try {
7641
+ real = realpathSync(cwd);
7642
+ } catch {
7643
+ real = resolve(cwd);
7644
+ }
7645
+ const projectId = real.replace(/[^a-zA-Z0-9-]/g, "-");
7646
+ const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR || join$1(os$1.homedir(), ".claude");
7647
+ return join$1(claudeConfigDir, "projects", projectId, `${claudeSessionId}.jsonl`);
7648
+ }
7649
+ function normalizeText(t) {
7650
+ return (t || "").replace(/\s+/g, " ").trim();
7651
+ }
7652
+ function parseTranscript(file) {
7653
+ const content = readFileSync$1(file, "utf-8");
7654
+ return content.split("\n").filter((l) => l.trim().length > 0).map((raw) => {
7655
+ let obj = null;
7656
+ try {
7657
+ obj = JSON.parse(raw);
7658
+ } catch {
7659
+ }
7660
+ return { raw, obj };
7661
+ });
7662
+ }
7663
+ function assistantText(obj) {
7664
+ const content = obj?.message?.content;
7665
+ if (typeof content === "string") return content;
7666
+ if (!Array.isArray(content)) return "";
7667
+ return content.filter((b) => b && b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n");
7668
+ }
7669
+ function isRealUserRecord(obj) {
7670
+ return !!obj && obj.type === "user" && !obj.isSidechain && typeof obj?.message?.content === "string";
7671
+ }
7672
+ function isAgentTextRecord(obj) {
7673
+ if (!obj || obj.type !== "assistant" || obj.isSidechain) return false;
7674
+ const content = obj?.message?.content;
7675
+ if (!Array.isArray(content)) return false;
7676
+ return content.some((b) => b && b.type === "text" && typeof b.text === "string");
7677
+ }
7678
+ function isRoleTextRecord(obj, role) {
7679
+ return role === "user" ? isRealUserRecord(obj) : isAgentTextRecord(obj);
7680
+ }
7681
+ function recordText(obj, role) {
7682
+ return role === "user" ? String(obj?.message?.content ?? "") : assistantText(obj);
7683
+ }
7684
+ function findAnchorIndex(lines, anchor) {
7685
+ const roleIdx = [];
7686
+ for (let i = 0; i < lines.length; i++) {
7687
+ if (isRoleTextRecord(lines[i].obj, anchor.role)) roleIdx.push(i);
7688
+ }
7689
+ if (roleIdx.length === 0) {
7690
+ return { ok: false, reason: `no ${anchor.role} text records in transcript` };
7691
+ }
7692
+ const pos = roleIdx.length - 1 - anchor.fromEnd;
7693
+ if (pos < 0 || pos >= roleIdx.length) {
7694
+ return { ok: false, reason: `anchor position out of range (fromEnd=${anchor.fromEnd}, have ${roleIdx.length})` };
7695
+ }
7696
+ const index = roleIdx[pos];
7697
+ const got = normalizeText(recordText(lines[index].obj, anchor.role));
7698
+ const want = normalizeText(anchor.oldText);
7699
+ if (got !== want) {
7700
+ if (!(want.length > 0 && got.startsWith(want))) {
7701
+ return { ok: false, reason: "anchor text mismatch \u2014 stores out of sync, refusing to edit" };
7702
+ }
7703
+ }
7704
+ return { ok: true, index };
7705
+ }
7706
+ function orphanCheckObjs(objs) {
7707
+ const toolUseIds = /* @__PURE__ */ new Set();
7708
+ const toolResultIds = /* @__PURE__ */ new Set();
7709
+ for (const obj of objs) {
7710
+ const content = obj?.message?.content;
7711
+ if (!Array.isArray(content)) continue;
7712
+ for (const b of content) {
7713
+ if (b?.type === "tool_use" && b.id) toolUseIds.add(b.id);
7714
+ if (b?.type === "tool_result" && b.tool_use_id) toolResultIds.add(b.tool_use_id);
7715
+ }
7716
+ }
7717
+ for (const id of toolUseIds) {
7718
+ if (!toolResultIds.has(id)) return true;
7719
+ }
7720
+ return false;
7721
+ }
7722
+ function applyTranscriptEdit(file, index, newText) {
7723
+ if (!existsSync$1(file)) return { ok: false, reason: `transcript not found: ${file}` };
7724
+ const lines = parseTranscript(file);
7725
+ if (index < 0 || index >= lines.length) return { ok: false, reason: "index out of range" };
7726
+ const target = lines[index].obj;
7727
+ if (!target) return { ok: false, reason: "target line not parseable" };
7728
+ if (target.type === "assistant") {
7729
+ const content = target?.message?.content;
7730
+ if (!Array.isArray(content)) return { ok: false, reason: "assistant content is not a block array" };
7731
+ const textBlocks = content.filter((b) => b && b.type === "text" && typeof b.text === "string");
7732
+ if (textBlocks.length !== 1) {
7733
+ return { ok: false, reason: `expected exactly 1 text block, found ${textBlocks.length}` };
7734
+ }
7735
+ textBlocks[0].text = newText;
7736
+ } else if (target.type === "user") {
7737
+ if (typeof target?.message?.content !== "string") {
7738
+ return { ok: false, reason: "user content is not a string" };
7739
+ }
7740
+ target.message.content = newText;
7741
+ } else {
7742
+ return { ok: false, reason: `unsupported record type: ${target.type}` };
7743
+ }
7744
+ const keptObjs = [];
7745
+ const kept = [];
7746
+ for (let i = 0; i <= index; i++) {
7747
+ kept.push(i === index ? JSON.stringify(target) : lines[i].raw);
7748
+ keptObjs.push(lines[i].obj);
7749
+ }
7750
+ if (orphanCheckObjs(keptObjs)) {
7751
+ return { ok: false, reason: "edit would orphan a tool_use (truncation drops its tool_result)" };
7752
+ }
7753
+ const out = kept.join("\n") + "\n";
7754
+ const tmp = `${file}.tmp-${process.pid}`;
7755
+ try {
7756
+ writeFileSync$1(tmp, out);
7757
+ renameSync$1(tmp, file);
7758
+ } catch (err) {
7759
+ return { ok: false, reason: `write failed: ${err?.message ?? err}` };
7760
+ }
7761
+ return { ok: true, truncatedAfter: index, totalBefore: lines.length };
7762
+ }
7763
+ function saveUndoSnapshot(undoDir, transcriptFile, messagesFile, meta) {
7764
+ mkdirSync$1(undoDir, { recursive: true });
7765
+ if (existsSync$1(transcriptFile)) copyFileSync(transcriptFile, join$1(undoDir, "transcript.jsonl"));
7766
+ if (existsSync$1(messagesFile)) copyFileSync(messagesFile, join$1(undoDir, "messages.jsonl"));
7767
+ writeFileSync$1(join$1(undoDir, "meta.json"), JSON.stringify(meta));
7768
+ }
7769
+ function readUndoMeta(undoDir) {
7770
+ const p = join$1(undoDir, "meta.json");
7771
+ if (!existsSync$1(p)) return null;
7772
+ try {
7773
+ return JSON.parse(readFileSync$1(p, "utf-8"));
7774
+ } catch {
7775
+ return null;
7776
+ }
7777
+ }
7778
+ function restoreTranscriptFromUndo(undoDir, targetFile) {
7779
+ const backup = join$1(undoDir, "transcript.jsonl");
7780
+ if (!existsSync$1(backup)) return false;
7781
+ const tmp = `${targetFile}.tmp-${process.pid}`;
7782
+ writeFileSync$1(tmp, readFileSync$1(backup));
7783
+ renameSync$1(tmp, targetFile);
7784
+ return true;
7785
+ }
7786
+ function clearUndoSnapshot(undoDir) {
7787
+ try {
7788
+ rmSync$1(undoDir, { recursive: true, force: true });
7789
+ } catch {
7790
+ }
7791
+ }
7792
+ function transcriptSessionIdMatches(file, expectedSessionId) {
7793
+ if (!existsSync$1(file)) return false;
7794
+ const lines = parseTranscript(file);
7795
+ for (const { obj } of lines) {
7796
+ if (obj && typeof obj.sessionId === "string") {
7797
+ return obj.sessionId === expectedSessionId;
7798
+ }
7799
+ }
7800
+ return false;
7801
+ }
7802
+
7192
7803
  const execFileAsync = promisify$1(execFile$1);
7193
7804
  const SVAMP_TOOLS_DIR = join(homedir(), ".svamp", "tools");
7194
7805
  const SVAMP_BIN_DIR = join(SVAMP_TOOLS_DIR, "bin");
@@ -9079,6 +9690,111 @@ class ProcessSupervisor {
9079
9690
  }
9080
9691
  }
9081
9692
 
9693
+ const STATUS_TO_MARKER = {
9694
+ todo: " ",
9695
+ active: "~",
9696
+ verifying: "*",
9697
+ awaiting_review: "?",
9698
+ rework: "r",
9699
+ blocked: "!",
9700
+ done: "x"
9701
+ };
9702
+ function renderCriteria(items) {
9703
+ if (!items.length) return "";
9704
+ const lines = items.map((it) => {
9705
+ const tag = it.disposition === "delegated" ? " (delegated)" : "";
9706
+ return `- [${STATUS_TO_MARKER[it.status] ?? " "}] ${it.text}${tag}`;
9707
+ });
9708
+ const { done, total } = summarize(items);
9709
+ return `Success criteria \u2014 drive this checklist to all-done (${done}/${total}):
9710
+ ${lines.join("\n")}`;
9711
+ }
9712
+ function compileChecklist(items) {
9713
+ const criteria = renderCriteria(items);
9714
+ const oracleItem = items.find((i) => i.disposition === "inline" && i.eval?.type === "oracle");
9715
+ const oracle = oracleItem && oracleItem.eval?.type === "oracle" ? oracleItem.eval.cmd : void 0;
9716
+ return { criteria, ...oracle ? { oracle } : {} };
9717
+ }
9718
+ function routeVerdictToChecklist(items, v) {
9719
+ let matched = false;
9720
+ const next = items.map((it) => {
9721
+ if (it.child?.sessionId && it.child.sessionId === v.sessionId) {
9722
+ matched = true;
9723
+ return applyVerdict(it, v);
9724
+ }
9725
+ return it;
9726
+ });
9727
+ return { items: next, matched };
9728
+ }
9729
+ function applyVerdict(item, v) {
9730
+ const lastVerdict = { verdict: v.verdict, ...v.guidance ? { guidance: v.guidance } : {}, round: v.round, ts: v.ts };
9731
+ if (v.verdict === "approved") return { ...item, status: "done", doneAt: Date.parse(v.ts) || item.createdAt, lastVerdict };
9732
+ return { ...item, status: "rework", lastVerdict };
9733
+ }
9734
+ const VALID_STATUS = /* @__PURE__ */ new Set(["todo", "active", "verifying", "awaiting_review", "rework", "blocked", "done"]);
9735
+ function validateChecklist(items) {
9736
+ const errs = [];
9737
+ const ids = /* @__PURE__ */ new Set();
9738
+ for (const it of items) {
9739
+ if (!it.id) errs.push("item missing id");
9740
+ else if (ids.has(it.id)) errs.push(`duplicate item id "${it.id}"`);
9741
+ else ids.add(it.id);
9742
+ if (!it.text || !it.text.trim()) errs.push(`item "${it.id}" has empty text`);
9743
+ if (it.disposition !== "inline" && it.disposition !== "delegated") errs.push(`item "${it.id}" bad disposition`);
9744
+ if (!VALID_STATUS.has(it.status)) errs.push(`item "${it.id}" bad status "${it.status}"`);
9745
+ if (it.eval?.type === "oracle" && !it.eval.cmd.trim()) errs.push(`item "${it.id}" oracle eval needs a cmd`);
9746
+ }
9747
+ return errs;
9748
+ }
9749
+ function summarize(items) {
9750
+ const by = (s) => items.filter((i) => i.status === s).length;
9751
+ const done = by("done");
9752
+ return {
9753
+ total: items.length,
9754
+ done,
9755
+ todo: by("todo"),
9756
+ active: by("active"),
9757
+ blocked: by("blocked"),
9758
+ awaiting_review: by("awaiting_review"),
9759
+ delegated: items.filter((i) => i.disposition === "delegated").length,
9760
+ allDone: items.length > 0 && done === items.length
9761
+ };
9762
+ }
9763
+ function checklistPath(projectRoot, sessionId) {
9764
+ return join(projectRoot, ".svamp", sessionId, "loop", "checklist.json");
9765
+ }
9766
+
9767
+ function readChecklist(projectRoot, sessionId) {
9768
+ const p = checklistPath(projectRoot, sessionId);
9769
+ if (!existsSync(p)) return [];
9770
+ try {
9771
+ const parsed = JSON.parse(readFileSync(p, "utf8"));
9772
+ return Array.isArray(parsed?.items) ? parsed.items : [];
9773
+ } catch {
9774
+ return [];
9775
+ }
9776
+ }
9777
+ function writeChecklist(projectRoot, sessionId, items) {
9778
+ const p = checklistPath(projectRoot, sessionId);
9779
+ mkdirSync(dirname(p), { recursive: true });
9780
+ const file = { version: 1, items, updatedAt: Date.now() };
9781
+ const tmp = p + ".tmp";
9782
+ writeFileSync(tmp, JSON.stringify(file, null, 2));
9783
+ renameSync(tmp, p);
9784
+ }
9785
+ function clearChecklist(projectRoot, sessionId) {
9786
+ const p = checklistPath(projectRoot, sessionId);
9787
+ try {
9788
+ if (existsSync(p)) rmSync(p);
9789
+ } catch {
9790
+ }
9791
+ }
9792
+ function mutateChecklist(projectRoot, sessionId, fn) {
9793
+ const next = fn(readChecklist(projectRoot, sessionId));
9794
+ writeChecklist(projectRoot, sessionId, next);
9795
+ return next;
9796
+ }
9797
+
9082
9798
  const OAUTH_TOKEN_ENDPOINT = "https://platform.claude.com/v1/oauth/token";
9083
9799
  const OAUTH_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
9084
9800
  const OAUTH_SCOPES = "user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload";
@@ -9162,10 +9878,10 @@ async function checkAndRefreshOAuthToken(force = false, logger) {
9162
9878
  }
9163
9879
  }
9164
9880
 
9165
- function buildBaselineSystemPrompt(sessionId) {
9881
+ function buildBaselineSystemPrompt(sessionId, handle) {
9166
9882
  return `# Svamp Session
9167
9883
 
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.
9884
+ 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
9885
 
9170
9886
  **Session state:**
9171
9887
  - \`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 +9894,7 @@ You are running inside a Svamp session (id: ${sessionId}) on Hypha Cloud. Use th
9178
9894
 
9179
9895
  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
9896
 
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.
9897
+ **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
9898
  `;
9183
9899
  }
9184
9900
 
@@ -10097,6 +10813,8 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
10097
10813
  messageQueue: [...existingQueue, {
10098
10814
  id: randomUUID$1(),
10099
10815
  text: kickoff,
10816
+ // Compact chat badge; full task lives in LOOP.md. Show an ellipsis
10817
+ // when truncated so it's clear the preview is shortened, not the task.
10100
10818
  displayText: `\u{1F501} Loop started: ${lp.task.trim().slice(0, 100)}${lp.task.trim().length > 100 ? "\u2026" : ""}`,
10101
10819
  createdAt: Date.now()
10102
10820
  }]
@@ -10169,6 +10887,38 @@ Continue working until they are met, or verify and finish \u2014 an independent
10169
10887
  const { supervisor: _s, ...restPatch } = patch;
10170
10888
  patch = restPatch;
10171
10889
  }
10890
+ if ("checklist" in patch) {
10891
+ const raw = patch.checklist;
10892
+ const items = Array.isArray(raw) ? raw : [];
10893
+ if (items.length) {
10894
+ const errs = validateChecklist(items);
10895
+ if (errs.length) {
10896
+ sessionService.pushMessage({ type: "message", message: `Checklist rejected: ${errs.join("; ")}`, level: "error" }, "event");
10897
+ } else {
10898
+ writeChecklist(directory, sessionId, items);
10899
+ const { criteria, oracle } = compileChecklist(items);
10900
+ const ok = initLoop(directory, { task: criteria, criteria, oracle, maxIterations: 20, evaluator: true, sessionId });
10901
+ const s = summarize(items);
10902
+ if (ok) {
10903
+ const idle = getMetadata().lifecycleState === "idle";
10904
+ if (idle) {
10905
+ const q = getMetadata().messageQueue || [];
10906
+ const nudge = `Your goal checklist was updated (${s.done}/${s.total} done). Work the open items in order; an independent Stop gate re-checks the criteria before you can stop.`;
10907
+ setMetadata((m) => ({ ...m, messageQueue: [...q, { id: randomUUID$1(), text: nudge, displayText: `\u{1F4CB} Checklist updated`, createdAt: Date.now() }] }));
10908
+ onLoopActivated?.();
10909
+ }
10910
+ sessionService.pushMessage({ type: "message", message: `\u{1F4CB} Checklist set \u2014 ${s.total} item${s.total === 1 ? "" : "s"} (${s.delegated} delegated), ${s.done} done.` }, "event");
10911
+ logger.log(`[svampConfig] Checklist set (${s.total} items, ${s.delegated} delegated)`);
10912
+ }
10913
+ }
10914
+ } else {
10915
+ clearChecklist(directory, sessionId);
10916
+ deactivateLoop(directory, sessionId);
10917
+ sessionService.pushMessage({ type: "message", message: "Checklist cleared." }, "event");
10918
+ }
10919
+ const { checklist: _c, ...restPatch } = patch;
10920
+ patch = restPatch;
10921
+ }
10172
10922
  if (Object.keys(patch).length > 0) {
10173
10923
  const config = readSvampConfig(configPath);
10174
10924
  for (const [key, value] of Object.entries(patch)) {
@@ -10596,7 +11346,7 @@ async function startDaemon(options) {
10596
11346
  const list = loadExposedTunnels().filter((t) => t.name !== name);
10597
11347
  saveExposedTunnels(list);
10598
11348
  }
10599
- const { ServeManager } = await import('./serveManager-XsXnI804.mjs');
11349
+ const { ServeManager } = await import('./serveManager-BSwiPu1O.mjs');
10600
11350
  const serveManager = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
10601
11351
  ensureAutoInstalledSkills(logger).catch(() => {
10602
11352
  });
@@ -10622,6 +11372,16 @@ async function startDaemon(options) {
10622
11372
  clientId: hyphaClientId || machineId
10623
11373
  });
10624
11374
  logger.log(`Connected to Hypha (workspace: ${server.config.workspace})`);
11375
+ writeDaemonStateFile({
11376
+ pid: process.pid,
11377
+ startTime: (/* @__PURE__ */ new Date()).toISOString(),
11378
+ version: DAEMON_VERSION,
11379
+ hyphaServerUrl,
11380
+ workspace: server.config.workspace,
11381
+ machineId,
11382
+ hyphaClientId: server.config.client_id,
11383
+ supervised: process.env.SVAMP_SUPERVISED === "1"
11384
+ });
10625
11385
  server.on("disconnected", (reason) => {
10626
11386
  logger.log(`Hypha connection permanently lost: ${reason}. Daemon continues running \u2014 restart manually to reconnect.`);
10627
11387
  });
@@ -10647,6 +11407,13 @@ ${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
10647
11407
  hopCount: 1
10648
11408
  })).catch(() => {
10649
11409
  });
11410
+ try {
11411
+ if (parent?.directory) {
11412
+ const res = mutateChecklist(parent.directory, parentId, (items) => routeVerdictToChecklist(items, { sessionId: v.sessionId, verdict: v.verdict, guidance: v.guidance, round: v.round, ts: v.ts }).items);
11413
+ if (res.some((i) => i.child?.sessionId === v.sessionId)) logger.log(`[supervision] reflected '${v.verdict}' onto parent ${parentId.slice(0, 8)} checklist`);
11414
+ }
11415
+ } catch {
11416
+ }
10650
11417
  logger.log(`[supervision] verdict '${v.verdict}' for ${v.sessionId.slice(0, 8)} \u2192 parent ${parentId.slice(0, 8)}`);
10651
11418
  } catch {
10652
11419
  }
@@ -10668,6 +11435,8 @@ ${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
10668
11435
  metadata = {
10669
11436
  flavor: m.flavor,
10670
11437
  name: m.name,
11438
+ friendlyName: m.friendlyName,
11439
+ projectName: m.projectName,
10671
11440
  path: m.path,
10672
11441
  host: m.host,
10673
11442
  lifecycleState: m.lifecycleState,
@@ -10689,6 +11458,24 @@ ${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
10689
11458
  };
10690
11459
  });
10691
11460
  };
11461
+ const collectKnownFriendlyNames = () => {
11462
+ const taken = /* @__PURE__ */ new Set();
11463
+ for (const s of pidToTrackedSession.values()) {
11464
+ try {
11465
+ const fn = s.hyphaService?.getMetadata?.()?.friendlyName;
11466
+ if (fn) taken.add(fn);
11467
+ } catch {
11468
+ }
11469
+ }
11470
+ try {
11471
+ for (const p of loadPersistedSessions()) {
11472
+ const fn = p.metadata?.friendlyName;
11473
+ if (fn) taken.add(fn);
11474
+ }
11475
+ } catch {
11476
+ }
11477
+ return taken;
11478
+ };
10692
11479
  let machineServiceRef = null;
10693
11480
  const spawnSession = async (options2) => {
10694
11481
  logger.log("Spawning session:", JSON.stringify(options2));
@@ -10807,6 +11594,8 @@ ${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
10807
11594
  var parseBashPermission = parseBashPermission2, shouldAutoAllow = shouldAutoAllow2, killAndWaitForExit = killAndWaitForExit2, buildIsolationConfig = buildIsolationConfig2;
10808
11595
  let sessionMetadata = {
10809
11596
  path: directory,
11597
+ // Project namespace for the friendly handle (folder/repo basename).
11598
+ projectName: projectName(directory),
10810
11599
  host: os$1.hostname(),
10811
11600
  version: "0.1.0",
10812
11601
  machineId,
@@ -10833,6 +11622,10 @@ ${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
10833
11622
  const allPersisted = loadPersistedSessions();
10834
11623
  const persisted = allPersisted.find((p) => p.sessionId === sessionId) || (resumeSessionId ? allPersisted.find((p) => p.claudeResumeId === resumeSessionId) : void 0);
10835
11624
  let claudeResumeId = persisted?.claudeResumeId || (resumeSessionId || void 0);
11625
+ sessionMetadata = {
11626
+ ...sessionMetadata,
11627
+ friendlyName: persisted?.metadata?.friendlyName || generateFriendlyName(collectKnownFriendlyNames())
11628
+ };
10836
11629
  let currentPermissionMode = options2.permissionMode || persisted?.permissionMode || "bypassPermissions";
10837
11630
  const sessionCreatedAt = persisted?.createdAt || Date.now();
10838
11631
  let lastSpawnMeta = persisted?.spawnMeta || {};
@@ -11117,7 +11910,7 @@ ${parts.join("\n")}`);
11117
11910
  const permissionMode = toClaudePermissionMode(rawPermissionMode);
11118
11911
  currentPermissionMode = permissionMode;
11119
11912
  const model = effectiveMeta.model || agentConfig.default_model || void 0;
11120
- const appendSystemPrompt = effectiveMeta.appendSystemPrompt || agentConfig.append_system_prompt || buildBaselineSystemPrompt(sessionId);
11913
+ const appendSystemPrompt = effectiveMeta.appendSystemPrompt || agentConfig.append_system_prompt || buildBaselineSystemPrompt(sessionId, formatHandle(sessionMetadata.projectName, sessionMetadata.friendlyName));
11121
11914
  const args = [
11122
11915
  "--output-format",
11123
11916
  "stream-json",
@@ -11179,6 +11972,10 @@ ${parts.join("\n")}`);
11179
11972
  let spawnEnv = { ...process.env, ...extraEnv };
11180
11973
  delete spawnEnv.CLAUDECODE;
11181
11974
  spawnEnv.SVAMP_SESSION_ID = sessionId;
11975
+ {
11976
+ const handle = formatHandle(sessionMetadata.projectName, sessionMetadata.friendlyName);
11977
+ if (handle) spawnEnv.SVAMP_SESSION_HANDLE = handle;
11978
+ }
11182
11979
  delete spawnEnv.SVAMP_SANDBOXED;
11183
11980
  const proxyDesc = applyClaudeProxyEnv(spawnEnv);
11184
11981
  if (proxyDesc) {
@@ -11673,12 +12470,13 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11673
12470
  }
11674
12471
  return child;
11675
12472
  };
11676
- const restartClaudeHandler = async () => {
12473
+ const restartClaudeHandler = async (opts) => {
11677
12474
  logger.log(`[Session ${sessionId}] Restart Claude requested`);
11678
12475
  if (isRestartingClaude || isSwitchingMode) {
11679
12476
  return { success: false, message: "Restart already in progress." };
11680
12477
  }
11681
12478
  isRestartingClaude = true;
12479
+ let beforeRespawnError;
11682
12480
  try {
11683
12481
  if (claudeProcess && claudeProcess.exitCode === null) {
11684
12482
  isKillingClaude = true;
@@ -11690,6 +12488,14 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11690
12488
  if (trackedSession?.stopped) {
11691
12489
  return { success: false, message: "Session was stopped during restart." };
11692
12490
  }
12491
+ if (opts?.beforeRespawn) {
12492
+ try {
12493
+ await opts.beforeRespawn();
12494
+ } catch (hookErr) {
12495
+ beforeRespawnError = hookErr?.message ?? String(hookErr);
12496
+ logger.log(`[Session ${sessionId}] beforeRespawn hook failed: ${beforeRespawnError}`);
12497
+ }
12498
+ }
11693
12499
  if (claudeResumeId) {
11694
12500
  if (!stagedCredentials && shouldIsolateSession()) {
11695
12501
  try {
@@ -11702,6 +12508,9 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11702
12508
  spawnClaude(void 0, { permissionMode: currentPermissionMode });
11703
12509
  sessionService.updateMetadata(sessionMetadata);
11704
12510
  logger.log(`[Session ${sessionId}] Claude respawned with --resume ${claudeResumeId}`);
12511
+ if (beforeRespawnError) {
12512
+ return { success: false, message: `Edit failed (session restored): ${beforeRespawnError}` };
12513
+ }
11705
12514
  return { success: true, message: "Claude process restarted successfully." };
11706
12515
  } else {
11707
12516
  logger.log(`[Session ${sessionId}] No resume ID \u2014 cannot restart`);
@@ -11715,6 +12524,26 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11715
12524
  isRestartingClaude = false;
11716
12525
  }
11717
12526
  };
12527
+ const editHistoryIdleGuard = () => {
12528
+ const lifecycle = sessionMetadata.lifecycleState;
12529
+ if (lifecycle === "running" || lifecycle === "restarting") {
12530
+ return { success: false, message: "Cannot edit while the agent is working \u2014 wait until it is idle." };
12531
+ }
12532
+ if (isRestartingClaude || isSwitchingMode || isKillingClaude) {
12533
+ return { success: false, message: "Cannot edit during a restart/mode switch \u2014 try again in a moment." };
12534
+ }
12535
+ const queueLen = sessionMetadata.messageQueue?.length ?? 0;
12536
+ if (queueLen > 0) {
12537
+ return { success: false, message: "Cannot edit while messages are queued." };
12538
+ }
12539
+ if (isLoopActiveForSession(directory, sessionId)) {
12540
+ return { success: false, message: "Cannot edit history while a loop is active." };
12541
+ }
12542
+ if (!claudeResumeId) {
12543
+ return { success: false, message: "No Claude transcript to edit yet." };
12544
+ }
12545
+ return null;
12546
+ };
11718
12547
  if (shouldIsolateSession()) {
11719
12548
  try {
11720
12549
  stagedCredentials = await stageCredentialsForSharing(sessionId);
@@ -11735,6 +12564,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11735
12564
  logger.log(`[Session ${sessionId}] User message received`);
11736
12565
  userMessagePending = true;
11737
12566
  turnInitiatedByUser = true;
12567
+ clearUndoSnapshot(join$1(getSessionDir(directory, sessionId), "undo"));
11738
12568
  let text;
11739
12569
  let msgMeta = meta;
11740
12570
  try {
@@ -11954,6 +12784,86 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11954
12784
  }
11955
12785
  },
11956
12786
  onRestartClaude: restartClaudeHandler,
12787
+ onEditTranscript: async ({ role, fromEnd, oldText, newText, applyStoreA }) => {
12788
+ const gate = editHistoryIdleGuard();
12789
+ if (gate) return gate;
12790
+ logger.log(`[Session ${sessionId}] Edit history: role=${role} fromEnd=${fromEnd} \u2192 kill + rewrite + resume`);
12791
+ const result = await restartClaudeHandler({
12792
+ beforeRespawn: async () => {
12793
+ const file = resolveTranscriptPath(directory, claudeResumeId);
12794
+ if (!transcriptSessionIdMatches(file, claudeResumeId)) {
12795
+ throw new Error("transcript file id mismatch (session rotated) \u2014 aborting edit");
12796
+ }
12797
+ const lines = parseTranscript(file);
12798
+ const anchor = findAnchorIndex(lines, { role, fromEnd, oldText });
12799
+ if (!anchor.ok) throw new Error(anchor.reason);
12800
+ const sessDir = getSessionDir(directory, sessionId);
12801
+ saveUndoSnapshot(join$1(sessDir, "undo"), file, join$1(sessDir, "messages.jsonl"), {
12802
+ preEditClaudeSessionId: claudeResumeId,
12803
+ createdAt: Date.now(),
12804
+ label: "edit"
12805
+ });
12806
+ const edit = applyTranscriptEdit(file, anchor.index, newText);
12807
+ if (!edit.ok) throw new Error(edit.reason);
12808
+ applyStoreA();
12809
+ logger.log(`[Session ${sessionId}] Edit applied \u2014 transcript truncated after line ${anchor.index}`);
12810
+ }
12811
+ });
12812
+ if (result.success && claudeResumeId && !trackedSession.stopped) {
12813
+ saveSession({
12814
+ sessionId,
12815
+ directory,
12816
+ claudeResumeId,
12817
+ permissionMode: currentPermissionMode,
12818
+ spawnMeta: lastSpawnMeta,
12819
+ metadata: sessionMetadata,
12820
+ createdAt: sessionCreatedAt,
12821
+ machineId,
12822
+ wasProcessing: false
12823
+ });
12824
+ artifactSync.syncSession(sessionId, getSessionDir(directory, sessionId), sessionMetadata, machineId).catch(() => {
12825
+ });
12826
+ }
12827
+ return result;
12828
+ },
12829
+ onUndoEdit: async ({ restoreStoreA }) => {
12830
+ const gate = editHistoryIdleGuard();
12831
+ if (gate) return gate;
12832
+ const undoDir = join$1(getSessionDir(directory, sessionId), "undo");
12833
+ const meta = readUndoMeta(undoDir);
12834
+ if (!meta) return { success: false, message: "Nothing to undo." };
12835
+ logger.log(`[Session ${sessionId}] Undo edit \u2192 restore pre-edit transcript ${meta.preEditClaudeSessionId} + messages, resume`);
12836
+ const result = await restartClaudeHandler({
12837
+ beforeRespawn: async () => {
12838
+ const target = resolveTranscriptPath(directory, meta.preEditClaudeSessionId);
12839
+ if (!restoreTranscriptFromUndo(undoDir, target)) {
12840
+ throw new Error("undo snapshot missing transcript backup");
12841
+ }
12842
+ restoreStoreA();
12843
+ claudeResumeId = meta.preEditClaudeSessionId;
12844
+ sessionMetadata = { ...sessionMetadata, claudeSessionId: claudeResumeId };
12845
+ }
12846
+ });
12847
+ if (result.success) {
12848
+ clearUndoSnapshot(undoDir);
12849
+ if (claudeResumeId && !trackedSession.stopped) {
12850
+ saveSession({
12851
+ sessionId,
12852
+ directory,
12853
+ claudeResumeId,
12854
+ permissionMode: currentPermissionMode,
12855
+ spawnMeta: lastSpawnMeta,
12856
+ metadata: sessionMetadata,
12857
+ createdAt: sessionCreatedAt,
12858
+ machineId,
12859
+ wasProcessing: false
12860
+ });
12861
+ artifactSync.syncSession(sessionId, getSessionDir(directory, sessionId), sessionMetadata, machineId).catch(() => {
12862
+ });
12863
+ }
12864
+ }
12865
+ return result;
12866
+ },
11957
12867
  onUpdateSecurityContext: async (newSecurityContext) => {
11958
12868
  logger.log(`[Session ${sessionId}] Security context update requested \u2014 restarting agent`);
11959
12869
  sessionMetadata = { ...sessionMetadata, securityContext: newSecurityContext };
@@ -12333,8 +13243,12 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
12333
13243
  return false;
12334
13244
  };
12335
13245
  var parseBashPermission = parseBashPermission2, shouldAutoAllow = shouldAutoAllow2;
13246
+ const acpPersistedName = loadPersistedSessions().find((p) => p.sessionId === sessionId)?.metadata?.friendlyName;
12336
13247
  let sessionMetadata = {
12337
13248
  path: directory,
13249
+ // Project namespace for the friendly handle (folder/repo basename).
13250
+ projectName: projectName(directory),
13251
+ friendlyName: acpPersistedName || generateFriendlyName(collectKnownFriendlyNames()),
12338
13252
  host: os$1.hostname(),
12339
13253
  version: "0.1.0",
12340
13254
  machineId,
@@ -13108,7 +14022,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
13108
14022
  const specs = loadExposedTunnels();
13109
14023
  if (specs.length === 0) return;
13110
14024
  logger.log(`[exposed-tunnels] Restoring ${specs.length} tunnel(s) from ${EXPOSED_TUNNELS_FILE}`);
13111
- const { FrpcTunnel } = await import('./frpc-CdcXdQde.mjs');
14025
+ const { FrpcTunnel } = await import('./frpc-DRWpAeZW.mjs');
13112
14026
  for (const spec of specs) {
13113
14027
  if (tunnels.has(spec.name)) continue;
13114
14028
  try {
@@ -13884,4 +14798,4 @@ var run = /*#__PURE__*/Object.freeze({
13884
14798
  writeStopMarker: writeStopMarker
13885
14799
  });
13886
14800
 
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 };
14801
+ 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 };