svamp-cli 0.2.115 → 0.2.116

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import os$1, { homedir as homedir$1 } from 'os';
2
2
  import fs, { mkdir as mkdir$1, readdir as readdir$1, readFile, writeFile as writeFile$1, rename, unlink } from 'fs/promises';
3
- import { readFileSync as readFileSync$1, mkdirSync as mkdirSync$1, writeFileSync as writeFileSync$1, renameSync as renameSync$1, existsSync as existsSync$1, 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';
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-1aSnPVrE.mjs');
2679
+ const { FrpcTunnel } = await import('./frpc-9qgaimIN.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-vimBRRDF.mjs');
2940
+ const { toolsForRole } = await import('./sideband-CYNK4foC.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-B3NhziMR.mjs');
3039
+ const { queryCore } = await import('./commands-DdW5M7Le.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 r = await forkClaudePrint(question, claudeSessionId, metadata.path || process.cwd());
4443
- return r.success ? { success: true, answer: r.text } : { success: false, error: r.error };
4444
- },
4445
- editMessage: async (messageId, newText, context) => {
4446
- authorizeRequest(context, metadata.sharing, "admin");
4447
- if (!messageId) return { success: false, message: "messageId is required." };
4448
- const target = messages.find((m) => m.id === messageId);
4449
- if (!target) {
4450
- return { success: false, message: "Message not found in the active window \u2014 only recent messages can be edited." };
4451
- }
4452
- return performEdit(target, newText);
4453
- },
4454
- refineLastReply: async (instruction, context) => {
4455
- authorizeRequest(context, metadata.sharing, "admin");
4456
- if (!instruction || typeof instruction !== "string") {
4457
- return { success: false, message: "An instruction is required." };
4458
- }
4459
- const claudeSessionId = metadata.claudeSessionId;
4460
- if (!claudeSessionId) return { success: false, message: "No active Claude session to refine." };
4461
- let last;
4462
- for (let i = messages.length - 1; i >= 0; i--) {
4463
- const info = editableInfo(messages[i]);
4464
- if (info && info.role === "assistant") {
4465
- last = messages[i];
4466
- break;
4467
- }
4468
- }
4469
- if (!last) return { success: false, message: "No assistant reply to refine." };
4470
- const oldText = editableInfo(last).text;
4471
- const prompt = `You previously wrote this reply:
4472
-
4473
- <previous_reply>
4474
- ${oldText}
4475
- </previous_reply>
4476
-
4477
- Revise it according to this instruction:
4478
- <instruction>
4479
- ${instruction}
4480
- </instruction>
4481
-
4482
- Output ONLY the full revised reply text \u2014 no preamble, no commentary, no surrounding quotes or code fences.`;
4483
- const revised = await forkClaudePrint(prompt, claudeSessionId, metadata.path || process.cwd());
4484
- if (!revised.success || !revised.text?.trim()) {
4485
- return { success: false, message: revised.error || "Failed to generate a revised reply." };
4486
- }
4487
- return performEdit(last, revised.text.trim());
4488
- },
4489
- undoLastEdit: async (context) => {
4490
- authorizeRequest(context, metadata.sharing, "admin");
4491
- if (!callbacks.onUndoEdit) return { success: false, message: "Undo is not supported for this session." };
4492
- const restoreStoreA = () => {
4493
- if (!options?.messagesDir) return;
4494
- try {
4495
- const undoBackup = join(options.messagesDir, "undo", "messages.jsonl");
4496
- const target = join(options.messagesDir, "messages.jsonl");
4497
- if (existsSync(undoBackup)) writeFileSync(target, readFileSync(undoBackup));
4498
- const reloaded = loadMessages(options.messagesDir, sessionId);
4499
- messages.length = 0;
4500
- messages.push(...reloaded);
4501
- nextSeq = messages.length ? messages[messages.length - 1].seq + 1 : 1;
4502
- notifyListeners({ type: "messages-edited", sessionId, latestSeq: nextSeq - 1 });
4503
- } catch (err) {
4504
- console.error(`[HYPHA SESSION ${sessionId}] Undo Store A restore failed: ${err?.message ?? err}`);
4505
- }
4506
- };
4507
- return callbacks.onUndoEdit({ restoreStoreA });
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");
@@ -10234,7 +9920,15 @@ function deactivateLoop(directory, sessionId) {
10234
9920
  } catch {
10235
9921
  }
10236
9922
  }
10237
- function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata, sessionService, logger, onLoopActivated) {
9923
+ function readLoopJson(directory, sessionId, file) {
9924
+ try {
9925
+ const p = join$1(getLoopDir(directory, sessionId), file);
9926
+ return existsSync$1(p) ? JSON.parse(readFileSync$1(p, "utf-8")) : null;
9927
+ } catch {
9928
+ return null;
9929
+ }
9930
+ }
9931
+ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata, sessionService, logger, onLoopActivated, onSupervisionVerdict) {
10238
9932
  const configPath = getSvampConfigPath(directory, sessionId);
10239
9933
  let lastConfigContent = "";
10240
9934
  if (existsSync$1(configPath)) {
@@ -10323,6 +10017,27 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
10323
10017
  "event"
10324
10018
  );
10325
10019
  logger.log(`[svampConfig] ${s.phase === "done" ? "Loop complete" : "Loop gave up"} (iter ${iter})`);
10020
+ try {
10021
+ const cfg = readLoopJson(directory, sessionId, "loop.config.json") || {};
10022
+ const ev = readLoopJson(directory, sessionId, "evaluator-verdict.json") || {};
10023
+ const verdict = {
10024
+ type: "supervision:verdict",
10025
+ sessionId,
10026
+ round: iter,
10027
+ verdict: s.phase === "done" ? "approved" : "rework",
10028
+ criteria: typeof cfg.criteria === "string" ? cfg.criteria : "",
10029
+ judge: cfg.evaluator?.enabled !== false ? "agent" : cfg.oracle ? "oracle" : "agent",
10030
+ ...s.phase === "gave_up" ? { guidance: s.gave_up_reason || "gave up before meeting the criteria" } : ev.guidance ? { guidance: String(ev.guidance) } : {},
10031
+ ts: (/* @__PURE__ */ new Date()).toISOString()
10032
+ };
10033
+ try {
10034
+ const vp = join$1(getLoopDir(directory, sessionId), "supervisor-verdict.json");
10035
+ writeFileSync$1(vp, JSON.stringify(verdict, null, 2));
10036
+ } catch {
10037
+ }
10038
+ onSupervisionVerdict?.(verdict);
10039
+ } catch {
10040
+ }
10326
10041
  } catch {
10327
10042
  }
10328
10043
  };
@@ -10378,6 +10093,53 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
10378
10093
  const { loop: _, ...restPatch } = patch;
10379
10094
  patch = restPatch;
10380
10095
  }
10096
+ if ("supervisor" in patch) {
10097
+ const sup = patch.supervisor;
10098
+ if (sup && typeof sup === "object" && typeof sup.criteria === "string" && sup.criteria.trim()) {
10099
+ const criteria = sup.criteria.trim();
10100
+ const judges = Array.isArray(sup.judges) ? sup.judges : [];
10101
+ const oracleJudge = judges.find((j) => j?.type === "oracle");
10102
+ 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;
10103
+ const hasAgentJudge = judges.length === 0 || judges.some((j) => j?.type === "agent");
10104
+ const maxRounds = typeof sup.max_rounds === "number" ? sup.max_rounds : 20;
10105
+ const ok = initLoop(directory, {
10106
+ task: criteria,
10107
+ // P1: criteria seeds LOOP.md as the goal (skill carries it as config.criteria in #30)
10108
+ criteria,
10109
+ oracle,
10110
+ maxIterations: maxRounds,
10111
+ evaluator: hasAgentJudge,
10112
+ sessionId
10113
+ });
10114
+ if (ok) {
10115
+ const judgeLabel = judges.length ? judges.map((j) => j.type).join("\u2192") : "agent";
10116
+ const idle = getMetadata().lifecycleState === "idle";
10117
+ if (idle) {
10118
+ const existingQueue = getMetadata().messageQueue || [];
10119
+ const nudge = `You are now under supervision. Success criteria: ${criteria}
10120
+ Continue working until they are met, or verify and finish \u2014 an independent Stop gate will re-check before you can stop.`;
10121
+ setMetadata((m) => ({ ...m, messageQueue: [...existingQueue, { id: randomUUID$1(), text: nudge, displayText: `\u{1F441} Supervisor attached`, createdAt: Date.now() }] }));
10122
+ onLoopActivated?.();
10123
+ }
10124
+ sessionService.pushMessage(
10125
+ { type: "message", message: `\u{1F441} Supervisor attached \u2014 judges: ${judgeLabel}, max ${maxRounds}. Criteria: ${criteria.slice(0, 120)}${criteria.length > 120 ? "\u2026" : ""}` },
10126
+ "event"
10127
+ );
10128
+ logger.log(`[svampConfig] Supervisor attached (judges: ${judgeLabel}, max ${maxRounds})`);
10129
+ } else {
10130
+ sessionService.pushMessage(
10131
+ { type: "message", message: "Failed to attach supervisor \u2014 the loop skill could not be located. Reinstall with: svamp skills install loop --force", level: "error" },
10132
+ "event"
10133
+ );
10134
+ }
10135
+ } else {
10136
+ deactivateLoop(directory, sessionId);
10137
+ sessionService.pushMessage({ type: "message", message: "Supervisor detached." }, "event");
10138
+ logger.log(`[svampConfig] Supervisor detached`);
10139
+ }
10140
+ const { supervisor: _s, ...restPatch } = patch;
10141
+ patch = restPatch;
10142
+ }
10381
10143
  if (Object.keys(patch).length > 0) {
10382
10144
  const config = readSvampConfig(configPath);
10383
10145
  for (const [key, value] of Object.entries(patch)) {
@@ -10805,7 +10567,7 @@ async function startDaemon(options) {
10805
10567
  const list = loadExposedTunnels().filter((t) => t.name !== name);
10806
10568
  saveExposedTunnels(list);
10807
10569
  }
10808
- const { ServeManager } = await import('./serveManager-BnwAe-VX.mjs');
10570
+ const { ServeManager } = await import('./serveManager-B19qVJeZ.mjs');
10809
10571
  const serveManager = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
10810
10572
  ensureAutoInstalledSkills(logger).catch(() => {
10811
10573
  });
@@ -10835,6 +10597,31 @@ async function startDaemon(options) {
10835
10597
  logger.log(`Hypha connection permanently lost: ${reason}. Daemon continues running \u2014 restart manually to reconnect.`);
10836
10598
  });
10837
10599
  const pidToTrackedSession = /* @__PURE__ */ new Map();
10600
+ const routeSupervisionVerdict = (parentId, v) => {
10601
+ try {
10602
+ if (!parentId) return;
10603
+ const parent = Array.from(pidToTrackedSession.values()).find((t) => t.svampSessionId === parentId);
10604
+ const send = parent?.sessionRPCHandlers?.sendInboxMessage;
10605
+ if (!send) return;
10606
+ Promise.resolve(send({
10607
+ messageId: randomUUID$1(),
10608
+ from: `agent:${v.sessionId}`,
10609
+ fromSession: v.sessionId,
10610
+ to: parentId,
10611
+ subject: `supervision: ${v.verdict} (child ${v.sessionId.slice(0, 8)})`,
10612
+ body: `<supervision-verdict child="${v.sessionId}" verdict="${v.verdict}" round="${v.round}" judge="${v.judge}">
10613
+ ${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
10614
+ </supervision-verdict>`,
10615
+ timestamp: Date.now(),
10616
+ read: false,
10617
+ urgency: "normal",
10618
+ hopCount: 1
10619
+ })).catch(() => {
10620
+ });
10621
+ logger.log(`[supervision] verdict '${v.verdict}' for ${v.sessionId.slice(0, 8)} \u2192 parent ${parentId.slice(0, 8)}`);
10622
+ } catch {
10623
+ }
10624
+ };
10838
10625
  server.on("services_registered", () => {
10839
10626
  if (consecutiveHeartbeatFailures > 0) {
10840
10627
  logger.log(`Hypha reconnection successful \u2014 services re-registered (resetting ${consecutiveHeartbeatFailures} failures)`);
@@ -11857,13 +11644,12 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11857
11644
  }
11858
11645
  return child;
11859
11646
  };
11860
- const restartClaudeHandler = async (opts) => {
11647
+ const restartClaudeHandler = async () => {
11861
11648
  logger.log(`[Session ${sessionId}] Restart Claude requested`);
11862
11649
  if (isRestartingClaude || isSwitchingMode) {
11863
11650
  return { success: false, message: "Restart already in progress." };
11864
11651
  }
11865
11652
  isRestartingClaude = true;
11866
- let beforeRespawnError;
11867
11653
  try {
11868
11654
  if (claudeProcess && claudeProcess.exitCode === null) {
11869
11655
  isKillingClaude = true;
@@ -11875,14 +11661,6 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11875
11661
  if (trackedSession?.stopped) {
11876
11662
  return { success: false, message: "Session was stopped during restart." };
11877
11663
  }
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
11664
  if (claudeResumeId) {
11887
11665
  if (!stagedCredentials && shouldIsolateSession()) {
11888
11666
  try {
@@ -11895,9 +11673,6 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11895
11673
  spawnClaude(void 0, { permissionMode: currentPermissionMode });
11896
11674
  sessionService.updateMetadata(sessionMetadata);
11897
11675
  logger.log(`[Session ${sessionId}] Claude respawned with --resume ${claudeResumeId}`);
11898
- if (beforeRespawnError) {
11899
- return { success: false, message: `Edit failed (session restored): ${beforeRespawnError}` };
11900
- }
11901
11676
  return { success: true, message: "Claude process restarted successfully." };
11902
11677
  } else {
11903
11678
  logger.log(`[Session ${sessionId}] No resume ID \u2014 cannot restart`);
@@ -11911,26 +11686,6 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11911
11686
  isRestartingClaude = false;
11912
11687
  }
11913
11688
  };
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
11689
  if (shouldIsolateSession()) {
11935
11690
  try {
11936
11691
  stagedCredentials = await stageCredentialsForSharing(sessionId);
@@ -11951,7 +11706,6 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11951
11706
  logger.log(`[Session ${sessionId}] User message received`);
11952
11707
  userMessagePending = true;
11953
11708
  turnInitiatedByUser = true;
11954
- clearUndoSnapshot(join$1(getSessionDir(directory, sessionId), "undo"));
11955
11709
  let text;
11956
11710
  let msgMeta = meta;
11957
11711
  try {
@@ -12171,86 +11925,6 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
12171
11925
  }
12172
11926
  },
12173
11927
  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
11928
  onUpdateSecurityContext: async (newSecurityContext) => {
12255
11929
  logger.log(`[Session ${sessionId}] Security context update requested \u2014 restarting agent`);
12256
11930
  sessionMetadata = { ...sessionMetadata, securityContext: newSecurityContext };
@@ -12511,7 +12185,8 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
12511
12185
  logger,
12512
12186
  () => {
12513
12187
  if (!trackedSession?.stopped) setTimeout(() => processMessageQueueRef?.(), 200);
12514
- }
12188
+ },
12189
+ (v) => routeSupervisionVerdict(sessionMetadata.parentSessionId, v)
12515
12190
  );
12516
12191
  checkSvampConfig = svampConfig.check;
12517
12192
  cleanupSvampConfig = svampConfig.cleanup;
@@ -13008,7 +12683,8 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
13008
12683
  }
13009
12684
  });
13010
12685
  }
13011
- }
12686
+ },
12687
+ (v) => routeSupervisionVerdict(sessionMetadata.parentSessionId, v)
13012
12688
  );
13013
12689
  const checkSvampConfig = svampConfigChecker.check;
13014
12690
  const writeSvampConfigPatchAcp = svampConfigChecker.writeConfig;
@@ -13403,7 +13079,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
13403
13079
  const specs = loadExposedTunnels();
13404
13080
  if (specs.length === 0) return;
13405
13081
  logger.log(`[exposed-tunnels] Restoring ${specs.length} tunnel(s) from ${EXPOSED_TUNNELS_FILE}`);
13406
- const { FrpcTunnel } = await import('./frpc-1aSnPVrE.mjs');
13082
+ const { FrpcTunnel } = await import('./frpc-9qgaimIN.mjs');
13407
13083
  for (const spec of specs) {
13408
13084
  if (tunnels.has(spec.name)) continue;
13409
13085
  try {