svamp-cli 0.2.115 → 0.2.117
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/bin/skills/crew/SKILL.md +86 -0
- package/bin/skills/loop/SKILL.md +1 -1
- package/bin/skills/loop/bin/loop-init.mjs +3 -0
- package/dist/{agentCommands-CFWM6S7e.mjs → agentCommands-CZgIpC1Z.mjs} +4 -4
- package/dist/{auth-B3NsDWG9.mjs → auth-Dkiiq1bg.mjs} +1 -1
- package/dist/cli.mjs +87 -75
- package/dist/{commands-DbQ14J-R.mjs → commands-BvKgI__M.mjs} +1 -1
- package/dist/{commands-B3NhziMR.mjs → commands-BxXUQlBD.mjs} +40 -47
- package/dist/{commands-Vudp6ihZ.mjs → commands-CsJyJgin.mjs} +5 -5
- package/dist/commands-Do6Od6jK.mjs +504 -0
- package/dist/{commands-Dmh59asw.mjs → commands-Q1xvobXM.mjs} +2 -2
- package/dist/{commands-Dj2M3sTB.mjs → commands-QWVhHPCf.mjs} +1 -1
- package/dist/{fleet-F8KB5IcM.mjs → fleet-l4Ku9jry.mjs} +1 -1
- package/dist/{frpc-1aSnPVrE.mjs → frpc-63NvAmuT.mjs} +1 -1
- package/dist/{headlessCli-6Cps9gnO.mjs → headlessCli-BsVvbKhN.mjs} +2 -2
- package/dist/index.mjs +1 -1
- package/dist/{package-BuIQUz-N.mjs → package-uOEzgreT.mjs} +3 -3
- package/dist/{run-BnPtZvoP.mjs → run-C1jI28ZZ.mjs} +1 -1
- package/dist/{run-DXzaCfex.mjs → run-Ci6VToZR.mjs} +181 -491
- package/dist/{serveCommands-CFO3GtKq.mjs → serveCommands-BuA8btah.mjs} +5 -5
- package/dist/{serveManager-BnwAe-VX.mjs → serveManager-DUjR8RGw.mjs} +2 -2
- package/dist/{sideband-vimBRRDF.mjs → sideband-DE8X9tg3.mjs} +1 -1
- package/package.json +3 -3
|
@@ -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,
|
|
4
|
-
import path__default, { join as join$1,
|
|
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';
|
|
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-63NvAmuT.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-DE8X9tg3.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-BxXUQlBD.mjs');
|
|
3040
3040
|
const timeout = c.reply?.timeout_sec || 120;
|
|
3041
3041
|
let result;
|
|
3042
3042
|
try {
|
|
@@ -3479,48 +3479,6 @@ 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
|
-
}
|
|
3524
3482
|
function createSessionStore(server, sessionId, initialMetadata, initialAgentState, callbacks, options) {
|
|
3525
3483
|
const messages = options?.messagesDir ? loadMessages(options.messagesDir) : [];
|
|
3526
3484
|
let nextSeq = messages.length > 0 ? messages[messages.length - 1].seq + 1 : 1;
|
|
@@ -3666,100 +3624,6 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
3666
3624
|
notifyListeners({ type: "update-session", sessionId, metadata: { value: metadata, version: metadataVersion } });
|
|
3667
3625
|
callbacks.onMetadataUpdate?.(metadata);
|
|
3668
3626
|
};
|
|
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
|
-
};
|
|
3763
3627
|
const rpcHandlers = {
|
|
3764
3628
|
// ── Messages ──
|
|
3765
3629
|
getMessages: async (afterSeq, limit, context) => {
|
|
@@ -4439,72 +4303,59 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
4439
4303
|
if (!claudeSessionId) {
|
|
4440
4304
|
return { success: false, error: "No active Claude session to query" };
|
|
4441
4305
|
}
|
|
4442
|
-
const
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
const
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
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 });
|
|
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
|
+
});
|
|
4508
4359
|
}
|
|
4509
4360
|
};
|
|
4510
4361
|
const store = {
|
|
@@ -7338,171 +7189,6 @@ var GeminiTransport$1 = /*#__PURE__*/Object.freeze({
|
|
|
7338
7189
|
GeminiTransport: GeminiTransport
|
|
7339
7190
|
});
|
|
7340
7191
|
|
|
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
|
-
|
|
7506
7192
|
const execFileAsync = promisify$1(execFile$1);
|
|
7507
7193
|
const SVAMP_TOOLS_DIR = join(homedir(), ".svamp", "tools");
|
|
7508
7194
|
const SVAMP_BIN_DIR = join(SVAMP_TOOLS_DIR, "bin");
|
|
@@ -10037,6 +9723,20 @@ async function ensureAutoInstalledSkills(logger) {
|
|
|
10037
9723
|
}
|
|
10038
9724
|
},
|
|
10039
9725
|
marketplaceVersion: async () => readBundledSkillVersion("loop")
|
|
9726
|
+
},
|
|
9727
|
+
{
|
|
9728
|
+
// The `crew` skill: a lead session delegates features to managed worktree
|
|
9729
|
+
// children (svamp feature start/list/done/merge) and merges them back.
|
|
9730
|
+
// Bundled (bin/skills/crew/) so it's available offline / pre-publish.
|
|
9731
|
+
name: "crew",
|
|
9732
|
+
install: async () => {
|
|
9733
|
+
try {
|
|
9734
|
+
installBundledSkill("crew");
|
|
9735
|
+
} catch {
|
|
9736
|
+
await installSkillFromMarketplace("crew");
|
|
9737
|
+
}
|
|
9738
|
+
},
|
|
9739
|
+
marketplaceVersion: async () => readBundledSkillVersion("crew")
|
|
10040
9740
|
}
|
|
10041
9741
|
];
|
|
10042
9742
|
for (const task of tasks) {
|
|
@@ -10234,7 +9934,15 @@ function deactivateLoop(directory, sessionId) {
|
|
|
10234
9934
|
} catch {
|
|
10235
9935
|
}
|
|
10236
9936
|
}
|
|
10237
|
-
function
|
|
9937
|
+
function readLoopJson(directory, sessionId, file) {
|
|
9938
|
+
try {
|
|
9939
|
+
const p = join$1(getLoopDir(directory, sessionId), file);
|
|
9940
|
+
return existsSync$1(p) ? JSON.parse(readFileSync$1(p, "utf-8")) : null;
|
|
9941
|
+
} catch {
|
|
9942
|
+
return null;
|
|
9943
|
+
}
|
|
9944
|
+
}
|
|
9945
|
+
function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata, sessionService, logger, onLoopActivated, onSupervisionVerdict) {
|
|
10238
9946
|
const configPath = getSvampConfigPath(directory, sessionId);
|
|
10239
9947
|
let lastConfigContent = "";
|
|
10240
9948
|
if (existsSync$1(configPath)) {
|
|
@@ -10323,6 +10031,27 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
|
|
|
10323
10031
|
"event"
|
|
10324
10032
|
);
|
|
10325
10033
|
logger.log(`[svampConfig] ${s.phase === "done" ? "Loop complete" : "Loop gave up"} (iter ${iter})`);
|
|
10034
|
+
try {
|
|
10035
|
+
const cfg = readLoopJson(directory, sessionId, "loop.config.json") || {};
|
|
10036
|
+
const ev = readLoopJson(directory, sessionId, "evaluator-verdict.json") || {};
|
|
10037
|
+
const verdict = {
|
|
10038
|
+
type: "supervision:verdict",
|
|
10039
|
+
sessionId,
|
|
10040
|
+
round: iter,
|
|
10041
|
+
verdict: s.phase === "done" ? "approved" : "rework",
|
|
10042
|
+
criteria: typeof cfg.criteria === "string" ? cfg.criteria : "",
|
|
10043
|
+
judge: cfg.evaluator?.enabled !== false ? "agent" : cfg.oracle ? "oracle" : "agent",
|
|
10044
|
+
...s.phase === "gave_up" ? { guidance: s.gave_up_reason || "gave up before meeting the criteria" } : ev.guidance ? { guidance: String(ev.guidance) } : {},
|
|
10045
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
10046
|
+
};
|
|
10047
|
+
try {
|
|
10048
|
+
const vp = join$1(getLoopDir(directory, sessionId), "supervisor-verdict.json");
|
|
10049
|
+
writeFileSync$1(vp, JSON.stringify(verdict, null, 2));
|
|
10050
|
+
} catch {
|
|
10051
|
+
}
|
|
10052
|
+
onSupervisionVerdict?.(verdict);
|
|
10053
|
+
} catch {
|
|
10054
|
+
}
|
|
10326
10055
|
} catch {
|
|
10327
10056
|
}
|
|
10328
10057
|
};
|
|
@@ -10378,6 +10107,53 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
|
|
|
10378
10107
|
const { loop: _, ...restPatch } = patch;
|
|
10379
10108
|
patch = restPatch;
|
|
10380
10109
|
}
|
|
10110
|
+
if ("supervisor" in patch) {
|
|
10111
|
+
const sup = patch.supervisor;
|
|
10112
|
+
if (sup && typeof sup === "object" && typeof sup.criteria === "string" && sup.criteria.trim()) {
|
|
10113
|
+
const criteria = sup.criteria.trim();
|
|
10114
|
+
const judges = Array.isArray(sup.judges) ? sup.judges : [];
|
|
10115
|
+
const oracleJudge = judges.find((j) => j?.type === "oracle");
|
|
10116
|
+
const oracle = oracleJudge && typeof oracleJudge.cmd === "string" && oracleJudge.cmd.trim() ? oracleJudge.cmd.trim() : typeof sup.oracle === "string" && sup.oracle.trim() ? sup.oracle.trim() : void 0;
|
|
10117
|
+
const hasAgentJudge = judges.length === 0 || judges.some((j) => j?.type === "agent");
|
|
10118
|
+
const maxRounds = typeof sup.max_rounds === "number" ? sup.max_rounds : 20;
|
|
10119
|
+
const ok = initLoop(directory, {
|
|
10120
|
+
task: criteria,
|
|
10121
|
+
// P1: criteria seeds LOOP.md as the goal (skill carries it as config.criteria in #30)
|
|
10122
|
+
criteria,
|
|
10123
|
+
oracle,
|
|
10124
|
+
maxIterations: maxRounds,
|
|
10125
|
+
evaluator: hasAgentJudge,
|
|
10126
|
+
sessionId
|
|
10127
|
+
});
|
|
10128
|
+
if (ok) {
|
|
10129
|
+
const judgeLabel = judges.length ? judges.map((j) => j.type).join("\u2192") : "agent";
|
|
10130
|
+
const idle = getMetadata().lifecycleState === "idle";
|
|
10131
|
+
if (idle) {
|
|
10132
|
+
const existingQueue = getMetadata().messageQueue || [];
|
|
10133
|
+
const nudge = `You are now under supervision. Success criteria: ${criteria}
|
|
10134
|
+
Continue working until they are met, or verify and finish \u2014 an independent Stop gate will re-check before you can stop.`;
|
|
10135
|
+
setMetadata((m) => ({ ...m, messageQueue: [...existingQueue, { id: randomUUID$1(), text: nudge, displayText: `\u{1F441} Supervisor attached`, createdAt: Date.now() }] }));
|
|
10136
|
+
onLoopActivated?.();
|
|
10137
|
+
}
|
|
10138
|
+
sessionService.pushMessage(
|
|
10139
|
+
{ type: "message", message: `\u{1F441} Supervisor attached \u2014 judges: ${judgeLabel}, max ${maxRounds}. Criteria: ${criteria.slice(0, 120)}${criteria.length > 120 ? "\u2026" : ""}` },
|
|
10140
|
+
"event"
|
|
10141
|
+
);
|
|
10142
|
+
logger.log(`[svampConfig] Supervisor attached (judges: ${judgeLabel}, max ${maxRounds})`);
|
|
10143
|
+
} else {
|
|
10144
|
+
sessionService.pushMessage(
|
|
10145
|
+
{ type: "message", message: "Failed to attach supervisor \u2014 the loop skill could not be located. Reinstall with: svamp skills install loop --force", level: "error" },
|
|
10146
|
+
"event"
|
|
10147
|
+
);
|
|
10148
|
+
}
|
|
10149
|
+
} else {
|
|
10150
|
+
deactivateLoop(directory, sessionId);
|
|
10151
|
+
sessionService.pushMessage({ type: "message", message: "Supervisor detached." }, "event");
|
|
10152
|
+
logger.log(`[svampConfig] Supervisor detached`);
|
|
10153
|
+
}
|
|
10154
|
+
const { supervisor: _s, ...restPatch } = patch;
|
|
10155
|
+
patch = restPatch;
|
|
10156
|
+
}
|
|
10381
10157
|
if (Object.keys(patch).length > 0) {
|
|
10382
10158
|
const config = readSvampConfig(configPath);
|
|
10383
10159
|
for (const [key, value] of Object.entries(patch)) {
|
|
@@ -10805,7 +10581,7 @@ async function startDaemon(options) {
|
|
|
10805
10581
|
const list = loadExposedTunnels().filter((t) => t.name !== name);
|
|
10806
10582
|
saveExposedTunnels(list);
|
|
10807
10583
|
}
|
|
10808
|
-
const { ServeManager } = await import('./serveManager-
|
|
10584
|
+
const { ServeManager } = await import('./serveManager-DUjR8RGw.mjs');
|
|
10809
10585
|
const serveManager = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
|
|
10810
10586
|
ensureAutoInstalledSkills(logger).catch(() => {
|
|
10811
10587
|
});
|
|
@@ -10835,6 +10611,31 @@ async function startDaemon(options) {
|
|
|
10835
10611
|
logger.log(`Hypha connection permanently lost: ${reason}. Daemon continues running \u2014 restart manually to reconnect.`);
|
|
10836
10612
|
});
|
|
10837
10613
|
const pidToTrackedSession = /* @__PURE__ */ new Map();
|
|
10614
|
+
const routeSupervisionVerdict = (parentId, v) => {
|
|
10615
|
+
try {
|
|
10616
|
+
if (!parentId) return;
|
|
10617
|
+
const parent = Array.from(pidToTrackedSession.values()).find((t) => t.svampSessionId === parentId);
|
|
10618
|
+
const send = parent?.sessionRPCHandlers?.sendInboxMessage;
|
|
10619
|
+
if (!send) return;
|
|
10620
|
+
Promise.resolve(send({
|
|
10621
|
+
messageId: randomUUID$1(),
|
|
10622
|
+
from: `agent:${v.sessionId}`,
|
|
10623
|
+
fromSession: v.sessionId,
|
|
10624
|
+
to: parentId,
|
|
10625
|
+
subject: `supervision: ${v.verdict} (child ${v.sessionId.slice(0, 8)})`,
|
|
10626
|
+
body: `<supervision-verdict child="${v.sessionId}" verdict="${v.verdict}" round="${v.round}" judge="${v.judge}">
|
|
10627
|
+
${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
|
|
10628
|
+
</supervision-verdict>`,
|
|
10629
|
+
timestamp: Date.now(),
|
|
10630
|
+
read: false,
|
|
10631
|
+
urgency: "normal",
|
|
10632
|
+
hopCount: 1
|
|
10633
|
+
})).catch(() => {
|
|
10634
|
+
});
|
|
10635
|
+
logger.log(`[supervision] verdict '${v.verdict}' for ${v.sessionId.slice(0, 8)} \u2192 parent ${parentId.slice(0, 8)}`);
|
|
10636
|
+
} catch {
|
|
10637
|
+
}
|
|
10638
|
+
};
|
|
10838
10639
|
server.on("services_registered", () => {
|
|
10839
10640
|
if (consecutiveHeartbeatFailures > 0) {
|
|
10840
10641
|
logger.log(`Hypha reconnection successful \u2014 services re-registered (resetting ${consecutiveHeartbeatFailures} failures)`);
|
|
@@ -11857,13 +11658,12 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
11857
11658
|
}
|
|
11858
11659
|
return child;
|
|
11859
11660
|
};
|
|
11860
|
-
const restartClaudeHandler = async (
|
|
11661
|
+
const restartClaudeHandler = async () => {
|
|
11861
11662
|
logger.log(`[Session ${sessionId}] Restart Claude requested`);
|
|
11862
11663
|
if (isRestartingClaude || isSwitchingMode) {
|
|
11863
11664
|
return { success: false, message: "Restart already in progress." };
|
|
11864
11665
|
}
|
|
11865
11666
|
isRestartingClaude = true;
|
|
11866
|
-
let beforeRespawnError;
|
|
11867
11667
|
try {
|
|
11868
11668
|
if (claudeProcess && claudeProcess.exitCode === null) {
|
|
11869
11669
|
isKillingClaude = true;
|
|
@@ -11875,14 +11675,6 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
11875
11675
|
if (trackedSession?.stopped) {
|
|
11876
11676
|
return { success: false, message: "Session was stopped during restart." };
|
|
11877
11677
|
}
|
|
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
|
-
}
|
|
11886
11678
|
if (claudeResumeId) {
|
|
11887
11679
|
if (!stagedCredentials && shouldIsolateSession()) {
|
|
11888
11680
|
try {
|
|
@@ -11895,9 +11687,6 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
11895
11687
|
spawnClaude(void 0, { permissionMode: currentPermissionMode });
|
|
11896
11688
|
sessionService.updateMetadata(sessionMetadata);
|
|
11897
11689
|
logger.log(`[Session ${sessionId}] Claude respawned with --resume ${claudeResumeId}`);
|
|
11898
|
-
if (beforeRespawnError) {
|
|
11899
|
-
return { success: false, message: `Edit failed (session restored): ${beforeRespawnError}` };
|
|
11900
|
-
}
|
|
11901
11690
|
return { success: true, message: "Claude process restarted successfully." };
|
|
11902
11691
|
} else {
|
|
11903
11692
|
logger.log(`[Session ${sessionId}] No resume ID \u2014 cannot restart`);
|
|
@@ -11911,26 +11700,6 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
11911
11700
|
isRestartingClaude = false;
|
|
11912
11701
|
}
|
|
11913
11702
|
};
|
|
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
|
-
};
|
|
11934
11703
|
if (shouldIsolateSession()) {
|
|
11935
11704
|
try {
|
|
11936
11705
|
stagedCredentials = await stageCredentialsForSharing(sessionId);
|
|
@@ -11951,7 +11720,6 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
11951
11720
|
logger.log(`[Session ${sessionId}] User message received`);
|
|
11952
11721
|
userMessagePending = true;
|
|
11953
11722
|
turnInitiatedByUser = true;
|
|
11954
|
-
clearUndoSnapshot(join$1(getSessionDir(directory, sessionId), "undo"));
|
|
11955
11723
|
let text;
|
|
11956
11724
|
let msgMeta = meta;
|
|
11957
11725
|
try {
|
|
@@ -12171,86 +11939,6 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
12171
11939
|
}
|
|
12172
11940
|
},
|
|
12173
11941
|
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
|
-
},
|
|
12254
11942
|
onUpdateSecurityContext: async (newSecurityContext) => {
|
|
12255
11943
|
logger.log(`[Session ${sessionId}] Security context update requested \u2014 restarting agent`);
|
|
12256
11944
|
sessionMetadata = { ...sessionMetadata, securityContext: newSecurityContext };
|
|
@@ -12511,7 +12199,8 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
12511
12199
|
logger,
|
|
12512
12200
|
() => {
|
|
12513
12201
|
if (!trackedSession?.stopped) setTimeout(() => processMessageQueueRef?.(), 200);
|
|
12514
|
-
}
|
|
12202
|
+
},
|
|
12203
|
+
(v) => routeSupervisionVerdict(sessionMetadata.parentSessionId, v)
|
|
12515
12204
|
);
|
|
12516
12205
|
checkSvampConfig = svampConfig.check;
|
|
12517
12206
|
cleanupSvampConfig = svampConfig.cleanup;
|
|
@@ -13008,7 +12697,8 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
13008
12697
|
}
|
|
13009
12698
|
});
|
|
13010
12699
|
}
|
|
13011
|
-
}
|
|
12700
|
+
},
|
|
12701
|
+
(v) => routeSupervisionVerdict(sessionMetadata.parentSessionId, v)
|
|
13012
12702
|
);
|
|
13013
12703
|
const checkSvampConfig = svampConfigChecker.check;
|
|
13014
12704
|
const writeSvampConfigPatchAcp = svampConfigChecker.writeConfig;
|
|
@@ -13403,7 +13093,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
13403
13093
|
const specs = loadExposedTunnels();
|
|
13404
13094
|
if (specs.length === 0) return;
|
|
13405
13095
|
logger.log(`[exposed-tunnels] Restoring ${specs.length} tunnel(s) from ${EXPOSED_TUNNELS_FILE}`);
|
|
13406
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
13096
|
+
const { FrpcTunnel } = await import('./frpc-63NvAmuT.mjs');
|
|
13407
13097
|
for (const spec of specs) {
|
|
13408
13098
|
if (tunnels.has(spec.name)) continue;
|
|
13409
13099
|
try {
|