svamp-cli 0.1.59 → 0.1.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { b as stopDaemon, s as startDaemon, d as daemonStatus } from './run-Ct_XgELJ.mjs';
1
+ import { b as stopDaemon, s as startDaemon, d as daemonStatus } from './run-Bds_JVXp.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -106,10 +106,10 @@ async function main() {
106
106
  } else if (subcommand === "skills") {
107
107
  await handleSkillsCommand();
108
108
  } else if (subcommand === "service" || subcommand === "svc") {
109
- const { handleServiceCommand } = await import('./commands-Dvftls28.mjs');
109
+ const { handleServiceCommand } = await import('./commands-T_SLBdOH.mjs');
110
110
  await handleServiceCommand();
111
111
  } else if (subcommand === "process" || subcommand === "proc") {
112
- const { processCommand } = await import('./commands-Cs3IShSz.mjs');
112
+ const { processCommand } = await import('./commands-DTNg_sCX.mjs');
113
113
  let machineId;
114
114
  const processArgs = args.slice(1);
115
115
  const mIdx = processArgs.findIndex((a) => a === "--machine" || a === "-m");
@@ -127,7 +127,7 @@ async function main() {
127
127
  } else if (!subcommand || subcommand === "start") {
128
128
  await handleInteractiveCommand();
129
129
  } else if (subcommand === "--version" || subcommand === "-v") {
130
- const pkg = await import('./package-CHMwNqVN.mjs').catch(() => ({ default: { version: "unknown" } }));
130
+ const pkg = await import('./package-D8bLCX8i.mjs').catch(() => ({ default: { version: "unknown" } }));
131
131
  console.log(`svamp version: ${pkg.default.version}`);
132
132
  } else {
133
133
  console.error(`Unknown command: ${subcommand}`);
@@ -136,7 +136,7 @@ async function main() {
136
136
  }
137
137
  }
138
138
  async function handleInteractiveCommand() {
139
- const { runInteractive } = await import('./run-D7RQy1VK.mjs');
139
+ const { runInteractive } = await import('./run-Dg1i7D_o.mjs');
140
140
  const interactiveArgs = subcommand === "start" ? args.slice(1) : args;
141
141
  let directory = process.cwd();
142
142
  let resumeSessionId;
@@ -181,7 +181,7 @@ async function handleAgentCommand() {
181
181
  return;
182
182
  }
183
183
  if (agentArgs[0] === "list") {
184
- const { KNOWN_ACP_AGENTS, KNOWN_MCP_AGENTS: KNOWN_MCP_AGENTS2 } = await import('./run-Ct_XgELJ.mjs').then(function (n) { return n.i; });
184
+ const { KNOWN_ACP_AGENTS, KNOWN_MCP_AGENTS: KNOWN_MCP_AGENTS2 } = await import('./run-Bds_JVXp.mjs').then(function (n) { return n.i; });
185
185
  console.log("Known agents:");
186
186
  for (const [name, config2] of Object.entries(KNOWN_ACP_AGENTS)) {
187
187
  console.log(` ${name.padEnd(12)} ${config2.command} ${config2.args.join(" ")} (ACP)`);
@@ -193,7 +193,7 @@ async function handleAgentCommand() {
193
193
  console.log('Use "svamp agent -- <command> [args]" for a custom ACP agent.');
194
194
  return;
195
195
  }
196
- const { resolveAcpAgentConfig, KNOWN_MCP_AGENTS } = await import('./run-Ct_XgELJ.mjs').then(function (n) { return n.i; });
196
+ const { resolveAcpAgentConfig, KNOWN_MCP_AGENTS } = await import('./run-Bds_JVXp.mjs').then(function (n) { return n.i; });
197
197
  let cwd = process.cwd();
198
198
  const filteredArgs = [];
199
199
  for (let i = 0; i < agentArgs.length; i++) {
@@ -217,12 +217,12 @@ async function handleAgentCommand() {
217
217
  console.log(`Starting ${config.agentName} agent in ${cwd}...`);
218
218
  let backend;
219
219
  if (KNOWN_MCP_AGENTS[config.agentName]) {
220
- const { CodexMcpBackend } = await import('./run-Ct_XgELJ.mjs').then(function (n) { return n.j; });
220
+ const { CodexMcpBackend } = await import('./run-Bds_JVXp.mjs').then(function (n) { return n.j; });
221
221
  backend = new CodexMcpBackend({ cwd, log: logFn });
222
222
  } else {
223
- const { AcpBackend } = await import('./run-Ct_XgELJ.mjs').then(function (n) { return n.h; });
224
- const { GeminiTransport } = await import('./run-Ct_XgELJ.mjs').then(function (n) { return n.G; });
225
- const { DefaultTransport } = await import('./run-Ct_XgELJ.mjs').then(function (n) { return n.D; });
223
+ const { AcpBackend } = await import('./run-Bds_JVXp.mjs').then(function (n) { return n.h; });
224
+ const { GeminiTransport } = await import('./run-Bds_JVXp.mjs').then(function (n) { return n.G; });
225
+ const { DefaultTransport } = await import('./run-Bds_JVXp.mjs').then(function (n) { return n.D; });
226
226
  const transportHandler = config.agentName === "gemini" ? new GeminiTransport() : new DefaultTransport(config.agentName);
227
227
  backend = new AcpBackend({
228
228
  agentName: config.agentName,
@@ -340,7 +340,7 @@ async function handleSessionCommand() {
340
340
  printSessionHelp();
341
341
  return;
342
342
  }
343
- const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach, sessionMachines, sessionSend, sessionWait, sessionShare, sessionRalphStart, sessionRalphCancel, sessionRalphStatus, sessionQueueAdd, sessionQueueList, sessionQueueClear } = await import('./commands-D-_qHKWM.mjs');
343
+ const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach, sessionMachines, sessionSend, sessionWait, sessionShare, sessionRalphStart, sessionRalphCancel, sessionRalphStatus, sessionQueueAdd, sessionQueueList, sessionQueueClear } = await import('./commands-CBTCiCjK.mjs');
344
344
  const parseFlagStr = (flag, shortFlag) => {
345
345
  for (let i = 1; i < sessionArgs.length; i++) {
346
346
  if ((sessionArgs[i] === flag || shortFlag) && i + 1 < sessionArgs.length) {
@@ -400,7 +400,7 @@ async function handleSessionCommand() {
400
400
  allowDomain.push(sessionArgs[++i]);
401
401
  }
402
402
  }
403
- const { parseShareArg } = await import('./commands-D-_qHKWM.mjs');
403
+ const { parseShareArg } = await import('./commands-CBTCiCjK.mjs');
404
404
  const shareEntries = share.map((s) => parseShareArg(s));
405
405
  await sessionSpawn(agent, dir, targetMachineId, {
406
406
  message,
@@ -484,7 +484,7 @@ async function handleSessionCommand() {
484
484
  console.error("Usage: svamp session approve <session-id> [request-id] [--json]");
485
485
  process.exit(1);
486
486
  }
487
- const { sessionApprove } = await import('./commands-D-_qHKWM.mjs');
487
+ const { sessionApprove } = await import('./commands-CBTCiCjK.mjs');
488
488
  const approveReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
489
489
  await sessionApprove(sessionArgs[1], approveReqId, targetMachineId, {
490
490
  json: hasFlag("--json")
@@ -494,7 +494,7 @@ async function handleSessionCommand() {
494
494
  console.error("Usage: svamp session deny <session-id> [request-id] [--json]");
495
495
  process.exit(1);
496
496
  }
497
- const { sessionDeny } = await import('./commands-D-_qHKWM.mjs');
497
+ const { sessionDeny } = await import('./commands-CBTCiCjK.mjs');
498
498
  const denyReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
499
499
  await sessionDeny(sessionArgs[1], denyReqId, targetMachineId, {
500
500
  json: hasFlag("--json")
@@ -563,7 +563,7 @@ async function handleMachineCommand() {
563
563
  return;
564
564
  }
565
565
  if (machineSubcommand === "share") {
566
- const { machineShare } = await import('./commands-D-_qHKWM.mjs');
566
+ const { machineShare } = await import('./commands-CBTCiCjK.mjs');
567
567
  let machineId;
568
568
  const shareArgs = [];
569
569
  for (let i = 1; i < machineArgs.length; i++) {
@@ -593,7 +593,7 @@ async function handleMachineCommand() {
593
593
  }
594
594
  await machineShare(machineId, { add, remove, list, configPath, showConfig });
595
595
  } else if (machineSubcommand === "exec") {
596
- const { machineExec } = await import('./commands-D-_qHKWM.mjs');
596
+ const { machineExec } = await import('./commands-CBTCiCjK.mjs');
597
597
  let machineId;
598
598
  let cwd;
599
599
  const cmdParts = [];
@@ -613,7 +613,7 @@ async function handleMachineCommand() {
613
613
  }
614
614
  await machineExec(machineId, command, cwd);
615
615
  } else if (machineSubcommand === "info") {
616
- const { machineInfo } = await import('./commands-D-_qHKWM.mjs');
616
+ const { machineInfo } = await import('./commands-CBTCiCjK.mjs');
617
617
  let machineId;
618
618
  for (let i = 1; i < machineArgs.length; i++) {
619
619
  if ((machineArgs[i] === "--machine" || machineArgs[i] === "-m") && i + 1 < machineArgs.length) {
@@ -622,7 +622,7 @@ async function handleMachineCommand() {
622
622
  }
623
623
  await machineInfo(machineId);
624
624
  } else if (machineSubcommand === "ls") {
625
- const { machineLs } = await import('./commands-D-_qHKWM.mjs');
625
+ const { machineLs } = await import('./commands-CBTCiCjK.mjs');
626
626
  let machineId;
627
627
  let showHidden = false;
628
628
  let path;
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from 'node:fs';
2
2
  import { execSync } from 'node:child_process';
3
3
  import { resolve, join } from 'node:path';
4
4
  import os from 'node:os';
5
- import { l as loadSecurityContextConfig, e as resolveSecurityContext, f as buildSecurityContextFromFlags, m as mergeSecurityContexts, c as connectToHypha } from './run-Ct_XgELJ.mjs';
5
+ import { l as loadSecurityContextConfig, e as resolveSecurityContext, f as buildSecurityContextFromFlags, m as mergeSecurityContexts, c as connectToHypha } from './run-Bds_JVXp.mjs';
6
6
  import 'os';
7
7
  import 'fs/promises';
8
8
  import 'fs';
@@ -1,11 +1,11 @@
1
1
  import { writeFileSync, readFileSync } from 'fs';
2
2
  import { resolve } from 'path';
3
- import { connectAndGetMachine } from './commands-D-_qHKWM.mjs';
3
+ import { connectAndGetMachine } from './commands-CBTCiCjK.mjs';
4
4
  import 'node:fs';
5
5
  import 'node:child_process';
6
6
  import 'node:path';
7
7
  import 'node:os';
8
- import './run-Ct_XgELJ.mjs';
8
+ import './run-Bds_JVXp.mjs';
9
9
  import 'os';
10
10
  import 'fs/promises';
11
11
  import 'url';
@@ -296,7 +296,7 @@ Service is live:`);
296
296
  }
297
297
  } else {
298
298
  console.log(`No SANDBOX_ID detected \u2014 starting reverse tunnel.`);
299
- const { runTunnel } = await import('./tunnel-Dh1bJZ6R.mjs');
299
+ const { runTunnel } = await import('./tunnel-DTyspats.mjs');
300
300
  await runTunnel(name, ports);
301
301
  }
302
302
  } catch (err) {
@@ -312,7 +312,7 @@ async function serviceTunnel(args) {
312
312
  console.error("Usage: svamp service tunnel <name> --port <port> [--port <port2>]");
313
313
  process.exit(1);
314
314
  }
315
- const { runTunnel } = await import('./tunnel-Dh1bJZ6R.mjs');
315
+ const { runTunnel } = await import('./tunnel-DTyspats.mjs');
316
316
  await runTunnel(name, ports);
317
317
  }
318
318
  async function handleServiceCommand() {
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { c as connectToHypha, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, a as registerSessionService, s as startDaemon, b as stopDaemon } from './run-Ct_XgELJ.mjs';
1
+ export { c as connectToHypha, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, a as registerSessionService, s as startDaemon, b as stopDaemon } from './run-Bds_JVXp.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.1.59";
2
+ var version = "0.1.61";
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";
@@ -313,35 +313,9 @@ async function registerMachineService(server, machineId, metadata, daemonState,
313
313
  let currentDaemonState = { ...daemonState };
314
314
  let metadataVersion = 1;
315
315
  let daemonStateVersion = 1;
316
- const listeners = [];
317
- const removeListener = (listener, reason) => {
318
- const idx = listeners.indexOf(listener);
319
- if (idx >= 0) {
320
- listeners.splice(idx, 1);
321
- console.log(`[HYPHA MACHINE] Listener removed (${reason}), remaining: ${listeners.length}`);
322
- const rintfId = listener._rintf_service_id;
323
- if (rintfId) {
324
- server.unregisterService(rintfId).catch(() => {
325
- });
326
- }
327
- }
328
- };
329
- const notifyListeners = (update) => {
330
- for (let i = listeners.length - 1; i >= 0; i--) {
331
- try {
332
- const result = listeners[i].onUpdate(update);
333
- if (result && typeof result.catch === "function") {
334
- const listener = listeners[i];
335
- result.catch((err) => {
336
- console.error(`[HYPHA MACHINE] Async listener error:`, err);
337
- removeListener(listener, "async error");
338
- });
339
- }
340
- } catch (err) {
341
- console.error(`[HYPHA MACHINE] Listener error:`, err);
342
- removeListener(listeners[i], "sync error");
343
- }
344
- }
316
+ const subscribers = /* @__PURE__ */ new Set();
317
+ const notifySubscribers = (update) => {
318
+ for (const push of subscribers) push(update);
345
319
  };
346
320
  const serviceInfo = await server.registerService(
347
321
  {
@@ -427,7 +401,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
427
401
  machineId
428
402
  });
429
403
  if (result.type === "success" && result.sessionId) {
430
- notifyListeners({
404
+ notifySubscribers({
431
405
  type: "new-session",
432
406
  sessionId: result.sessionId,
433
407
  machineId
@@ -440,7 +414,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
440
414
  stopSession: async (sessionId, context) => {
441
415
  authorizeRequest(context, currentMetadata.sharing, "admin");
442
416
  const result = handlers.stopSession(sessionId);
443
- notifyListeners({
417
+ notifySubscribers({
444
418
  type: "session-stopped",
445
419
  sessionId,
446
420
  machineId
@@ -481,7 +455,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
481
455
  sharing: currentMetadata.sharing,
482
456
  securityContextConfig: currentMetadata.securityContextConfig
483
457
  });
484
- notifyListeners({
458
+ notifySubscribers({
485
459
  type: "update-machine",
486
460
  machineId,
487
461
  metadata: { value: currentMetadata, version: metadataVersion }
@@ -511,7 +485,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
511
485
  }
512
486
  currentDaemonState = newState;
513
487
  daemonStateVersion++;
514
- notifyListeners({
488
+ notifySubscribers({
515
489
  type: "update-machine",
516
490
  machineId,
517
491
  daemonState: { value: currentDaemonState, version: daemonStateVersion }
@@ -541,7 +515,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
541
515
  sharing: currentMetadata.sharing,
542
516
  securityContextConfig: currentMetadata.securityContextConfig
543
517
  });
544
- notifyListeners({
518
+ notifySubscribers({
545
519
  type: "update-machine",
546
520
  machineId,
547
521
  metadata: { value: currentMetadata, version: metadataVersion }
@@ -562,19 +536,48 @@ async function registerMachineService(server, machineId, metadata, daemonState,
562
536
  sharing: currentMetadata.sharing,
563
537
  securityContextConfig: currentMetadata.securityContextConfig
564
538
  });
565
- notifyListeners({
539
+ notifySubscribers({
566
540
  type: "update-machine",
567
541
  machineId,
568
542
  metadata: { value: currentMetadata, version: metadataVersion }
569
543
  });
570
544
  return { success: true };
571
545
  },
572
- // Register a listener for real-time updates (app calls this with _rintf callback)
573
- registerListener: async (callback, context) => {
546
+ // Live update stream yields machine state changes as they happen.
547
+ // Frontend iterates with `for await (const update of machine.subscribe())`.
548
+ subscribe: async function* (context) {
574
549
  authorizeRequest(context, currentMetadata.sharing, "view");
575
- listeners.push(callback);
576
- console.log(`[HYPHA MACHINE] Listener registered (total: ${listeners.length})`);
577
- return { success: true, listenerId: listeners.length - 1 };
550
+ const pending = [];
551
+ let wake = null;
552
+ const push = (update) => {
553
+ pending.push(update);
554
+ wake?.();
555
+ };
556
+ subscribers.add(push);
557
+ console.log(`[HYPHA MACHINE] subscribe() started (total: ${subscribers.size})`);
558
+ try {
559
+ yield {
560
+ type: "update-machine",
561
+ machineId,
562
+ metadata: { value: currentMetadata, version: metadataVersion },
563
+ daemonState: { value: currentDaemonState, version: daemonStateVersion }
564
+ };
565
+ while (true) {
566
+ while (pending.length === 0) {
567
+ await new Promise((r) => {
568
+ wake = r;
569
+ });
570
+ wake = null;
571
+ }
572
+ while (pending.length > 0) {
573
+ yield pending.shift();
574
+ }
575
+ }
576
+ } finally {
577
+ subscribers.delete(push);
578
+ wake?.();
579
+ console.log(`[HYPHA MACHINE] subscribe() ended (remaining: ${subscribers.size})`);
580
+ }
578
581
  },
579
582
  // Shell access
580
583
  bash: async (command, cwd, context) => {
@@ -761,7 +764,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
761
764
  updateMetadata: (newMetadata) => {
762
765
  currentMetadata = newMetadata;
763
766
  metadataVersion++;
764
- notifyListeners({
767
+ notifySubscribers({
765
768
  type: "update-machine",
766
769
  machineId,
767
770
  metadata: { value: currentMetadata, version: metadataVersion }
@@ -770,7 +773,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
770
773
  updateDaemonState: (newState) => {
771
774
  currentDaemonState = newState;
772
775
  daemonStateVersion++;
773
- notifyListeners({
776
+ notifySubscribers({
774
777
  type: "update-machine",
775
778
  machineId,
776
779
  daemonState: { value: currentDaemonState, version: daemonStateVersion }
@@ -819,35 +822,9 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
819
822
  mode: "remote",
820
823
  time: Date.now()
821
824
  };
822
- const listeners = [];
823
- const removeListener = (listener, reason) => {
824
- const idx = listeners.indexOf(listener);
825
- if (idx >= 0) {
826
- listeners.splice(idx, 1);
827
- console.log(`[HYPHA SESSION ${sessionId}] Listener removed (${reason}), remaining: ${listeners.length}`);
828
- const rintfId = listener._rintf_service_id;
829
- if (rintfId) {
830
- server.unregisterService(rintfId).catch(() => {
831
- });
832
- }
833
- }
834
- };
835
- const notifyListeners = (update) => {
836
- for (let i = listeners.length - 1; i >= 0; i--) {
837
- try {
838
- const result = listeners[i].onUpdate(update);
839
- if (result && typeof result.catch === "function") {
840
- const listener = listeners[i];
841
- result.catch((err) => {
842
- console.error(`[HYPHA SESSION ${sessionId}] Async listener error:`, err);
843
- removeListener(listener, "async error");
844
- });
845
- }
846
- } catch (err) {
847
- console.error(`[HYPHA SESSION ${sessionId}] Listener error:`, err);
848
- removeListener(listeners[i], "sync error");
849
- }
850
- }
825
+ const subscribers = /* @__PURE__ */ new Set();
826
+ const notifySubscribers = (update) => {
827
+ for (const push of subscribers) push(update);
851
828
  };
852
829
  const pushMessage = (content, role = "agent") => {
853
830
  let wrappedContent;
@@ -878,7 +855,7 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
878
855
  if (options?.messagesDir) {
879
856
  appendMessage(options.messagesDir, sessionId, msg);
880
857
  }
881
- notifyListeners({
858
+ notifySubscribers({
882
859
  type: "new-message",
883
860
  sessionId,
884
861
  message: msg
@@ -939,7 +916,7 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
939
916
  if (options?.messagesDir) {
940
917
  appendMessage(options.messagesDir, sessionId, msg);
941
918
  }
942
- notifyListeners({
919
+ notifySubscribers({
943
920
  type: "new-message",
944
921
  sessionId,
945
922
  message: msg
@@ -966,7 +943,7 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
966
943
  }
967
944
  metadata = newMetadata;
968
945
  metadataVersion++;
969
- notifyListeners({
946
+ notifySubscribers({
970
947
  type: "update-session",
971
948
  sessionId,
972
949
  metadata: { value: metadata, version: metadataVersion }
@@ -1007,7 +984,7 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
1007
984
  }
1008
985
  agentState = newState;
1009
986
  agentStateVersion++;
1010
- notifyListeners({
987
+ notifySubscribers({
1011
988
  type: "update-session",
1012
989
  sessionId,
1013
990
  agentState: { value: agentState, version: agentStateVersion }
@@ -1046,7 +1023,7 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
1046
1023
  // ── Activity ──
1047
1024
  keepAlive: async (thinking, mode, context) => {
1048
1025
  lastActivity = { active: true, thinking: thinking || false, mode: mode || "remote", time: Date.now() };
1049
- notifyListeners({
1026
+ notifySubscribers({
1050
1027
  type: "activity",
1051
1028
  sessionId,
1052
1029
  ...lastActivity
@@ -1054,7 +1031,7 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
1054
1031
  },
1055
1032
  sessionEnd: async (context) => {
1056
1033
  lastActivity = { active: false, thinking: false, mode: "remote", time: Date.now() };
1057
- notifyListeners({
1034
+ notifySubscribers({
1058
1035
  type: "activity",
1059
1036
  sessionId,
1060
1037
  ...lastActivity
@@ -1128,7 +1105,7 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
1128
1105
  }
1129
1106
  metadata = { ...metadata, sharing: newSharing };
1130
1107
  metadataVersion++;
1131
- notifyListeners({
1108
+ notifySubscribers({
1132
1109
  type: "update-session",
1133
1110
  sessionId,
1134
1111
  metadata: { value: metadata, version: metadataVersion }
@@ -1146,7 +1123,7 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
1146
1123
  }
1147
1124
  metadata = { ...metadata, securityContext: newSecurityContext };
1148
1125
  metadataVersion++;
1149
- notifyListeners({
1126
+ notifySubscribers({
1150
1127
  type: "update-session",
1151
1128
  sessionId,
1152
1129
  metadata: { value: metadata, version: metadataVersion }
@@ -1161,64 +1138,55 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
1161
1138
  }
1162
1139
  return await callbacks.onApplySystemPrompt(prompt);
1163
1140
  },
1164
- // ── Listener Registration ──
1165
- registerListener: async (callback, context) => {
1141
+ // ── Live Update Stream ──
1142
+ //
1143
+ // Returns an async generator that yields real-time updates for this session.
1144
+ // hypha-rpc proxies the generator across the RPC boundary — the frontend
1145
+ // iterates with `for await (const update of service.subscribe())`.
1146
+ //
1147
+ // Initial state is replayed as the first batch of yields so the frontend
1148
+ // can reconstruct full session state without a separate RPC call.
1149
+ // Cleanup is automatic: when the frontend disconnects, hypha-rpc calls the
1150
+ // generator's close method, triggering the finally block which removes the
1151
+ // subscriber. No reverse `_rintf` service is registered.
1152
+ subscribe: async function* (context) {
1166
1153
  authorizeRequest(context, metadata.sharing, "view");
1167
- listeners.push(callback);
1168
- const replayMessages = messages.slice(-50);
1169
- console.log(`[HYPHA SESSION ${sessionId}] Listener registered (total: ${listeners.length}), replaying ${replayMessages.length} of ${messages.length} messages`);
1170
- for (const msg of replayMessages) {
1171
- if (listeners.indexOf(callback) < 0) break;
1172
- try {
1173
- const result = callback.onUpdate({
1174
- type: "new-message",
1175
- sessionId,
1176
- message: msg
1177
- });
1178
- if (result && typeof result.catch === "function") {
1179
- try {
1180
- await result;
1181
- } catch (err) {
1182
- console.error(`[HYPHA SESSION ${sessionId}] Replay listener error, removing:`, err?.message ?? err);
1183
- removeListener(callback, "replay error");
1184
- return { success: false, error: "Listener removed during replay" };
1185
- }
1186
- }
1187
- } catch (err) {
1188
- console.error(`[HYPHA SESSION ${sessionId}] Replay listener error, removing:`, err?.message ?? err);
1189
- removeListener(callback, "replay error");
1190
- return { success: false, error: "Listener removed during replay" };
1191
- }
1192
- }
1193
- if (listeners.indexOf(callback) < 0) {
1194
- return { success: false, error: "Listener was removed during replay" };
1195
- }
1154
+ const pending = [];
1155
+ let wake = null;
1156
+ const push = (update) => {
1157
+ pending.push(update);
1158
+ wake?.();
1159
+ };
1160
+ subscribers.add(push);
1161
+ console.log(`[HYPHA SESSION ${sessionId}] subscribe() started (total: ${subscribers.size})`);
1196
1162
  try {
1197
- const result = callback.onUpdate({
1163
+ yield {
1198
1164
  type: "update-session",
1199
1165
  sessionId,
1200
1166
  metadata: { value: metadata, version: metadataVersion },
1201
1167
  agentState: { value: agentState, version: agentStateVersion }
1202
- });
1203
- if (result && typeof result.catch === "function") {
1204
- result.catch(() => {
1205
- });
1168
+ };
1169
+ const MAX_REPLAY = 50;
1170
+ for (const msg of messages.slice(-MAX_REPLAY)) {
1171
+ yield { type: "new-message", sessionId, message: msg };
1206
1172
  }
1207
- } catch {
1208
- }
1209
- try {
1210
- const result = callback.onUpdate({
1211
- type: "activity",
1212
- sessionId,
1213
- ...lastActivity
1214
- });
1215
- if (result && typeof result.catch === "function") {
1216
- result.catch(() => {
1217
- });
1173
+ yield { type: "activity", sessionId, ...lastActivity };
1174
+ while (true) {
1175
+ while (pending.length === 0) {
1176
+ await new Promise((r) => {
1177
+ wake = r;
1178
+ });
1179
+ wake = null;
1180
+ }
1181
+ while (pending.length > 0) {
1182
+ yield pending.shift();
1183
+ }
1218
1184
  }
1219
- } catch {
1185
+ } finally {
1186
+ subscribers.delete(push);
1187
+ wake?.();
1188
+ console.log(`[HYPHA SESSION ${sessionId}] subscribe() ended (remaining: ${subscribers.size})`);
1220
1189
  }
1221
- return { success: true, listenerId: listeners.length - 1 };
1222
1190
  }
1223
1191
  },
1224
1192
  { overwrite: true }
@@ -1233,7 +1201,7 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
1233
1201
  updateMetadata: (newMetadata) => {
1234
1202
  metadata = newMetadata;
1235
1203
  metadataVersion++;
1236
- notifyListeners({
1204
+ notifySubscribers({
1237
1205
  type: "update-session",
1238
1206
  sessionId,
1239
1207
  metadata: { value: metadata, version: metadataVersion }
@@ -1242,7 +1210,7 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
1242
1210
  updateAgentState: (newAgentState) => {
1243
1211
  agentState = newAgentState;
1244
1212
  agentStateVersion++;
1245
- notifyListeners({
1213
+ notifySubscribers({
1246
1214
  type: "update-session",
1247
1215
  sessionId,
1248
1216
  agentState: { value: agentState, version: agentStateVersion }
@@ -1250,7 +1218,7 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
1250
1218
  },
1251
1219
  sendKeepAlive: (thinking, mode) => {
1252
1220
  lastActivity = { active: true, thinking: thinking || false, mode: mode || "remote", time: Date.now() };
1253
- notifyListeners({
1221
+ notifySubscribers({
1254
1222
  type: "activity",
1255
1223
  sessionId,
1256
1224
  ...lastActivity
@@ -1258,7 +1226,7 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
1258
1226
  },
1259
1227
  sendSessionEnd: () => {
1260
1228
  lastActivity = { active: false, thinking: false, mode: "remote", time: Date.now() };
1261
- notifyListeners({
1229
+ notifySubscribers({
1262
1230
  type: "activity",
1263
1231
  sessionId,
1264
1232
  ...lastActivity
@@ -1274,7 +1242,7 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
1274
1242
  } catch {
1275
1243
  }
1276
1244
  }
1277
- notifyListeners({
1245
+ notifySubscribers({
1278
1246
  type: "clear-messages",
1279
1247
  sessionId
1280
1248
  });
@@ -4985,20 +4953,7 @@ async function startDaemon(options) {
4985
4953
  logger.log(`Hypha connection permanently lost: ${reason}`);
4986
4954
  requestShutdown("hypha-disconnected", String(reason));
4987
4955
  });
4988
- let lastHyphaMessageAt = Date.now();
4989
- const attachMessageTimestampListener = () => {
4990
- const conn = server.rpc?._connection;
4991
- const ws = conn?._websocket;
4992
- if (ws) {
4993
- ws.addEventListener("message", () => {
4994
- lastHyphaMessageAt = Date.now();
4995
- });
4996
- lastHyphaMessageAt = Date.now();
4997
- }
4998
- };
4999
- attachMessageTimestampListener();
5000
4956
  server.on("services_registered", () => {
5001
- attachMessageTimestampListener();
5002
4957
  if (consecutiveHeartbeatFailures > 0) {
5003
4958
  logger.log(`Hypha reconnection successful \u2014 services re-registered (resetting ${consecutiveHeartbeatFailures} failures)`);
5004
4959
  consecutiveHeartbeatFailures = 0;
@@ -6747,7 +6702,7 @@ The automated loop has finished. Review the progress above and let me know if yo
6747
6702
  console.log(` Service: svamp-machine-${machineId}`);
6748
6703
  console.log(` Log file: ${logger.logFilePath}`);
6749
6704
  const HEARTBEAT_INTERVAL_MS = 1e4;
6750
- const GHOST_TIMEOUT_MS = 6e4;
6705
+ const PING_TIMEOUT_MS = 15e3;
6751
6706
  const MAX_FAILURES = 60;
6752
6707
  const POST_RECONNECT_GRACE_MS = 2e4;
6753
6708
  let heartbeatRunning = false;
@@ -6792,31 +6747,44 @@ The automated loop has finished. Review the progress above and let me know if yo
6792
6747
  }
6793
6748
  }
6794
6749
  const inGrace = lastReconnectAt > 0 && Date.now() - lastReconnectAt < POST_RECONNECT_GRACE_MS;
6795
- const timeSinceMsg = Date.now() - lastHyphaMessageAt;
6796
- if (!inGrace && timeSinceMsg > GHOST_TIMEOUT_MS) {
6797
- const conn = server.rpc?._connection;
6798
- const ws = conn?._websocket;
6799
- if (ws?.readyState === 1) {
6750
+ if (!inGrace) {
6751
+ try {
6752
+ const pingStart = Date.now();
6753
+ await Promise.race([
6754
+ server.echo("ping"),
6755
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Ping timed out")), PING_TIMEOUT_MS))
6756
+ ]);
6757
+ const pingMs = Date.now() - pingStart;
6758
+ if (pingMs > 5e3) {
6759
+ logger.log(`Slow ping: ${pingMs}ms (server under load)`);
6760
+ }
6761
+ if (consecutiveHeartbeatFailures > 0) {
6762
+ logger.log(`Heartbeat recovered after ${consecutiveHeartbeatFailures} failures`);
6763
+ consecutiveHeartbeatFailures = 0;
6764
+ }
6765
+ } catch (err) {
6800
6766
  consecutiveHeartbeatFailures++;
6801
6767
  if (consecutiveHeartbeatFailures === 1) {
6802
- logger.log(`No message from server in ${Math.round(timeSinceMsg / 1e3)}s \u2014 ghost connection, force-closing WebSocket`);
6768
+ logger.log(`Ping failed: ${err.message}`);
6803
6769
  } else if (consecutiveHeartbeatFailures % 6 === 0) {
6804
- logger.log(`Still reconnecting \u2014 ${consecutiveHeartbeatFailures} failures (${consecutiveHeartbeatFailures * HEARTBEAT_INTERVAL_MS / 1e3}s)`);
6770
+ logger.log(`Connection down for ${consecutiveHeartbeatFailures * HEARTBEAT_INTERVAL_MS / 1e3}s (${consecutiveHeartbeatFailures}/${MAX_FAILURES})`);
6805
6771
  }
6806
- try {
6807
- ws.close(4e3, "Ghost connection");
6808
- } catch {
6772
+ if (consecutiveHeartbeatFailures === 1 || consecutiveHeartbeatFailures % 3 === 0) {
6773
+ const conn = server.rpc?._connection;
6774
+ const ws = conn?._websocket;
6775
+ if (ws?.readyState === 1) {
6776
+ logger.log("Force-closing stale WebSocket to trigger reconnection");
6777
+ try {
6778
+ ws.close(4e3, "Stale connection");
6779
+ } catch {
6780
+ }
6781
+ }
6809
6782
  }
6810
6783
  if (consecutiveHeartbeatFailures >= MAX_FAILURES) {
6811
6784
  logger.log(`Heartbeat failed ${MAX_FAILURES} times. Shutting down.`);
6812
- requestShutdown("heartbeat-timeout", `No messages for ${Math.round(timeSinceMsg / 1e3)}s`);
6785
+ requestShutdown("heartbeat-timeout", err.message);
6813
6786
  }
6814
- } else {
6815
- lastHyphaMessageAt = Date.now();
6816
6787
  }
6817
- } else if (consecutiveHeartbeatFailures > 0 && timeSinceMsg < GHOST_TIMEOUT_MS / 2) {
6818
- logger.log(`Heartbeat recovered after ${consecutiveHeartbeatFailures} failures`);
6819
- consecutiveHeartbeatFailures = 0;
6820
6788
  }
6821
6789
  } finally {
6822
6790
  heartbeatRunning = false;
@@ -2,7 +2,7 @@ import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(im
2
2
  import os from 'node:os';
3
3
  import { join, resolve } from 'node:path';
4
4
  import { mkdirSync, writeFileSync, existsSync, unlinkSync, readFileSync, watch } from 'node:fs';
5
- import { c as connectToHypha, a as registerSessionService } from './run-Ct_XgELJ.mjs';
5
+ import { c as connectToHypha, a as registerSessionService } from './run-Bds_JVXp.mjs';
6
6
  import { createServer } from 'node:http';
7
7
  import { spawn } from 'node:child_process';
8
8
  import { createInterface } from 'node:readline';
@@ -144,6 +144,7 @@ class TunnelClient {
144
144
  const bodyBase64 = bodyBuffer.byteLength > 0 ? Buffer.from(bodyBuffer).toString("base64") : void 0;
145
145
  const headers = {};
146
146
  res.headers.forEach((value, key) => {
147
+ if (key === "content-encoding" || key === "content-length") return;
147
148
  headers[key] = value;
148
149
  });
149
150
  this.send({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svamp-cli",
3
- "version": "0.1.59",
3
+ "version": "0.1.61",
4
4
  "description": "Svamp CLI — AI workspace daemon on Hypha Cloud",
5
5
  "author": "Amun AI AB",
6
6
  "license": "SEE LICENSE IN LICENSE",