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.
- package/dist/{agentCommands-D53qtjbo.mjs → agentCommands-CFWM6S7e.mjs} +4 -4
- package/dist/{auth-Ykv_kVPM.mjs → auth-B3NsDWG9.mjs} +1 -1
- package/dist/cli.mjs +75 -51
- package/dist/{commands-C29ciVUe.mjs → commands-B3NhziMR.mjs} +47 -2
- package/dist/{commands-1DLUHWZ5.mjs → commands-DbQ14J-R.mjs} +1 -1
- package/dist/{commands-BcIz9gl2.mjs → commands-Dj2M3sTB.mjs} +1 -1
- package/dist/{commands-BBQI_6EM.mjs → commands-Dmh59asw.mjs} +2 -2
- package/dist/{commands-BYBzqLJZ.mjs → commands-Vudp6ihZ.mjs} +5 -5
- package/dist/{fleet--CAdje9C.mjs → fleet-F8KB5IcM.mjs} +1 -1
- package/dist/{frpc-BjWZ129L.mjs → frpc-1aSnPVrE.mjs} +1 -1
- package/dist/{headlessCli-C6MzdWRW.mjs → headlessCli-6Cps9gnO.mjs} +2 -2
- package/dist/index.mjs +1 -1
- package/dist/{package-PUmSILNZ.mjs → package-BuIQUz-N.mjs} +2 -2
- package/dist/{run-CiiUhPNf.mjs → run-BnPtZvoP.mjs} +1 -1
- package/dist/{run-aNUfTpRW.mjs → run-DXzaCfex.mjs} +488 -61
- package/dist/{serveCommands-BoFSdzkv.mjs → serveCommands-CFO3GtKq.mjs} +5 -5
- package/dist/{serveManager-DrPFeC2o.mjs → serveManager-BnwAe-VX.mjs} +2 -2
- package/dist/{sideband-BCKpph51.mjs → sideband-vimBRRDF.mjs} +1 -1
- package/package.json +2 -2
|
@@ -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,
|
|
4
|
-
import path__default, { join as join$1, dirname as dirname$1, basename as basename$1
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
const
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
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-
|
|
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-
|
|
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 {
|