svamp-cli 0.2.181 → 0.2.183

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.
Files changed (25) hide show
  1. package/dist/{agentCommands-CKSZimNF.mjs → agentCommands-CDRs-TTe.mjs} +5 -5
  2. package/dist/{auth-BVDufOmQ.mjs → auth-BwjvUcKt.mjs} +1 -1
  3. package/dist/cli.mjs +60 -60
  4. package/dist/{commands-klb9OsmV.mjs → commands-Bv7vbi-z.mjs} +1 -1
  5. package/dist/{commands-DUTReua3.mjs → commands-C2mXJNwA.mjs} +20 -3
  6. package/dist/{commands-DuvX1TtC.mjs → commands-D4hGdv1B.mjs} +5 -5
  7. package/dist/{commands-CL9hxClH.mjs → commands-DQ_kpWqH.mjs} +1 -1
  8. package/dist/{commands-BS2iZc30.mjs → commands-D_wD9qIr.mjs} +2 -2
  9. package/dist/{commands-qbv-1BoA.mjs → commands-ix9L1jaQ.mjs} +1 -1
  10. package/dist/{commands-BG-Y0806.mjs → commands-pMwoqGpe.mjs} +2 -2
  11. package/dist/{fleet-IKkKrYCK.mjs → fleet-B4hfyHp4.mjs} +1 -1
  12. package/dist/{frpc-CLJ1PNTL.mjs → frpc-BzkAtKLy.mjs} +1 -1
  13. package/dist/{headlessCli-C8-hCKbi.mjs → headlessCli-ohRvD7CT.mjs} +2 -2
  14. package/dist/index.mjs +1 -1
  15. package/dist/{package-D4USifdR.mjs → package-CcFzcCql.mjs} +1 -1
  16. package/dist/{rpc-D3J1JM29.mjs → rpc-BwqRLir7.mjs} +1 -1
  17. package/dist/{rpc-DLPG3JvW.mjs → rpc-degyMl0V.mjs} +8 -2
  18. package/dist/{run-C9PMX7NQ.mjs → run-68xel1pO.mjs} +180 -95
  19. package/dist/{run-D5-ounOl.mjs → run-B2aWNd0R.mjs} +1 -1
  20. package/dist/{scheduler-Bgs6H33k.mjs → scheduler-D0FGGxti.mjs} +3 -2
  21. package/dist/{serveCommands-Dx5BiAfU.mjs → serveCommands-Bo2xZPIZ.mjs} +5 -5
  22. package/dist/{serveManager-B6bHAn0B.mjs → serveManager-BKbhWnMy.mjs} +2 -2
  23. package/dist/{sideband-Dd1I6S5u.mjs → sideband-B1jzlbuf.mjs} +1 -1
  24. package/dist/{store-BPL__e4D.mjs → store-D-fJ9ANL.mjs} +17 -2
  25. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  import os from 'os';
2
2
  import fs__default from 'fs';
3
3
  import { resolve, join, relative } from 'path';
4
- import { C as parseFrontmatter, D as getSkillsServer, E as getSkillsWorkspaceName, F as getSkillsCollectionName, G as fetchWithTimeout, H as searchSkills, I as SKILLS_DIR, J as getSkillInfo, K as downloadSkillFile, L as listSkillFiles } from './run-C9PMX7NQ.mjs';
4
+ import { C as parseFrontmatter, D as getSkillsServer, E as getSkillsWorkspaceName, F as getSkillsCollectionName, G as fetchWithTimeout, H as searchSkills, I as SKILLS_DIR, J as getSkillInfo, K as downloadSkillFile, L as listSkillFiles } from './run-68xel1pO.mjs';
5
5
  import 'fs/promises';
6
6
  import 'url';
7
7
  import 'child_process';
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from 'node:fs';
2
- import { connectAndGetMachine, resolveSessionId, createWorktree, connectAndResolveSession } from './commands-CL9hxClH.mjs';
2
+ import { connectAndGetMachine, resolveSessionId, createWorktree, connectAndResolveSession } from './commands-DQ_kpWqH.mjs';
3
3
  import { execSync } from 'node:child_process';
4
- import { u as updateIssue, o as addComment, p as addIssue, x as shortId } from './run-C9PMX7NQ.mjs';
4
+ import { u as updateIssue, o as addComment, p as addIssue, x as shortId } from './run-68xel1pO.mjs';
5
5
  import 'node:path';
6
6
  import 'node:os';
7
7
  import 'os';
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import os from 'node:os';
4
- import { c as connectToHypha } from './run-C9PMX7NQ.mjs';
4
+ import { c as connectToHypha } from './run-68xel1pO.mjs';
5
5
  import { PINNED_CLAUDE_CODE_VERSION } from './pinnedClaudeCode-HydRNEt7.mjs';
6
6
  import 'os';
7
7
  import 'fs/promises';
@@ -4,7 +4,7 @@ import { mkdirSync, writeFileSync, unlinkSync, existsSync, chmodSync, readFileSy
4
4
  import { join } from 'path';
5
5
  import { homedir, platform, arch } from 'os';
6
6
  import { randomUUID, createHash } from 'crypto';
7
- import { h as getFrpsSubdomainHost, i as getFrpsServerPort, j as getFrpsServerAddr } from './run-C9PMX7NQ.mjs';
7
+ import { h as getFrpsSubdomainHost, i as getFrpsServerPort, j as getFrpsServerAddr } from './run-68xel1pO.mjs';
8
8
  import 'fs/promises';
9
9
  import 'url';
10
10
  import 'node:crypto';
@@ -1,5 +1,5 @@
1
- import { M as resolveModel, _ as describeMisconfiguration, $ as buildMachineDeps } from './run-C9PMX7NQ.mjs';
2
- import { handleRealtimeEvent, initMachineVoiceSession } from './sideband-Dd1I6S5u.mjs';
1
+ import { M as resolveModel, _ as describeMisconfiguration, $ as buildMachineDeps } from './run-68xel1pO.mjs';
2
+ import { handleRealtimeEvent, initMachineVoiceSession } from './sideband-B1jzlbuf.mjs';
3
3
  import { WebSocket } from 'ws';
4
4
  import { execSync, spawn } from 'child_process';
5
5
  import 'os';
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { c as connectToHypha, a as createSessionStore, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, s as startDaemon, b as stopDaemon } from './run-C9PMX7NQ.mjs';
1
+ export { c as connectToHypha, a as createSessionStore, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, s as startDaemon, b as stopDaemon } from './run-68xel1pO.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -1,5 +1,5 @@
1
1
  var name = "svamp-cli";
2
- var version = "0.2.181";
2
+ var version = "0.2.183";
3
3
  var description = "Svamp CLI — AI workspace daemon on Hypha Cloud";
4
4
  var author = "Amun AI AB";
5
5
  var license = "SEE LICENSE IN LICENSE";
@@ -1,4 +1,4 @@
1
- import { m as resolveProjectRoot, u as updateIssue, n as getIssue, o as addComment, p as addIssue, q as listIssues, t as searchIssues } from './run-C9PMX7NQ.mjs';
1
+ import { m as resolveProjectRoot, u as updateIssue, n as getIssue, o as addComment, p as addIssue, q as listIssues, t as searchIssues } from './run-68xel1pO.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -1,6 +1,6 @@
1
1
  import { spawnSync } from 'node:child_process';
2
- import { m as resolveProjectRoot } from './run-C9PMX7NQ.mjs';
3
- import { g as getWorkflow, w as workflowSteps, r as removeWorkflow, s as saveWorkflow, a as rawWorkflow, l as listWorkflows } from './store-BPL__e4D.mjs';
2
+ import { m as resolveProjectRoot } from './run-68xel1pO.mjs';
3
+ import { g as getWorkflow, w as workflowSteps, s as setWorkflowEnabled, r as removeWorkflow, a as saveWorkflow, b as rawWorkflow, l as listWorkflows } from './store-D-fJ9ANL.mjs';
4
4
  import 'os';
5
5
  import 'fs/promises';
6
6
  import 'fs';
@@ -53,6 +53,12 @@ function workflowRpc(cwd, params) {
53
53
  }
54
54
  case "remove":
55
55
  return { removed: removeWorkflow(root, String(params.name)) };
56
+ case "enable":
57
+ case "disable": {
58
+ const wf = setWorkflowEnabled(root, String(params.name), op === "enable");
59
+ if (!wf) throw new Error(`Workflow not found: ${params.name}`);
60
+ return wf;
61
+ }
56
62
  case "run": {
57
63
  const wf = getWorkflow(root, String(params.name));
58
64
  if (!wf) throw new Error(`Workflow not found: ${params.name}`);
@@ -84,6 +84,16 @@ function normalizeAllowedUser(input, addedBy) {
84
84
  }
85
85
  const ISOLATION_PREFERENCE = ["nono", "docker", "podman"];
86
86
 
87
+ let ownerWorkspace;
88
+ function setOwnerWorkspace(ws) {
89
+ ownerWorkspace = ws || void 0;
90
+ }
91
+ function isSameOwnerWorkspace(context) {
92
+ const ws = context?.ws;
93
+ if (!ownerWorkspace || ownerWorkspace === "*") return false;
94
+ if (!ws || ws === "*") return false;
95
+ return ws === ownerWorkspace;
96
+ }
87
97
  function resolveRoleLevel(sharing, userEmail) {
88
98
  let level = -1;
89
99
  if (userEmail && Array.isArray(sharing.allowedUsers)) {
@@ -107,6 +117,7 @@ function levelToRole(level) {
107
117
  }
108
118
  function authorizeRequest(context, sharing, requiredRole = "view") {
109
119
  if (!context?.user) return;
120
+ if (isSameOwnerWorkspace(context)) return;
110
121
  const userEmail = context.user.email;
111
122
  if (!sharing) return;
112
123
  if (userEmail && sharing.owner && userEmail.toLowerCase() === sharing.owner.toLowerCase()) return;
@@ -129,6 +140,7 @@ function authorizeRequest(context, sharing, requiredRole = "view") {
129
140
  }
130
141
  function getEffectiveRole(context, sharing) {
131
142
  if (!context?.user) return "admin";
143
+ if (isSameOwnerWorkspace(context)) return "admin";
132
144
  if (!sharing) return "admin";
133
145
  const userEmail = context.user.email;
134
146
  if (userEmail && sharing.owner && userEmail.toLowerCase() === sharing.owner.toLowerCase()) return "admin";
@@ -1759,6 +1771,10 @@ async function registerMachineService(server, machineId, metadata, daemonState,
1759
1771
  let currentDaemonState = { ...daemonState };
1760
1772
  let metadataVersion = 1;
1761
1773
  let daemonStateVersion = 1;
1774
+ try {
1775
+ setOwnerWorkspace(server.config?.workspace);
1776
+ } catch {
1777
+ }
1762
1778
  let lastInboundRpcAt = Date.now();
1763
1779
  const trackInbound = () => {
1764
1780
  lastInboundRpcAt = Date.now();
@@ -2699,7 +2715,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
2699
2715
  const tunnels = handlers.tunnels;
2700
2716
  if (!tunnels) throw new Error("Tunnel management not available");
2701
2717
  if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
2702
- const { FrpcTunnel } = await import('./frpc-CLJ1PNTL.mjs');
2718
+ const { FrpcTunnel } = await import('./frpc-BzkAtKLy.mjs');
2703
2719
  const tunnel = new FrpcTunnel({
2704
2720
  name: params.name,
2705
2721
  ports: params.ports,
@@ -3146,7 +3162,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
3146
3162
  }
3147
3163
  const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
3148
3164
  const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
3149
- const { toolsForRole } = await import('./sideband-Dd1I6S5u.mjs');
3165
+ const { toolsForRole } = await import('./sideband-B1jzlbuf.mjs');
3150
3166
  const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
3151
3167
  return fmt(r2);
3152
3168
  }
@@ -3245,7 +3261,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
3245
3261
  if (r.error || !r.sender) return { error: r.error || "unauthorized" };
3246
3262
  const callId = "call_" + Math.random().toString(16).slice(2, 12);
3247
3263
  const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
3248
- const { queryCore } = await import('./commands-CL9hxClH.mjs');
3264
+ const { queryCore } = await import('./commands-DQ_kpWqH.mjs');
3249
3265
  const timeout = c.reply?.timeout_sec || 120;
3250
3266
  let result;
3251
3267
  try {
@@ -3515,6 +3531,35 @@ function applyInboxClear(inbox, opts) {
3515
3531
  return { kept, removed: inbox.length - kept.length };
3516
3532
  }
3517
3533
 
3534
+ function envInt(name, fallback) {
3535
+ const raw = process.env[name];
3536
+ if (raw === void 0 || raw === "") return fallback;
3537
+ const n = parseInt(raw, 10);
3538
+ return Number.isFinite(n) && n >= 0 ? n : fallback;
3539
+ }
3540
+ function getRateLimitRetryConfig() {
3541
+ return {
3542
+ maxRetries: envInt("SVAMP_RATELIMIT_MAX_RETRIES", 10),
3543
+ baseMs: envInt("SVAMP_RATELIMIT_BASE_MS", 5e3),
3544
+ capMs: envInt("SVAMP_RATELIMIT_CAP_MS", 12e4)
3545
+ };
3546
+ }
3547
+ function computeRetryDelayMs(attempt, cfg = getRateLimitRetryConfig(), rng = Math.random) {
3548
+ const a = Math.max(0, Math.floor(attempt));
3549
+ const expo = Math.min(a, 30);
3550
+ const nominal = Math.min(cfg.capMs, cfg.baseMs * Math.pow(2, expo));
3551
+ const half = nominal / 2;
3552
+ return Math.round(half + rng() * half);
3553
+ }
3554
+ function isRetryableRateLimit(text, apiErrorStatus) {
3555
+ const t = (text || "").toLowerCase();
3556
+ const hardLimit = t.includes("quota") || t.includes("credit") || t.includes("balance") || t.includes("billing") || t.includes("payment") || t.includes("subscription") || t.includes("1m-context") || t.includes("1m context") || t.includes("insufficient") || t.includes("usage limit") && !t.includes("not your usage limit");
3557
+ if (hardLimit) return false;
3558
+ const authIssue = t.includes("unauthorized") || t.includes("invalid api key") || t.includes("authentication") || t.includes("forbidden");
3559
+ if (authIssue) return false;
3560
+ return apiErrorStatus === 429 || apiErrorStatus === 529 || apiErrorStatus === 503 || /\b(429|529|503)\b/.test(t) || t.includes("temporarily limiting") || t.includes("not your usage limit") || t.includes("overloaded") || t.includes("overload") || t.includes("too many requests") || t.includes("server is busy") || t.includes("rate limit") || t.includes("rate-limit") || t.includes("rate limited");
3561
+ }
3562
+
3518
3563
  const PARTICIPANTS_CHANNEL_ID = "sys-participants";
3519
3564
  function channelPublicView(c) {
3520
3565
  return { id: c.id, name: c.name, description: c.description, identity: { mode: c.identity?.mode }, action: c.action?.kind, bind: c.bind };
@@ -3845,7 +3890,7 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
3845
3890
  }
3846
3891
  };
3847
3892
  ensureParticipantsChannel(initialMetadata.sharing);
3848
- const forkClaudePrint = async (prompt, claudeSessionId, cwd, maxTurns = 6, timeoutMs = 6e4) => {
3893
+ const forkClaudeOnce = async (prompt, claudeSessionId, cwd, maxTurns, timeoutMs) => {
3849
3894
  const { spawn } = await import('child_process');
3850
3895
  return new Promise((resolve) => {
3851
3896
  const child = spawn("claude", [
@@ -3882,14 +3927,37 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
3882
3927
  }
3883
3928
  try {
3884
3929
  const result = JSON.parse(stdout);
3930
+ if (result && result.is_error) {
3931
+ resolve({ success: false, error: String(result.result || result.error || stderr || "error") });
3932
+ return;
3933
+ }
3885
3934
  resolve({ success: true, text: result.result || result.text || stdout });
3886
3935
  } catch {
3936
+ if (code !== 0) {
3937
+ resolve({ success: false, error: stderr || stdout.trim() || `claude exited with code ${code}` });
3938
+ return;
3939
+ }
3887
3940
  resolve({ success: true, text: stdout.trim() });
3888
3941
  }
3889
3942
  });
3890
3943
  child.on("error", (err) => resolve({ success: false, error: err.message }));
3891
3944
  });
3892
3945
  };
3946
+ const forkClaudePrint = async (prompt, claudeSessionId, cwd, maxTurns = 6, timeoutMs = 6e4) => {
3947
+ const cfg2 = getRateLimitRetryConfig();
3948
+ let last = { success: false, error: "" };
3949
+ for (let attempt = 0; ; attempt++) {
3950
+ last = await forkClaudeOnce(prompt, claudeSessionId, cwd, maxTurns, timeoutMs);
3951
+ if (last.success) return last;
3952
+ if (!isRetryableRateLimit(last.error || "") || attempt >= cfg2.maxRetries) return last;
3953
+ const delay = computeRetryDelayMs(attempt, cfg2);
3954
+ try {
3955
+ console.error(`[btw] rate-limited \u2014 holding ${Math.round(delay / 1e3)}s then retrying (attempt ${attempt + 1}/${cfg2.maxRetries})`);
3956
+ } catch {
3957
+ }
3958
+ await new Promise((r) => setTimeout(r, delay));
3959
+ }
3960
+ };
3893
3961
  const performEdit = async (target, newText) => {
3894
3962
  if (!callbacks.onEditTranscript) return { success: false, message: "Editing history is not supported for this session." };
3895
3963
  const info = editableInfo(target);
@@ -10201,35 +10269,6 @@ function resolveContextWindow(opts) {
10201
10269
  return candidate;
10202
10270
  }
10203
10271
 
10204
- function envInt(name, fallback) {
10205
- const raw = process.env[name];
10206
- if (raw === void 0 || raw === "") return fallback;
10207
- const n = parseInt(raw, 10);
10208
- return Number.isFinite(n) && n >= 0 ? n : fallback;
10209
- }
10210
- function getRateLimitRetryConfig() {
10211
- return {
10212
- maxRetries: envInt("SVAMP_RATELIMIT_MAX_RETRIES", 10),
10213
- baseMs: envInt("SVAMP_RATELIMIT_BASE_MS", 5e3),
10214
- capMs: envInt("SVAMP_RATELIMIT_CAP_MS", 12e4)
10215
- };
10216
- }
10217
- function computeRetryDelayMs(attempt, cfg = getRateLimitRetryConfig(), rng = Math.random) {
10218
- const a = Math.max(0, Math.floor(attempt));
10219
- const expo = Math.min(a, 30);
10220
- const nominal = Math.min(cfg.capMs, cfg.baseMs * Math.pow(2, expo));
10221
- const half = nominal / 2;
10222
- return Math.round(half + rng() * half);
10223
- }
10224
- function isRetryableRateLimit(text, apiErrorStatus) {
10225
- const t = (text || "").toLowerCase();
10226
- const hardLimit = t.includes("quota") || t.includes("credit") || t.includes("balance") || t.includes("billing") || t.includes("payment") || t.includes("subscription") || t.includes("1m-context") || t.includes("1m context") || t.includes("insufficient") || t.includes("usage limit") && !t.includes("not your usage limit");
10227
- if (hardLimit) return false;
10228
- const authIssue = t.includes("unauthorized") || t.includes("invalid api key") || t.includes("authentication") || t.includes("forbidden");
10229
- if (authIssue) return false;
10230
- return apiErrorStatus === 429 || apiErrorStatus === 529 || apiErrorStatus === 503 || /\b(429|529|503)\b/.test(t) || t.includes("temporarily limiting") || t.includes("not your usage limit") || t.includes("overloaded") || t.includes("overload") || t.includes("too many requests") || t.includes("server is busy") || t.includes("rate limit") || t.includes("rate-limit") || t.includes("rate limited");
10231
- }
10232
-
10233
10272
  const SVAMP_HOME$1 = process.env.SVAMP_HOME || join(os.homedir(), ".svamp");
10234
10273
  function generateHookSettings(portOrOptions = {}) {
10235
10274
  const opts = typeof portOrOptions === "number" ? { sessionStartPort: portOrOptions } : portOrOptions;
@@ -11568,7 +11607,7 @@ async function startDaemon(options) {
11568
11607
  saveExposedTunnels(list);
11569
11608
  }
11570
11609
  async function createExposedTunnel(spec) {
11571
- const { FrpcTunnel } = await import('./frpc-CLJ1PNTL.mjs');
11610
+ const { FrpcTunnel } = await import('./frpc-BzkAtKLy.mjs');
11572
11611
  const tunnel = new FrpcTunnel({
11573
11612
  name: spec.name,
11574
11613
  ports: spec.ports,
@@ -11588,7 +11627,7 @@ async function startDaemon(options) {
11588
11627
  return tunnel;
11589
11628
  }
11590
11629
  const tunnelRecreateState = /* @__PURE__ */ new Map();
11591
- const { ServeManager } = await import('./serveManager-B6bHAn0B.mjs');
11630
+ const { ServeManager } = await import('./serveManager-BKbhWnMy.mjs');
11592
11631
  const serveManager = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
11593
11632
  ensureAutoInstalledSkills(logger).catch(() => {
11594
11633
  });
@@ -12942,73 +12981,119 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
12942
12981
  return;
12943
12982
  }
12944
12983
  if (msgMeta?.btw && claudeResumeId) {
12984
+ const btwResumeId = claudeResumeId;
12945
12985
  logger.log(`[Session ${sessionId}] /btw side-channel: "${text.substring(0, 80)}..."`);
12946
12986
  sessionService.pushMessage(
12947
12987
  { type: "message", message: `/btw ${text}`, level: "btw-question" },
12948
12988
  "event"
12949
12989
  );
12950
12990
  signalProcessing(true);
12951
- const btwArgs = [
12952
- "--print",
12953
- text,
12954
- "--resume",
12955
- claudeResumeId,
12956
- "--fork-session",
12957
- "--no-session-persistence",
12958
- // /btw is non-interactive; without bypass the
12959
- // forked Claude pauses on tool prompts and the
12960
- // user just sees a hanging side-channel.
12961
- "--permission-mode",
12962
- "bypassPermissions",
12963
- "--output-format",
12964
- "stream-json",
12965
- "--verbose"
12966
- ];
12967
- const btwProcess = spawn$1("claude", btwArgs, {
12968
- cwd: directory,
12969
- env: { ...process.env },
12970
- stdio: ["pipe", "pipe", "pipe"]
12971
- });
12972
- let btwResult = "";
12973
- let btwBuffer = "";
12974
- btwProcess.stdout.on("data", (chunk) => {
12975
- btwBuffer += chunk.toString();
12976
- let newlineIdx;
12977
- while ((newlineIdx = btwBuffer.indexOf("\n")) !== -1) {
12978
- const line = btwBuffer.substring(0, newlineIdx);
12979
- btwBuffer = btwBuffer.substring(newlineIdx + 1);
12980
- if (!line.trim()) continue;
12981
- try {
12982
- const msg = JSON.parse(line);
12983
- if (msg.type === "assistant" && msg.message?.content) {
12984
- for (const block of msg.message.content) {
12985
- if (block.type === "text" && block.text) {
12986
- btwResult += block.text;
12991
+ const runBtwOnce = () => new Promise((resolve2, reject) => {
12992
+ const btwArgs = [
12993
+ "--print",
12994
+ text,
12995
+ "--resume",
12996
+ btwResumeId,
12997
+ "--fork-session",
12998
+ "--no-session-persistence",
12999
+ // /btw is non-interactive; without bypass the forked Claude
13000
+ // pauses on tool prompts and the user sees a hanging side-channel.
13001
+ "--permission-mode",
13002
+ "bypassPermissions",
13003
+ "--output-format",
13004
+ "stream-json",
13005
+ "--verbose"
13006
+ ];
13007
+ const btwProcess = spawn$1("claude", btwArgs, {
13008
+ cwd: directory,
13009
+ env: { ...process.env },
13010
+ stdio: ["pipe", "pipe", "pipe"]
13011
+ });
13012
+ let btwResult = "";
13013
+ let btwBuffer = "";
13014
+ let btwStderr = "";
13015
+ let settled = false;
13016
+ btwProcess.stderr?.on("data", (chunk) => {
13017
+ btwStderr += chunk.toString();
13018
+ });
13019
+ btwProcess.stdout.on("data", (chunk) => {
13020
+ btwBuffer += chunk.toString();
13021
+ let newlineIdx;
13022
+ while ((newlineIdx = btwBuffer.indexOf("\n")) !== -1) {
13023
+ const line = btwBuffer.substring(0, newlineIdx);
13024
+ btwBuffer = btwBuffer.substring(newlineIdx + 1);
13025
+ if (!line.trim()) continue;
13026
+ try {
13027
+ const msg = JSON.parse(line);
13028
+ if (msg.type === "assistant" && msg.message?.content) {
13029
+ for (const block of msg.message.content) {
13030
+ if (block.type === "text" && block.text) {
13031
+ btwResult += block.text;
13032
+ }
13033
+ }
13034
+ } else if (msg.type === "result") {
13035
+ if (settled) continue;
13036
+ settled = true;
13037
+ if (msg.is_error) {
13038
+ const err = new Error(String(msg.result || "error"));
13039
+ err.retryable = isRetryableRateLimit(String(msg.result || ""), msg.api_error_status);
13040
+ reject(err);
13041
+ } else {
13042
+ resolve2(btwResult || msg.result || "(no response)");
12987
13043
  }
12988
13044
  }
12989
- } else if (msg.type === "result") {
12990
- const finalText = btwResult || msg.result || "(no response)";
12991
- sessionService.pushMessage(
12992
- { type: "message", message: finalText, level: "btw-answer" },
12993
- "event"
12994
- );
12995
- signalProcessing(false);
12996
- sessionService.sendSessionEnd();
12997
- logger.log(`[Session ${sessionId}] /btw complete (${finalText.length} chars)`);
13045
+ } catch {
12998
13046
  }
12999
- } catch {
13000
13047
  }
13001
- }
13048
+ });
13049
+ btwProcess.on("exit", (code) => {
13050
+ if (settled) return;
13051
+ settled = true;
13052
+ if (code === 0) {
13053
+ resolve2(btwResult || "(no response)");
13054
+ return;
13055
+ }
13056
+ const detail = btwStderr.trim() || btwResult.trim();
13057
+ const err = new Error(detail || `claude exited with code ${code}`);
13058
+ err.retryable = isRetryableRateLimit(detail || "");
13059
+ reject(err);
13060
+ });
13061
+ btwProcess.on("error", (e) => {
13062
+ if (settled) return;
13063
+ settled = true;
13064
+ const err = new Error(e.message);
13065
+ err.retryable = false;
13066
+ reject(err);
13067
+ });
13002
13068
  });
13003
- btwProcess.on("exit", (code) => {
13004
- if (code !== 0) {
13005
- sessionService.pushMessage(
13006
- { type: "message", message: `/btw failed (exit code ${code})`, level: "error" },
13007
- "event"
13008
- );
13009
- signalProcessing(false);
13010
- sessionService.sendSessionEnd();
13069
+ (async () => {
13070
+ let lastErr = null;
13071
+ for (let attempt = 0; ; attempt++) {
13072
+ try {
13073
+ const finalText = await runBtwOnce();
13074
+ sessionService.pushMessage(
13075
+ { type: "message", message: finalText, level: "btw-answer" },
13076
+ "event"
13077
+ );
13078
+ logger.log(`[Session ${sessionId}] /btw complete (${finalText.length} chars)`);
13079
+ return;
13080
+ } catch (e) {
13081
+ lastErr = e;
13082
+ if (!e?.retryable || attempt >= RATELIMIT_CFG.maxRetries) break;
13083
+ const delayMs = computeRetryDelayMs(attempt, RATELIMIT_CFG);
13084
+ logger.log(`[Session ${sessionId}] /btw rate-limited \u2014 holding ${Math.round(delayMs / 1e3)}s then retrying (attempt ${attempt + 1}/${RATELIMIT_CFG.maxRetries})`);
13085
+ await new Promise((r) => setTimeout(r, delayMs));
13086
+ }
13011
13087
  }
13088
+ const detail = (lastErr?.message || "unknown error").toString().slice(0, 500);
13089
+ sessionService.pushMessage(
13090
+ { type: "message", message: `/btw failed: ${detail}`, level: "error" },
13091
+ "event"
13092
+ );
13093
+ logger.log(`[Session ${sessionId}] /btw failed: ${detail}`);
13094
+ })().finally(() => {
13095
+ signalProcessing(false);
13096
+ sessionService.sendSessionEnd();
13012
13097
  });
13013
13098
  return;
13014
13099
  }
@@ -13379,11 +13464,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
13379
13464
  });
13380
13465
  },
13381
13466
  onIssue: async (params) => {
13382
- const { issueRpc } = await import('./rpc-D3J1JM29.mjs');
13467
+ const { issueRpc } = await import('./rpc-BwqRLir7.mjs');
13383
13468
  return issueRpc(params?.cwd || directory, params || {});
13384
13469
  },
13385
13470
  onWorkflow: async (params) => {
13386
- const { workflowRpc } = await import('./rpc-DLPG3JvW.mjs');
13471
+ const { workflowRpc } = await import('./rpc-degyMl0V.mjs');
13387
13472
  return workflowRpc(params?.cwd || directory, params || {});
13388
13473
  },
13389
13474
  onRipgrep: async (args, cwd) => {
@@ -13889,11 +13974,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
13889
13974
  });
13890
13975
  },
13891
13976
  onIssue: async (params) => {
13892
- const { issueRpc } = await import('./rpc-D3J1JM29.mjs');
13977
+ const { issueRpc } = await import('./rpc-BwqRLir7.mjs');
13893
13978
  return issueRpc(params?.cwd || directory, params || {});
13894
13979
  },
13895
13980
  onWorkflow: async (params) => {
13896
- const { workflowRpc } = await import('./rpc-DLPG3JvW.mjs');
13981
+ const { workflowRpc } = await import('./rpc-degyMl0V.mjs');
13897
13982
  return workflowRpc(params?.cwd || directory, params || {});
13898
13983
  },
13899
13984
  onRipgrep: async (args, cwd) => {
@@ -14646,7 +14731,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
14646
14731
  const PING_TIMEOUT_MS = 15e3;
14647
14732
  const POST_RECONNECT_GRACE_MS = 2e4;
14648
14733
  const RECONNECT_JITTER_MS = 2500;
14649
- const { WorkflowScheduler } = await import('./scheduler-Bgs6H33k.mjs');
14734
+ const { WorkflowScheduler } = await import('./scheduler-D0FGGxti.mjs');
14650
14735
  const workflowScheduler = new WorkflowScheduler({
14651
14736
  projectRoots: () => {
14652
14737
  const dirs = /* @__PURE__ */ new Set();
@@ -1,4 +1,4 @@
1
- import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import { a0 as composeSessionId, a1 as generateFriendlyName, c as connectToHypha, a as createSessionStore, r as registerMachineService, a2 as generateHookSettings } from './run-C9PMX7NQ.mjs';
1
+ import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import { a0 as composeSessionId, a1 as generateFriendlyName, c as connectToHypha, a as createSessionStore, r as registerMachineService, a2 as generateHookSettings } from './run-68xel1pO.mjs';
2
2
  import os from 'node:os';
3
3
  import { resolve, join } from 'node:path';
4
4
  import { existsSync, readFileSync, watch } from 'node:fs';
@@ -1,6 +1,6 @@
1
1
  import { spawn } from 'node:child_process';
2
- import { m as resolveProjectRoot, v as cronMatches } from './run-C9PMX7NQ.mjs';
3
- import { l as listWorkflows, b as workflowCrons, w as workflowSteps } from './store-BPL__e4D.mjs';
2
+ import { m as resolveProjectRoot, v as cronMatches } from './run-68xel1pO.mjs';
3
+ import { l as listWorkflows, i as isWorkflowEnabled, c as workflowCrons, w as workflowSteps } from './store-D-fJ9ANL.mjs';
4
4
  import 'os';
5
5
  import 'fs/promises';
6
6
  import 'fs';
@@ -73,6 +73,7 @@ class WorkflowScheduler {
73
73
  continue;
74
74
  }
75
75
  for (const wf of workflows) {
76
+ if (!isWorkflowEnabled(wf)) continue;
76
77
  const crons = workflowCrons(wf);
77
78
  if (!crons.length) continue;
78
79
  if (crons.some((c) => cronMatchesSafe(c, now))) {
@@ -54,7 +54,7 @@ async function handleServeCommand() {
54
54
  }
55
55
  }
56
56
  async function serveAdd(args, machineId) {
57
- const { connectAndGetMachine } = await import('./commands-CL9hxClH.mjs');
57
+ const { connectAndGetMachine } = await import('./commands-DQ_kpWqH.mjs');
58
58
  const pos = positionalArgs(args);
59
59
  const name = pos[0];
60
60
  if (!name) {
@@ -93,7 +93,7 @@ async function serveAdd(args, machineId) {
93
93
  }
94
94
  }
95
95
  async function serveApply(args, machineId) {
96
- const { connectAndGetMachine } = await import('./commands-CL9hxClH.mjs');
96
+ const { connectAndGetMachine } = await import('./commands-DQ_kpWqH.mjs');
97
97
  const fs = await import('fs');
98
98
  const yaml = await import('yaml');
99
99
  const file = positionalArgs(args)[0];
@@ -182,7 +182,7 @@ async function serveApply(args, machineId) {
182
182
  }
183
183
  }
184
184
  async function serveRemove(args, machineId) {
185
- const { connectAndGetMachine } = await import('./commands-CL9hxClH.mjs');
185
+ const { connectAndGetMachine } = await import('./commands-DQ_kpWqH.mjs');
186
186
  const pos = positionalArgs(args);
187
187
  const name = pos[0];
188
188
  if (!name) {
@@ -202,7 +202,7 @@ async function serveRemove(args, machineId) {
202
202
  }
203
203
  }
204
204
  async function serveList(args, machineId) {
205
- const { connectAndGetMachine } = await import('./commands-CL9hxClH.mjs');
205
+ const { connectAndGetMachine } = await import('./commands-DQ_kpWqH.mjs');
206
206
  const all = hasFlag(args, "--all", "-a");
207
207
  const json = hasFlag(args, "--json");
208
208
  const sessionId = getFlag(args, "--session");
@@ -235,7 +235,7 @@ async function serveList(args, machineId) {
235
235
  }
236
236
  }
237
237
  async function serveInfo(machineId) {
238
- const { connectAndGetMachine } = await import('./commands-CL9hxClH.mjs');
238
+ const { connectAndGetMachine } = await import('./commands-DQ_kpWqH.mjs');
239
239
  const { machine, server } = await connectAndGetMachine(machineId);
240
240
  try {
241
241
  const info = await machine.serveInfo();
@@ -4,7 +4,7 @@ import * as fs from 'fs';
4
4
  import * as http from 'http';
5
5
  import * as net from 'net';
6
6
  import * as path from 'path';
7
- import { k as getHyphaServerUrl, S as ServeAuth, l as hasCookieToken } from './run-C9PMX7NQ.mjs';
7
+ import { k as getHyphaServerUrl, S as ServeAuth, l as hasCookieToken } from './run-68xel1pO.mjs';
8
8
  import 'os';
9
9
  import 'fs/promises';
10
10
  import 'url';
@@ -733,7 +733,7 @@ class ServeManager {
733
733
  const mount = this.mounts.get(mountName);
734
734
  const subdomainOverride = mount?.access === "link" && mount.linkToken ? /* @__PURE__ */ new Map([[this.port, buildLinkSubdomain(subdomainSafe, mount.linkToken)]]) : void 0;
735
735
  try {
736
- const { FrpcTunnel } = await import('./frpc-CLJ1PNTL.mjs');
736
+ const { FrpcTunnel } = await import('./frpc-BzkAtKLy.mjs');
737
737
  let tunnel;
738
738
  tunnel = new FrpcTunnel({
739
739
  name: tunnelName,
@@ -1,4 +1,4 @@
1
- import { R as READ_ONLY_TOOLS, y as loadMachineContext, z as buildMachineInstructions, A as machineToolsForRole, B as buildMachineTools } from './run-C9PMX7NQ.mjs';
1
+ import { R as READ_ONLY_TOOLS, y as loadMachineContext, z as buildMachineInstructions, A as machineToolsForRole, B as buildMachineTools } from './run-68xel1pO.mjs';
2
2
  import 'node:child_process';
3
3
  import 'os';
4
4
  import 'fs/promises';