svamp-cli 0.2.115 → 0.2.117

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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-63NvAmuT.mjs');
2680
2680
  const tunnel = new FrpcTunnel({
2681
2681
  name: params.name,
2682
2682
  ports: params.ports,
@@ -2937,7 +2937,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
2937
2937
  }
2938
2938
  const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
2939
2939
  const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
2940
- const { toolsForRole } = await import('./sideband-vimBRRDF.mjs');
2940
+ const { toolsForRole } = await import('./sideband-DE8X9tg3.mjs');
2941
2941
  const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
2942
2942
  return fmt(r2);
2943
2943
  }
@@ -3036,7 +3036,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
3036
3036
  if (r.error || !r.sender) return { error: r.error || "unauthorized" };
3037
3037
  const callId = "call_" + Math.random().toString(16).slice(2, 12);
3038
3038
  const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
3039
- const { queryCore } = await import('./commands-B3NhziMR.mjs');
3039
+ const { queryCore } = await import('./commands-BxXUQlBD.mjs');
3040
3040
  const timeout = c.reply?.timeout_sec || 120;
3041
3041
  let result;
3042
3042
  try {
@@ -3479,48 +3479,6 @@ function appendMessage(messagesDir, sessionId, msg) {
3479
3479
  console.error(`[HYPHA SESSION ${sessionId}] Failed to persist message: ${err?.message ?? err}`);
3480
3480
  }
3481
3481
  }
3482
- function editableInfo(msg) {
3483
- const c = msg?.content;
3484
- if (!c) return null;
3485
- if (c.role === "user" && c.content?.type === "text") {
3486
- return { role: "user", text: String(c.content.text ?? "") };
3487
- }
3488
- if (c.role === "agent" && c.content?.type === "output") {
3489
- const data = c.content.data;
3490
- if (data?.type === "assistant" && Array.isArray(data?.message?.content)) {
3491
- const textBlocks = data.message.content.filter((b) => b && b.type === "text" && typeof b.text === "string");
3492
- if (textBlocks.length >= 1) {
3493
- return { role: "assistant", text: textBlocks.map((b) => b.text).join("\n") };
3494
- }
3495
- }
3496
- if (data?.type === "user" && typeof data?.message?.content === "string") {
3497
- return { role: "user", text: data.message.content };
3498
- }
3499
- }
3500
- return null;
3501
- }
3502
- function patchStoredText(msg, newText) {
3503
- const c = msg?.content;
3504
- if (c?.role === "user" && c.content?.type === "text") {
3505
- c.content.text = newText;
3506
- return true;
3507
- }
3508
- if (c?.role === "agent" && c.content?.type === "output") {
3509
- const data = c.content.data;
3510
- if (data?.type === "user" && typeof data?.message?.content === "string") {
3511
- data.message.content = newText;
3512
- return true;
3513
- }
3514
- if (data?.type === "assistant" && Array.isArray(data?.message?.content)) {
3515
- const textBlocks = data.message.content.filter((b) => b && b.type === "text" && typeof b.text === "string");
3516
- if (textBlocks.length === 1) {
3517
- textBlocks[0].text = newText;
3518
- return true;
3519
- }
3520
- }
3521
- }
3522
- return false;
3523
- }
3524
3482
  function createSessionStore(server, sessionId, initialMetadata, initialAgentState, callbacks, options) {
3525
3483
  const messages = options?.messagesDir ? loadMessages(options.messagesDir) : [];
3526
3484
  let nextSeq = messages.length > 0 ? messages[messages.length - 1].seq + 1 : 1;
@@ -3666,100 +3624,6 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
3666
3624
  notifyListeners({ type: "update-session", sessionId, metadata: { value: metadata, version: metadataVersion } });
3667
3625
  callbacks.onMetadataUpdate?.(metadata);
3668
3626
  };
3669
- const forkClaudePrint = async (prompt, claudeSessionId, cwd, maxTurns = 6) => {
3670
- const { spawn } = await import('child_process');
3671
- return new Promise((resolve) => {
3672
- const child = spawn("claude", [
3673
- "--print",
3674
- prompt,
3675
- "--resume",
3676
- claudeSessionId,
3677
- "--fork-session",
3678
- "--no-session-persistence",
3679
- "--permission-mode",
3680
- "bypassPermissions",
3681
- "--output-format",
3682
- "json",
3683
- "--max-turns",
3684
- String(maxTurns)
3685
- ], {
3686
- cwd,
3687
- timeout: 6e4,
3688
- stdio: ["ignore", "pipe", "pipe"],
3689
- env: { ...process.env, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1" }
3690
- });
3691
- let stdout = "";
3692
- let stderr = "";
3693
- child.stdout?.on("data", (d) => {
3694
- stdout += d.toString();
3695
- });
3696
- child.stderr?.on("data", (d) => {
3697
- stderr += d.toString();
3698
- });
3699
- child.on("close", (code) => {
3700
- if (code !== 0 && !stdout) {
3701
- resolve({ success: false, error: stderr || `claude exited with code ${code}` });
3702
- return;
3703
- }
3704
- try {
3705
- const result = JSON.parse(stdout);
3706
- resolve({ success: true, text: result.result || result.text || stdout });
3707
- } catch {
3708
- resolve({ success: true, text: stdout.trim() });
3709
- }
3710
- });
3711
- child.on("error", (err) => resolve({ success: false, error: err.message }));
3712
- });
3713
- };
3714
- const performEdit = async (target, newText) => {
3715
- if (!callbacks.onEditTranscript) return { success: false, message: "Editing history is not supported for this session." };
3716
- const info = editableInfo(target);
3717
- if (!info) return { success: false, message: "This message cannot be edited." };
3718
- const text = typeof newText === "string" ? newText : "";
3719
- if (!text.trim()) return { success: false, message: "New text is empty." };
3720
- let fromEnd = 0;
3721
- for (const m of messages) {
3722
- if (m.seq <= target.seq) continue;
3723
- const i2 = editableInfo(m);
3724
- if (i2 && i2.role === info.role) fromEnd++;
3725
- }
3726
- const anchorSeq = target.seq;
3727
- const applyStoreA = () => {
3728
- for (let i = messages.length - 1; i >= 0; i--) {
3729
- if (messages[i].seq > anchorSeq) messages.splice(i, 1);
3730
- }
3731
- const tgt = messages.find((m) => m.seq === anchorSeq);
3732
- if (tgt) {
3733
- patchStoredText(tgt, text);
3734
- tgt.updatedAt = Date.now();
3735
- }
3736
- nextSeq = anchorSeq + 1;
3737
- if (options?.messagesDir) {
3738
- const filePath = join(options.messagesDir, "messages.jsonl");
3739
- try {
3740
- const lines = existsSync(filePath) ? readFileSync(filePath, "utf-8").split("\n").filter((l) => l.trim()) : [];
3741
- const kept = [];
3742
- for (const line of lines) {
3743
- let m;
3744
- try {
3745
- m = JSON.parse(line);
3746
- } catch {
3747
- continue;
3748
- }
3749
- if (typeof m.seq === "number" && m.seq > anchorSeq) continue;
3750
- kept.push(m.seq === anchorSeq && tgt ? JSON.stringify(tgt) : line);
3751
- }
3752
- const tmp = `${filePath}.tmp-${process.pid}`;
3753
- writeFileSync(tmp, kept.length ? kept.join("\n") + "\n" : "");
3754
- renameSync(tmp, filePath);
3755
- } catch (err) {
3756
- console.error(`[HYPHA SESSION ${sessionId}] Store A rewrite failed: ${err?.message ?? err}`);
3757
- }
3758
- }
3759
- notifyListeners({ type: "messages-edited", sessionId, latestSeq: nextSeq - 1 });
3760
- };
3761
- return callbacks.onEditTranscript({ role: info.role, fromEnd, oldText: info.text, newText: text, applyStoreA });
3762
- };
3763
3627
  const rpcHandlers = {
3764
3628
  // ── Messages ──
3765
3629
  getMessages: async (afterSeq, limit, context) => {
@@ -4439,72 +4303,59 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
4439
4303
  if (!claudeSessionId) {
4440
4304
  return { success: false, error: "No active Claude session to query" };
4441
4305
  }
4442
- const 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");
@@ -10037,6 +9723,20 @@ async function ensureAutoInstalledSkills(logger) {
10037
9723
  }
10038
9724
  },
10039
9725
  marketplaceVersion: async () => readBundledSkillVersion("loop")
9726
+ },
9727
+ {
9728
+ // The `crew` skill: a lead session delegates features to managed worktree
9729
+ // children (svamp feature start/list/done/merge) and merges them back.
9730
+ // Bundled (bin/skills/crew/) so it's available offline / pre-publish.
9731
+ name: "crew",
9732
+ install: async () => {
9733
+ try {
9734
+ installBundledSkill("crew");
9735
+ } catch {
9736
+ await installSkillFromMarketplace("crew");
9737
+ }
9738
+ },
9739
+ marketplaceVersion: async () => readBundledSkillVersion("crew")
10040
9740
  }
10041
9741
  ];
10042
9742
  for (const task of tasks) {
@@ -10234,7 +9934,15 @@ function deactivateLoop(directory, sessionId) {
10234
9934
  } catch {
10235
9935
  }
10236
9936
  }
10237
- function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata, sessionService, logger, onLoopActivated) {
9937
+ function readLoopJson(directory, sessionId, file) {
9938
+ try {
9939
+ const p = join$1(getLoopDir(directory, sessionId), file);
9940
+ return existsSync$1(p) ? JSON.parse(readFileSync$1(p, "utf-8")) : null;
9941
+ } catch {
9942
+ return null;
9943
+ }
9944
+ }
9945
+ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata, sessionService, logger, onLoopActivated, onSupervisionVerdict) {
10238
9946
  const configPath = getSvampConfigPath(directory, sessionId);
10239
9947
  let lastConfigContent = "";
10240
9948
  if (existsSync$1(configPath)) {
@@ -10323,6 +10031,27 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
10323
10031
  "event"
10324
10032
  );
10325
10033
  logger.log(`[svampConfig] ${s.phase === "done" ? "Loop complete" : "Loop gave up"} (iter ${iter})`);
10034
+ try {
10035
+ const cfg = readLoopJson(directory, sessionId, "loop.config.json") || {};
10036
+ const ev = readLoopJson(directory, sessionId, "evaluator-verdict.json") || {};
10037
+ const verdict = {
10038
+ type: "supervision:verdict",
10039
+ sessionId,
10040
+ round: iter,
10041
+ verdict: s.phase === "done" ? "approved" : "rework",
10042
+ criteria: typeof cfg.criteria === "string" ? cfg.criteria : "",
10043
+ judge: cfg.evaluator?.enabled !== false ? "agent" : cfg.oracle ? "oracle" : "agent",
10044
+ ...s.phase === "gave_up" ? { guidance: s.gave_up_reason || "gave up before meeting the criteria" } : ev.guidance ? { guidance: String(ev.guidance) } : {},
10045
+ ts: (/* @__PURE__ */ new Date()).toISOString()
10046
+ };
10047
+ try {
10048
+ const vp = join$1(getLoopDir(directory, sessionId), "supervisor-verdict.json");
10049
+ writeFileSync$1(vp, JSON.stringify(verdict, null, 2));
10050
+ } catch {
10051
+ }
10052
+ onSupervisionVerdict?.(verdict);
10053
+ } catch {
10054
+ }
10326
10055
  } catch {
10327
10056
  }
10328
10057
  };
@@ -10378,6 +10107,53 @@ function createSvampConfigChecker(directory, sessionId, getMetadata, setMetadata
10378
10107
  const { loop: _, ...restPatch } = patch;
10379
10108
  patch = restPatch;
10380
10109
  }
10110
+ if ("supervisor" in patch) {
10111
+ const sup = patch.supervisor;
10112
+ if (sup && typeof sup === "object" && typeof sup.criteria === "string" && sup.criteria.trim()) {
10113
+ const criteria = sup.criteria.trim();
10114
+ const judges = Array.isArray(sup.judges) ? sup.judges : [];
10115
+ const oracleJudge = judges.find((j) => j?.type === "oracle");
10116
+ const oracle = oracleJudge && typeof oracleJudge.cmd === "string" && oracleJudge.cmd.trim() ? oracleJudge.cmd.trim() : typeof sup.oracle === "string" && sup.oracle.trim() ? sup.oracle.trim() : void 0;
10117
+ const hasAgentJudge = judges.length === 0 || judges.some((j) => j?.type === "agent");
10118
+ const maxRounds = typeof sup.max_rounds === "number" ? sup.max_rounds : 20;
10119
+ const ok = initLoop(directory, {
10120
+ task: criteria,
10121
+ // P1: criteria seeds LOOP.md as the goal (skill carries it as config.criteria in #30)
10122
+ criteria,
10123
+ oracle,
10124
+ maxIterations: maxRounds,
10125
+ evaluator: hasAgentJudge,
10126
+ sessionId
10127
+ });
10128
+ if (ok) {
10129
+ const judgeLabel = judges.length ? judges.map((j) => j.type).join("\u2192") : "agent";
10130
+ const idle = getMetadata().lifecycleState === "idle";
10131
+ if (idle) {
10132
+ const existingQueue = getMetadata().messageQueue || [];
10133
+ const nudge = `You are now under supervision. Success criteria: ${criteria}
10134
+ Continue working until they are met, or verify and finish \u2014 an independent Stop gate will re-check before you can stop.`;
10135
+ setMetadata((m) => ({ ...m, messageQueue: [...existingQueue, { id: randomUUID$1(), text: nudge, displayText: `\u{1F441} Supervisor attached`, createdAt: Date.now() }] }));
10136
+ onLoopActivated?.();
10137
+ }
10138
+ sessionService.pushMessage(
10139
+ { type: "message", message: `\u{1F441} Supervisor attached \u2014 judges: ${judgeLabel}, max ${maxRounds}. Criteria: ${criteria.slice(0, 120)}${criteria.length > 120 ? "\u2026" : ""}` },
10140
+ "event"
10141
+ );
10142
+ logger.log(`[svampConfig] Supervisor attached (judges: ${judgeLabel}, max ${maxRounds})`);
10143
+ } else {
10144
+ sessionService.pushMessage(
10145
+ { type: "message", message: "Failed to attach supervisor \u2014 the loop skill could not be located. Reinstall with: svamp skills install loop --force", level: "error" },
10146
+ "event"
10147
+ );
10148
+ }
10149
+ } else {
10150
+ deactivateLoop(directory, sessionId);
10151
+ sessionService.pushMessage({ type: "message", message: "Supervisor detached." }, "event");
10152
+ logger.log(`[svampConfig] Supervisor detached`);
10153
+ }
10154
+ const { supervisor: _s, ...restPatch } = patch;
10155
+ patch = restPatch;
10156
+ }
10381
10157
  if (Object.keys(patch).length > 0) {
10382
10158
  const config = readSvampConfig(configPath);
10383
10159
  for (const [key, value] of Object.entries(patch)) {
@@ -10805,7 +10581,7 @@ async function startDaemon(options) {
10805
10581
  const list = loadExposedTunnels().filter((t) => t.name !== name);
10806
10582
  saveExposedTunnels(list);
10807
10583
  }
10808
- const { ServeManager } = await import('./serveManager-BnwAe-VX.mjs');
10584
+ const { ServeManager } = await import('./serveManager-DUjR8RGw.mjs');
10809
10585
  const serveManager = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
10810
10586
  ensureAutoInstalledSkills(logger).catch(() => {
10811
10587
  });
@@ -10835,6 +10611,31 @@ async function startDaemon(options) {
10835
10611
  logger.log(`Hypha connection permanently lost: ${reason}. Daemon continues running \u2014 restart manually to reconnect.`);
10836
10612
  });
10837
10613
  const pidToTrackedSession = /* @__PURE__ */ new Map();
10614
+ const routeSupervisionVerdict = (parentId, v) => {
10615
+ try {
10616
+ if (!parentId) return;
10617
+ const parent = Array.from(pidToTrackedSession.values()).find((t) => t.svampSessionId === parentId);
10618
+ const send = parent?.sessionRPCHandlers?.sendInboxMessage;
10619
+ if (!send) return;
10620
+ Promise.resolve(send({
10621
+ messageId: randomUUID$1(),
10622
+ from: `agent:${v.sessionId}`,
10623
+ fromSession: v.sessionId,
10624
+ to: parentId,
10625
+ subject: `supervision: ${v.verdict} (child ${v.sessionId.slice(0, 8)})`,
10626
+ body: `<supervision-verdict child="${v.sessionId}" verdict="${v.verdict}" round="${v.round}" judge="${v.judge}">
10627
+ ${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
10628
+ </supervision-verdict>`,
10629
+ timestamp: Date.now(),
10630
+ read: false,
10631
+ urgency: "normal",
10632
+ hopCount: 1
10633
+ })).catch(() => {
10634
+ });
10635
+ logger.log(`[supervision] verdict '${v.verdict}' for ${v.sessionId.slice(0, 8)} \u2192 parent ${parentId.slice(0, 8)}`);
10636
+ } catch {
10637
+ }
10638
+ };
10838
10639
  server.on("services_registered", () => {
10839
10640
  if (consecutiveHeartbeatFailures > 0) {
10840
10641
  logger.log(`Hypha reconnection successful \u2014 services re-registered (resetting ${consecutiveHeartbeatFailures} failures)`);
@@ -11857,13 +11658,12 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11857
11658
  }
11858
11659
  return child;
11859
11660
  };
11860
- const restartClaudeHandler = async (opts) => {
11661
+ const restartClaudeHandler = async () => {
11861
11662
  logger.log(`[Session ${sessionId}] Restart Claude requested`);
11862
11663
  if (isRestartingClaude || isSwitchingMode) {
11863
11664
  return { success: false, message: "Restart already in progress." };
11864
11665
  }
11865
11666
  isRestartingClaude = true;
11866
- let beforeRespawnError;
11867
11667
  try {
11868
11668
  if (claudeProcess && claudeProcess.exitCode === null) {
11869
11669
  isKillingClaude = true;
@@ -11875,14 +11675,6 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11875
11675
  if (trackedSession?.stopped) {
11876
11676
  return { success: false, message: "Session was stopped during restart." };
11877
11677
  }
11878
- if (opts?.beforeRespawn) {
11879
- try {
11880
- await opts.beforeRespawn();
11881
- } catch (hookErr) {
11882
- beforeRespawnError = hookErr?.message ?? String(hookErr);
11883
- logger.log(`[Session ${sessionId}] beforeRespawn hook failed: ${beforeRespawnError}`);
11884
- }
11885
- }
11886
11678
  if (claudeResumeId) {
11887
11679
  if (!stagedCredentials && shouldIsolateSession()) {
11888
11680
  try {
@@ -11895,9 +11687,6 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11895
11687
  spawnClaude(void 0, { permissionMode: currentPermissionMode });
11896
11688
  sessionService.updateMetadata(sessionMetadata);
11897
11689
  logger.log(`[Session ${sessionId}] Claude respawned with --resume ${claudeResumeId}`);
11898
- if (beforeRespawnError) {
11899
- return { success: false, message: `Edit failed (session restored): ${beforeRespawnError}` };
11900
- }
11901
11690
  return { success: true, message: "Claude process restarted successfully." };
11902
11691
  } else {
11903
11692
  logger.log(`[Session ${sessionId}] No resume ID \u2014 cannot restart`);
@@ -11911,26 +11700,6 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11911
11700
  isRestartingClaude = false;
11912
11701
  }
11913
11702
  };
11914
- const editHistoryIdleGuard = () => {
11915
- const lifecycle = sessionMetadata.lifecycleState;
11916
- if (lifecycle === "running" || lifecycle === "restarting") {
11917
- return { success: false, message: "Cannot edit while the agent is working \u2014 wait until it is idle." };
11918
- }
11919
- if (isRestartingClaude || isSwitchingMode || isKillingClaude) {
11920
- return { success: false, message: "Cannot edit during a restart/mode switch \u2014 try again in a moment." };
11921
- }
11922
- const queueLen = sessionMetadata.messageQueue?.length ?? 0;
11923
- if (queueLen > 0) {
11924
- return { success: false, message: "Cannot edit while messages are queued." };
11925
- }
11926
- if (isLoopActiveForSession(directory, sessionId)) {
11927
- return { success: false, message: "Cannot edit history while a loop is active." };
11928
- }
11929
- if (!claudeResumeId) {
11930
- return { success: false, message: "No Claude transcript to edit yet." };
11931
- }
11932
- return null;
11933
- };
11934
11703
  if (shouldIsolateSession()) {
11935
11704
  try {
11936
11705
  stagedCredentials = await stageCredentialsForSharing(sessionId);
@@ -11951,7 +11720,6 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
11951
11720
  logger.log(`[Session ${sessionId}] User message received`);
11952
11721
  userMessagePending = true;
11953
11722
  turnInitiatedByUser = true;
11954
- clearUndoSnapshot(join$1(getSessionDir(directory, sessionId), "undo"));
11955
11723
  let text;
11956
11724
  let msgMeta = meta;
11957
11725
  try {
@@ -12171,86 +11939,6 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
12171
11939
  }
12172
11940
  },
12173
11941
  onRestartClaude: restartClaudeHandler,
12174
- onEditTranscript: async ({ role, fromEnd, oldText, newText, applyStoreA }) => {
12175
- const gate = editHistoryIdleGuard();
12176
- if (gate) return gate;
12177
- logger.log(`[Session ${sessionId}] Edit history: role=${role} fromEnd=${fromEnd} \u2192 kill + rewrite + resume`);
12178
- const result = await restartClaudeHandler({
12179
- beforeRespawn: async () => {
12180
- const file = resolveTranscriptPath(directory, claudeResumeId);
12181
- if (!transcriptSessionIdMatches(file, claudeResumeId)) {
12182
- throw new Error("transcript file id mismatch (session rotated) \u2014 aborting edit");
12183
- }
12184
- const lines = parseTranscript(file);
12185
- const anchor = findAnchorIndex(lines, { role, fromEnd, oldText });
12186
- if (!anchor.ok) throw new Error(anchor.reason);
12187
- const sessDir = getSessionDir(directory, sessionId);
12188
- saveUndoSnapshot(join$1(sessDir, "undo"), file, join$1(sessDir, "messages.jsonl"), {
12189
- preEditClaudeSessionId: claudeResumeId,
12190
- createdAt: Date.now(),
12191
- label: "edit"
12192
- });
12193
- const edit = applyTranscriptEdit(file, anchor.index, newText);
12194
- if (!edit.ok) throw new Error(edit.reason);
12195
- applyStoreA();
12196
- logger.log(`[Session ${sessionId}] Edit applied \u2014 transcript truncated after line ${anchor.index}`);
12197
- }
12198
- });
12199
- if (result.success && claudeResumeId && !trackedSession.stopped) {
12200
- saveSession({
12201
- sessionId,
12202
- directory,
12203
- claudeResumeId,
12204
- permissionMode: currentPermissionMode,
12205
- spawnMeta: lastSpawnMeta,
12206
- metadata: sessionMetadata,
12207
- createdAt: sessionCreatedAt,
12208
- machineId,
12209
- wasProcessing: false
12210
- });
12211
- artifactSync.syncSession(sessionId, getSessionDir(directory, sessionId), sessionMetadata, machineId).catch(() => {
12212
- });
12213
- }
12214
- return result;
12215
- },
12216
- onUndoEdit: async ({ restoreStoreA }) => {
12217
- const gate = editHistoryIdleGuard();
12218
- if (gate) return gate;
12219
- const undoDir = join$1(getSessionDir(directory, sessionId), "undo");
12220
- const meta = readUndoMeta(undoDir);
12221
- if (!meta) return { success: false, message: "Nothing to undo." };
12222
- logger.log(`[Session ${sessionId}] Undo edit \u2192 restore pre-edit transcript ${meta.preEditClaudeSessionId} + messages, resume`);
12223
- const result = await restartClaudeHandler({
12224
- beforeRespawn: async () => {
12225
- const target = resolveTranscriptPath(directory, meta.preEditClaudeSessionId);
12226
- if (!restoreTranscriptFromUndo(undoDir, target)) {
12227
- throw new Error("undo snapshot missing transcript backup");
12228
- }
12229
- restoreStoreA();
12230
- claudeResumeId = meta.preEditClaudeSessionId;
12231
- sessionMetadata = { ...sessionMetadata, claudeSessionId: claudeResumeId };
12232
- }
12233
- });
12234
- if (result.success) {
12235
- clearUndoSnapshot(undoDir);
12236
- if (claudeResumeId && !trackedSession.stopped) {
12237
- saveSession({
12238
- sessionId,
12239
- directory,
12240
- claudeResumeId,
12241
- permissionMode: currentPermissionMode,
12242
- spawnMeta: lastSpawnMeta,
12243
- metadata: sessionMetadata,
12244
- createdAt: sessionCreatedAt,
12245
- machineId,
12246
- wasProcessing: false
12247
- });
12248
- artifactSync.syncSession(sessionId, getSessionDir(directory, sessionId), sessionMetadata, machineId).catch(() => {
12249
- });
12250
- }
12251
- }
12252
- return result;
12253
- },
12254
11942
  onUpdateSecurityContext: async (newSecurityContext) => {
12255
11943
  logger.log(`[Session ${sessionId}] Security context update requested \u2014 restarting agent`);
12256
11944
  sessionMetadata = { ...sessionMetadata, securityContext: newSecurityContext };
@@ -12511,7 +12199,8 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
12511
12199
  logger,
12512
12200
  () => {
12513
12201
  if (!trackedSession?.stopped) setTimeout(() => processMessageQueueRef?.(), 200);
12514
- }
12202
+ },
12203
+ (v) => routeSupervisionVerdict(sessionMetadata.parentSessionId, v)
12515
12204
  );
12516
12205
  checkSvampConfig = svampConfig.check;
12517
12206
  cleanupSvampConfig = svampConfig.cleanup;
@@ -13008,7 +12697,8 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
13008
12697
  }
13009
12698
  });
13010
12699
  }
13011
- }
12700
+ },
12701
+ (v) => routeSupervisionVerdict(sessionMetadata.parentSessionId, v)
13012
12702
  );
13013
12703
  const checkSvampConfig = svampConfigChecker.check;
13014
12704
  const writeSvampConfigPatchAcp = svampConfigChecker.writeConfig;
@@ -13403,7 +13093,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
13403
13093
  const specs = loadExposedTunnels();
13404
13094
  if (specs.length === 0) return;
13405
13095
  logger.log(`[exposed-tunnels] Restoring ${specs.length} tunnel(s) from ${EXPOSED_TUNNELS_FILE}`);
13406
- const { FrpcTunnel } = await import('./frpc-1aSnPVrE.mjs');
13096
+ const { FrpcTunnel } = await import('./frpc-63NvAmuT.mjs');
13407
13097
  for (const spec of specs) {
13408
13098
  if (tunnels.has(spec.name)) continue;
13409
13099
  try {