svamp-cli 0.2.10 → 0.2.12

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.
@@ -148,7 +148,7 @@ async function sessionBroadcast(action, args) {
148
148
  console.log(`Broadcast sent: ${action}`);
149
149
  }
150
150
  async function connectToMachineService() {
151
- const { connectAndGetMachine } = await import('./commands-BjLVgTDn.mjs');
151
+ const { connectAndGetMachine } = await import('./commands-CSezLS5f.mjs');
152
152
  return connectAndGetMachine();
153
153
  }
154
154
  async function inboxSend(targetSessionId, opts) {
@@ -165,7 +165,7 @@ async function inboxSend(targetSessionId, opts) {
165
165
  }
166
166
  const { server, machine } = await connectToMachineService();
167
167
  try {
168
- const { resolveSessionId } = await import('./commands-BjLVgTDn.mjs');
168
+ const { resolveSessionId } = await import('./commands-CSezLS5f.mjs');
169
169
  const sessions = await machine.listSessions();
170
170
  const match = resolveSessionId(sessions, targetSessionId);
171
171
  const fullTargetId = match.sessionId;
package/dist/cli.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { b as stopDaemon, s as startDaemon, d as daemonStatus } from './run-w_M8ajhF.mjs';
1
+ import { s as startDaemon, b as stopDaemon, d as daemonStatus } from './run-T27-mChg.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -36,8 +36,9 @@ async function main() {
36
36
  await logoutFromHypha();
37
37
  } else if (subcommand === "daemon") {
38
38
  if (daemonSubcommand === "restart") {
39
- await stopDaemon();
40
- daemonSubcommand = "start";
39
+ const { restartDaemon } = await import('./run-T27-mChg.mjs').then(function (n) { return n.k; });
40
+ await restartDaemon();
41
+ process.exit(0);
41
42
  }
42
43
  if (daemonSubcommand === "start") {
43
44
  const { spawn } = await import('child_process');
@@ -111,6 +112,7 @@ async function main() {
111
112
  let consecutiveRapidCrashes = 0;
112
113
  let currentChild = null;
113
114
  let stopping = false;
115
+ let restarting = false;
114
116
  const onSignal = (sig) => {
115
117
  stopping = true;
116
118
  if (currentChild && !currentChild.killed) {
@@ -120,6 +122,13 @@ async function main() {
120
122
  process.on("SIGTERM", () => onSignal("SIGTERM"));
121
123
  process.on("SIGINT", () => onSignal("SIGINT"));
122
124
  process.on("SIGUSR1", () => onSignal("SIGUSR1"));
125
+ process.on("SIGUSR2", () => {
126
+ restarting = true;
127
+ log("Received SIGUSR2 \u2014 graceful restart requested");
128
+ if (currentChild && !currentChild.killed) {
129
+ currentChild.kill("SIGTERM");
130
+ }
131
+ });
123
132
  log("Supervisor started");
124
133
  while (!stopping) {
125
134
  const startTime = Date.now();
@@ -148,6 +157,12 @@ async function main() {
148
157
  log("Supervisor received stop signal, exiting");
149
158
  break;
150
159
  }
160
+ if (restarting) {
161
+ restarting = false;
162
+ consecutiveRapidCrashes = 0;
163
+ log("Graceful restart: respawning daemon with new binary from disk");
164
+ continue;
165
+ }
151
166
  if (exitCode === 0) {
152
167
  log("Daemon exited cleanly (exit 0), not restarting");
153
168
  break;
@@ -231,7 +246,7 @@ async function main() {
231
246
  console.error("svamp serve: Serve commands are not available in sandboxed sessions.");
232
247
  process.exit(1);
233
248
  }
234
- const { handleServeCommand } = await import('./serveCommands-KG7QRNTH.mjs');
249
+ const { handleServeCommand } = await import('./serveCommands-NNnTnukT.mjs');
235
250
  await handleServeCommand();
236
251
  process.exit(0);
237
252
  } else if (subcommand === "process" || subcommand === "proc") {
@@ -240,7 +255,7 @@ async function main() {
240
255
  console.error("svamp process: Process commands are not available in sandboxed sessions.");
241
256
  process.exit(1);
242
257
  }
243
- const { processCommand } = await import('./commands-CAfSJwcS.mjs');
258
+ const { processCommand } = await import('./commands-7E_XB3FL.mjs');
244
259
  let machineId;
245
260
  const processArgs = args.slice(1);
246
261
  const mIdx = processArgs.findIndex((a) => a === "--machine" || a === "-m");
@@ -258,7 +273,7 @@ async function main() {
258
273
  } else if (!subcommand || subcommand === "start") {
259
274
  await handleInteractiveCommand();
260
275
  } else if (subcommand === "--version" || subcommand === "-v") {
261
- const pkg = await import('./package-Bf0XCjn8.mjs').catch(() => ({ default: { version: "unknown" } }));
276
+ const pkg = await import('./package-ubZ2DKzJ.mjs').catch(() => ({ default: { version: "unknown" } }));
262
277
  console.log(`svamp version: ${pkg.default.version}`);
263
278
  } else {
264
279
  console.error(`Unknown command: ${subcommand}`);
@@ -267,7 +282,7 @@ async function main() {
267
282
  }
268
283
  }
269
284
  async function handleInteractiveCommand() {
270
- const { runInteractive } = await import('./run-oyFHdge9.mjs');
285
+ const { runInteractive } = await import('./run-CnNARx71.mjs');
271
286
  const interactiveArgs = subcommand === "start" ? args.slice(1) : args;
272
287
  let directory = process.cwd();
273
288
  let resumeSessionId;
@@ -312,7 +327,7 @@ async function handleAgentCommand() {
312
327
  return;
313
328
  }
314
329
  if (agentArgs[0] === "list") {
315
- const { KNOWN_ACP_AGENTS, KNOWN_MCP_AGENTS: KNOWN_MCP_AGENTS2 } = await import('./run-w_M8ajhF.mjs').then(function (n) { return n.i; });
330
+ const { KNOWN_ACP_AGENTS, KNOWN_MCP_AGENTS: KNOWN_MCP_AGENTS2 } = await import('./run-T27-mChg.mjs').then(function (n) { return n.i; });
316
331
  console.log("Known agents:");
317
332
  for (const [name, config2] of Object.entries(KNOWN_ACP_AGENTS)) {
318
333
  console.log(` ${name.padEnd(12)} ${config2.command} ${config2.args.join(" ")} (ACP)`);
@@ -324,7 +339,7 @@ async function handleAgentCommand() {
324
339
  console.log('Use "svamp agent -- <command> [args]" for a custom ACP agent.');
325
340
  return;
326
341
  }
327
- const { resolveAcpAgentConfig, KNOWN_MCP_AGENTS } = await import('./run-w_M8ajhF.mjs').then(function (n) { return n.i; });
342
+ const { resolveAcpAgentConfig, KNOWN_MCP_AGENTS } = await import('./run-T27-mChg.mjs').then(function (n) { return n.i; });
328
343
  let cwd = process.cwd();
329
344
  const filteredArgs = [];
330
345
  for (let i = 0; i < agentArgs.length; i++) {
@@ -348,12 +363,12 @@ async function handleAgentCommand() {
348
363
  console.log(`Starting ${config.agentName} agent in ${cwd}...`);
349
364
  let backend;
350
365
  if (KNOWN_MCP_AGENTS[config.agentName]) {
351
- const { CodexMcpBackend } = await import('./run-w_M8ajhF.mjs').then(function (n) { return n.j; });
366
+ const { CodexMcpBackend } = await import('./run-T27-mChg.mjs').then(function (n) { return n.j; });
352
367
  backend = new CodexMcpBackend({ cwd, log: logFn });
353
368
  } else {
354
- const { AcpBackend } = await import('./run-w_M8ajhF.mjs').then(function (n) { return n.h; });
355
- const { GeminiTransport } = await import('./run-w_M8ajhF.mjs').then(function (n) { return n.G; });
356
- const { DefaultTransport } = await import('./run-w_M8ajhF.mjs').then(function (n) { return n.D; });
369
+ const { AcpBackend } = await import('./run-T27-mChg.mjs').then(function (n) { return n.h; });
370
+ const { GeminiTransport } = await import('./run-T27-mChg.mjs').then(function (n) { return n.G; });
371
+ const { DefaultTransport } = await import('./run-T27-mChg.mjs').then(function (n) { return n.D; });
357
372
  const transportHandler = config.agentName === "gemini" ? new GeminiTransport() : new DefaultTransport(config.agentName);
358
373
  backend = new AcpBackend({
359
374
  agentName: config.agentName,
@@ -480,7 +495,7 @@ async function handleSessionCommand() {
480
495
  process.exit(1);
481
496
  }
482
497
  }
483
- const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach, sessionMachines, sessionSend, sessionWait, sessionShare, sessionRalphStart, sessionRalphCancel, sessionRalphStatus, sessionInboxSend, sessionInboxList, sessionInboxRead, sessionInboxReply, sessionInboxClear } = await import('./commands-BjLVgTDn.mjs');
498
+ const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach, sessionMachines, sessionSend, sessionWait, sessionShare, sessionRalphStart, sessionRalphCancel, sessionRalphStatus, sessionInboxSend, sessionInboxList, sessionInboxRead, sessionInboxReply, sessionInboxClear } = await import('./commands-CSezLS5f.mjs');
484
499
  const parseFlagStr = (flag, shortFlag) => {
485
500
  for (let i = 1; i < sessionArgs.length; i++) {
486
501
  if ((sessionArgs[i] === flag || shortFlag) && i + 1 < sessionArgs.length) {
@@ -540,7 +555,7 @@ async function handleSessionCommand() {
540
555
  allowDomain.push(sessionArgs[++i]);
541
556
  }
542
557
  }
543
- const { parseShareArg } = await import('./commands-BjLVgTDn.mjs');
558
+ const { parseShareArg } = await import('./commands-CSezLS5f.mjs');
544
559
  const shareEntries = share.map((s) => parseShareArg(s));
545
560
  await sessionSpawn(agent, dir, targetMachineId, {
546
561
  message,
@@ -626,7 +641,7 @@ async function handleSessionCommand() {
626
641
  console.error("Usage: svamp session approve <session-id> [request-id] [--json]");
627
642
  process.exit(1);
628
643
  }
629
- const { sessionApprove } = await import('./commands-BjLVgTDn.mjs');
644
+ const { sessionApprove } = await import('./commands-CSezLS5f.mjs');
630
645
  const approveReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
631
646
  await sessionApprove(sessionArgs[1], approveReqId, targetMachineId, {
632
647
  json: hasFlag("--json")
@@ -636,7 +651,7 @@ async function handleSessionCommand() {
636
651
  console.error("Usage: svamp session deny <session-id> [request-id] [--json]");
637
652
  process.exit(1);
638
653
  }
639
- const { sessionDeny } = await import('./commands-BjLVgTDn.mjs');
654
+ const { sessionDeny } = await import('./commands-CSezLS5f.mjs');
640
655
  const denyReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
641
656
  await sessionDeny(sessionArgs[1], denyReqId, targetMachineId, {
642
657
  json: hasFlag("--json")
@@ -672,7 +687,7 @@ async function handleSessionCommand() {
672
687
  console.error("Usage: svamp session set-title <title>");
673
688
  process.exit(1);
674
689
  }
675
- const { sessionSetTitle } = await import('./agentCommands-DNFlwUrE.mjs');
690
+ const { sessionSetTitle } = await import('./agentCommands-DvpIXbrt.mjs');
676
691
  await sessionSetTitle(title);
677
692
  } else if (sessionSubcommand === "set-link") {
678
693
  const url = sessionArgs[1];
@@ -681,7 +696,7 @@ async function handleSessionCommand() {
681
696
  process.exit(1);
682
697
  }
683
698
  const label = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
684
- const { sessionSetLink } = await import('./agentCommands-DNFlwUrE.mjs');
699
+ const { sessionSetLink } = await import('./agentCommands-DvpIXbrt.mjs');
685
700
  await sessionSetLink(url, label);
686
701
  } else if (sessionSubcommand === "notify") {
687
702
  const message = sessionArgs[1];
@@ -690,7 +705,7 @@ async function handleSessionCommand() {
690
705
  process.exit(1);
691
706
  }
692
707
  const level = parseFlagStr("--level") || "info";
693
- const { sessionNotify } = await import('./agentCommands-DNFlwUrE.mjs');
708
+ const { sessionNotify } = await import('./agentCommands-DvpIXbrt.mjs');
694
709
  await sessionNotify(message, level);
695
710
  } else if (sessionSubcommand === "broadcast") {
696
711
  const action = sessionArgs[1];
@@ -698,7 +713,7 @@ async function handleSessionCommand() {
698
713
  console.error("Usage: svamp session broadcast <action> [args...]\nActions: open-canvas <url> [label], close-canvas, toast <message>");
699
714
  process.exit(1);
700
715
  }
701
- const { sessionBroadcast } = await import('./agentCommands-DNFlwUrE.mjs');
716
+ const { sessionBroadcast } = await import('./agentCommands-DvpIXbrt.mjs');
702
717
  await sessionBroadcast(action, sessionArgs.slice(2).filter((a) => !a.startsWith("--")));
703
718
  } else if (sessionSubcommand === "inbox") {
704
719
  const inboxSubcmd = sessionArgs[1];
@@ -709,7 +724,7 @@ async function handleSessionCommand() {
709
724
  process.exit(1);
710
725
  }
711
726
  if (agentSessionId) {
712
- const { inboxSend } = await import('./agentCommands-DNFlwUrE.mjs');
727
+ const { inboxSend } = await import('./agentCommands-DvpIXbrt.mjs');
713
728
  await inboxSend(sessionArgs[2], {
714
729
  body: sessionArgs[3],
715
730
  subject: parseFlagStr("--subject"),
@@ -724,7 +739,7 @@ async function handleSessionCommand() {
724
739
  }
725
740
  } else if (inboxSubcmd === "list" || inboxSubcmd === "ls") {
726
741
  if (agentSessionId && !sessionArgs[2]) {
727
- const { inboxList } = await import('./agentCommands-DNFlwUrE.mjs');
742
+ const { inboxList } = await import('./agentCommands-DvpIXbrt.mjs');
728
743
  await inboxList({
729
744
  unread: hasFlag("--unread"),
730
745
  limit: parseFlagInt("--limit"),
@@ -746,7 +761,7 @@ async function handleSessionCommand() {
746
761
  process.exit(1);
747
762
  }
748
763
  if (agentSessionId && !sessionArgs[3]) {
749
- const { inboxList } = await import('./agentCommands-DNFlwUrE.mjs');
764
+ const { inboxList } = await import('./agentCommands-DvpIXbrt.mjs');
750
765
  await sessionInboxRead(agentSessionId, sessionArgs[2], targetMachineId);
751
766
  } else if (sessionArgs[3]) {
752
767
  await sessionInboxRead(sessionArgs[2], sessionArgs[3], targetMachineId);
@@ -756,7 +771,7 @@ async function handleSessionCommand() {
756
771
  }
757
772
  } else if (inboxSubcmd === "reply") {
758
773
  if (agentSessionId && sessionArgs[2] && sessionArgs[3] && !sessionArgs[4]) {
759
- const { inboxReply } = await import('./agentCommands-DNFlwUrE.mjs');
774
+ const { inboxReply } = await import('./agentCommands-DvpIXbrt.mjs');
760
775
  await inboxReply(sessionArgs[2], sessionArgs[3]);
761
776
  } else if (sessionArgs[2] && sessionArgs[3] && sessionArgs[4]) {
762
777
  await sessionInboxReply(sessionArgs[2], sessionArgs[3], sessionArgs[4], targetMachineId);
@@ -792,7 +807,7 @@ async function handleMachineCommand() {
792
807
  return;
793
808
  }
794
809
  if (machineSubcommand === "share") {
795
- const { machineShare } = await import('./commands-BjLVgTDn.mjs');
810
+ const { machineShare } = await import('./commands-CSezLS5f.mjs');
796
811
  let machineId;
797
812
  const shareArgs = [];
798
813
  for (let i = 1; i < machineArgs.length; i++) {
@@ -822,7 +837,7 @@ async function handleMachineCommand() {
822
837
  }
823
838
  await machineShare(machineId, { add, remove, list, configPath, showConfig });
824
839
  } else if (machineSubcommand === "exec") {
825
- const { machineExec } = await import('./commands-BjLVgTDn.mjs');
840
+ const { machineExec } = await import('./commands-CSezLS5f.mjs');
826
841
  let machineId;
827
842
  let cwd;
828
843
  const cmdParts = [];
@@ -842,7 +857,7 @@ async function handleMachineCommand() {
842
857
  }
843
858
  await machineExec(machineId, command, cwd);
844
859
  } else if (machineSubcommand === "info") {
845
- const { machineInfo } = await import('./commands-BjLVgTDn.mjs');
860
+ const { machineInfo } = await import('./commands-CSezLS5f.mjs');
846
861
  let machineId;
847
862
  for (let i = 1; i < machineArgs.length; i++) {
848
863
  if ((machineArgs[i] === "--machine" || machineArgs[i] === "-m") && i + 1 < machineArgs.length) {
@@ -862,10 +877,10 @@ async function handleMachineCommand() {
862
877
  level = machineArgs[++i];
863
878
  }
864
879
  }
865
- const { machineNotify } = await import('./agentCommands-DNFlwUrE.mjs');
880
+ const { machineNotify } = await import('./agentCommands-DvpIXbrt.mjs');
866
881
  await machineNotify(message, level);
867
882
  } else if (machineSubcommand === "ls") {
868
- const { machineLs } = await import('./commands-BjLVgTDn.mjs');
883
+ const { machineLs } = await import('./commands-CSezLS5f.mjs');
869
884
  let machineId;
870
885
  let showHidden = false;
871
886
  let path;
@@ -1299,7 +1314,7 @@ Usage:
1299
1314
  svamp daemon start --no-auto-continue Start without auto-continuing interrupted sessions
1300
1315
  svamp daemon stop Stop the daemon (sessions preserved for restart)
1301
1316
  svamp daemon stop --cleanup Stop and mark all sessions as stopped
1302
- svamp daemon restart Restart daemon (sessions resume seamlessly)
1317
+ svamp daemon restart Graceful restart \u2014 picks up new binary, sessions resume seamlessly
1303
1318
  svamp daemon status Show daemon status
1304
1319
  svamp daemon install Install as login service (macOS/Linux) \u2014 auto-start at login
1305
1320
  svamp daemon uninstall Remove login service
@@ -1,11 +1,11 @@
1
1
  import { writeFileSync, readFileSync } from 'fs';
2
2
  import { resolve } from 'path';
3
- import { connectAndGetMachine } from './commands-BjLVgTDn.mjs';
3
+ import { connectAndGetMachine } from './commands-CSezLS5f.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-w_M8ajhF.mjs';
8
+ import './run-T27-mChg.mjs';
9
9
  import 'os';
10
10
  import 'fs/promises';
11
11
  import 'url';
@@ -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-w_M8ajhF.mjs';
5
+ import { l as loadSecurityContextConfig, e as resolveSecurityContext, f as buildSecurityContextFromFlags, m as mergeSecurityContexts, c as connectToHypha } from './run-T27-mChg.mjs';
6
6
  import 'os';
7
7
  import 'fs/promises';
8
8
  import 'fs';
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-w_M8ajhF.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-T27-mChg.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.10";
2
+ var version = "0.2.12";
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";
@@ -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-w_M8ajhF.mjs';
5
+ import { c as connectToHypha, a as registerSessionService } from './run-T27-mChg.mjs';
6
6
  import { createServer } from 'node:http';
7
7
  import { spawn } from 'node:child_process';
8
8
  import { createInterface } from 'node:readline';
@@ -2028,7 +2028,7 @@ async function registerDebugService(server, machineId, deps) {
2028
2028
  };
2029
2029
  }
2030
2030
 
2031
- const COLLECTION_ALIAS$1 = "svamp-agent-sessions";
2031
+ const COLLECTION_ALIAS = "svamp-agent-sessions";
2032
2032
  class SessionArtifactSync {
2033
2033
  server;
2034
2034
  artifactManager = null;
@@ -2063,14 +2063,14 @@ class SessionArtifactSync {
2063
2063
  async ensureCollection() {
2064
2064
  try {
2065
2065
  const existing = await this.artifactManager.read({
2066
- artifact_id: COLLECTION_ALIAS$1,
2066
+ artifact_id: COLLECTION_ALIAS,
2067
2067
  _rkwargs: true
2068
2068
  });
2069
2069
  this.collectionId = existing.id;
2070
2070
  this.log(`[ARTIFACT SYNC] Found existing collection: ${this.collectionId}`);
2071
2071
  } catch {
2072
2072
  const collection = await this.artifactManager.create({
2073
- alias: COLLECTION_ALIAS$1,
2073
+ alias: COLLECTION_ALIAS,
2074
2074
  type: "collection",
2075
2075
  manifest: {
2076
2076
  name: "Svamp Agent Sessions",
@@ -2349,17 +2349,22 @@ class SessionArtifactSync {
2349
2349
  }
2350
2350
  }
2351
2351
 
2352
- const COLLECTION_ALIAS = "svamp-shared-sessions";
2352
+ const SHARE_COLLECTION_ALIAS = "svamp-shared-sessions";
2353
+ const EVENT_COLLECTION_ALIAS = "svamp-user-events";
2353
2354
  function emailHash(email) {
2354
2355
  return createHash("sha256").update(email.toLowerCase()).digest("hex").slice(0, 12);
2355
2356
  }
2356
- function notificationAlias(sessionId, recipientEmail) {
2357
+ function shareAlias(sessionId, recipientEmail) {
2357
2358
  return `share-${sessionId.slice(0, 8)}-${emailHash(recipientEmail)}`;
2358
2359
  }
2360
+ function eventAlias(eventId, recipientEmail) {
2361
+ return `evt-${emailHash(recipientEmail)}-${eventId.slice(0, 8)}`;
2362
+ }
2359
2363
  class SharingNotificationSync {
2360
2364
  server;
2361
2365
  artifactManager = null;
2362
- collectionId = null;
2366
+ shareCollectionId = null;
2367
+ eventCollectionId = null;
2363
2368
  initialized = false;
2364
2369
  log;
2365
2370
  constructor(server, log) {
@@ -2373,49 +2378,48 @@ class SharingNotificationSync {
2373
2378
  this.log("[SHARING NOTIFY] Artifact manager not available");
2374
2379
  return;
2375
2380
  }
2376
- await this.ensureCollection();
2381
+ await Promise.all([
2382
+ this.ensureCollection(SHARE_COLLECTION_ALIAS, "Svamp Shared Sessions", "Cross-workspace share notifications for session/machine bookmarks").then((id) => {
2383
+ this.shareCollectionId = id;
2384
+ }),
2385
+ this.ensureCollection(EVENT_COLLECTION_ALIAS, "Svamp User Events", "General-purpose user event inbox for bell notifications").then((id) => {
2386
+ this.eventCollectionId = id;
2387
+ })
2388
+ ]);
2377
2389
  this.initialized = true;
2378
- this.log("[SHARING NOTIFY] Initialized");
2390
+ this.log("[SHARING NOTIFY] Initialized (share + events collections)");
2379
2391
  } catch (err) {
2380
2392
  this.log(`[SHARING NOTIFY] Init failed: ${err.message}`);
2381
2393
  }
2382
2394
  }
2383
- async ensureCollection() {
2395
+ async ensureCollection(alias, name, description) {
2384
2396
  try {
2385
2397
  const existing = await this.artifactManager.read({
2386
- artifact_id: COLLECTION_ALIAS,
2398
+ artifact_id: alias,
2387
2399
  _rkwargs: true
2388
2400
  });
2389
- this.collectionId = existing.id;
2401
+ return existing.id;
2390
2402
  } catch {
2391
2403
  const collection = await this.artifactManager.create({
2392
- alias: COLLECTION_ALIAS,
2404
+ alias,
2393
2405
  type: "collection",
2394
2406
  stage: true,
2395
- manifest: {
2396
- name: "Svamp Shared Sessions",
2397
- description: "Cross-workspace share notifications for session/machine bookmarks"
2398
- },
2399
- config: {
2400
- permissions: { "*": "r", "@": "rw+" }
2401
- },
2407
+ manifest: { name, description },
2408
+ config: { permissions: { "*": "r", "@": "rw+" } },
2402
2409
  _rkwargs: true
2403
2410
  });
2404
- this.collectionId = collection.id;
2405
2411
  await this.artifactManager.commit({
2406
- artifact_id: this.collectionId,
2412
+ artifact_id: collection.id,
2407
2413
  _rkwargs: true
2408
2414
  });
2409
- this.log(`[SHARING NOTIFY] Created collection: ${this.collectionId}`);
2415
+ this.log(`[SHARING NOTIFY] Created collection: ${alias} (${collection.id})`);
2416
+ return collection.id;
2410
2417
  }
2411
2418
  }
2412
- /**
2413
- * Publish a share notification for a recipient.
2414
- * Idempotent — uses deterministic alias, so re-sharing updates the existing artifact.
2415
- */
2419
+ // ── Share bookmark artifacts (svamp-shared-sessions) ──────────────
2416
2420
  async notifyShare(params) {
2417
- if (!this.initialized || !this.collectionId) return;
2418
- const alias = notificationAlias(params.sessionId, params.recipientEmail);
2421
+ if (!this.initialized || !this.shareCollectionId) return;
2422
+ const alias = shareAlias(params.sessionId, params.recipientEmail);
2419
2423
  const manifest = {
2420
2424
  recipientEmail: params.recipientEmail.toLowerCase(),
2421
2425
  sessionId: params.sessionId,
@@ -2431,7 +2435,7 @@ class SharingNotificationSync {
2431
2435
  try {
2432
2436
  const existing = await this.artifactManager.read({
2433
2437
  artifact_id: alias,
2434
- parent_id: this.collectionId,
2438
+ parent_id: this.shareCollectionId,
2435
2439
  _rkwargs: true
2436
2440
  });
2437
2441
  await this.artifactManager.edit({
@@ -2447,7 +2451,7 @@ class SharingNotificationSync {
2447
2451
  try {
2448
2452
  const artifact = await this.artifactManager.create({
2449
2453
  alias,
2450
- parent_id: this.collectionId,
2454
+ parent_id: this.shareCollectionId,
2451
2455
  type: "share-notification",
2452
2456
  stage: true,
2453
2457
  manifest,
@@ -2458,36 +2462,110 @@ class SharingNotificationSync {
2458
2462
  _rkwargs: true
2459
2463
  });
2460
2464
  } catch (createErr) {
2461
- this.log(`[SHARING NOTIFY] Failed to create notification for ${params.recipientEmail}: ${createErr.message}`);
2465
+ this.log(`[SHARING NOTIFY] Failed to create share notification for ${params.recipientEmail}: ${createErr.message}`);
2462
2466
  return;
2463
2467
  }
2464
2468
  }
2465
- this.log(`[SHARING NOTIFY] Notified ${params.recipientEmail} about ${params.shareType || "session"} ${params.sessionId.slice(0, 8)}`);
2469
+ this.log(`[SHARING NOTIFY] Share notified ${params.recipientEmail} about ${params.shareType || "session"} ${params.sessionId.slice(0, 8)}`);
2470
+ const shareType = params.shareType || "session";
2471
+ const typeLabel = shareType === "machine" ? "Machine" : "Session";
2472
+ await this.publishUserEvent({
2473
+ id: randomUUID(),
2474
+ recipientEmail: params.recipientEmail.toLowerCase(),
2475
+ type: "sharing-invite",
2476
+ source: {
2477
+ service: "svamp-machine",
2478
+ workspace: params.ownerWorkspace,
2479
+ userEmail: params.ownerEmail
2480
+ },
2481
+ title: `${typeLabel} shared with you`,
2482
+ body: `${params.ownerEmail} shared "${params.label || "Untitled"}" with you as ${params.role}`,
2483
+ level: "info",
2484
+ action: shareType === "session" ? {
2485
+ type: "add-session-bookmark",
2486
+ sessionId: params.sessionId,
2487
+ workspace: params.ownerWorkspace,
2488
+ machineServiceId: params.machineServiceId,
2489
+ label: params.label || ""
2490
+ } : {
2491
+ type: "add-machine-bookmark",
2492
+ machineId: params.machineId,
2493
+ workspace: params.ownerWorkspace,
2494
+ label: params.label || ""
2495
+ },
2496
+ createdAt: Date.now(),
2497
+ expiresAt: Date.now() + 30 * 24 * 60 * 60 * 1e3
2498
+ // 30 days
2499
+ });
2466
2500
  }
2467
- /**
2468
- * Remove a share notification when a user is unshared.
2469
- */
2470
2501
  async removeNotification(sessionId, recipientEmail) {
2471
- if (!this.initialized || !this.collectionId) return;
2472
- const alias = notificationAlias(sessionId, recipientEmail);
2502
+ if (!this.initialized || !this.shareCollectionId) return;
2503
+ const alias = shareAlias(sessionId, recipientEmail);
2473
2504
  try {
2474
2505
  const existing = await this.artifactManager.read({
2475
2506
  artifact_id: alias,
2476
- parent_id: this.collectionId,
2507
+ parent_id: this.shareCollectionId,
2477
2508
  _rkwargs: true
2478
2509
  });
2479
2510
  await this.artifactManager.delete({
2480
2511
  artifact_id: existing.id,
2481
2512
  _rkwargs: true
2482
2513
  });
2483
- this.log(`[SHARING NOTIFY] Removed notification for ${recipientEmail} on ${sessionId.slice(0, 8)}`);
2514
+ this.log(`[SHARING NOTIFY] Removed share notification for ${recipientEmail} on ${sessionId.slice(0, 8)}`);
2484
2515
  } catch {
2485
2516
  }
2517
+ await this.publishUserEvent({
2518
+ id: randomUUID(),
2519
+ recipientEmail: recipientEmail.toLowerCase(),
2520
+ type: "sharing-revoked",
2521
+ source: {
2522
+ service: "svamp-machine",
2523
+ workspace: this.server.config?.workspace || "",
2524
+ userEmail: ""
2525
+ },
2526
+ title: "Session access removed",
2527
+ body: `Your access to a shared session has been revoked`,
2528
+ level: "warning",
2529
+ createdAt: Date.now(),
2530
+ expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1e3
2531
+ // 7 days
2532
+ });
2486
2533
  }
2534
+ // ── General user event artifacts (svamp-user-events) ─────────────
2487
2535
  /**
2488
- * Sync all sharing notifications for a session based on its current sharing config.
2489
- * Adds notifications for new users, removes for removed users.
2536
+ * Publish a general user event to the event inbox collection.
2537
+ * Any service can call this to deliver an event to any user's bell icon.
2490
2538
  */
2539
+ async publishUserEvent(event) {
2540
+ if (!this.initialized || !this.eventCollectionId) return;
2541
+ const alias = eventAlias(event.id, event.recipientEmail);
2542
+ try {
2543
+ const artifact = await this.artifactManager.create({
2544
+ alias,
2545
+ parent_id: this.eventCollectionId,
2546
+ type: "user-event",
2547
+ stage: true,
2548
+ manifest: event,
2549
+ _rkwargs: true
2550
+ });
2551
+ await this.artifactManager.commit({
2552
+ artifact_id: artifact.id,
2553
+ _rkwargs: true
2554
+ });
2555
+ this.log(`[USER EVENT] Published ${event.type} to ${event.recipientEmail}`);
2556
+ try {
2557
+ this.server.emit?.({
2558
+ type: "svamp:user-event",
2559
+ data: event,
2560
+ to: "*"
2561
+ });
2562
+ } catch {
2563
+ }
2564
+ } catch (err) {
2565
+ this.log(`[USER EVENT] Failed to publish ${event.type} for ${event.recipientEmail}: ${err.message}`);
2566
+ }
2567
+ }
2568
+ // ── Sharing config diff + sync ───────────────────────────────────
2491
2569
  async syncSharing(sessionId, oldSharing, newSharing, context) {
2492
2570
  if (!this.initialized) return;
2493
2571
  const oldEmails = new Set(
@@ -6639,7 +6717,8 @@ async function startDaemon(options) {
6639
6717
  }
6640
6718
  }
6641
6719
  if (msg.type === "result") {
6642
- if (!turnInitiatedByUser) {
6720
+ const ralphActive = !!readRalphState(getRalphStateFilePath(directory, sessionId));
6721
+ if (!turnInitiatedByUser && !ralphActive) {
6643
6722
  logger.log(`[Session ${sessionId}] Skipping stale result from SDK-initiated turn`);
6644
6723
  const hasBackgroundTasks = backgroundTaskCount > 0;
6645
6724
  if (hasBackgroundTasks) {
@@ -6664,6 +6743,10 @@ async function startDaemon(options) {
6664
6743
  turnInitiatedByUser = true;
6665
6744
  continue;
6666
6745
  }
6746
+ if (!turnInitiatedByUser && ralphActive) {
6747
+ logger.log(`[Session ${sessionId}] SDK-initiated result during active Ralph loop \u2014 processing anyway to avoid stalling`);
6748
+ turnInitiatedByUser = true;
6749
+ }
6667
6750
  if (msg.session_id) {
6668
6751
  claudeResumeId = msg.session_id;
6669
6752
  }
@@ -8786,6 +8869,85 @@ async function stopDaemon(options) {
8786
8869
  } catch {
8787
8870
  }
8788
8871
  }
8872
+ async function restartDaemon() {
8873
+ const supervisorPidFile = join(SVAMP_HOME, "supervisor.pid");
8874
+ let supervisorPid = null;
8875
+ try {
8876
+ if (existsSync$1(supervisorPidFile)) {
8877
+ supervisorPid = parseInt(readFileSync$1(supervisorPidFile, "utf-8").trim(), 10);
8878
+ if (isNaN(supervisorPid)) supervisorPid = null;
8879
+ if (supervisorPid) {
8880
+ try {
8881
+ process.kill(supervisorPid, 0);
8882
+ } catch {
8883
+ supervisorPid = null;
8884
+ }
8885
+ }
8886
+ }
8887
+ } catch {
8888
+ }
8889
+ const doFullRestart = async (reason) => {
8890
+ console.log(`${reason} \u2014 doing full stop + start`);
8891
+ await stopDaemon();
8892
+ const { spawn: spawn2 } = await import('child_process');
8893
+ const child = spawn2(process.execPath, [
8894
+ "--no-warnings",
8895
+ "--no-deprecation",
8896
+ ...process.argv.slice(1, 2),
8897
+ "daemon",
8898
+ "start-supervised"
8899
+ ], {
8900
+ detached: true,
8901
+ stdio: "ignore",
8902
+ env: process.env
8903
+ });
8904
+ child.unref();
8905
+ const stateFile2 = join(SVAMP_HOME, "daemon.state.json");
8906
+ for (let i = 0; i < 100; i++) {
8907
+ await new Promise((r) => setTimeout(r, 100));
8908
+ if (existsSync$1(stateFile2)) {
8909
+ console.log("Daemon restarted successfully");
8910
+ return;
8911
+ }
8912
+ }
8913
+ console.error("Failed to restart daemon (timeout waiting for state file)");
8914
+ process.exit(1);
8915
+ };
8916
+ if (!supervisorPid) {
8917
+ await doFullRestart("No supervisor process found");
8918
+ return;
8919
+ }
8920
+ const oldState = readDaemonStateFile();
8921
+ const oldPid = oldState?.pid;
8922
+ console.log(`Sending SIGUSR2 to supervisor (PID ${supervisorPid}) \u2014 graceful restart`);
8923
+ try {
8924
+ process.kill(supervisorPid, "SIGUSR2");
8925
+ } catch {
8926
+ await doFullRestart("Failed to signal supervisor");
8927
+ return;
8928
+ }
8929
+ const stateFile = join(SVAMP_HOME, "daemon.state.json");
8930
+ for (let i = 0; i < 300; i++) {
8931
+ await new Promise((r) => setTimeout(r, 100));
8932
+ try {
8933
+ process.kill(supervisorPid, 0);
8934
+ } catch {
8935
+ await doFullRestart("Supervisor exited (old binary without SIGUSR2 support?)");
8936
+ return;
8937
+ }
8938
+ const newState = readDaemonStateFile();
8939
+ if (newState && newState.pid !== oldPid) {
8940
+ console.log("Daemon restarted successfully (new binary in effect)");
8941
+ return;
8942
+ }
8943
+ }
8944
+ if (existsSync$1(stateFile)) {
8945
+ console.log("Daemon restarted (state file present)");
8946
+ } else {
8947
+ console.error("Daemon restart may have failed \u2014 no state file after 30s. Falling back to full restart.");
8948
+ await doFullRestart("Graceful restart timed out");
8949
+ }
8950
+ }
8789
8951
  function daemonStatus() {
8790
8952
  const state = readDaemonStateFile();
8791
8953
  if (!state) {
@@ -8817,4 +8979,12 @@ function daemonStatus() {
8817
8979
  }
8818
8980
  }
8819
8981
 
8820
- export { DefaultTransport$1 as D, GeminiTransport$1 as G, registerSessionService as a, stopDaemon as b, connectToHypha as c, daemonStatus as d, resolveSecurityContext as e, buildSecurityContextFromFlags as f, getHyphaServerUrl as g, acpBackend as h, acpAgentConfig as i, codexMcpBackend as j, loadSecurityContextConfig as l, mergeSecurityContexts as m, registerMachineService as r, startDaemon as s };
8982
+ var run = /*#__PURE__*/Object.freeze({
8983
+ __proto__: null,
8984
+ daemonStatus: daemonStatus,
8985
+ restartDaemon: restartDaemon,
8986
+ startDaemon: startDaemon,
8987
+ stopDaemon: stopDaemon
8988
+ });
8989
+
8990
+ export { DefaultTransport$1 as D, GeminiTransport$1 as G, registerSessionService as a, stopDaemon as b, connectToHypha as c, daemonStatus as d, resolveSecurityContext as e, buildSecurityContextFromFlags as f, getHyphaServerUrl as g, acpBackend as h, acpAgentConfig as i, codexMcpBackend as j, run as k, loadSecurityContextConfig as l, mergeSecurityContexts as m, registerMachineService as r, startDaemon as s };
@@ -52,7 +52,7 @@ async function handleServeCommand() {
52
52
  }
53
53
  }
54
54
  async function serveAdd(args, machineId) {
55
- const { connectAndGetMachine } = await import('./commands-BjLVgTDn.mjs');
55
+ const { connectAndGetMachine } = await import('./commands-CSezLS5f.mjs');
56
56
  const pos = positionalArgs(args);
57
57
  const name = pos[0];
58
58
  if (!name) {
@@ -84,7 +84,7 @@ async function serveAdd(args, machineId) {
84
84
  }
85
85
  }
86
86
  async function serveRemove(args, machineId) {
87
- const { connectAndGetMachine } = await import('./commands-BjLVgTDn.mjs');
87
+ const { connectAndGetMachine } = await import('./commands-CSezLS5f.mjs');
88
88
  const pos = positionalArgs(args);
89
89
  const name = pos[0];
90
90
  if (!name) {
@@ -104,7 +104,7 @@ async function serveRemove(args, machineId) {
104
104
  }
105
105
  }
106
106
  async function serveList(args, machineId) {
107
- const { connectAndGetMachine } = await import('./commands-BjLVgTDn.mjs');
107
+ const { connectAndGetMachine } = await import('./commands-CSezLS5f.mjs');
108
108
  const all = hasFlag(args, "--all", "-a");
109
109
  const json = hasFlag(args, "--json");
110
110
  const sessionId = getFlag(args, "--session");
@@ -137,7 +137,7 @@ async function serveList(args, machineId) {
137
137
  }
138
138
  }
139
139
  async function serveInfo(machineId) {
140
- const { connectAndGetMachine } = await import('./commands-BjLVgTDn.mjs');
140
+ const { connectAndGetMachine } = await import('./commands-CSezLS5f.mjs');
141
141
  const { machine, server } = await connectAndGetMachine(machineId);
142
142
  try {
143
143
  const info = await machine.serveInfo({ _rkwargs: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svamp-cli",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
4
4
  "description": "Svamp CLI — AI workspace daemon on Hypha Cloud",
5
5
  "author": "Amun AI AB",
6
6
  "license": "SEE LICENSE IN LICENSE",