svamp-cli 0.2.356 → 0.2.358

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 (26) hide show
  1. package/dist/{agentCommands-D91RcjqQ.mjs → agentCommands-C_KvvYFp.mjs} +5 -5
  2. package/dist/{claudeAuth-C0wc1GTM.mjs → claudeAuth-DMDvPNhO.mjs} +11 -6
  3. package/dist/cli.mjs +136 -67
  4. package/dist/{commands-CoOc4qiw.mjs → commands-B19lVZjm.mjs} +10 -3
  5. package/dist/{commands-C13-yE2R.mjs → commands-Bmoy6Rlw.mjs} +5 -4
  6. package/dist/{commands-sPvRcG3J.mjs → commands-CFBKkfX8.mjs} +9 -9
  7. package/dist/{commands-B-yczgz_.mjs → commands-CcdoQIkb.mjs} +114 -17
  8. package/dist/{commands-BMeDrpSv.mjs → commands-DH_YQrAP.mjs} +5 -21
  9. package/dist/{commands-BbmypSgx.mjs → commands-DRxANaS5.mjs} +61 -18
  10. package/dist/{commands-BkdbelFo.mjs → commands-nkvTnUJj.mjs} +6 -5
  11. package/dist/friendlyName-B-OEwt0t.mjs +341 -0
  12. package/dist/{headlessCli-C8HzQvis.mjs → headlessCli-BpTXz6FD.mjs} +24 -16
  13. package/dist/{hookSettings-FFc1pi8j.mjs → hookSettings-CvvUhwCK.mjs} +122 -30
  14. package/dist/{friendlyName-1UZcyDhu.mjs → inboxGuard-CDqmIypF.mjs} +32 -347
  15. package/dist/index.mjs +7 -6
  16. package/dist/{loopVerify-DALtTk2V.mjs → loopVerify-CxAKJB5a.mjs} +1 -1
  17. package/dist/package-DTTWvJbp.mjs +64 -0
  18. package/dist/{rpc-ccY7xvlo.mjs → rpc-CAlAHQAy.mjs} +3 -1
  19. package/dist/{rpc-CBkSkzsI.mjs → rpc-CacreLQ8.mjs} +10 -6
  20. package/dist/{run-DCq3aEiF.mjs → run-Bc81iurl.mjs} +26 -7
  21. package/dist/{run-DDmeV9K3.mjs → run-DKOND4dO.mjs} +48 -30
  22. package/dist/{scheduler-BkVJ27Ki.mjs → scheduler-IyLWBF8T.mjs} +25 -7
  23. package/dist/{serveCommands-CLhkccJM.mjs → serveCommands-B-Ym_QXc.mjs} +8 -8
  24. package/dist/{store-B4E6NzG6.mjs → store-D8dok6_d.mjs} +53 -14
  25. package/package.json +3 -3
  26. package/dist/package-BrsiNutS.mjs +0 -64
@@ -0,0 +1,341 @@
1
+ import { randomBytes } from 'node:crypto';
2
+
3
+ const ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
4
+ const LOWER_ALPHABET = "abcdefghijklmnopqrstuvwxyz";
5
+ function shortId(length = 10, alphabet = ALPHABET) {
6
+ const maxUnbiased = Math.floor(256 / alphabet.length) * alphabet.length;
7
+ let out = "";
8
+ while (out.length < length) {
9
+ const buf = randomBytes(length - out.length);
10
+ for (let i = 0; i < buf.length && out.length < length; i++) {
11
+ const b = buf[i];
12
+ if (b < maxUnbiased) out += alphabet[b % alphabet.length];
13
+ }
14
+ }
15
+ return out;
16
+ }
17
+
18
+ const ADJECTIVES = [
19
+ "able",
20
+ "amber",
21
+ "amused",
22
+ "ancient",
23
+ "arctic",
24
+ "autumn",
25
+ "azure",
26
+ "blithe",
27
+ "bold",
28
+ "brave",
29
+ "breezy",
30
+ "bright",
31
+ "brisk",
32
+ "calm",
33
+ "candid",
34
+ "cheery",
35
+ "chill",
36
+ "clever",
37
+ "cobalt",
38
+ "cosmic",
39
+ "cozy",
40
+ "crimson",
41
+ "crisp",
42
+ "curious",
43
+ "dapper",
44
+ "daring",
45
+ "dawn",
46
+ "deft",
47
+ "dewy",
48
+ "eager",
49
+ "early",
50
+ "easy",
51
+ "electric",
52
+ "fancy",
53
+ "feisty",
54
+ "fleet",
55
+ "fond",
56
+ "frosty",
57
+ "gallant",
58
+ "gentle",
59
+ "giddy",
60
+ "glad",
61
+ "gleaming",
62
+ "golden",
63
+ "graceful",
64
+ "grand",
65
+ "hardy",
66
+ "hazel",
67
+ "hearty",
68
+ "honest",
69
+ "humble",
70
+ "jolly",
71
+ "jovial",
72
+ "keen",
73
+ "kind",
74
+ "lively",
75
+ "loyal",
76
+ "lucky",
77
+ "lunar",
78
+ "mellow",
79
+ "merry",
80
+ "mighty",
81
+ "mint",
82
+ "misty",
83
+ "nimble",
84
+ "noble",
85
+ "opal",
86
+ "patient",
87
+ "peppy",
88
+ "placid",
89
+ "plucky",
90
+ "polar",
91
+ "prim",
92
+ "proud",
93
+ "quick",
94
+ "quiet",
95
+ "quirky",
96
+ "radiant",
97
+ "rapid",
98
+ "ready",
99
+ "regal",
100
+ "rosy",
101
+ "royal",
102
+ "rugged",
103
+ "sage",
104
+ "sandy",
105
+ "scarlet",
106
+ "serene",
107
+ "sharp",
108
+ "shiny",
109
+ "silent",
110
+ "silver",
111
+ "sleek",
112
+ "smooth",
113
+ "snappy",
114
+ "snowy",
115
+ "solar",
116
+ "spry",
117
+ "stellar",
118
+ "sturdy",
119
+ "sunny",
120
+ "swift",
121
+ "tender",
122
+ "tidal",
123
+ "tidy",
124
+ "tranquil",
125
+ "trusty",
126
+ "upbeat",
127
+ "valiant",
128
+ "vivid",
129
+ "warm",
130
+ "whimsical",
131
+ "wise",
132
+ "witty",
133
+ "zesty",
134
+ "zippy"
135
+ ];
136
+ const ANIMALS = [
137
+ "ant",
138
+ "badger",
139
+ "bat",
140
+ "bear",
141
+ "beaver",
142
+ "bee",
143
+ "bison",
144
+ "boar",
145
+ "bobcat",
146
+ "buffalo",
147
+ "camel",
148
+ "caribou",
149
+ "cat",
150
+ "cheetah",
151
+ "cobra",
152
+ "condor",
153
+ "cougar",
154
+ "coyote",
155
+ "crab",
156
+ "crane",
157
+ "cricket",
158
+ "crow",
159
+ "deer",
160
+ "dingo",
161
+ "dolphin",
162
+ "donkey",
163
+ "dove",
164
+ "dragon",
165
+ "duck",
166
+ "eagle",
167
+ "eel",
168
+ "egret",
169
+ "elk",
170
+ "falcon",
171
+ "ferret",
172
+ "finch",
173
+ "fox",
174
+ "frog",
175
+ "gecko",
176
+ "gibbon",
177
+ "goat",
178
+ "goose",
179
+ "gopher",
180
+ "hare",
181
+ "hawk",
182
+ "hedgehog",
183
+ "heron",
184
+ "hippo",
185
+ "horse",
186
+ "ibex",
187
+ "ibis",
188
+ "iguana",
189
+ "jackal",
190
+ "jaguar",
191
+ "jay",
192
+ "kestrel",
193
+ "koala",
194
+ "krill",
195
+ "lark",
196
+ "lemur",
197
+ "leopard",
198
+ "lion",
199
+ "llama",
200
+ "lynx",
201
+ "macaw",
202
+ "magpie",
203
+ "mantis",
204
+ "marmot",
205
+ "marten",
206
+ "meerkat",
207
+ "mink",
208
+ "mole",
209
+ "moose",
210
+ "moth",
211
+ "mouse",
212
+ "newt",
213
+ "ocelot",
214
+ "octopus",
215
+ "orca",
216
+ "osprey",
217
+ "otter",
218
+ "owl",
219
+ "ox",
220
+ "panda",
221
+ "panther",
222
+ "parrot",
223
+ "pelican",
224
+ "penguin",
225
+ "pheasant",
226
+ "pigeon",
227
+ "puffin",
228
+ "puma",
229
+ "quail",
230
+ "rabbit",
231
+ "raccoon",
232
+ "ram",
233
+ "raven",
234
+ "robin",
235
+ "salmon",
236
+ "seal",
237
+ "shark",
238
+ "sheep",
239
+ "shrew",
240
+ "skunk",
241
+ "sloth",
242
+ "snail",
243
+ "sparrow",
244
+ "spider",
245
+ "squid",
246
+ "stag",
247
+ "stoat",
248
+ "stork",
249
+ "swan",
250
+ "tapir",
251
+ "tiger",
252
+ "toad",
253
+ "trout",
254
+ "turtle",
255
+ "viper",
256
+ "vole",
257
+ "walrus",
258
+ "weasel",
259
+ "whale",
260
+ "wolf",
261
+ "wombat",
262
+ "wren",
263
+ "yak",
264
+ "zebra"
265
+ ];
266
+ function pick(arr) {
267
+ const n = arr.length;
268
+ const max = Math.floor(256 / n) * n;
269
+ let b;
270
+ do {
271
+ b = randomBytes(1)[0];
272
+ } while (b >= max);
273
+ return arr[b % n];
274
+ }
275
+ function generateFriendlyName(taken = []) {
276
+ const used = taken instanceof Set ? taken : new Set(taken);
277
+ for (let i = 0; i < 1e3; i++) {
278
+ const name = `${pick(ADJECTIVES)}-${pick(ANIMALS)}`;
279
+ if (!used.has(name)) return name;
280
+ }
281
+ const base = `${pick(ADJECTIVES)}-${pick(ANIMALS)}`;
282
+ let n = 2;
283
+ while (used.has(`${base}-${n}`)) n++;
284
+ return `${base}-${n}`;
285
+ }
286
+ function sanitizeSegment(s) {
287
+ return String(s || "").toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
288
+ }
289
+ function friendlyNameFromId(id) {
290
+ const m = /^(.+)-[a-zA-Z0-9]{6,10}$/.exec(String(id || ""));
291
+ return m ? m[1] : void 0;
292
+ }
293
+ function composeSessionId(friendlyName, takenIds = [], len = 6) {
294
+ const taken = takenIds instanceof Set ? takenIds : new Set(takenIds);
295
+ const A = LOWER_ALPHABET.length;
296
+ const nthSuffix = (i, width2) => {
297
+ let s = "";
298
+ for (let k = 0; k < width2; k++) {
299
+ s = LOWER_ALPHABET[i % A] + s;
300
+ i = Math.floor(i / A);
301
+ }
302
+ return s;
303
+ };
304
+ let width = Math.max(1, len);
305
+ for (; ; ) {
306
+ const space = Math.pow(A, width);
307
+ for (let i = 0; i < 64; i++) {
308
+ const candidate = `${friendlyName}-${shortId(width, LOWER_ALPHABET)}`;
309
+ if (!taken.has(candidate)) return candidate;
310
+ }
311
+ if (space <= 1e6) {
312
+ for (let i = 0; i < space; i++) {
313
+ const candidate = `${friendlyName}-${nthSuffix(i, width)}`;
314
+ if (!taken.has(candidate)) return candidate;
315
+ }
316
+ }
317
+ width++;
318
+ }
319
+ }
320
+ function formatHandle(projectName, friendlyName) {
321
+ if (!friendlyName) return void 0;
322
+ const proj = sanitizeSegment(projectName || "");
323
+ return proj ? `${proj}:${friendlyName}` : friendlyName;
324
+ }
325
+ function parseHandle(input) {
326
+ const str = String(input || "").trim().replace(/^@/, "");
327
+ if (!str) return null;
328
+ const i = str.indexOf(":");
329
+ if (i === -1) return { name: str.toLowerCase() };
330
+ return { project: sanitizeSegment(str.slice(0, i)), name: str.slice(i + 1).toLowerCase() };
331
+ }
332
+ function handleMatchesMetadata(parsed, metadata) {
333
+ const fn = String(metadata?.friendlyName || "").toLowerCase();
334
+ if (!fn || fn !== parsed.name) return false;
335
+ if (parsed.project) {
336
+ return sanitizeSegment(metadata?.projectName || "") === parsed.project;
337
+ }
338
+ return true;
339
+ }
340
+
341
+ export { friendlyNameFromId as a, composeSessionId as c, formatHandle as f, generateFriendlyName as g, handleMatchesMetadata as h, parseHandle as p, shortId as s };
@@ -261,6 +261,10 @@ async function createPorcupineWakeWord(opts) {
261
261
  };
262
262
  }
263
263
 
264
+ const DEFAULT_REALTIME_MODEL = "gpt-realtime-mini";
265
+ function resolveRealtimeModel(explicit, env) {
266
+ return explicit || env.WISE_VOICE_MODEL || DEFAULT_REALTIME_MODEL;
267
+ }
264
268
  async function runWiseVoiceCli(opts = {}) {
265
269
  const resolved = resolveModel({ provider: "openai" }, process.env);
266
270
  const mis = describeMisconfiguration(resolved);
@@ -286,30 +290,34 @@ async function runWiseVoiceCli(opts = {}) {
286
290
  }
287
291
  }
288
292
  const audio = createSoxAudioAdapter();
289
- console.log(`\u25CF WISE headless voice \u2014 ${tools.length} tools | model ${opts.model || resolved.model || "gpt-realtime-mini"} | ${wakeWord ? `wake: "${opts.wakeKeywordPath}"` : "always-listening (Ctrl-C to stop)"}`);
293
+ const realtimeModel = resolveRealtimeModel(opts.model, process.env);
294
+ console.log(`\u25CF WISE headless voice \u2014 ${tools.length} tools | model ${realtimeModel} | ${wakeWord ? `wake: "${opts.wakeKeywordPath}"` : "always-listening (Ctrl-C to stop)"}`);
290
295
  const loop = runHeadlessVoice({
291
296
  apiKey: resolved.apiKey,
292
297
  baseUrl: resolved.baseUrl,
293
- config: { instructions, tools, voice: opts.voice, model: opts.model || resolved.model },
298
+ config: { instructions, tools, voice: opts.voice, model: realtimeModel },
299
+ // #1710
294
300
  audio,
295
301
  wakeWord,
296
302
  onTool: (t) => console.log(` \u21B3 ${t.name}: ${t.output.slice(0, 120).replace(/\n/g, " ")}`),
297
303
  onError: (e) => console.error(` ! ${e.message}`)
298
304
  });
299
- const shutdown = () => {
300
- console.log("\nstopping\u2026");
301
- try {
302
- loop.stop();
303
- } catch {
304
- }
305
- try {
306
- audio.close();
307
- } catch {
308
- }
309
- process.exit(0);
310
- };
311
- process.on("SIGINT", shutdown);
312
- process.on("SIGTERM", shutdown);
305
+ await new Promise((resolve) => {
306
+ const shutdown = () => {
307
+ console.log("\nstopping\u2026");
308
+ try {
309
+ loop.stop();
310
+ } catch {
311
+ }
312
+ try {
313
+ audio.close();
314
+ } catch {
315
+ }
316
+ resolve();
317
+ };
318
+ process.on("SIGINT", shutdown);
319
+ process.on("SIGTERM", shutdown);
320
+ });
313
321
  }
314
322
 
315
323
  export { runWiseVoiceCli };
@@ -22,10 +22,10 @@ import { existsSync as existsSync$1, mkdirSync as mkdirSync$1, readFileSync as r
22
22
  import os, { homedir as homedir$1 } from 'node:os';
23
23
  import { join as join$1, extname, resolve, sep, basename, dirname } from 'node:path';
24
24
  import { e as envFlagEnabled, a as envFlagDisabled } from './envFlag-COOuJ3AH.mjs';
25
- import { normalizeProxyUrl, resolveHyphaProxyUrl, applyClaudeProxyEnv } from './claudeAuth-C0wc1GTM.mjs';
25
+ import { normalizeProxyUrl, resolveHyphaProxyUrl, applyClaudeProxyEnv } from './claudeAuth-DMDvPNhO.mjs';
26
26
  import { CODEX_PROVIDER_KEY_ENV } from './codexProvider-BtxYc5Rj.mjs';
27
27
  import { spawn as spawn$1 } from 'node:child_process';
28
- import { b as applyInboxClear } from './friendlyName-1UZcyDhu.mjs';
28
+ import { applyInboxClear } from './inboxGuard-CDqmIypF.mjs';
29
29
  import { w as withFileLock, s as sleepSync } from './fileLock-BMPwCrB6.mjs';
30
30
  import { EventEmitter } from 'node:events';
31
31
  import { r as renderTemplate } from './cron-hHGdb5kU.mjs';
@@ -603,8 +603,11 @@ class ProcessSupervisor {
603
603
  if (!file.endsWith(".json")) continue;
604
604
  try {
605
605
  const raw = await readFile(path__default.join(this.persistDir, file), "utf-8");
606
- const spec = JSON.parse(raw);
607
- const entry = this.makeEntry(spec);
606
+ const parsed = JSON.parse(raw);
607
+ const restored = parsed?.__state;
608
+ delete parsed.__state;
609
+ const spec = parsed;
610
+ const entry = this.makeEntry(spec, restored);
608
611
  this.entries.set(spec.id, entry);
609
612
  if (spec.keepAlive && !spec.desiredStopped) {
610
613
  await this.startEntry(
@@ -627,12 +630,49 @@ class ProcessSupervisor {
627
630
  console.log(`[SUPERVISOR] Restored ${loaded} supervised process(es)`);
628
631
  }
629
632
  }
630
- async persistSpec(spec) {
633
+ /**
634
+ * #1643: the crash-loop LEDGER travels with the spec.
635
+ *
636
+ * `ProcessState` is annotated "not persisted", and `makeEntry` zeroes restartCount /
637
+ * consecutiveFailures / crashLoopBackOff on every restore — so a permanently-broken keepAlive
638
+ * spec (dead upstream, taken port, vanished workdir) got a FRESH 10-restart budget on every
639
+ * daemon start, forever. It burns CRASH_LOOP_FAILURE_THRESHOLD restarts and ~15 min of
640
+ * exponential spawn churn, parks crash-loop-backoff, and starts over on the next boot —
641
+ * undermining the cap that exists because of the "11,508 restarts in 4 days" incident, and
642
+ * worst right after a reboot when several dead specs churn at once. `svamp process list` also
643
+ * showed RESTARTS 0 after every restart, which hid it. The #1633 docblock states this for the
644
+ * missing-workdir case; it is general.
645
+ *
646
+ * Written under a `__state` key so the file stays a superset of ProcessSpec: an older daemon
647
+ * ignores it, and `loadAll` strips it before the object becomes `entry.spec`.
648
+ */
649
+ async persistSpec(spec, state, failureWindowStart) {
631
650
  const filePath = path__default.join(this.persistDir, `${spec.id}.json`);
651
+ if (!state) {
652
+ const live = this.entries.get(spec.id);
653
+ if (live) {
654
+ state = live.state;
655
+ failureWindowStart = live.failureWindowStart;
656
+ }
657
+ }
632
658
  const tmpPath = `${filePath}.tmp-${process.pid}-${randomBytes(4).toString("hex")}`;
633
- await writeFile(tmpPath, JSON.stringify(spec, null, 2), "utf-8");
659
+ const payload = { ...spec };
660
+ if (state) {
661
+ payload.__state = {
662
+ restartCount: state.restartCount,
663
+ userRestartCount: state.userRestartCount,
664
+ consecutiveFailures: state.consecutiveFailures,
665
+ crashLoopBackOff: state.crashLoopBackOff,
666
+ failureWindowStart
667
+ };
668
+ }
669
+ await writeFile(tmpPath, JSON.stringify(payload, null, 2), "utf-8");
634
670
  await rename(tmpPath, filePath);
635
671
  }
672
+ /** #1643: rewrite this spec's file including its live crash-loop ledger. Best-effort. */
673
+ persistLedger(entry) {
674
+ void this.persistSpec(entry.spec, entry.state, entry.failureWindowStart).catch((err) => console.error(`[SUPERVISOR] Failed to persist crash-loop ledger for ${entry.spec.name}: ${err?.message || err}`));
675
+ }
636
676
  async deleteSpec(id) {
637
677
  try {
638
678
  await unlink(path__default.join(this.persistDir, `${id}.json`));
@@ -640,17 +680,22 @@ class ProcessSupervisor {
640
680
  }
641
681
  }
642
682
  // ── Internal helpers ──────────────────────────────────────────────────────
643
- makeEntry(spec) {
683
+ makeEntry(spec, restored) {
644
684
  return {
645
685
  spec,
646
686
  state: {
647
687
  id: spec.id,
648
688
  status: "pending",
649
- restartCount: 0,
650
- userRestartCount: 0,
689
+ // #1643: a restored ledger carries the crash-loop budget ACROSS the restart. Without
690
+ // it, `maxRestarts` and CRASH_LOOP_FAILURE_THRESHOLD were per-boot rather than
691
+ // lifetime caps, which is not what either of them claims to be.
692
+ restartCount: restored?.restartCount ?? 0,
693
+ userRestartCount: restored?.userRestartCount ?? 0,
651
694
  consecutiveProbeFailures: 0,
652
- consecutiveFailures: 0
695
+ consecutiveFailures: restored?.consecutiveFailures ?? 0,
696
+ crashLoopBackOff: restored?.crashLoopBackOff
653
697
  },
698
+ failureWindowStart: restored?.failureWindowStart,
654
699
  logBuffer: [],
655
700
  stopping: false
656
701
  };
@@ -781,6 +826,7 @@ class ProcessSupervisor {
781
826
  );
782
827
  return;
783
828
  }
829
+ this.persistLedger(entry);
784
830
  if (spec.maxRestarts > 0 && state.restartCount >= spec.maxRestarts) {
785
831
  console.warn(`[SUPERVISOR] Process '${spec.name}' reached max restarts (${spec.maxRestarts}), not restarting`);
786
832
  state.status = "failed";
@@ -804,6 +850,7 @@ class ProcessSupervisor {
804
850
  if (entry.child) return;
805
851
  state.restartCount++;
806
852
  state.status = "starting";
853
+ this.persistLedger(entry);
807
854
  this.spawnProcess(entry);
808
855
  }, delayMs);
809
856
  }
@@ -6066,6 +6113,15 @@ async function registerMachineService(server, machineId, metadata, daemonState,
6066
6113
  const callerEmail = context?.user?.email;
6067
6114
  const machineOwner = currentMetadata.sharing?.owner;
6068
6115
  const isSharedUser = callerEmail && machineOwner && callerEmail.toLowerCase() !== machineOwner.toLowerCase();
6116
+ if (options.sharing?.allowedUsers?.length) {
6117
+ options = {
6118
+ ...options,
6119
+ sharing: {
6120
+ ...options.sharing,
6121
+ allowedUsers: options.sharing.allowedUsers.map((u) => normalizeAllowedUser(u, "spawn"))
6122
+ }
6123
+ };
6124
+ }
6069
6125
  if (isSharedUser) {
6070
6126
  const sharing = {
6071
6127
  enabled: true,
@@ -7570,7 +7626,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
7570
7626
  return { ok: false, call_id: callId, status: "busy", error: "channel is busy (too many concurrent requests) \u2014 retry shortly" };
7571
7627
  }
7572
7628
  const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
7573
- const { queryCore } = await import('./commands-B-yczgz_.mjs');
7629
+ const { queryCore } = await import('./commands-CcdoQIkb.mjs').then(function (n) { return n.d; });
7574
7630
  const timeout = c.reply?.timeout_sec || 120;
7575
7631
  let result;
7576
7632
  let thrownSessionId;
@@ -7957,6 +8013,14 @@ function loadInbox(projectDir, sessionId) {
7957
8013
  return [];
7958
8014
  }
7959
8015
  }
8016
+ const reportedInboxWriteErrnos = /* @__PURE__ */ new Set();
8017
+ function reportInboxWriteFailure(projectDir, sessionId, err) {
8018
+ const code = String(err?.code || err?.name || "UNKNOWN");
8019
+ if (reportedInboxWriteErrnos.has(code)) return;
8020
+ reportedInboxWriteErrnos.add(code);
8021
+ const hint = code === "ENOSPC" ? ' \u2014 THE DISK IS FULL. Pending inter-agent messages will be LOST on the next daemon restart and `inbox reply` will answer "not found"; session transcripts are likely failing too.' : code === "EROFS" ? " \u2014 the filesystem is read-only." : code === "EACCES" || code === "EPERM" ? " \u2014 permission denied on the session .svamp directory." : code === "ENOTDIR" ? " \u2014 a path component of .svamp/<sessionId>/ is a FILE, not a directory." : "";
8022
+ console.error(`[inbox] persist failed for session ${sessionId} in ${projectDir} (${code})${hint} Further ${code} failures will not be repeated. (#1714)`);
8023
+ }
7960
8024
  function saveInbox(projectDir, sessionId, inbox) {
7961
8025
  try {
7962
8026
  const p = inboxFilePath(projectDir, sessionId);
@@ -7964,7 +8028,8 @@ function saveInbox(projectDir, sessionId, inbox) {
7964
8028
  const tmp = `${p}.tmp-${process.pid}-${randomBytes$1(4).toString("hex")}`;
7965
8029
  writeFileSync$1(tmp, JSON.stringify(inbox));
7966
8030
  renameSync(tmp, p);
7967
- } catch {
8031
+ } catch (err) {
8032
+ reportInboxWriteFailure(projectDir, sessionId, err);
7968
8033
  }
7969
8034
  }
7970
8035
  function isPending(m) {
@@ -8255,25 +8320,34 @@ async function _mintPairingCodeLocked(opts, resolver, ttlMs) {
8255
8320
  stage: true,
8256
8321
  _rkwargs: true
8257
8322
  });
8258
- const putUrl = await am.put_file({ artifact_id: artifactId, file_path: "pair.json", download_weight: 0, _rkwargs: true });
8259
- if (!putUrl || typeof putUrl !== "string") throw new Error(`put_file returned invalid URL: ${putUrl}`);
8260
- const controller = new AbortController();
8261
- const timer = setTimeout(() => controller.abort(), 3e4);
8262
- try {
8263
- const resp = await fetch(putUrl, {
8264
- method: "PUT",
8265
- body: pairJson,
8266
- headers: { "Content-Type": "application/json" },
8267
- signal: controller.signal
8268
- });
8269
- if (!resp.ok) throw new Error(`pair.json upload failed: ${resp.status} ${resp.statusText}`);
8270
- } finally {
8271
- clearTimeout(timer);
8272
- }
8273
- await am.commit({ artifact_id: artifactId, _rkwargs: true });
8274
8323
  const all = loadPairings(opts.svampHome);
8275
8324
  all.push({ session, code, artifactId, expiresAt, createdAt: now });
8276
8325
  savePairings(all, opts.svampHome);
8326
+ try {
8327
+ const putUrl = await am.put_file({ artifact_id: artifactId, file_path: "pair.json", download_weight: 0, _rkwargs: true });
8328
+ if (!putUrl || typeof putUrl !== "string") throw new Error(`put_file returned invalid URL: ${putUrl}`);
8329
+ const controller = new AbortController();
8330
+ const timer = setTimeout(() => controller.abort(), 3e4);
8331
+ try {
8332
+ const resp = await fetch(putUrl, {
8333
+ method: "PUT",
8334
+ body: pairJson,
8335
+ headers: { "Content-Type": "application/json" },
8336
+ signal: controller.signal
8337
+ });
8338
+ if (!resp.ok) throw new Error(`pair.json upload failed: ${resp.status} ${resp.statusText}`);
8339
+ } finally {
8340
+ clearTimeout(timer);
8341
+ }
8342
+ await am.commit({ artifact_id: artifactId, _rkwargs: true });
8343
+ } catch (err) {
8344
+ try {
8345
+ await deletePairingArtifact(am, artifactId, log);
8346
+ savePairings(loadPairings(opts.svampHome).filter((p) => p.artifactId !== artifactId), opts.svampHome);
8347
+ } catch {
8348
+ }
8349
+ throw err;
8350
+ }
8277
8351
  log?.(`[OUTPOST PAIRING] minted ${code} for ${session} (expires in ${Math.round(ttlMs / 6e4)}m)`);
8278
8352
  return { code, resolver, expiresAt };
8279
8353
  }
@@ -8499,6 +8573,24 @@ function channelPublicView(c) {
8499
8573
  function isStructuredMessage(msg) {
8500
8574
  return !!(msg.from || msg.fromSession || msg.subject || msg.replyTo || msg.threadId || msg.channel);
8501
8575
  }
8576
+ function reportInboxEviction(result, sessionId) {
8577
+ for (const m of result.droppedUndelivered) {
8578
+ console.warn(
8579
+ `[HYPHA SESSION ${sessionId}] inbox at cap: DROPPED AN UNDELIVERED message ${m.messageId || "<no id>"} from ${m.from || "<unknown>"} (urgency=${m.urgency || "normal"}). Every resident message was undelivered, so there was no delivered one to evict instead. The sender was told it was sent; it is now gone (#0328).`
8580
+ );
8581
+ }
8582
+ }
8583
+ function evictInboxOverflow(inbox, max) {
8584
+ const dropped = [];
8585
+ const droppedUndelivered = [];
8586
+ while (inbox.length > max) {
8587
+ const idx = inbox.findIndex((m) => m.handled);
8588
+ const victim = inbox.splice(idx < 0 ? 0 : idx, 1)[0];
8589
+ dropped.push(victim);
8590
+ if (!victim.handled) droppedUndelivered.push(victim);
8591
+ }
8592
+ return { dropped, droppedUndelivered };
8593
+ }
8502
8594
  function escapeXml(s) {
8503
8595
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
8504
8596
  }
@@ -8958,7 +9050,7 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
8958
9050
  subject: `Remote outpost connected: ${who}`
8959
9051
  };
8960
9052
  inbox.push(msg);
8961
- while (inbox.length > INBOX_MAX) inbox.shift();
9053
+ reportInboxEviction(evictInboxOverflow(inbox, INBOX_MAX), sessionId);
8962
9054
  syncInboxToMetadata();
8963
9055
  callbacks.onInboxMessage?.(msg);
8964
9056
  notifyListeners({ type: "inbox-update", sessionId, message: msg });
@@ -10075,7 +10167,7 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
10075
10167
  const trusted = !context?.user || isSameOwnerWorkspace(context) || !!metadata.sharing && getEffectiveRole(context, metadata.sharing) === "admin";
10076
10168
  const msg = sanitizeInboundInboxMessage(message, { trusted, callerEmail: context?.user?.email });
10077
10169
  inbox.push(msg);
10078
- while (inbox.length > INBOX_MAX) inbox.shift();
10170
+ reportInboxEviction(evictInboxOverflow(inbox, INBOX_MAX), sessionId);
10079
10171
  syncInboxToMetadata();
10080
10172
  callbacks.onInboxMessage?.(msg);
10081
10173
  notifyListeners({ type: "inbox-update", sessionId, message: msg });