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