svamp-cli 0.2.114 → 0.2.115

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-BjWZ129L.mjs');
2679
+ const { FrpcTunnel } = await import('./frpc-1aSnPVrE.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-BCKpph51.mjs');
2940
+ const { toolsForRole } = await import('./sideband-vimBRRDF.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-C29ciVUe.mjs');
3039
+ const { queryCore } = await import('./commands-B3NhziMR.mjs');
3040
3040
  const timeout = c.reply?.timeout_sec || 120;
3041
3041
  let result;
3042
3042
  try {
@@ -3479,6 +3479,48 @@ function appendMessage(messagesDir, sessionId, msg) {
3479
3479
  console.error(`[HYPHA SESSION ${sessionId}] Failed to persist message: ${err?.message ?? err}`);
3480
3480
  }
3481
3481
  }
3482
+ function editableInfo(msg) {
3483
+ const c = msg?.content;
3484
+ if (!c) return null;
3485
+ if (c.role === "user" && c.content?.type === "text") {
3486
+ return { role: "user", text: String(c.content.text ?? "") };
3487
+ }
3488
+ if (c.role === "agent" && c.content?.type === "output") {
3489
+ const data = c.content.data;
3490
+ if (data?.type === "assistant" && Array.isArray(data?.message?.content)) {
3491
+ const textBlocks = data.message.content.filter((b) => b && b.type === "text" && typeof b.text === "string");
3492
+ if (textBlocks.length >= 1) {
3493
+ return { role: "assistant", text: textBlocks.map((b) => b.text).join("\n") };
3494
+ }
3495
+ }
3496
+ if (data?.type === "user" && typeof data?.message?.content === "string") {
3497
+ return { role: "user", text: data.message.content };
3498
+ }
3499
+ }
3500
+ return null;
3501
+ }
3502
+ function patchStoredText(msg, newText) {
3503
+ const c = msg?.content;
3504
+ if (c?.role === "user" && c.content?.type === "text") {
3505
+ c.content.text = newText;
3506
+ return true;
3507
+ }
3508
+ if (c?.role === "agent" && c.content?.type === "output") {
3509
+ const data = c.content.data;
3510
+ if (data?.type === "user" && typeof data?.message?.content === "string") {
3511
+ data.message.content = newText;
3512
+ return true;
3513
+ }
3514
+ if (data?.type === "assistant" && Array.isArray(data?.message?.content)) {
3515
+ const textBlocks = data.message.content.filter((b) => b && b.type === "text" && typeof b.text === "string");
3516
+ if (textBlocks.length === 1) {
3517
+ textBlocks[0].text = newText;
3518
+ return true;
3519
+ }
3520
+ }
3521
+ }
3522
+ return false;
3523
+ }
3482
3524
  function createSessionStore(server, sessionId, initialMetadata, initialAgentState, callbacks, options) {
3483
3525
  const messages = options?.messagesDir ? loadMessages(options.messagesDir) : [];
3484
3526
  let nextSeq = messages.length > 0 ? messages[messages.length - 1].seq + 1 : 1;
@@ -3624,6 +3666,100 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
3624
3666
  notifyListeners({ type: "update-session", sessionId, metadata: { value: metadata, version: metadataVersion } });
3625
3667
  callbacks.onMetadataUpdate?.(metadata);
3626
3668
  };
3669
+ const forkClaudePrint = async (prompt, claudeSessionId, cwd, maxTurns = 6) => {
3670
+ const { spawn } = await import('child_process');
3671
+ return new Promise((resolve) => {
3672
+ const child = spawn("claude", [
3673
+ "--print",
3674
+ prompt,
3675
+ "--resume",
3676
+ claudeSessionId,
3677
+ "--fork-session",
3678
+ "--no-session-persistence",
3679
+ "--permission-mode",
3680
+ "bypassPermissions",
3681
+ "--output-format",
3682
+ "json",
3683
+ "--max-turns",
3684
+ String(maxTurns)
3685
+ ], {
3686
+ cwd,
3687
+ timeout: 6e4,
3688
+ stdio: ["ignore", "pipe", "pipe"],
3689
+ env: { ...process.env, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1" }
3690
+ });
3691
+ let stdout = "";
3692
+ let stderr = "";
3693
+ child.stdout?.on("data", (d) => {
3694
+ stdout += d.toString();
3695
+ });
3696
+ child.stderr?.on("data", (d) => {
3697
+ stderr += d.toString();
3698
+ });
3699
+ child.on("close", (code) => {
3700
+ if (code !== 0 && !stdout) {
3701
+ resolve({ success: false, error: stderr || `claude exited with code ${code}` });
3702
+ return;
3703
+ }
3704
+ try {
3705
+ const result = JSON.parse(stdout);
3706
+ resolve({ success: true, text: result.result || result.text || stdout });
3707
+ } catch {
3708
+ resolve({ success: true, text: stdout.trim() });
3709
+ }
3710
+ });
3711
+ child.on("error", (err) => resolve({ success: false, error: err.message }));
3712
+ });
3713
+ };
3714
+ const performEdit = async (target, newText) => {
3715
+ if (!callbacks.onEditTranscript) return { success: false, message: "Editing history is not supported for this session." };
3716
+ const info = editableInfo(target);
3717
+ if (!info) return { success: false, message: "This message cannot be edited." };
3718
+ const text = typeof newText === "string" ? newText : "";
3719
+ if (!text.trim()) return { success: false, message: "New text is empty." };
3720
+ let fromEnd = 0;
3721
+ for (const m of messages) {
3722
+ if (m.seq <= target.seq) continue;
3723
+ const i2 = editableInfo(m);
3724
+ if (i2 && i2.role === info.role) fromEnd++;
3725
+ }
3726
+ const anchorSeq = target.seq;
3727
+ const applyStoreA = () => {
3728
+ for (let i = messages.length - 1; i >= 0; i--) {
3729
+ if (messages[i].seq > anchorSeq) messages.splice(i, 1);
3730
+ }
3731
+ const tgt = messages.find((m) => m.seq === anchorSeq);
3732
+ if (tgt) {
3733
+ patchStoredText(tgt, text);
3734
+ tgt.updatedAt = Date.now();
3735
+ }
3736
+ nextSeq = anchorSeq + 1;
3737
+ if (options?.messagesDir) {
3738
+ const filePath = join(options.messagesDir, "messages.jsonl");
3739
+ try {
3740
+ const lines = existsSync(filePath) ? readFileSync(filePath, "utf-8").split("\n").filter((l) => l.trim()) : [];
3741
+ const kept = [];
3742
+ for (const line of lines) {
3743
+ let m;
3744
+ try {
3745
+ m = JSON.parse(line);
3746
+ } catch {
3747
+ continue;
3748
+ }
3749
+ if (typeof m.seq === "number" && m.seq > anchorSeq) continue;
3750
+ kept.push(m.seq === anchorSeq && tgt ? JSON.stringify(tgt) : line);
3751
+ }
3752
+ const tmp = `${filePath}.tmp-${process.pid}`;
3753
+ writeFileSync(tmp, kept.length ? kept.join("\n") + "\n" : "");
3754
+ renameSync(tmp, filePath);
3755
+ } catch (err) {
3756
+ console.error(`[HYPHA SESSION ${sessionId}] Store A rewrite failed: ${err?.message ?? err}`);
3757
+ }
3758
+ }
3759
+ notifyListeners({ type: "messages-edited", sessionId, latestSeq: nextSeq - 1 });
3760
+ };
3761
+ return callbacks.onEditTranscript({ role: info.role, fromEnd, oldText: info.text, newText: text, applyStoreA });
3762
+ };
3627
3763
  const rpcHandlers = {
3628
3764
  // ── Messages ──
3629
3765
  getMessages: async (afterSeq, limit, context) => {
@@ -4303,59 +4439,72 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
4303
4439
  if (!claudeSessionId) {
4304
4440
  return { success: false, error: "No active Claude session to query" };
4305
4441
  }
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
- });
4442
+ const r = await forkClaudePrint(question, claudeSessionId, metadata.path || process.cwd());
4443
+ return r.success ? { success: true, answer: r.text } : { success: false, error: r.error };
4444
+ },
4445
+ editMessage: async (messageId, newText, context) => {
4446
+ authorizeRequest(context, metadata.sharing, "admin");
4447
+ if (!messageId) return { success: false, message: "messageId is required." };
4448
+ const target = messages.find((m) => m.id === messageId);
4449
+ if (!target) {
4450
+ return { success: false, message: "Message not found in the active window \u2014 only recent messages can be edited." };
4451
+ }
4452
+ return performEdit(target, newText);
4453
+ },
4454
+ refineLastReply: async (instruction, context) => {
4455
+ authorizeRequest(context, metadata.sharing, "admin");
4456
+ if (!instruction || typeof instruction !== "string") {
4457
+ return { success: false, message: "An instruction is required." };
4458
+ }
4459
+ const claudeSessionId = metadata.claudeSessionId;
4460
+ if (!claudeSessionId) return { success: false, message: "No active Claude session to refine." };
4461
+ let last;
4462
+ for (let i = messages.length - 1; i >= 0; i--) {
4463
+ const info = editableInfo(messages[i]);
4464
+ if (info && info.role === "assistant") {
4465
+ last = messages[i];
4466
+ break;
4467
+ }
4468
+ }
4469
+ if (!last) return { success: false, message: "No assistant reply to refine." };
4470
+ const oldText = editableInfo(last).text;
4471
+ const prompt = `You previously wrote this reply:
4472
+
4473
+ <previous_reply>
4474
+ ${oldText}
4475
+ </previous_reply>
4476
+
4477
+ Revise it according to this instruction:
4478
+ <instruction>
4479
+ ${instruction}
4480
+ </instruction>
4481
+
4482
+ Output ONLY the full revised reply text \u2014 no preamble, no commentary, no surrounding quotes or code fences.`;
4483
+ const revised = await forkClaudePrint(prompt, claudeSessionId, metadata.path || process.cwd());
4484
+ if (!revised.success || !revised.text?.trim()) {
4485
+ return { success: false, message: revised.error || "Failed to generate a revised reply." };
4486
+ }
4487
+ return performEdit(last, revised.text.trim());
4488
+ },
4489
+ undoLastEdit: async (context) => {
4490
+ authorizeRequest(context, metadata.sharing, "admin");
4491
+ if (!callbacks.onUndoEdit) return { success: false, message: "Undo is not supported for this session." };
4492
+ const restoreStoreA = () => {
4493
+ if (!options?.messagesDir) return;
4494
+ try {
4495
+ const undoBackup = join(options.messagesDir, "undo", "messages.jsonl");
4496
+ const target = join(options.messagesDir, "messages.jsonl");
4497
+ if (existsSync(undoBackup)) writeFileSync(target, readFileSync(undoBackup));
4498
+ const reloaded = loadMessages(options.messagesDir, sessionId);
4499
+ messages.length = 0;
4500
+ messages.push(...reloaded);
4501
+ nextSeq = messages.length ? messages[messages.length - 1].seq + 1 : 1;
4502
+ notifyListeners({ type: "messages-edited", sessionId, latestSeq: nextSeq - 1 });
4503
+ } catch (err) {
4504
+ console.error(`[HYPHA SESSION ${sessionId}] Undo Store A restore failed: ${err?.message ?? err}`);
4505
+ }
4506
+ };
4507
+ return callbacks.onUndoEdit({ restoreStoreA });
4359
4508
  }
4360
4509
  };
4361
4510
  const store = {
@@ -7189,6 +7338,171 @@ var GeminiTransport$1 = /*#__PURE__*/Object.freeze({
7189
7338
  GeminiTransport: GeminiTransport
7190
7339
  });
7191
7340
 
7341
+ function resolveTranscriptPath(cwd, claudeSessionId) {
7342
+ let real;
7343
+ try {
7344
+ real = realpathSync(cwd);
7345
+ } catch {
7346
+ real = resolve(cwd);
7347
+ }
7348
+ const projectId = real.replace(/[^a-zA-Z0-9-]/g, "-");
7349
+ const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR || join$1(os$1.homedir(), ".claude");
7350
+ return join$1(claudeConfigDir, "projects", projectId, `${claudeSessionId}.jsonl`);
7351
+ }
7352
+ function normalizeText(t) {
7353
+ return (t || "").replace(/\s+/g, " ").trim();
7354
+ }
7355
+ function parseTranscript(file) {
7356
+ const content = readFileSync$1(file, "utf-8");
7357
+ return content.split("\n").filter((l) => l.trim().length > 0).map((raw) => {
7358
+ let obj = null;
7359
+ try {
7360
+ obj = JSON.parse(raw);
7361
+ } catch {
7362
+ }
7363
+ return { raw, obj };
7364
+ });
7365
+ }
7366
+ function assistantText(obj) {
7367
+ const content = obj?.message?.content;
7368
+ if (typeof content === "string") return content;
7369
+ if (!Array.isArray(content)) return "";
7370
+ return content.filter((b) => b && b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n");
7371
+ }
7372
+ function isRealUserRecord(obj) {
7373
+ return !!obj && obj.type === "user" && !obj.isSidechain && typeof obj?.message?.content === "string";
7374
+ }
7375
+ function isAgentTextRecord(obj) {
7376
+ if (!obj || obj.type !== "assistant" || obj.isSidechain) return false;
7377
+ const content = obj?.message?.content;
7378
+ if (!Array.isArray(content)) return false;
7379
+ return content.some((b) => b && b.type === "text" && typeof b.text === "string");
7380
+ }
7381
+ function isRoleTextRecord(obj, role) {
7382
+ return role === "user" ? isRealUserRecord(obj) : isAgentTextRecord(obj);
7383
+ }
7384
+ function recordText(obj, role) {
7385
+ return role === "user" ? String(obj?.message?.content ?? "") : assistantText(obj);
7386
+ }
7387
+ function findAnchorIndex(lines, anchor) {
7388
+ const roleIdx = [];
7389
+ for (let i = 0; i < lines.length; i++) {
7390
+ if (isRoleTextRecord(lines[i].obj, anchor.role)) roleIdx.push(i);
7391
+ }
7392
+ if (roleIdx.length === 0) {
7393
+ return { ok: false, reason: `no ${anchor.role} text records in transcript` };
7394
+ }
7395
+ const pos = roleIdx.length - 1 - anchor.fromEnd;
7396
+ if (pos < 0 || pos >= roleIdx.length) {
7397
+ return { ok: false, reason: `anchor position out of range (fromEnd=${anchor.fromEnd}, have ${roleIdx.length})` };
7398
+ }
7399
+ const index = roleIdx[pos];
7400
+ const got = normalizeText(recordText(lines[index].obj, anchor.role));
7401
+ const want = normalizeText(anchor.oldText);
7402
+ if (got !== want) {
7403
+ if (!(want.length > 0 && got.startsWith(want))) {
7404
+ return { ok: false, reason: "anchor text mismatch \u2014 stores out of sync, refusing to edit" };
7405
+ }
7406
+ }
7407
+ return { ok: true, index };
7408
+ }
7409
+ function orphanCheckObjs(objs) {
7410
+ const toolUseIds = /* @__PURE__ */ new Set();
7411
+ const toolResultIds = /* @__PURE__ */ new Set();
7412
+ for (const obj of objs) {
7413
+ const content = obj?.message?.content;
7414
+ if (!Array.isArray(content)) continue;
7415
+ for (const b of content) {
7416
+ if (b?.type === "tool_use" && b.id) toolUseIds.add(b.id);
7417
+ if (b?.type === "tool_result" && b.tool_use_id) toolResultIds.add(b.tool_use_id);
7418
+ }
7419
+ }
7420
+ for (const id of toolUseIds) {
7421
+ if (!toolResultIds.has(id)) return true;
7422
+ }
7423
+ return false;
7424
+ }
7425
+ function applyTranscriptEdit(file, index, newText) {
7426
+ if (!existsSync$1(file)) return { ok: false, reason: `transcript not found: ${file}` };
7427
+ const lines = parseTranscript(file);
7428
+ if (index < 0 || index >= lines.length) return { ok: false, reason: "index out of range" };
7429
+ const target = lines[index].obj;
7430
+ if (!target) return { ok: false, reason: "target line not parseable" };
7431
+ if (target.type === "assistant") {
7432
+ const content = target?.message?.content;
7433
+ if (!Array.isArray(content)) return { ok: false, reason: "assistant content is not a block array" };
7434
+ const textBlocks = content.filter((b) => b && b.type === "text" && typeof b.text === "string");
7435
+ if (textBlocks.length !== 1) {
7436
+ return { ok: false, reason: `expected exactly 1 text block, found ${textBlocks.length}` };
7437
+ }
7438
+ textBlocks[0].text = newText;
7439
+ } else if (target.type === "user") {
7440
+ if (typeof target?.message?.content !== "string") {
7441
+ return { ok: false, reason: "user content is not a string" };
7442
+ }
7443
+ target.message.content = newText;
7444
+ } else {
7445
+ return { ok: false, reason: `unsupported record type: ${target.type}` };
7446
+ }
7447
+ const keptObjs = [];
7448
+ const kept = [];
7449
+ for (let i = 0; i <= index; i++) {
7450
+ kept.push(i === index ? JSON.stringify(target) : lines[i].raw);
7451
+ keptObjs.push(lines[i].obj);
7452
+ }
7453
+ if (orphanCheckObjs(keptObjs)) {
7454
+ return { ok: false, reason: "edit would orphan a tool_use (truncation drops its tool_result)" };
7455
+ }
7456
+ const out = kept.join("\n") + "\n";
7457
+ const tmp = `${file}.tmp-${process.pid}`;
7458
+ try {
7459
+ writeFileSync$1(tmp, out);
7460
+ renameSync$1(tmp, file);
7461
+ } catch (err) {
7462
+ return { ok: false, reason: `write failed: ${err?.message ?? err}` };
7463
+ }
7464
+ return { ok: true, truncatedAfter: index, totalBefore: lines.length };
7465
+ }
7466
+ function saveUndoSnapshot(undoDir, transcriptFile, messagesFile, meta) {
7467
+ mkdirSync$1(undoDir, { recursive: true });
7468
+ if (existsSync$1(transcriptFile)) copyFileSync(transcriptFile, join$1(undoDir, "transcript.jsonl"));
7469
+ if (existsSync$1(messagesFile)) copyFileSync(messagesFile, join$1(undoDir, "messages.jsonl"));
7470
+ writeFileSync$1(join$1(undoDir, "meta.json"), JSON.stringify(meta));
7471
+ }
7472
+ function readUndoMeta(undoDir) {
7473
+ const p = join$1(undoDir, "meta.json");
7474
+ if (!existsSync$1(p)) return null;
7475
+ try {
7476
+ return JSON.parse(readFileSync$1(p, "utf-8"));
7477
+ } catch {
7478
+ return null;
7479
+ }
7480
+ }
7481
+ function restoreTranscriptFromUndo(undoDir, targetFile) {
7482
+ const backup = join$1(undoDir, "transcript.jsonl");
7483
+ if (!existsSync$1(backup)) return false;
7484
+ const tmp = `${targetFile}.tmp-${process.pid}`;
7485
+ writeFileSync$1(tmp, readFileSync$1(backup));
7486
+ renameSync$1(tmp, targetFile);
7487
+ return true;
7488
+ }
7489
+ function clearUndoSnapshot(undoDir) {
7490
+ try {
7491
+ rmSync$1(undoDir, { recursive: true, force: true });
7492
+ } catch {
7493
+ }
7494
+ }
7495
+ function transcriptSessionIdMatches(file, expectedSessionId) {
7496
+ if (!existsSync$1(file)) return false;
7497
+ const lines = parseTranscript(file);
7498
+ for (const { obj } of lines) {
7499
+ if (obj && typeof obj.sessionId === "string") {
7500
+ return obj.sessionId === expectedSessionId;
7501
+ }
7502
+ }
7503
+ return false;
7504
+ }
7505
+
7192
7506
  const execFileAsync = promisify$1(execFile$1);
7193
7507
  const SVAMP_TOOLS_DIR = join(homedir(), ".svamp", "tools");
7194
7508
  const SVAMP_BIN_DIR = join(SVAMP_TOOLS_DIR, "bin");
@@ -10491,7 +10805,7 @@ async function startDaemon(options) {
10491
10805
  const list = loadExposedTunnels().filter((t) => t.name !== name);
10492
10806
  saveExposedTunnels(list);
10493
10807
  }
10494
- const { ServeManager } = await import('./serveManager-DrPFeC2o.mjs');
10808
+ const { ServeManager } = await import('./serveManager-BnwAe-VX.mjs');
10495
10809
  const serveManager = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
10496
10810
  ensureAutoInstalledSkills(logger).catch(() => {
10497
10811
  });
@@ -11543,12 +11857,13 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11543
11857
  }
11544
11858
  return child;
11545
11859
  };
11546
- const restartClaudeHandler = async () => {
11860
+ const restartClaudeHandler = async (opts) => {
11547
11861
  logger.log(`[Session ${sessionId}] Restart Claude requested`);
11548
11862
  if (isRestartingClaude || isSwitchingMode) {
11549
11863
  return { success: false, message: "Restart already in progress." };
11550
11864
  }
11551
11865
  isRestartingClaude = true;
11866
+ let beforeRespawnError;
11552
11867
  try {
11553
11868
  if (claudeProcess && claudeProcess.exitCode === null) {
11554
11869
  isKillingClaude = true;
@@ -11560,6 +11875,14 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11560
11875
  if (trackedSession?.stopped) {
11561
11876
  return { success: false, message: "Session was stopped during restart." };
11562
11877
  }
11878
+ if (opts?.beforeRespawn) {
11879
+ try {
11880
+ await opts.beforeRespawn();
11881
+ } catch (hookErr) {
11882
+ beforeRespawnError = hookErr?.message ?? String(hookErr);
11883
+ logger.log(`[Session ${sessionId}] beforeRespawn hook failed: ${beforeRespawnError}`);
11884
+ }
11885
+ }
11563
11886
  if (claudeResumeId) {
11564
11887
  if (!stagedCredentials && shouldIsolateSession()) {
11565
11888
  try {
@@ -11572,6 +11895,9 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11572
11895
  spawnClaude(void 0, { permissionMode: currentPermissionMode });
11573
11896
  sessionService.updateMetadata(sessionMetadata);
11574
11897
  logger.log(`[Session ${sessionId}] Claude respawned with --resume ${claudeResumeId}`);
11898
+ if (beforeRespawnError) {
11899
+ return { success: false, message: `Edit failed (session restored): ${beforeRespawnError}` };
11900
+ }
11575
11901
  return { success: true, message: "Claude process restarted successfully." };
11576
11902
  } else {
11577
11903
  logger.log(`[Session ${sessionId}] No resume ID \u2014 cannot restart`);
@@ -11585,6 +11911,26 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11585
11911
  isRestartingClaude = false;
11586
11912
  }
11587
11913
  };
11914
+ const editHistoryIdleGuard = () => {
11915
+ const lifecycle = sessionMetadata.lifecycleState;
11916
+ if (lifecycle === "running" || lifecycle === "restarting") {
11917
+ return { success: false, message: "Cannot edit while the agent is working \u2014 wait until it is idle." };
11918
+ }
11919
+ if (isRestartingClaude || isSwitchingMode || isKillingClaude) {
11920
+ return { success: false, message: "Cannot edit during a restart/mode switch \u2014 try again in a moment." };
11921
+ }
11922
+ const queueLen = sessionMetadata.messageQueue?.length ?? 0;
11923
+ if (queueLen > 0) {
11924
+ return { success: false, message: "Cannot edit while messages are queued." };
11925
+ }
11926
+ if (isLoopActiveForSession(directory, sessionId)) {
11927
+ return { success: false, message: "Cannot edit history while a loop is active." };
11928
+ }
11929
+ if (!claudeResumeId) {
11930
+ return { success: false, message: "No Claude transcript to edit yet." };
11931
+ }
11932
+ return null;
11933
+ };
11588
11934
  if (shouldIsolateSession()) {
11589
11935
  try {
11590
11936
  stagedCredentials = await stageCredentialsForSharing(sessionId);
@@ -11605,6 +11951,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11605
11951
  logger.log(`[Session ${sessionId}] User message received`);
11606
11952
  userMessagePending = true;
11607
11953
  turnInitiatedByUser = true;
11954
+ clearUndoSnapshot(join$1(getSessionDir(directory, sessionId), "undo"));
11608
11955
  let text;
11609
11956
  let msgMeta = meta;
11610
11957
  try {
@@ -11824,6 +12171,86 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11824
12171
  }
11825
12172
  },
11826
12173
  onRestartClaude: restartClaudeHandler,
12174
+ onEditTranscript: async ({ role, fromEnd, oldText, newText, applyStoreA }) => {
12175
+ const gate = editHistoryIdleGuard();
12176
+ if (gate) return gate;
12177
+ logger.log(`[Session ${sessionId}] Edit history: role=${role} fromEnd=${fromEnd} \u2192 kill + rewrite + resume`);
12178
+ const result = await restartClaudeHandler({
12179
+ beforeRespawn: async () => {
12180
+ const file = resolveTranscriptPath(directory, claudeResumeId);
12181
+ if (!transcriptSessionIdMatches(file, claudeResumeId)) {
12182
+ throw new Error("transcript file id mismatch (session rotated) \u2014 aborting edit");
12183
+ }
12184
+ const lines = parseTranscript(file);
12185
+ const anchor = findAnchorIndex(lines, { role, fromEnd, oldText });
12186
+ if (!anchor.ok) throw new Error(anchor.reason);
12187
+ const sessDir = getSessionDir(directory, sessionId);
12188
+ saveUndoSnapshot(join$1(sessDir, "undo"), file, join$1(sessDir, "messages.jsonl"), {
12189
+ preEditClaudeSessionId: claudeResumeId,
12190
+ createdAt: Date.now(),
12191
+ label: "edit"
12192
+ });
12193
+ const edit = applyTranscriptEdit(file, anchor.index, newText);
12194
+ if (!edit.ok) throw new Error(edit.reason);
12195
+ applyStoreA();
12196
+ logger.log(`[Session ${sessionId}] Edit applied \u2014 transcript truncated after line ${anchor.index}`);
12197
+ }
12198
+ });
12199
+ if (result.success && claudeResumeId && !trackedSession.stopped) {
12200
+ saveSession({
12201
+ sessionId,
12202
+ directory,
12203
+ claudeResumeId,
12204
+ permissionMode: currentPermissionMode,
12205
+ spawnMeta: lastSpawnMeta,
12206
+ metadata: sessionMetadata,
12207
+ createdAt: sessionCreatedAt,
12208
+ machineId,
12209
+ wasProcessing: false
12210
+ });
12211
+ artifactSync.syncSession(sessionId, getSessionDir(directory, sessionId), sessionMetadata, machineId).catch(() => {
12212
+ });
12213
+ }
12214
+ return result;
12215
+ },
12216
+ onUndoEdit: async ({ restoreStoreA }) => {
12217
+ const gate = editHistoryIdleGuard();
12218
+ if (gate) return gate;
12219
+ const undoDir = join$1(getSessionDir(directory, sessionId), "undo");
12220
+ const meta = readUndoMeta(undoDir);
12221
+ if (!meta) return { success: false, message: "Nothing to undo." };
12222
+ logger.log(`[Session ${sessionId}] Undo edit \u2192 restore pre-edit transcript ${meta.preEditClaudeSessionId} + messages, resume`);
12223
+ const result = await restartClaudeHandler({
12224
+ beforeRespawn: async () => {
12225
+ const target = resolveTranscriptPath(directory, meta.preEditClaudeSessionId);
12226
+ if (!restoreTranscriptFromUndo(undoDir, target)) {
12227
+ throw new Error("undo snapshot missing transcript backup");
12228
+ }
12229
+ restoreStoreA();
12230
+ claudeResumeId = meta.preEditClaudeSessionId;
12231
+ sessionMetadata = { ...sessionMetadata, claudeSessionId: claudeResumeId };
12232
+ }
12233
+ });
12234
+ if (result.success) {
12235
+ clearUndoSnapshot(undoDir);
12236
+ if (claudeResumeId && !trackedSession.stopped) {
12237
+ saveSession({
12238
+ sessionId,
12239
+ directory,
12240
+ claudeResumeId,
12241
+ permissionMode: currentPermissionMode,
12242
+ spawnMeta: lastSpawnMeta,
12243
+ metadata: sessionMetadata,
12244
+ createdAt: sessionCreatedAt,
12245
+ machineId,
12246
+ wasProcessing: false
12247
+ });
12248
+ artifactSync.syncSession(sessionId, getSessionDir(directory, sessionId), sessionMetadata, machineId).catch(() => {
12249
+ });
12250
+ }
12251
+ }
12252
+ return result;
12253
+ },
11827
12254
  onUpdateSecurityContext: async (newSecurityContext) => {
11828
12255
  logger.log(`[Session ${sessionId}] Security context update requested \u2014 restarting agent`);
11829
12256
  sessionMetadata = { ...sessionMetadata, securityContext: newSecurityContext };
@@ -12976,7 +13403,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
12976
13403
  const specs = loadExposedTunnels();
12977
13404
  if (specs.length === 0) return;
12978
13405
  logger.log(`[exposed-tunnels] Restoring ${specs.length} tunnel(s) from ${EXPOSED_TUNNELS_FILE}`);
12979
- const { FrpcTunnel } = await import('./frpc-BjWZ129L.mjs');
13406
+ const { FrpcTunnel } = await import('./frpc-1aSnPVrE.mjs');
12980
13407
  for (const spec of specs) {
12981
13408
  if (tunnels.has(spec.name)) continue;
12982
13409
  try {