@moxxy/cli 0.7.0 → 0.7.1

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/bin.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { z as z$1, defineProvider, definePlugin, defineTool, MoxxyError, writeFileAtomic, asTurnId, defineMode, asPluginId, defineChannel, defineTunnelProvider, createMutex, defineWorkflowExecutor, toFriendlyError, estimateTextTokens, classifyHttpStatus, createStuckLoopDetector, runCompactionIfNeeded, runElisionIfNeeded, collectProviderStream, usageEventFields, isContextOverflowError, emitRequestsAndDetectStuck, executeToolUses, buildSystemPromptWithSkills, projectMessages, defineCompactor, defineCacheStrategy, denyByDefaultResolver, createAllowListResolver, zodToJsonSchema, runSingleShotTurn, bearerTokenMatches, resolveChannelToken, estimateContextTokens, readRequestBody, moxxyPath, defineEmbedder, bearerGuard, skillFrontmatterSchema, asSkillId, getInstallHint, moxxyHome, defineTranscriber, summarizeTokensByModel, migrateModeName, createDeferredPermissionResolver, classifyNetworkError, addModelTotals, ISOLATION_RANK, moxxyPackageSchema, defineCommand, createCallbackResolver, autoAllowResolver, asSessionId, asToolCallId, defineViewRenderer, DEFAULT_VIEW_TAGS, evaluateToolRule, summarizeSessionTokensFromEvents, asEventId } from '@moxxy/sdk';
3
+ import { z as z$1, defineProvider, definePlugin, defineTool, MoxxyError, writeFileAtomic, asTurnId, defineMode, asPluginId, defineChannel, defineTunnelProvider, createMutex, defineWorkflowExecutor, toFriendlyError, estimateTextTokens, classifyHttpStatus, createStuckLoopDetector, runCompactionIfNeeded, runElisionIfNeeded, collectProviderStream, usageEventFields, isContextOverflowError, emitRequestsAndDetectStuck, executeToolUses, buildSystemPromptWithSkills, projectMessages, defineCompactor, defineCacheStrategy, denyByDefaultResolver, createAllowListResolver, zodToJsonSchema, moxxyPath, runSingleShotTurn, bearerTokenMatches, resolveChannelToken, rotateChannelToken, estimateContextTokens, readRequestBody, MOXXY_WS_SUBPROTOCOL, defineEmbedder, bearerGuard, tokenFromWsProtocolHeader, skillFrontmatterSchema, asSkillId, getInstallHint, moxxyHome, defineTranscriber, summarizeTokensByModel, migrateModeName, createDeferredPermissionResolver, classifyNetworkError, addModelTotals, ISOLATION_RANK, moxxyPackageSchema, defineCommand, createCallbackResolver, autoAllowResolver, asSessionId, asToolCallId, defineViewRenderer, DEFAULT_VIEW_TAGS, evaluateToolRule, summarizeSessionTokensFromEvents, asEventId } from '@moxxy/sdk';
4
4
  import * as fs27 from 'fs';
5
5
  import fs27__default, { existsSync, promises, ReadStream, readFileSync, statSync, readdirSync, mkdirSync, writeFileSync, unlinkSync, renameSync, watch, createReadStream } from 'fs';
6
6
  import * as path3 from 'path';
7
- import path3__default, { basename } from 'path';
7
+ import path3__default, { join, dirname, basename } from 'path';
8
8
  import { z } from 'zod';
9
9
  import * as os5 from 'os';
10
10
  import os5__default, { homedir, userInfo } from 'os';
@@ -22,7 +22,7 @@ import V3, { stdin, stdout, env, cwd } from 'process';
22
22
  import tty, { ReadStream as ReadStream$1 } from 'tty';
23
23
  import { once, EventEmitter } from 'events';
24
24
  import { Buffer as Buffer$1 } from 'buffer';
25
- import { readFile, mkdir, unlink, writeFile } from 'fs/promises';
25
+ import { readFile, mkdir, open, rm, stat, rename as rename$1, unlink, writeFile } from 'fs/promises';
26
26
  import { spawn, spawnSync } from 'child_process';
27
27
  import { deprecate, inspect, styleText } from 'util';
28
28
  import { ReadableStream as ReadableStream$1 } from 'stream/web';
@@ -192,6 +192,7 @@ var init_log = __esm({
192
192
  EventLog = class {
193
193
  events = [];
194
194
  listeners = /* @__PURE__ */ new Set();
195
+ clearListeners = /* @__PURE__ */ new Set();
195
196
  now;
196
197
  constructor(seed = [], opts = {}) {
197
198
  this.now = opts.now ?? Date.now;
@@ -250,25 +251,44 @@ var init_log = __esm({
250
251
  const snapshot = [...this.listeners];
251
252
  for (const fn of snapshot) {
252
253
  try {
253
- void fn(event);
254
+ void Promise.resolve(fn(event)).catch(() => {
255
+ });
254
256
  } catch {
255
257
  }
256
258
  }
257
259
  }
258
260
  /**
259
- * Drop every event from the log. Used by `/new` (TUI) to start a
260
- * fresh session without rebuilding the entire Session object — the
261
+ * Drop every event from the log. Used by `/new` to start a fresh
262
+ * session without rebuilding the entire Session object — the
261
263
  * registries, resolvers, and active provider stay; only the
262
- * conversation context vanishes. Listeners are intentionally NOT
263
- * notified: there's no "event removed" event in the schema, and any
264
- * subscriber that wants to react to a wipe can observe the
265
- * subsequent next-`user_prompt` arriving with seq=0.
264
+ * conversation context vanishes. Per-event listeners are NOT
265
+ * notified (there's no "event removed" event in the schema), but
266
+ * {@link onClear} subscribers fire that's how the persistence
267
+ * sidecar truncates its JSONL (so `--resume` can't resurrect wiped
268
+ * history) and how the runner broadcasts a reset to attached
269
+ * mirrors, in lockstep with the wipe.
266
270
  *
267
271
  * Safe to call only when no turn is in flight — callers should abort
268
272
  * their AbortController and await any pending runTurn() first.
269
273
  */
270
274
  clear() {
271
275
  this.events.length = 0;
276
+ const snapshot = [...this.clearListeners];
277
+ for (const fn of snapshot) {
278
+ try {
279
+ fn();
280
+ } catch {
281
+ }
282
+ }
283
+ }
284
+ /**
285
+ * Subscribe to {@link clear}. Fires synchronously after the events array
286
+ * empties, so a listener reading the log observes the post-wipe state.
287
+ * Returns the unsubscribe callback.
288
+ */
289
+ onClear(fn) {
290
+ this.clearListeners.add(fn);
291
+ return () => this.clearListeners.delete(fn);
272
292
  }
273
293
  asReader() {
274
294
  return this;
@@ -1583,11 +1603,11 @@ function build(tokens, src) {
1583
1603
  if (!tok.selfClose)
1584
1604
  stack.push(node);
1585
1605
  } else {
1586
- const open = stack.pop();
1587
- if (!open)
1606
+ const open2 = stack.pop();
1607
+ if (!open2)
1588
1608
  fail(src, tok.pos, `unexpected closing tag </${tok.tag}>`);
1589
- if (open.tag !== tok.tag) {
1590
- fail(src, tok.pos, `mismatched closing tag: expected </${open.tag}>, got </${tok.tag}>`);
1609
+ if (open2.tag !== tok.tag) {
1610
+ fail(src, tok.pos, `mismatched closing tag: expected </${open2.tag}>, got </${tok.tag}>`);
1591
1611
  }
1592
1612
  }
1593
1613
  }
@@ -2140,8 +2160,8 @@ var init_tools2 = __esm({
2140
2160
  const parseResult = tool.inputSchema.safeParse(input);
2141
2161
  if (!parseResult.success) {
2142
2162
  const issues = parseResult.error.issues.map((iss) => {
2143
- const path59 = iss.path.length ? iss.path.join(".") : "(root)";
2144
- return `${path59}: ${iss.message}`;
2163
+ const path60 = iss.path.length ? iss.path.join(".") : "(root)";
2164
+ return `${path60}: ${iss.message}`;
2145
2165
  }).join("; ");
2146
2166
  throw new Error(`Invalid input for ${name}: ${issues}`);
2147
2167
  }
@@ -2813,19 +2833,25 @@ var init_logger = __esm({
2813
2833
  }
2814
2834
  });
2815
2835
  function wrapWithPolicy(inner, engine, getToolRule) {
2836
+ const policyDecision = (call) => {
2837
+ const policy = engine.check(call);
2838
+ if (policy)
2839
+ return policy;
2840
+ return evaluateToolRule(getToolRule(call.name), call);
2841
+ };
2816
2842
  return new Proxy(inner, {
2817
2843
  get(target, prop, receiver) {
2818
2844
  if (prop === "check") {
2819
2845
  return async (call, ctx) => {
2820
- const policy = engine.check(call);
2821
- if (policy)
2822
- return policy;
2823
- const toolDecision = evaluateToolRule(getToolRule(call.name), call);
2824
- if (toolDecision)
2825
- return toolDecision;
2846
+ const decided = policyDecision(call);
2847
+ if (decided)
2848
+ return decided;
2826
2849
  return target.check(call, ctx);
2827
2850
  };
2828
2851
  }
2852
+ if (prop === "policyCheck") {
2853
+ return async (call) => policyDecision(call);
2854
+ }
2829
2855
  return Reflect.get(target, prop, receiver);
2830
2856
  }
2831
2857
  });
@@ -3007,6 +3033,18 @@ var init_session = __esm({
3007
3033
  setApprovalResolver(resolver2) {
3008
3034
  this.approvalResolver = resolver2;
3009
3035
  }
3036
+ /**
3037
+ * `SessionLike.reset` — the authoritative `/new`. Clears the event log;
3038
+ * the log's clear listeners propagate the wipe to whatever observes it
3039
+ * (the persistence sidecar truncates its JSONL so `--resume` sees an
3040
+ * empty session; a wrapping RunnerServer broadcasts a reset so attached
3041
+ * mirrors clear in lockstep). Registries, resolvers, and the active
3042
+ * provider survive — only the conversation context vanishes. Callers
3043
+ * must abort any in-flight turn first (same contract as `log.clear()`).
3044
+ */
3045
+ async reset() {
3046
+ this.log.clear();
3047
+ }
3010
3048
  /**
3011
3049
  * Graceful shutdown: fire every plugin's `onShutdown` hook, then abort
3012
3050
  * the session. Idempotent — safe to call multiple times (subsequent
@@ -3746,7 +3784,7 @@ async function readIndex(dir = defaultSessionsDir()) {
3746
3784
  }));
3747
3785
  return metas.filter((_2, index) => checks[index]).sort((a2, b3) => b3.lastActivity.localeCompare(a2.lastActivity));
3748
3786
  }
3749
- async function restoreEvents(sessionId, dir = defaultSessionsDir()) {
3787
+ async function restoreEvents(sessionId, dir = defaultSessionsDir(), logger = createLogger()) {
3750
3788
  const logPath = path3.join(dir, `${sessionId}.jsonl`);
3751
3789
  let raw;
3752
3790
  try {
@@ -3755,12 +3793,40 @@ async function restoreEvents(sessionId, dir = defaultSessionsDir()) {
3755
3793
  throw new Error(`Session not found: ${sessionId}`);
3756
3794
  }
3757
3795
  const events = [];
3796
+ let corruptLines = 0;
3758
3797
  for (const line of raw.split("\n")) {
3759
3798
  if (!line.trim())
3760
3799
  continue;
3761
3800
  try {
3762
3801
  events.push(JSON.parse(line));
3763
3802
  } catch {
3803
+ corruptLines += 1;
3804
+ }
3805
+ }
3806
+ let resequenced = 0;
3807
+ for (let i2 = 0; i2 < events.length; i2 += 1) {
3808
+ if (events[i2].seq !== i2) {
3809
+ events[i2] = { ...events[i2], seq: i2 };
3810
+ resequenced += 1;
3811
+ }
3812
+ }
3813
+ if (corruptLines > 0 || resequenced > 0) {
3814
+ logger.warn("session log restored with gaps \u2014 re-sequenced to keep full history replayable", {
3815
+ sessionId,
3816
+ path: logPath,
3817
+ corruptLines,
3818
+ resequencedEvents: resequenced,
3819
+ restoredEvents: events.length
3820
+ });
3821
+ try {
3822
+ const repaired = events.map((e3) => JSON.stringify(e3) + "\n").join("");
3823
+ await writeFileAtomic(logPath, repaired);
3824
+ } catch (err) {
3825
+ logger.warn("failed to rewrite repaired session log on disk", {
3826
+ sessionId,
3827
+ path: logPath,
3828
+ error: err instanceof Error ? err.message : String(err)
3829
+ });
3764
3830
  }
3765
3831
  }
3766
3832
  return events;
@@ -3784,6 +3850,7 @@ function isSessionMeta(v3) {
3784
3850
  var SessionPersistence;
3785
3851
  var init_persistence = __esm({
3786
3852
  "../core/dist/sessions/persistence.js"() {
3853
+ init_logger();
3787
3854
  SessionPersistence = class {
3788
3855
  dir;
3789
3856
  id;
@@ -3797,9 +3864,17 @@ var init_persistence = __esm({
3797
3864
  */
3798
3865
  writeQueue = createMutex();
3799
3866
  closed = false;
3867
+ logger;
3868
+ /**
3869
+ * Latched while event-log writes are failing. Doubles as the warn-once
3870
+ * gate: only the first failure of a streak logs; a subsequent successful
3871
+ * write clears the latch (and re-arms the warning).
3872
+ */
3873
+ writeDegraded = false;
3800
3874
  constructor(opts) {
3801
3875
  this.dir = opts.dir ?? defaultSessionsDir();
3802
3876
  this.id = String(opts.sessionId);
3877
+ this.logger = opts.logger ?? createLogger();
3803
3878
  this.logPath = path3.join(this.dir, `${this.id}.jsonl`);
3804
3879
  const now = (/* @__PURE__ */ new Date()).toISOString();
3805
3880
  this.meta = {
@@ -3817,6 +3892,12 @@ var init_persistence = __esm({
3817
3892
  * Subscribe to the log; returns the unsubscribe callback. The first
3818
3893
  * call also writes the initial index row so `moxxy resume` lists
3819
3894
  * the session before any events arrive.
3895
+ *
3896
+ * Also subscribes to the log's `clear()`, truncating the JSONL in
3897
+ * lockstep so a `/new` wipe can't resurrect on `--resume`. The
3898
+ * session keeps its id and file: post-reset events restart at seq 0
3899
+ * in the same (now empty) JSONL, matching how the in-memory log
3900
+ * reuses the same Session object.
3820
3901
  */
3821
3902
  attach(log) {
3822
3903
  void this.ensureDir().then(() => this.ensureLogFile()).then(() => this.scheduleIndexWrite()).catch(() => void 0);
@@ -3825,9 +3906,15 @@ var init_persistence = __esm({
3825
3906
  return;
3826
3907
  this.enqueueAppend(event);
3827
3908
  });
3909
+ const unsubClear = log.onClear(() => {
3910
+ if (this.closed)
3911
+ return;
3912
+ this.enqueueTruncate();
3913
+ });
3828
3914
  return () => {
3829
3915
  this.closed = true;
3830
3916
  unsub();
3917
+ unsubClear();
3831
3918
  this.scheduleIndexWrite();
3832
3919
  };
3833
3920
  }
@@ -3853,7 +3940,48 @@ var init_persistence = __esm({
3853
3940
  };
3854
3941
  this.scheduleIndexWrite();
3855
3942
  const line = JSON.stringify(event) + "\n";
3856
- void this.writeQueue.run(() => promises.appendFile(this.logPath, line, "utf8")).catch(() => void 0);
3943
+ void this.writeQueue.run(() => promises.appendFile(this.logPath, line, "utf8")).then(() => this.noteWriteOk()).catch((err) => this.noteWriteFailure("append", err));
3944
+ }
3945
+ /**
3946
+ * True while event-log writes are failing (history is no longer being
3947
+ * persisted). Cleared automatically by the next successful write.
3948
+ */
3949
+ get degraded() {
3950
+ return this.writeDegraded;
3951
+ }
3952
+ noteWriteOk() {
3953
+ if (!this.writeDegraded)
3954
+ return;
3955
+ this.writeDegraded = false;
3956
+ this.logger.info("session event-log writes recovered", { path: this.logPath });
3957
+ }
3958
+ noteWriteFailure(op, err) {
3959
+ const alreadyDegraded = this.writeDegraded;
3960
+ this.writeDegraded = true;
3961
+ if (alreadyDegraded)
3962
+ return;
3963
+ this.logger.warn("session event-log write failed \u2014 history persistence degraded (resume will miss these events)", {
3964
+ op,
3965
+ path: this.logPath,
3966
+ sessionId: this.id,
3967
+ error: err instanceof Error ? err.message : String(err)
3968
+ });
3969
+ }
3970
+ /**
3971
+ * Mirror a `log.clear()` on disk: truncate the JSONL and reset the
3972
+ * sidecar's counters. Rides the same write queue as appends, so
3973
+ * pre-clear appends flush first and post-clear appends land in the
3974
+ * fresh (empty) file — ordering matches the in-memory log exactly.
3975
+ */
3976
+ enqueueTruncate() {
3977
+ this.meta = {
3978
+ ...this.meta,
3979
+ eventCount: 0,
3980
+ firstPrompt: null,
3981
+ lastActivity: (/* @__PURE__ */ new Date()).toISOString()
3982
+ };
3983
+ this.scheduleIndexWrite();
3984
+ void this.writeQueue.run(() => promises.writeFile(this.logPath, "", "utf8")).then(() => this.noteWriteOk()).catch((err) => this.noteWriteFailure("truncate", err));
3857
3985
  }
3858
3986
  scheduleIndexWrite() {
3859
3987
  if (this.indexUpdateScheduled)
@@ -4303,7 +4431,7 @@ var require_jiti = __commonJS({
4303
4431
  function V4() {
4304
4432
  return 10 !== n3.getToken() ? (k3(3, [], [2, 5]), false) : (P3(false), 6 === n3.getToken() ? (I3(":"), T2(), U3() || k3(4, [], [2, 5])) : k3(5, [], [2, 5]), a3.pop(), true);
4305
4433
  }
4306
- function z61() {
4434
+ function z62() {
4307
4435
  l3(), T2();
4308
4436
  let e6 = false;
4309
4437
  for (; 2 !== n3.getToken() && 17 !== n3.getToken(); ) {
@@ -4330,14 +4458,14 @@ var require_jiti = __commonJS({
4330
4458
  case 3:
4331
4459
  return G3();
4332
4460
  case 1:
4333
- return z61();
4461
+ return z62();
4334
4462
  case 10:
4335
4463
  return P3(true);
4336
4464
  default:
4337
4465
  return J2();
4338
4466
  }
4339
4467
  }
4340
- return r2(T2, "scanNext"), r2(k3, "handleError"), r2(P3, "parseString"), r2(J2, "parseLiteral"), r2(V4, "parseProperty"), r2(z61, "parseObject"), r2(G3, "parseArray"), r2(U3, "parseValue"), T2(), 17 === n3.getToken() ? !!i4.allowEmptyContent || (k3(4, [], []), false) : U3() ? (17 !== n3.getToken() && k3(9, [], []), true) : (k3(4, [], []), false);
4468
+ return r2(T2, "scanNext"), r2(k3, "handleError"), r2(P3, "parseString"), r2(J2, "parseLiteral"), r2(V4, "parseProperty"), r2(z62, "parseObject"), r2(G3, "parseArray"), r2(U3, "parseValue"), T2(), 17 === n3.getToken() ? !!i4.allowEmptyContent || (k3(4, [], []), false) : U3() ? (17 !== n3.getToken() && k3(9, [], []), true) : (k3(4, [], []), false);
4341
4469
  }
4342
4470
  new Array(W3).fill(0).map((e5, t4) => "\n" + " ".repeat(t4)), new Array(W3).fill(0).map((e5, t4) => "\r" + " ".repeat(t4)), new Array(W3).fill(0).map((e5, t4) => "\r\n" + " ".repeat(t4)), new Array(W3).fill(0).map((e5, t4) => "\n" + " ".repeat(t4)), new Array(W3).fill(0).map((e5, t4) => "\r" + " ".repeat(t4)), new Array(W3).fill(0).map((e5, t4) => "\r\n" + " ".repeat(t4)), (function(e5) {
4343
4471
  e5.DEFAULT = { allowTrailingComma: false };
@@ -8054,17 +8182,17 @@ var require_visit = __commonJS({
8054
8182
  visit.BREAK = BREAK;
8055
8183
  visit.SKIP = SKIP;
8056
8184
  visit.REMOVE = REMOVE;
8057
- function visit_(key, node, visitor, path59) {
8058
- const ctrl = callVisitor(key, node, visitor, path59);
8185
+ function visit_(key, node, visitor, path60) {
8186
+ const ctrl = callVisitor(key, node, visitor, path60);
8059
8187
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
8060
- replaceNode(key, path59, ctrl);
8061
- return visit_(key, ctrl, visitor, path59);
8188
+ replaceNode(key, path60, ctrl);
8189
+ return visit_(key, ctrl, visitor, path60);
8062
8190
  }
8063
8191
  if (typeof ctrl !== "symbol") {
8064
8192
  if (identity.isCollection(node)) {
8065
- path59 = Object.freeze(path59.concat(node));
8193
+ path60 = Object.freeze(path60.concat(node));
8066
8194
  for (let i2 = 0; i2 < node.items.length; ++i2) {
8067
- const ci = visit_(i2, node.items[i2], visitor, path59);
8195
+ const ci = visit_(i2, node.items[i2], visitor, path60);
8068
8196
  if (typeof ci === "number")
8069
8197
  i2 = ci - 1;
8070
8198
  else if (ci === BREAK)
@@ -8075,13 +8203,13 @@ var require_visit = __commonJS({
8075
8203
  }
8076
8204
  }
8077
8205
  } else if (identity.isPair(node)) {
8078
- path59 = Object.freeze(path59.concat(node));
8079
- const ck = visit_("key", node.key, visitor, path59);
8206
+ path60 = Object.freeze(path60.concat(node));
8207
+ const ck = visit_("key", node.key, visitor, path60);
8080
8208
  if (ck === BREAK)
8081
8209
  return BREAK;
8082
8210
  else if (ck === REMOVE)
8083
8211
  node.key = null;
8084
- const cv = visit_("value", node.value, visitor, path59);
8212
+ const cv = visit_("value", node.value, visitor, path60);
8085
8213
  if (cv === BREAK)
8086
8214
  return BREAK;
8087
8215
  else if (cv === REMOVE)
@@ -8102,17 +8230,17 @@ var require_visit = __commonJS({
8102
8230
  visitAsync.BREAK = BREAK;
8103
8231
  visitAsync.SKIP = SKIP;
8104
8232
  visitAsync.REMOVE = REMOVE;
8105
- async function visitAsync_(key, node, visitor, path59) {
8106
- const ctrl = await callVisitor(key, node, visitor, path59);
8233
+ async function visitAsync_(key, node, visitor, path60) {
8234
+ const ctrl = await callVisitor(key, node, visitor, path60);
8107
8235
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
8108
- replaceNode(key, path59, ctrl);
8109
- return visitAsync_(key, ctrl, visitor, path59);
8236
+ replaceNode(key, path60, ctrl);
8237
+ return visitAsync_(key, ctrl, visitor, path60);
8110
8238
  }
8111
8239
  if (typeof ctrl !== "symbol") {
8112
8240
  if (identity.isCollection(node)) {
8113
- path59 = Object.freeze(path59.concat(node));
8241
+ path60 = Object.freeze(path60.concat(node));
8114
8242
  for (let i2 = 0; i2 < node.items.length; ++i2) {
8115
- const ci = await visitAsync_(i2, node.items[i2], visitor, path59);
8243
+ const ci = await visitAsync_(i2, node.items[i2], visitor, path60);
8116
8244
  if (typeof ci === "number")
8117
8245
  i2 = ci - 1;
8118
8246
  else if (ci === BREAK)
@@ -8123,13 +8251,13 @@ var require_visit = __commonJS({
8123
8251
  }
8124
8252
  }
8125
8253
  } else if (identity.isPair(node)) {
8126
- path59 = Object.freeze(path59.concat(node));
8127
- const ck = await visitAsync_("key", node.key, visitor, path59);
8254
+ path60 = Object.freeze(path60.concat(node));
8255
+ const ck = await visitAsync_("key", node.key, visitor, path60);
8128
8256
  if (ck === BREAK)
8129
8257
  return BREAK;
8130
8258
  else if (ck === REMOVE)
8131
8259
  node.key = null;
8132
- const cv = await visitAsync_("value", node.value, visitor, path59);
8260
+ const cv = await visitAsync_("value", node.value, visitor, path60);
8133
8261
  if (cv === BREAK)
8134
8262
  return BREAK;
8135
8263
  else if (cv === REMOVE)
@@ -8156,23 +8284,23 @@ var require_visit = __commonJS({
8156
8284
  }
8157
8285
  return visitor;
8158
8286
  }
8159
- function callVisitor(key, node, visitor, path59) {
8287
+ function callVisitor(key, node, visitor, path60) {
8160
8288
  if (typeof visitor === "function")
8161
- return visitor(key, node, path59);
8289
+ return visitor(key, node, path60);
8162
8290
  if (identity.isMap(node))
8163
- return visitor.Map?.(key, node, path59);
8291
+ return visitor.Map?.(key, node, path60);
8164
8292
  if (identity.isSeq(node))
8165
- return visitor.Seq?.(key, node, path59);
8293
+ return visitor.Seq?.(key, node, path60);
8166
8294
  if (identity.isPair(node))
8167
- return visitor.Pair?.(key, node, path59);
8295
+ return visitor.Pair?.(key, node, path60);
8168
8296
  if (identity.isScalar(node))
8169
- return visitor.Scalar?.(key, node, path59);
8297
+ return visitor.Scalar?.(key, node, path60);
8170
8298
  if (identity.isAlias(node))
8171
- return visitor.Alias?.(key, node, path59);
8299
+ return visitor.Alias?.(key, node, path60);
8172
8300
  return void 0;
8173
8301
  }
8174
- function replaceNode(key, path59, node) {
8175
- const parent = path59[path59.length - 1];
8302
+ function replaceNode(key, path60, node) {
8303
+ const parent = path60[path60.length - 1];
8176
8304
  if (identity.isCollection(parent)) {
8177
8305
  parent.items[key] = node;
8178
8306
  } else if (identity.isPair(parent)) {
@@ -8773,10 +8901,10 @@ var require_Collection = __commonJS({
8773
8901
  var createNode2 = require_createNode();
8774
8902
  var identity = require_identity();
8775
8903
  var Node = require_Node();
8776
- function collectionFromPath(schema, path59, value) {
8904
+ function collectionFromPath(schema, path60, value) {
8777
8905
  let v3 = value;
8778
- for (let i2 = path59.length - 1; i2 >= 0; --i2) {
8779
- const k3 = path59[i2];
8906
+ for (let i2 = path60.length - 1; i2 >= 0; --i2) {
8907
+ const k3 = path60[i2];
8780
8908
  if (typeof k3 === "number" && Number.isInteger(k3) && k3 >= 0) {
8781
8909
  const a2 = [];
8782
8910
  a2[k3] = v3;
@@ -8795,7 +8923,7 @@ var require_Collection = __commonJS({
8795
8923
  sourceObjects: /* @__PURE__ */ new Map()
8796
8924
  });
8797
8925
  }
8798
- var isEmptyPath = (path59) => path59 == null || typeof path59 === "object" && !!path59[Symbol.iterator]().next().done;
8926
+ var isEmptyPath = (path60) => path60 == null || typeof path60 === "object" && !!path60[Symbol.iterator]().next().done;
8799
8927
  var Collection = class extends Node.NodeBase {
8800
8928
  constructor(type, schema) {
8801
8929
  super(type);
@@ -8825,11 +8953,11 @@ var require_Collection = __commonJS({
8825
8953
  * be a Pair instance or a `{ key, value }` object, which may not have a key
8826
8954
  * that already exists in the map.
8827
8955
  */
8828
- addIn(path59, value) {
8829
- if (isEmptyPath(path59))
8956
+ addIn(path60, value) {
8957
+ if (isEmptyPath(path60))
8830
8958
  this.add(value);
8831
8959
  else {
8832
- const [key, ...rest] = path59;
8960
+ const [key, ...rest] = path60;
8833
8961
  const node = this.get(key, true);
8834
8962
  if (identity.isCollection(node))
8835
8963
  node.addIn(rest, value);
@@ -8843,8 +8971,8 @@ var require_Collection = __commonJS({
8843
8971
  * Removes a value from the collection.
8844
8972
  * @returns `true` if the item was found and removed.
8845
8973
  */
8846
- deleteIn(path59) {
8847
- const [key, ...rest] = path59;
8974
+ deleteIn(path60) {
8975
+ const [key, ...rest] = path60;
8848
8976
  if (rest.length === 0)
8849
8977
  return this.delete(key);
8850
8978
  const node = this.get(key, true);
@@ -8858,8 +8986,8 @@ var require_Collection = __commonJS({
8858
8986
  * scalar values from their surrounding node; to disable set `keepScalar` to
8859
8987
  * `true` (collections are always returned intact).
8860
8988
  */
8861
- getIn(path59, keepScalar) {
8862
- const [key, ...rest] = path59;
8989
+ getIn(path60, keepScalar) {
8990
+ const [key, ...rest] = path60;
8863
8991
  const node = this.get(key, true);
8864
8992
  if (rest.length === 0)
8865
8993
  return !keepScalar && identity.isScalar(node) ? node.value : node;
@@ -8877,8 +9005,8 @@ var require_Collection = __commonJS({
8877
9005
  /**
8878
9006
  * Checks if the collection includes a value with the key `key`.
8879
9007
  */
8880
- hasIn(path59) {
8881
- const [key, ...rest] = path59;
9008
+ hasIn(path60) {
9009
+ const [key, ...rest] = path60;
8882
9010
  if (rest.length === 0)
8883
9011
  return this.has(key);
8884
9012
  const node = this.get(key, true);
@@ -8888,8 +9016,8 @@ var require_Collection = __commonJS({
8888
9016
  * Sets a value in this collection. For `!!set`, `value` needs to be a
8889
9017
  * boolean to add/remove the item from the set.
8890
9018
  */
8891
- setIn(path59, value) {
8892
- const [key, ...rest] = path59;
9019
+ setIn(path60, value) {
9020
+ const [key, ...rest] = path60;
8893
9021
  if (rest.length === 0) {
8894
9022
  this.set(key, value);
8895
9023
  } else {
@@ -11369,9 +11497,9 @@ var require_Document = __commonJS({
11369
11497
  this.contents.add(value);
11370
11498
  }
11371
11499
  /** Adds a value to the document. */
11372
- addIn(path59, value) {
11500
+ addIn(path60, value) {
11373
11501
  if (assertCollection(this.contents))
11374
- this.contents.addIn(path59, value);
11502
+ this.contents.addIn(path60, value);
11375
11503
  }
11376
11504
  /**
11377
11505
  * Create a new `Alias` node, ensuring that the target `node` has the required anchor.
@@ -11446,14 +11574,14 @@ var require_Document = __commonJS({
11446
11574
  * Removes a value from the document.
11447
11575
  * @returns `true` if the item was found and removed.
11448
11576
  */
11449
- deleteIn(path59) {
11450
- if (Collection.isEmptyPath(path59)) {
11577
+ deleteIn(path60) {
11578
+ if (Collection.isEmptyPath(path60)) {
11451
11579
  if (this.contents == null)
11452
11580
  return false;
11453
11581
  this.contents = null;
11454
11582
  return true;
11455
11583
  }
11456
- return assertCollection(this.contents) ? this.contents.deleteIn(path59) : false;
11584
+ return assertCollection(this.contents) ? this.contents.deleteIn(path60) : false;
11457
11585
  }
11458
11586
  /**
11459
11587
  * Returns item at `key`, or `undefined` if not found. By default unwraps
@@ -11468,10 +11596,10 @@ var require_Document = __commonJS({
11468
11596
  * scalar values from their surrounding node; to disable set `keepScalar` to
11469
11597
  * `true` (collections are always returned intact).
11470
11598
  */
11471
- getIn(path59, keepScalar) {
11472
- if (Collection.isEmptyPath(path59))
11599
+ getIn(path60, keepScalar) {
11600
+ if (Collection.isEmptyPath(path60))
11473
11601
  return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
11474
- return identity.isCollection(this.contents) ? this.contents.getIn(path59, keepScalar) : void 0;
11602
+ return identity.isCollection(this.contents) ? this.contents.getIn(path60, keepScalar) : void 0;
11475
11603
  }
11476
11604
  /**
11477
11605
  * Checks if the document includes a value with the key `key`.
@@ -11482,10 +11610,10 @@ var require_Document = __commonJS({
11482
11610
  /**
11483
11611
  * Checks if the document includes a value at `path`.
11484
11612
  */
11485
- hasIn(path59) {
11486
- if (Collection.isEmptyPath(path59))
11613
+ hasIn(path60) {
11614
+ if (Collection.isEmptyPath(path60))
11487
11615
  return this.contents !== void 0;
11488
- return identity.isCollection(this.contents) ? this.contents.hasIn(path59) : false;
11616
+ return identity.isCollection(this.contents) ? this.contents.hasIn(path60) : false;
11489
11617
  }
11490
11618
  /**
11491
11619
  * Sets a value in this document. For `!!set`, `value` needs to be a
@@ -11502,13 +11630,13 @@ var require_Document = __commonJS({
11502
11630
  * Sets a value in this document. For `!!set`, `value` needs to be a
11503
11631
  * boolean to add/remove the item from the set.
11504
11632
  */
11505
- setIn(path59, value) {
11506
- if (Collection.isEmptyPath(path59)) {
11633
+ setIn(path60, value) {
11634
+ if (Collection.isEmptyPath(path60)) {
11507
11635
  this.contents = value;
11508
11636
  } else if (this.contents == null) {
11509
- this.contents = Collection.collectionFromPath(this.schema, Array.from(path59), value);
11637
+ this.contents = Collection.collectionFromPath(this.schema, Array.from(path60), value);
11510
11638
  } else if (assertCollection(this.contents)) {
11511
- this.contents.setIn(path59, value);
11639
+ this.contents.setIn(path60, value);
11512
11640
  }
11513
11641
  }
11514
11642
  /**
@@ -13448,9 +13576,9 @@ var require_cst_visit = __commonJS({
13448
13576
  visit.BREAK = BREAK;
13449
13577
  visit.SKIP = SKIP;
13450
13578
  visit.REMOVE = REMOVE;
13451
- visit.itemAtPath = (cst, path59) => {
13579
+ visit.itemAtPath = (cst, path60) => {
13452
13580
  let item = cst;
13453
- for (const [field, index] of path59) {
13581
+ for (const [field, index] of path60) {
13454
13582
  const tok = item?.[field];
13455
13583
  if (tok && "items" in tok) {
13456
13584
  item = tok.items[index];
@@ -13459,23 +13587,23 @@ var require_cst_visit = __commonJS({
13459
13587
  }
13460
13588
  return item;
13461
13589
  };
13462
- visit.parentCollection = (cst, path59) => {
13463
- const parent = visit.itemAtPath(cst, path59.slice(0, -1));
13464
- const field = path59[path59.length - 1][0];
13590
+ visit.parentCollection = (cst, path60) => {
13591
+ const parent = visit.itemAtPath(cst, path60.slice(0, -1));
13592
+ const field = path60[path60.length - 1][0];
13465
13593
  const coll = parent?.[field];
13466
13594
  if (coll && "items" in coll)
13467
13595
  return coll;
13468
13596
  throw new Error("Parent collection not found");
13469
13597
  };
13470
- function _visit(path59, item, visitor) {
13471
- let ctrl = visitor(item, path59);
13598
+ function _visit(path60, item, visitor) {
13599
+ let ctrl = visitor(item, path60);
13472
13600
  if (typeof ctrl === "symbol")
13473
13601
  return ctrl;
13474
13602
  for (const field of ["key", "value"]) {
13475
13603
  const token = item[field];
13476
13604
  if (token && "items" in token) {
13477
13605
  for (let i2 = 0; i2 < token.items.length; ++i2) {
13478
- const ci = _visit(Object.freeze(path59.concat([[field, i2]])), token.items[i2], visitor);
13606
+ const ci = _visit(Object.freeze(path60.concat([[field, i2]])), token.items[i2], visitor);
13479
13607
  if (typeof ci === "number")
13480
13608
  i2 = ci - 1;
13481
13609
  else if (ci === BREAK)
@@ -13486,10 +13614,10 @@ var require_cst_visit = __commonJS({
13486
13614
  }
13487
13615
  }
13488
13616
  if (typeof ctrl === "function" && field === "key")
13489
- ctrl = ctrl(item, path59);
13617
+ ctrl = ctrl(item, path60);
13490
13618
  }
13491
13619
  }
13492
- return typeof ctrl === "function" ? ctrl(item, path59) : ctrl;
13620
+ return typeof ctrl === "function" ? ctrl(item, path60) : ctrl;
13493
13621
  }
13494
13622
  exports.visit = visit;
13495
13623
  }
@@ -16211,14 +16339,14 @@ var require_url_state_machine = __commonJS({
16211
16339
  return url2.replace(/\u0009|\u000A|\u000D/g, "");
16212
16340
  }
16213
16341
  function shortenPath(url2) {
16214
- const path59 = url2.path;
16215
- if (path59.length === 0) {
16342
+ const path60 = url2.path;
16343
+ if (path60.length === 0) {
16216
16344
  return;
16217
16345
  }
16218
- if (url2.scheme === "file" && path59.length === 1 && isNormalizedWindowsDriveLetter(path59[0])) {
16346
+ if (url2.scheme === "file" && path60.length === 1 && isNormalizedWindowsDriveLetter(path60[0])) {
16219
16347
  return;
16220
16348
  }
16221
- path59.pop();
16349
+ path60.pop();
16222
16350
  }
16223
16351
  function includesCredentials(url2) {
16224
16352
  return url2.username !== "" || url2.password !== "";
@@ -22721,25 +22849,25 @@ function kt(e3, t2, r2, o2, n2, a2) {
22721
22849
  m(e4);
22722
22850
  }
22723
22851
  function B2() {
22724
- return v3 = "closed", r2 ? L3() : z61((() => (Ge(t2) && (T2 = rt(t2), R4 = t2._state), T2 || "closed" === R4 ? c(void 0) : "erroring" === R4 || "errored" === R4 ? d(_2) : (T2 = true, l2.close()))), false, void 0), null;
22852
+ return v3 = "closed", r2 ? L3() : z62((() => (Ge(t2) && (T2 = rt(t2), R4 = t2._state), T2 || "closed" === R4 ? c(void 0) : "erroring" === R4 || "errored" === R4 ? d(_2) : (T2 = true, l2.close()))), false, void 0), null;
22725
22853
  }
22726
22854
  function A3(e4) {
22727
- return w4 || (v3 = "errored", s2 = e4, o2 ? L3(true, e4) : z61((() => l2.abort(e4)), true, e4)), null;
22855
+ return w4 || (v3 = "errored", s2 = e4, o2 ? L3(true, e4) : z62((() => l2.abort(e4)), true, e4)), null;
22728
22856
  }
22729
22857
  function j3(e4) {
22730
- return S2 || (R4 = "errored", _2 = e4, n2 ? L3(true, e4) : z61((() => i2.cancel(e4)), true, e4)), null;
22858
+ return S2 || (R4 = "errored", _2 = e4, n2 ? L3(true, e4) : z62((() => i2.cancel(e4)), true, e4)), null;
22731
22859
  }
22732
22860
  if (void 0 !== a2 && (k3 = () => {
22733
22861
  const e4 = void 0 !== a2.reason ? a2.reason : new Wt("Aborted", "AbortError"), t3 = [];
22734
- o2 || t3.push((() => "writable" === R4 ? l2.abort(e4) : c(void 0))), n2 || t3.push((() => "readable" === v3 ? i2.cancel(e4) : c(void 0))), z61((() => Promise.all(t3.map(((e5) => e5())))), true, e4);
22862
+ o2 || t3.push((() => "writable" === R4 ? l2.abort(e4) : c(void 0))), n2 || t3.push((() => "readable" === v3 ? i2.cancel(e4) : c(void 0))), z62((() => Promise.all(t3.map(((e5) => e5())))), true, e4);
22735
22863
  }, a2.aborted ? k3() : a2.addEventListener("abort", k3)), Vt(e3) && (v3 = e3._state, s2 = e3._storedError), Ge(t2) && (R4 = t2._state, _2 = t2._storedError, T2 = rt(t2)), Vt(e3) && Ge(t2) && (q3 = true, g2()), "errored" === v3) A3(s2);
22736
22864
  else if ("erroring" === R4 || "errored" === R4) j3(_2);
22737
22865
  else if ("closed" === v3) B2();
22738
22866
  else if (T2 || "closed" === R4) {
22739
22867
  const e4 = new TypeError("the destination writable stream closed before all data could be piped to it");
22740
- n2 ? L3(true, e4) : z61((() => i2.cancel(e4)), true, e4);
22868
+ n2 ? L3(true, e4) : z62((() => i2.cancel(e4)), true, e4);
22741
22869
  }
22742
- function z61(e4, t3, r3) {
22870
+ function z62(e4, t3, r3) {
22743
22871
  function o3() {
22744
22872
  return "writable" !== R4 || T2 ? n3() : h((function() {
22745
22873
  let e5;
@@ -22754,7 +22882,7 @@ function kt(e3, t2, r2, o2, n2, a2) {
22754
22882
  w4 || (w4 = true, q3 ? o3() : h(C3, o3));
22755
22883
  }
22756
22884
  function L3(e4, t3) {
22757
- z61(void 0, e4, t3);
22885
+ z62(void 0, e4, t3);
22758
22886
  }
22759
22887
  function F3(e4, t3) {
22760
22888
  return S2 = true, l2.releaseLock(), i2.releaseLock(), void 0 !== a2 && a2.removeEventListener("abort", k3), e4 ? W3(t3) : P3(void 0), null;
@@ -25441,14 +25569,14 @@ __export(fileFromPath_exports, {
25441
25569
  fileFromPathSync: () => fileFromPathSync,
25442
25570
  isFile: () => isFile
25443
25571
  });
25444
- function createFileFromPath(path59, { mtimeMs, size }, filenameOrOptions, options = {}) {
25572
+ function createFileFromPath(path60, { mtimeMs, size }, filenameOrOptions, options = {}) {
25445
25573
  let filename;
25446
25574
  if (isPlainObject_default2(filenameOrOptions)) {
25447
25575
  [options, filename] = [filenameOrOptions, void 0];
25448
25576
  } else {
25449
25577
  filename = filenameOrOptions;
25450
25578
  }
25451
- const file = new FileFromPath({ path: path59, size, lastModified: mtimeMs });
25579
+ const file = new FileFromPath({ path: path60, size, lastModified: mtimeMs });
25452
25580
  if (!filename) {
25453
25581
  filename = file.name;
25454
25582
  }
@@ -25457,13 +25585,13 @@ function createFileFromPath(path59, { mtimeMs, size }, filenameOrOptions, option
25457
25585
  lastModified: file.lastModified
25458
25586
  });
25459
25587
  }
25460
- function fileFromPathSync(path59, filenameOrOptions, options = {}) {
25461
- const stats = statSync(path59);
25462
- return createFileFromPath(path59, stats, filenameOrOptions, options);
25588
+ function fileFromPathSync(path60, filenameOrOptions, options = {}) {
25589
+ const stats = statSync(path60);
25590
+ return createFileFromPath(path60, stats, filenameOrOptions, options);
25463
25591
  }
25464
- async function fileFromPath2(path59, filenameOrOptions, options) {
25465
- const stats = await promises.stat(path59);
25466
- return createFileFromPath(path59, stats, filenameOrOptions, options);
25592
+ async function fileFromPath2(path60, filenameOrOptions, options) {
25593
+ const stats = await promises.stat(path60);
25594
+ return createFileFromPath(path60, stats, filenameOrOptions, options);
25467
25595
  }
25468
25596
  var import_node_domexception, __classPrivateFieldSet4, __classPrivateFieldGet5, _FileFromPath_path, _FileFromPath_start, MESSAGE, FileFromPath;
25469
25597
  var init_fileFromPath = __esm({
@@ -32919,7 +33047,7 @@ var require_bot = __commonJS({
32919
33047
  } else
32920
33048
  debugErr(error2);
32921
33049
  debugErr(`Call to getUpdates failed, retrying in ${sleepSeconds} seconds ...`);
32922
- await sleep4(sleepSeconds);
33050
+ await sleep5(sleepSeconds);
32923
33051
  }
32924
33052
  };
32925
33053
  exports.Bot = Bot3;
@@ -32939,7 +33067,7 @@ var require_bot = __commonJS({
32939
33067
  } else if (error2.error_code === 429) {
32940
33068
  const retryAfter = error2.parameters.retry_after;
32941
33069
  if (typeof retryAfter === "number") {
32942
- await sleep4(retryAfter, signal);
33070
+ await sleep5(retryAfter, signal);
32943
33071
  lastDelay = INITIAL_DELAY;
32944
33072
  } else {
32945
33073
  delay = true;
@@ -32949,7 +33077,7 @@ var require_bot = __commonJS({
32949
33077
  }
32950
33078
  if (delay) {
32951
33079
  if (lastDelay !== INITIAL_DELAY) {
32952
- await sleep4(lastDelay, signal);
33080
+ await sleep5(lastDelay, signal);
32953
33081
  }
32954
33082
  const TWENTY_MINUTES = 20 * 60 * 1e3;
32955
33083
  lastDelay = Math.min(TWENTY_MINUTES, 2 * lastDelay);
@@ -32973,7 +33101,7 @@ var require_bot = __commonJS({
32973
33101
  }
32974
33102
  return result.value;
32975
33103
  }
32976
- async function sleep4(seconds, signal) {
33104
+ async function sleep5(seconds, signal) {
32977
33105
  let handle2;
32978
33106
  let reject;
32979
33107
  function abort() {
@@ -41595,8 +41723,8 @@ var require_utils2 = __commonJS({
41595
41723
  }
41596
41724
  return ind;
41597
41725
  }
41598
- function removeDotSegments(path59) {
41599
- let input = path59;
41726
+ function removeDotSegments(path60) {
41727
+ let input = path60;
41600
41728
  const output = [];
41601
41729
  let nextSlash = -1;
41602
41730
  let len = 0;
@@ -41847,8 +41975,8 @@ var require_schemes = __commonJS({
41847
41975
  wsComponent.secure = void 0;
41848
41976
  }
41849
41977
  if (wsComponent.resourceName) {
41850
- const [path59, query] = wsComponent.resourceName.split("?");
41851
- wsComponent.path = path59 && path59 !== "/" ? path59 : void 0;
41978
+ const [path60, query] = wsComponent.resourceName.split("?");
41979
+ wsComponent.path = path60 && path60 !== "/" ? path60 : void 0;
41852
41980
  wsComponent.query = query;
41853
41981
  wsComponent.resourceName = void 0;
41854
41982
  }
@@ -46006,7 +46134,7 @@ var require_windows = __commonJS({
46006
46134
  module.exports = isexe;
46007
46135
  isexe.sync = sync;
46008
46136
  var fs43 = __require("fs");
46009
- function checkPathExt(path59, options) {
46137
+ function checkPathExt(path60, options) {
46010
46138
  var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
46011
46139
  if (!pathext) {
46012
46140
  return true;
@@ -46017,25 +46145,25 @@ var require_windows = __commonJS({
46017
46145
  }
46018
46146
  for (var i2 = 0; i2 < pathext.length; i2++) {
46019
46147
  var p3 = pathext[i2].toLowerCase();
46020
- if (p3 && path59.substr(-p3.length).toLowerCase() === p3) {
46148
+ if (p3 && path60.substr(-p3.length).toLowerCase() === p3) {
46021
46149
  return true;
46022
46150
  }
46023
46151
  }
46024
46152
  return false;
46025
46153
  }
46026
- function checkStat(stat, path59, options) {
46027
- if (!stat.isSymbolicLink() && !stat.isFile()) {
46154
+ function checkStat(stat2, path60, options) {
46155
+ if (!stat2.isSymbolicLink() && !stat2.isFile()) {
46028
46156
  return false;
46029
46157
  }
46030
- return checkPathExt(path59, options);
46158
+ return checkPathExt(path60, options);
46031
46159
  }
46032
- function isexe(path59, options, cb) {
46033
- fs43.stat(path59, function(er2, stat) {
46034
- cb(er2, er2 ? false : checkStat(stat, path59, options));
46160
+ function isexe(path60, options, cb) {
46161
+ fs43.stat(path60, function(er2, stat2) {
46162
+ cb(er2, er2 ? false : checkStat(stat2, path60, options));
46035
46163
  });
46036
46164
  }
46037
- function sync(path59, options) {
46038
- return checkStat(fs43.statSync(path59), path59, options);
46165
+ function sync(path60, options) {
46166
+ return checkStat(fs43.statSync(path60), path60, options);
46039
46167
  }
46040
46168
  }
46041
46169
  });
@@ -46046,21 +46174,21 @@ var require_mode = __commonJS({
46046
46174
  module.exports = isexe;
46047
46175
  isexe.sync = sync;
46048
46176
  var fs43 = __require("fs");
46049
- function isexe(path59, options, cb) {
46050
- fs43.stat(path59, function(er2, stat) {
46051
- cb(er2, er2 ? false : checkStat(stat, options));
46177
+ function isexe(path60, options, cb) {
46178
+ fs43.stat(path60, function(er2, stat2) {
46179
+ cb(er2, er2 ? false : checkStat(stat2, options));
46052
46180
  });
46053
46181
  }
46054
- function sync(path59, options) {
46055
- return checkStat(fs43.statSync(path59), options);
46182
+ function sync(path60, options) {
46183
+ return checkStat(fs43.statSync(path60), options);
46056
46184
  }
46057
- function checkStat(stat, options) {
46058
- return stat.isFile() && checkMode(stat, options);
46185
+ function checkStat(stat2, options) {
46186
+ return stat2.isFile() && checkMode(stat2, options);
46059
46187
  }
46060
- function checkMode(stat, options) {
46061
- var mod = stat.mode;
46062
- var uid = stat.uid;
46063
- var gid = stat.gid;
46188
+ function checkMode(stat2, options) {
46189
+ var mod = stat2.mode;
46190
+ var uid = stat2.uid;
46191
+ var gid = stat2.gid;
46064
46192
  var myUid = options.uid !== void 0 ? options.uid : process.getuid && process.getuid();
46065
46193
  var myGid = options.gid !== void 0 ? options.gid : process.getgid && process.getgid();
46066
46194
  var u2 = parseInt("100", 8);
@@ -46085,7 +46213,7 @@ var require_isexe = __commonJS({
46085
46213
  }
46086
46214
  module.exports = isexe;
46087
46215
  isexe.sync = sync;
46088
- function isexe(path59, options, cb) {
46216
+ function isexe(path60, options, cb) {
46089
46217
  if (typeof options === "function") {
46090
46218
  cb = options;
46091
46219
  options = {};
@@ -46095,7 +46223,7 @@ var require_isexe = __commonJS({
46095
46223
  throw new TypeError("callback not provided");
46096
46224
  }
46097
46225
  return new Promise(function(resolve12, reject) {
46098
- isexe(path59, options || {}, function(er2, is) {
46226
+ isexe(path60, options || {}, function(er2, is) {
46099
46227
  if (er2) {
46100
46228
  reject(er2);
46101
46229
  } else {
@@ -46104,7 +46232,7 @@ var require_isexe = __commonJS({
46104
46232
  });
46105
46233
  });
46106
46234
  }
46107
- core(path59, options || {}, function(er2, is) {
46235
+ core(path60, options || {}, function(er2, is) {
46108
46236
  if (er2) {
46109
46237
  if (er2.code === "EACCES" || options && options.ignoreErrors) {
46110
46238
  er2 = null;
@@ -46114,9 +46242,9 @@ var require_isexe = __commonJS({
46114
46242
  cb(er2, is);
46115
46243
  });
46116
46244
  }
46117
- function sync(path59, options) {
46245
+ function sync(path60, options) {
46118
46246
  try {
46119
- return core.sync(path59, options || {});
46247
+ return core.sync(path60, options || {});
46120
46248
  } catch (er2) {
46121
46249
  if (options && options.ignoreErrors || er2.code === "EACCES") {
46122
46250
  return false;
@@ -46132,7 +46260,7 @@ var require_isexe = __commonJS({
46132
46260
  var require_which = __commonJS({
46133
46261
  "../../node_modules/.pnpm/which@2.0.2/node_modules/which/which.js"(exports, module) {
46134
46262
  var isWindows3 = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys";
46135
- var path59 = __require("path");
46263
+ var path60 = __require("path");
46136
46264
  var COLON = isWindows3 ? ";" : ":";
46137
46265
  var isexe = require_isexe();
46138
46266
  var getNotFoundError = (cmd) => Object.assign(new Error(`not found: ${cmd}`), { code: "ENOENT" });
@@ -46170,7 +46298,7 @@ var require_which = __commonJS({
46170
46298
  return opt.all && found.length ? resolve12(found) : reject(getNotFoundError(cmd));
46171
46299
  const ppRaw = pathEnv[i2];
46172
46300
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
46173
- const pCmd = path59.join(pathPart, cmd);
46301
+ const pCmd = path60.join(pathPart, cmd);
46174
46302
  const p3 = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
46175
46303
  resolve12(subStep(p3, i2, 0));
46176
46304
  });
@@ -46197,7 +46325,7 @@ var require_which = __commonJS({
46197
46325
  for (let i2 = 0; i2 < pathEnv.length; i2++) {
46198
46326
  const ppRaw = pathEnv[i2];
46199
46327
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
46200
- const pCmd = path59.join(pathPart, cmd);
46328
+ const pCmd = path60.join(pathPart, cmd);
46201
46329
  const p3 = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
46202
46330
  for (let j3 = 0; j3 < pathExt.length; j3++) {
46203
46331
  const cur = p3 + pathExt[j3];
@@ -46243,7 +46371,7 @@ var require_path_key = __commonJS({
46243
46371
  // ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/resolveCommand.js
46244
46372
  var require_resolveCommand = __commonJS({
46245
46373
  "../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/resolveCommand.js"(exports, module) {
46246
- var path59 = __require("path");
46374
+ var path60 = __require("path");
46247
46375
  var which = require_which();
46248
46376
  var getPathKey = require_path_key();
46249
46377
  function resolveCommandAttempt(parsed, withoutPathExt) {
@@ -46261,7 +46389,7 @@ var require_resolveCommand = __commonJS({
46261
46389
  try {
46262
46390
  resolved = which.sync(parsed.command, {
46263
46391
  path: env3[getPathKey({ env: env3 })],
46264
- pathExt: withoutPathExt ? path59.delimiter : void 0
46392
+ pathExt: withoutPathExt ? path60.delimiter : void 0
46265
46393
  });
46266
46394
  } catch (e3) {
46267
46395
  } finally {
@@ -46270,7 +46398,7 @@ var require_resolveCommand = __commonJS({
46270
46398
  }
46271
46399
  }
46272
46400
  if (resolved) {
46273
- resolved = path59.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
46401
+ resolved = path60.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
46274
46402
  }
46275
46403
  return resolved;
46276
46404
  }
@@ -46321,8 +46449,8 @@ var require_shebang_command = __commonJS({
46321
46449
  if (!match) {
46322
46450
  return null;
46323
46451
  }
46324
- const [path59, argument] = match[0].replace(/#! ?/, "").split(" ");
46325
- const binary = path59.split("/").pop();
46452
+ const [path60, argument] = match[0].replace(/#! ?/, "").split(" ");
46453
+ const binary = path60.split("/").pop();
46326
46454
  if (binary === "env") {
46327
46455
  return argument;
46328
46456
  }
@@ -46355,7 +46483,7 @@ var require_readShebang = __commonJS({
46355
46483
  // ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/parse.js
46356
46484
  var require_parse = __commonJS({
46357
46485
  "../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/parse.js"(exports, module) {
46358
- var path59 = __require("path");
46486
+ var path60 = __require("path");
46359
46487
  var resolveCommand = require_resolveCommand();
46360
46488
  var escape4 = require_escape();
46361
46489
  var readShebang = require_readShebang();
@@ -46380,7 +46508,7 @@ var require_parse = __commonJS({
46380
46508
  const needsShell = !isExecutableRegExp.test(commandFile);
46381
46509
  if (parsed.options.forceShell || needsShell) {
46382
46510
  const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
46383
- parsed.command = path59.normalize(parsed.command);
46511
+ parsed.command = path60.normalize(parsed.command);
46384
46512
  parsed.command = escape4.command(parsed.command);
46385
46513
  parsed.args = parsed.args.map((arg) => escape4.argument(arg, needsDoubleEscapeMetaChars));
46386
46514
  const shellCommand = [parsed.command].concat(parsed.args).join(" ");
@@ -46974,11 +47102,11 @@ var init_dist3 = __esm({
46974
47102
  return;
46975
47103
  }
46976
47104
  const decoder = new TextDecoder(), reader = body.getReader();
46977
- let open = true;
47105
+ let open2 = true;
46978
47106
  do {
46979
47107
  const { done, value } = await reader.read();
46980
- value && __privateGet(this, _parser).feed(decoder.decode(value, { stream: !done })), done && (open = false, __privateGet(this, _parser).reset(), __privateMethod(this, _EventSource_instances, scheduleReconnect_fn).call(this));
46981
- } while (open);
47108
+ value && __privateGet(this, _parser).feed(decoder.decode(value, { stream: !done })), done && (open2 = false, __privateGet(this, _parser).reset(), __privateMethod(this, _EventSource_instances, scheduleReconnect_fn).call(this));
47109
+ } while (open2);
46982
47110
  }), __privateAdd(this, _onFetchError, (err) => {
46983
47111
  __privateSet(this, _controller, void 0), !(err.name === "AbortError" || err.type === "aborted") && __privateMethod(this, _EventSource_instances, scheduleReconnect_fn).call(this, flattenError(err));
46984
47112
  }), __privateAdd(this, _onEvent, (event) => {
@@ -49505,10 +49633,10 @@ var require_react_production_min = __commonJS({
49505
49633
  var w4 = /* @__PURE__ */ Symbol.for("react.suspense");
49506
49634
  var x4 = /* @__PURE__ */ Symbol.for("react.memo");
49507
49635
  var y2 = /* @__PURE__ */ Symbol.for("react.lazy");
49508
- var z61 = Symbol.iterator;
49636
+ var z62 = Symbol.iterator;
49509
49637
  function A3(a2) {
49510
49638
  if (null === a2 || "object" !== typeof a2) return null;
49511
- a2 = z61 && a2[z61] || a2["@@iterator"];
49639
+ a2 = z62 && a2[z62] || a2["@@iterator"];
49512
49640
  return "function" === typeof a2 ? a2 : null;
49513
49641
  }
49514
49642
  var B2 = { isMounted: function() {
@@ -52315,7 +52443,7 @@ var init_yoga_wasm_base64_esm = __esm({
52315
52443
  h3.noExitRuntime || true;
52316
52444
  "object" != typeof WebAssembly && x4("no native wasm support detected");
52317
52445
  var fa, ha = false;
52318
- function z61(a2, b3, c2) {
52446
+ function z62(a2, b3, c2) {
52319
52447
  c2 = b3 + c2;
52320
52448
  for (var d2 = ""; !(b3 >= c2); ) {
52321
52449
  var e3 = a2[b3++];
@@ -52965,7 +53093,7 @@ var init_yoga_wasm_base64_esm = __esm({
52965
53093
  return b3;
52966
53094
  }, Jb = {
52967
53095
  l: function(a2, b3, c2, d2) {
52968
- x4("Assertion failed: " + (a2 ? z61(A3, a2) : "") + ", at: " + [b3 ? b3 ? z61(A3, b3) : "" : "unknown filename", c2, d2 ? d2 ? z61(A3, d2) : "" : "unknown function"]);
53096
+ x4("Assertion failed: " + (a2 ? z62(A3, a2) : "") + ", at: " + [b3 ? b3 ? z62(A3, b3) : "" : "unknown filename", c2, d2 ? d2 ? z62(A3, d2) : "" : "unknown function"]);
52969
53097
  },
52970
53098
  q: function(a2, b3, c2) {
52971
53099
  a2 = N2(a2);
@@ -53218,7 +53346,7 @@ var init_yoga_wasm_base64_esm = __esm({
53218
53346
  if (c2) for (var g2 = f3, k3 = 0; k3 <= e3; ++k3) {
53219
53347
  var m3 = f3 + k3;
53220
53348
  if (k3 == e3 || 0 == A3[m3]) {
53221
- g2 = g2 ? z61(A3, g2, m3 - g2) : "";
53349
+ g2 = g2 ? z62(A3, g2, m3 - g2) : "";
53222
53350
  if (void 0 === l2) var l2 = g2;
53223
53351
  else l2 += String.fromCharCode(0), l2 += g2;
53224
53352
  g2 = m3 + 1;
@@ -53415,7 +53543,7 @@ var init_yoga_wasm_base64_esm = __esm({
53415
53543
  b3 += 8;
53416
53544
  for (var m3 = 0; m3 < k3; m3++) {
53417
53545
  var l2 = A3[g2 + m3], n2 = Fb[a2];
53418
- 0 === l2 || 10 === l2 ? ((1 === a2 ? ea : v3)(z61(n2, 0)), n2.length = 0) : n2.push(l2);
53546
+ 0 === l2 || 10 === l2 ? ((1 === a2 ? ea : v3)(z62(n2, 0)), n2.length = 0) : n2.push(l2);
53419
53547
  }
53420
53548
  e3 += k3;
53421
53549
  }
@@ -53903,7 +54031,7 @@ var require_scheduler_production_min = __commonJS({
53903
54031
  var u2 = 1;
53904
54032
  var v3 = null;
53905
54033
  var y2 = 3;
53906
- var z61 = false;
54034
+ var z62 = false;
53907
54035
  var A3 = false;
53908
54036
  var B2 = false;
53909
54037
  var D3 = "function" === typeof setTimeout ? setTimeout : null;
@@ -53930,7 +54058,7 @@ var require_scheduler_production_min = __commonJS({
53930
54058
  function J2(a2, b3) {
53931
54059
  A3 = false;
53932
54060
  B2 && (B2 = false, E3(L3), L3 = -1);
53933
- z61 = true;
54061
+ z62 = true;
53934
54062
  var c2 = y2;
53935
54063
  try {
53936
54064
  G3(b3);
@@ -53954,7 +54082,7 @@ var require_scheduler_production_min = __commonJS({
53954
54082
  }
53955
54083
  return w4;
53956
54084
  } finally {
53957
- v3 = null, y2 = c2, z61 = false;
54085
+ v3 = null, y2 = c2, z62 = false;
53958
54086
  }
53959
54087
  }
53960
54088
  var N2 = false;
@@ -54011,7 +54139,7 @@ var require_scheduler_production_min = __commonJS({
54011
54139
  a2.callback = null;
54012
54140
  };
54013
54141
  exports.unstable_continueExecution = function() {
54014
- A3 || z61 || (A3 = true, I2(J2));
54142
+ A3 || z62 || (A3 = true, I2(J2));
54015
54143
  };
54016
54144
  exports.unstable_forceFrameRate = function(a2) {
54017
54145
  0 > a2 || 125 < a2 ? console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported") : P3 = 0 < a2 ? Math.floor(1e3 / a2) : 5;
@@ -54084,7 +54212,7 @@ var require_scheduler_production_min = __commonJS({
54084
54212
  }
54085
54213
  e3 = c2 + e3;
54086
54214
  a2 = { id: u2++, callback: b3, priorityLevel: a2, startTime: c2, expirationTime: e3, sortIndex: -1 };
54087
- c2 > d2 ? (a2.sortIndex = c2, f3(t2, a2), null === h3(r2) && a2 === h3(t2) && (B2 ? (E3(L3), L3 = -1) : B2 = true, K4(H3, c2 - d2))) : (a2.sortIndex = e3, f3(r2, a2), A3 || z61 || (A3 = true, I2(J2)));
54215
+ c2 > d2 ? (a2.sortIndex = c2, f3(t2, a2), null === h3(r2) && a2 === h3(t2) && (B2 ? (E3(L3), L3 = -1) : B2 = true, K4(H3, c2 - d2))) : (a2.sortIndex = e3, f3(r2, a2), A3 || z62 || (A3 = true, I2(J2)));
54088
54216
  return a2;
54089
54217
  };
54090
54218
  exports.unstable_shouldYield = M2;
@@ -54841,7 +54969,7 @@ var require_react_reconciler_production_min = __commonJS({
54841
54969
  gc[hc] = a2.current;
54842
54970
  a2.current = b3;
54843
54971
  }
54844
- var jc = {}, x4 = ic(jc), z61 = ic(false), kc = jc;
54972
+ var jc = {}, x4 = ic(jc), z62 = ic(false), kc = jc;
54845
54973
  function mc(a2, b3) {
54846
54974
  var c2 = a2.type.contextTypes;
54847
54975
  if (!c2) return jc;
@@ -54857,13 +54985,13 @@ var require_react_reconciler_production_min = __commonJS({
54857
54985
  return null !== a2 && void 0 !== a2;
54858
54986
  }
54859
54987
  function nc() {
54860
- q3(z61);
54988
+ q3(z62);
54861
54989
  q3(x4);
54862
54990
  }
54863
54991
  function oc(a2, b3, c2) {
54864
54992
  if (x4.current !== jc) throw Error(n2(168));
54865
54993
  v3(x4, b3);
54866
- v3(z61, c2);
54994
+ v3(z62, c2);
54867
54995
  }
54868
54996
  function pc(a2, b3, c2) {
54869
54997
  var d2 = a2.stateNode;
@@ -54877,14 +55005,14 @@ var require_react_reconciler_production_min = __commonJS({
54877
55005
  a2 = (a2 = a2.stateNode) && a2.__reactInternalMemoizedMergedChildContext || jc;
54878
55006
  kc = x4.current;
54879
55007
  v3(x4, a2);
54880
- v3(z61, z61.current);
55008
+ v3(z62, z62.current);
54881
55009
  return true;
54882
55010
  }
54883
55011
  function rc(a2, b3, c2) {
54884
55012
  var d2 = a2.stateNode;
54885
55013
  if (!d2) throw Error(n2(169));
54886
- c2 ? (a2 = pc(a2, b3, kc), d2.__reactInternalMemoizedMergedChildContext = a2, q3(z61), q3(x4), v3(x4, a2)) : q3(z61);
54887
- v3(z61, c2);
55014
+ c2 ? (a2 = pc(a2, b3, kc), d2.__reactInternalMemoizedMergedChildContext = a2, q3(z62), q3(x4), v3(x4, a2)) : q3(z62);
55015
+ v3(z62, c2);
54888
55016
  }
54889
55017
  var tc = Math.clz32 ? Math.clz32 : sc, uc = Math.log, vc = Math.LN2;
54890
55018
  function sc(a2) {
@@ -56482,7 +56610,7 @@ var require_react_reconciler_production_min = __commonJS({
56482
56610
  g2.state = p3;
56483
56611
  ke2(b3, d2, g2, e3);
56484
56612
  k3 = b3.memoizedState;
56485
- h3 !== d2 || p3 !== k3 || z61.current || de2 ? ("function" === typeof m3 && (yf(b3, c2, m3, d2), k3 = b3.memoizedState), (h3 = de2 || Af(b3, c2, h3, d2, p3, k3, l2)) ? (r2 || "function" !== typeof g2.UNSAFE_componentWillMount && "function" !== typeof g2.componentWillMount || ("function" === typeof g2.componentWillMount && g2.componentWillMount(), "function" === typeof g2.UNSAFE_componentWillMount && g2.UNSAFE_componentWillMount()), "function" === typeof g2.componentDidMount && (b3.flags |= 4194308)) : ("function" === typeof g2.componentDidMount && (b3.flags |= 4194308), b3.memoizedProps = d2, b3.memoizedState = k3), g2.props = d2, g2.state = k3, g2.context = l2, d2 = h3) : ("function" === typeof g2.componentDidMount && (b3.flags |= 4194308), d2 = false);
56613
+ h3 !== d2 || p3 !== k3 || z62.current || de2 ? ("function" === typeof m3 && (yf(b3, c2, m3, d2), k3 = b3.memoizedState), (h3 = de2 || Af(b3, c2, h3, d2, p3, k3, l2)) ? (r2 || "function" !== typeof g2.UNSAFE_componentWillMount && "function" !== typeof g2.componentWillMount || ("function" === typeof g2.componentWillMount && g2.componentWillMount(), "function" === typeof g2.UNSAFE_componentWillMount && g2.UNSAFE_componentWillMount()), "function" === typeof g2.componentDidMount && (b3.flags |= 4194308)) : ("function" === typeof g2.componentDidMount && (b3.flags |= 4194308), b3.memoizedProps = d2, b3.memoizedState = k3), g2.props = d2, g2.state = k3, g2.context = l2, d2 = h3) : ("function" === typeof g2.componentDidMount && (b3.flags |= 4194308), d2 = false);
56486
56614
  } else {
56487
56615
  g2 = b3.stateNode;
56488
56616
  fe2(a2, b3);
@@ -56500,7 +56628,7 @@ var require_react_reconciler_production_min = __commonJS({
56500
56628
  g2.state = p3;
56501
56629
  ke2(b3, d2, g2, e3);
56502
56630
  var w4 = b3.memoizedState;
56503
- h3 !== r2 || p3 !== w4 || z61.current || de2 ? ("function" === typeof B2 && (yf(b3, c2, B2, d2), w4 = b3.memoizedState), (l2 = de2 || Af(b3, c2, l2, d2, p3, w4, k3) || false) ? (m3 || "function" !== typeof g2.UNSAFE_componentWillUpdate && "function" !== typeof g2.componentWillUpdate || ("function" === typeof g2.componentWillUpdate && g2.componentWillUpdate(d2, w4, k3), "function" === typeof g2.UNSAFE_componentWillUpdate && g2.UNSAFE_componentWillUpdate(d2, w4, k3)), "function" === typeof g2.componentDidUpdate && (b3.flags |= 4), "function" === typeof g2.getSnapshotBeforeUpdate && (b3.flags |= 1024)) : ("function" !== typeof g2.componentDidUpdate || h3 === a2.memoizedProps && p3 === a2.memoizedState || (b3.flags |= 4), "function" !== typeof g2.getSnapshotBeforeUpdate || h3 === a2.memoizedProps && p3 === a2.memoizedState || (b3.flags |= 1024), b3.memoizedProps = d2, b3.memoizedState = w4), g2.props = d2, g2.state = w4, g2.context = k3, d2 = l2) : ("function" !== typeof g2.componentDidUpdate || h3 === a2.memoizedProps && p3 === a2.memoizedState || (b3.flags |= 4), "function" !== typeof g2.getSnapshotBeforeUpdate || h3 === a2.memoizedProps && p3 === a2.memoizedState || (b3.flags |= 1024), d2 = false);
56631
+ h3 !== r2 || p3 !== w4 || z62.current || de2 ? ("function" === typeof B2 && (yf(b3, c2, B2, d2), w4 = b3.memoizedState), (l2 = de2 || Af(b3, c2, l2, d2, p3, w4, k3) || false) ? (m3 || "function" !== typeof g2.UNSAFE_componentWillUpdate && "function" !== typeof g2.componentWillUpdate || ("function" === typeof g2.componentWillUpdate && g2.componentWillUpdate(d2, w4, k3), "function" === typeof g2.UNSAFE_componentWillUpdate && g2.UNSAFE_componentWillUpdate(d2, w4, k3)), "function" === typeof g2.componentDidUpdate && (b3.flags |= 4), "function" === typeof g2.getSnapshotBeforeUpdate && (b3.flags |= 1024)) : ("function" !== typeof g2.componentDidUpdate || h3 === a2.memoizedProps && p3 === a2.memoizedState || (b3.flags |= 4), "function" !== typeof g2.getSnapshotBeforeUpdate || h3 === a2.memoizedProps && p3 === a2.memoizedState || (b3.flags |= 1024), b3.memoizedProps = d2, b3.memoizedState = w4), g2.props = d2, g2.state = w4, g2.context = k3, d2 = l2) : ("function" !== typeof g2.componentDidUpdate || h3 === a2.memoizedProps && p3 === a2.memoizedState || (b3.flags |= 4), "function" !== typeof g2.getSnapshotBeforeUpdate || h3 === a2.memoizedProps && p3 === a2.memoizedState || (b3.flags |= 1024), d2 = false);
56504
56632
  }
56505
56633
  return dg(a2, b3, c2, d2, f3, e3);
56506
56634
  }
@@ -56969,7 +57097,7 @@ var require_react_reconciler_production_min = __commonJS({
56969
57097
  case 3:
56970
57098
  c2 = b3.stateNode;
56971
57099
  te3();
56972
- q3(z61);
57100
+ q3(z62);
56973
57101
  q3(x4);
56974
57102
  ye3();
56975
57103
  c2.pendingContext && (c2.context = c2.pendingContext, c2.pendingContext = null);
@@ -57112,7 +57240,7 @@ var require_react_reconciler_production_min = __commonJS({
57112
57240
  case 1:
57113
57241
  return A3(b3.type) && nc(), a2 = b3.flags, a2 & 65536 ? (b3.flags = a2 & -65537 | 128, b3) : null;
57114
57242
  case 3:
57115
- return te3(), q3(z61), q3(x4), ye3(), a2 = b3.flags, 0 !== (a2 & 65536) && 0 === (a2 & 128) ? (b3.flags = a2 & -65537 | 128, b3) : null;
57243
+ return te3(), q3(z62), q3(x4), ye3(), a2 = b3.flags, 0 !== (a2 & 65536) && 0 === (a2 & 128) ? (b3.flags = a2 & -65537 | 128, b3) : null;
57116
57244
  case 5:
57117
57245
  return ve2(b3), null;
57118
57246
  case 13:
@@ -58167,7 +58295,7 @@ var require_react_reconciler_production_min = __commonJS({
58167
58295
  break;
58168
58296
  case 3:
58169
58297
  te3();
58170
- q3(z61);
58298
+ q3(z62);
58171
58299
  q3(x4);
58172
58300
  ye3();
58173
58301
  break;
@@ -58644,7 +58772,7 @@ var require_react_reconciler_production_min = __commonJS({
58644
58772
  }
58645
58773
  var ai;
58646
58774
  ai = function(a2, b3, c2) {
58647
- if (null !== a2) if (a2.memoizedProps !== b3.pendingProps || z61.current) G3 = true;
58775
+ if (null !== a2) if (a2.memoizedProps !== b3.pendingProps || z62.current) G3 = true;
58648
58776
  else {
58649
58777
  if (0 === (a2.lanes & c2) && 0 === (b3.flags & 128)) return G3 = false, sg(a2, b3, c2);
58650
58778
  G3 = 0 !== (a2.flags & 131072) ? true : false;
@@ -58753,7 +58881,7 @@ var require_react_reconciler_production_min = __commonJS({
58753
58881
  g2 = e3.value;
58754
58882
  Vd(b3, d2, g2);
58755
58883
  if (null !== f3) if (Vc(f3.value, g2)) {
58756
- if (f3.children === e3.children && !z61.current) {
58884
+ if (f3.children === e3.children && !z62.current) {
58757
58885
  b3 = Tf(a2, b3, c2);
58758
58886
  break a;
58759
58887
  }
@@ -74168,10 +74296,10 @@ var require_react_reconciler_development = __commonJS({
74168
74296
  var setErrorHandler = null;
74169
74297
  var setSuspenseHandler = null;
74170
74298
  {
74171
- var copyWithDeleteImpl = function(obj, path59, index2) {
74172
- var key = path59[index2];
74299
+ var copyWithDeleteImpl = function(obj, path60, index2) {
74300
+ var key = path60[index2];
74173
74301
  var updated = isArray(obj) ? obj.slice() : assign({}, obj);
74174
- if (index2 + 1 === path59.length) {
74302
+ if (index2 + 1 === path60.length) {
74175
74303
  if (isArray(updated)) {
74176
74304
  updated.splice(key, 1);
74177
74305
  } else {
@@ -74179,11 +74307,11 @@ var require_react_reconciler_development = __commonJS({
74179
74307
  }
74180
74308
  return updated;
74181
74309
  }
74182
- updated[key] = copyWithDeleteImpl(obj[key], path59, index2 + 1);
74310
+ updated[key] = copyWithDeleteImpl(obj[key], path60, index2 + 1);
74183
74311
  return updated;
74184
74312
  };
74185
- var copyWithDelete = function(obj, path59) {
74186
- return copyWithDeleteImpl(obj, path59, 0);
74313
+ var copyWithDelete = function(obj, path60) {
74314
+ return copyWithDeleteImpl(obj, path60, 0);
74187
74315
  };
74188
74316
  var copyWithRenameImpl = function(obj, oldPath, newPath, index2) {
74189
74317
  var oldKey = oldPath[index2];
@@ -74221,17 +74349,17 @@ var require_react_reconciler_development = __commonJS({
74221
74349
  }
74222
74350
  return copyWithRenameImpl(obj, oldPath, newPath, 0);
74223
74351
  };
74224
- var copyWithSetImpl = function(obj, path59, index2, value) {
74225
- if (index2 >= path59.length) {
74352
+ var copyWithSetImpl = function(obj, path60, index2, value) {
74353
+ if (index2 >= path60.length) {
74226
74354
  return value;
74227
74355
  }
74228
- var key = path59[index2];
74356
+ var key = path60[index2];
74229
74357
  var updated = isArray(obj) ? obj.slice() : assign({}, obj);
74230
- updated[key] = copyWithSetImpl(obj[key], path59, index2 + 1, value);
74358
+ updated[key] = copyWithSetImpl(obj[key], path60, index2 + 1, value);
74231
74359
  return updated;
74232
74360
  };
74233
- var copyWithSet = function(obj, path59, value) {
74234
- return copyWithSetImpl(obj, path59, 0, value);
74361
+ var copyWithSet = function(obj, path60, value) {
74362
+ return copyWithSetImpl(obj, path60, 0, value);
74235
74363
  };
74236
74364
  var findHook = function(fiber, id) {
74237
74365
  var currentHook2 = fiber.memoizedState;
@@ -74241,10 +74369,10 @@ var require_react_reconciler_development = __commonJS({
74241
74369
  }
74242
74370
  return currentHook2;
74243
74371
  };
74244
- overrideHookState = function(fiber, id, path59, value) {
74372
+ overrideHookState = function(fiber, id, path60, value) {
74245
74373
  var hook = findHook(fiber, id);
74246
74374
  if (hook !== null) {
74247
- var newState = copyWithSet(hook.memoizedState, path59, value);
74375
+ var newState = copyWithSet(hook.memoizedState, path60, value);
74248
74376
  hook.memoizedState = newState;
74249
74377
  hook.baseState = newState;
74250
74378
  fiber.memoizedProps = assign({}, fiber.memoizedProps);
@@ -74254,10 +74382,10 @@ var require_react_reconciler_development = __commonJS({
74254
74382
  }
74255
74383
  }
74256
74384
  };
74257
- overrideHookStateDeletePath = function(fiber, id, path59) {
74385
+ overrideHookStateDeletePath = function(fiber, id, path60) {
74258
74386
  var hook = findHook(fiber, id);
74259
74387
  if (hook !== null) {
74260
- var newState = copyWithDelete(hook.memoizedState, path59);
74388
+ var newState = copyWithDelete(hook.memoizedState, path60);
74261
74389
  hook.memoizedState = newState;
74262
74390
  hook.baseState = newState;
74263
74391
  fiber.memoizedProps = assign({}, fiber.memoizedProps);
@@ -74280,8 +74408,8 @@ var require_react_reconciler_development = __commonJS({
74280
74408
  }
74281
74409
  }
74282
74410
  };
74283
- overrideProps = function(fiber, path59, value) {
74284
- fiber.pendingProps = copyWithSet(fiber.memoizedProps, path59, value);
74411
+ overrideProps = function(fiber, path60, value) {
74412
+ fiber.pendingProps = copyWithSet(fiber.memoizedProps, path60, value);
74285
74413
  if (fiber.alternate) {
74286
74414
  fiber.alternate.pendingProps = fiber.pendingProps;
74287
74415
  }
@@ -74290,8 +74418,8 @@ var require_react_reconciler_development = __commonJS({
74290
74418
  scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
74291
74419
  }
74292
74420
  };
74293
- overridePropsDeletePath = function(fiber, path59) {
74294
- fiber.pendingProps = copyWithDelete(fiber.memoizedProps, path59);
74421
+ overridePropsDeletePath = function(fiber, path60) {
74422
+ fiber.pendingProps = copyWithDelete(fiber.memoizedProps, path60);
74295
74423
  if (fiber.alternate) {
74296
74424
  fiber.alternate.pendingProps = fiber.pendingProps;
74297
74425
  }
@@ -78818,7 +78946,7 @@ var require_stream = __commonJS({
78818
78946
  };
78819
78947
  duplex._final = function(callback) {
78820
78948
  if (ws.readyState === ws.CONNECTING) {
78821
- ws.once("open", function open() {
78949
+ ws.once("open", function open2() {
78822
78950
  duplex._final(callback);
78823
78951
  });
78824
78952
  return;
@@ -78839,7 +78967,7 @@ var require_stream = __commonJS({
78839
78967
  };
78840
78968
  duplex._write = function(chunk, encoding, callback) {
78841
78969
  if (ws.readyState === ws.CONNECTING) {
78842
- ws.once("open", function open() {
78970
+ ws.once("open", function open2() {
78843
78971
  duplex._write(chunk, encoding, callback);
78844
78972
  });
78845
78973
  return;
@@ -80204,18 +80332,18 @@ var init_source = __esm({
80204
80332
  }
80205
80333
  }
80206
80334
  });
80207
- createStyler = (open, close, parent) => {
80335
+ createStyler = (open2, close, parent) => {
80208
80336
  let openAll;
80209
80337
  let closeAll;
80210
80338
  if (parent === void 0) {
80211
- openAll = open;
80339
+ openAll = open2;
80212
80340
  closeAll = close;
80213
80341
  } else {
80214
- openAll = parent.openAll + open;
80342
+ openAll = parent.openAll + open2;
80215
80343
  closeAll = close + parent.closeAll;
80216
80344
  }
80217
80345
  return {
80218
- open,
80346
+ open: open2,
80219
80347
  close,
80220
80348
  openAll,
80221
80349
  closeAll,
@@ -81733,8 +81861,8 @@ var init_ErrorOverview = __esm({
81733
81861
  init_dist7();
81734
81862
  init_Box();
81735
81863
  init_Text();
81736
- cleanupPath = (path59) => {
81737
- return path59?.replace(`file://${cwd()}/`, "");
81864
+ cleanupPath = (path60) => {
81865
+ return path60?.replace(`file://${cwd()}/`, "");
81738
81866
  };
81739
81867
  stackUtils = new import_stack_utils.default({
81740
81868
  cwd: cwd(),
@@ -86081,8 +86209,8 @@ function readClipboardImageDarwin() {
86081
86209
  return null;
86082
86210
  }
86083
86211
  try {
86084
- const stat = statSync(target);
86085
- if (stat.size === 0) {
86212
+ const stat2 = statSync(target);
86213
+ if (stat2.size === 0) {
86086
86214
  unlinkSync(target);
86087
86215
  return null;
86088
86216
  }
@@ -88412,7 +88540,6 @@ var init_SessionView = __esm({
88412
88540
  const ctrl = turn.turnControllerRef.current;
88413
88541
  if (ctrl && !ctrl.signal.aborted)
88414
88542
  ctrl.abort("user reset");
88415
- session.log.clear();
88416
88543
  setOverlay(null);
88417
88544
  for (const p3 of permissions.pendingPermissions) {
88418
88545
  p3.resolve({ mode: "deny", reason: "/new \u2014 session reset" });
@@ -88423,8 +88550,18 @@ var init_SessionView = __esm({
88423
88550
  setYolo(false);
88424
88551
  turn.queueRef.current = [];
88425
88552
  turn.setQueueCount(0);
88426
- if (notice)
88427
- setSystemNotice(notice);
88553
+ if (typeof session.reset === "function") {
88554
+ void session.reset().then(() => {
88555
+ if (notice)
88556
+ setSystemNotice(notice);
88557
+ }, (err) => {
88558
+ setSystemNotice(`/new failed: ${err instanceof Error ? err.message : String(err)} \u2014 history NOT cleared`);
88559
+ });
88560
+ } else {
88561
+ session.log.clear();
88562
+ if (notice)
88563
+ setSystemNotice(notice);
88564
+ }
88428
88565
  };
88429
88566
  const handleSubmit = async (text) => {
88430
88567
  setSystemNotice(null);
@@ -90202,11 +90339,11 @@ var require_dijkstra = __commonJS({
90202
90339
  var predecessors = {};
90203
90340
  var costs = {};
90204
90341
  costs[s2] = 0;
90205
- var open = dijkstra.PriorityQueue.make();
90206
- open.push(s2, 0);
90342
+ var open2 = dijkstra.PriorityQueue.make();
90343
+ open2.push(s2, 0);
90207
90344
  var closest, u2, v3, cost_of_s_to_u, adjacent_nodes, cost_of_e, cost_of_s_to_u_plus_cost_of_e, cost_of_s_to_v, first_visit;
90208
- while (!open.empty()) {
90209
- closest = open.pop();
90345
+ while (!open2.empty()) {
90346
+ closest = open2.pop();
90210
90347
  u2 = closest.value;
90211
90348
  cost_of_s_to_u = closest.cost;
90212
90349
  adjacent_nodes = graph[u2] || {};
@@ -90218,7 +90355,7 @@ var require_dijkstra = __commonJS({
90218
90355
  first_visit = typeof costs[v3] === "undefined";
90219
90356
  if (first_visit || cost_of_s_to_v > cost_of_s_to_u_plus_cost_of_e) {
90220
90357
  costs[v3] = cost_of_s_to_u_plus_cost_of_e;
90221
- open.push(v3, cost_of_s_to_u_plus_cost_of_e);
90358
+ open2.push(v3, cost_of_s_to_u_plus_cost_of_e);
90222
90359
  predecessors[v3] = u2;
90223
90360
  }
90224
90361
  }
@@ -90464,10 +90601,10 @@ var require_segments = __commonJS({
90464
90601
  const segs = getSegmentsFromString(data, Utils.isKanjiModeEnabled());
90465
90602
  const nodes = buildNodes(segs);
90466
90603
  const graph = buildGraph(nodes, version);
90467
- const path59 = dijkstra.find_path(graph.map, "start", "end");
90604
+ const path60 = dijkstra.find_path(graph.map, "start", "end");
90468
90605
  const optimizedSegs = [];
90469
- for (let i2 = 1; i2 < path59.length - 1; i2++) {
90470
- optimizedSegs.push(graph.table[path59[i2]].node);
90606
+ for (let i2 = 1; i2 < path60.length - 1; i2++) {
90607
+ optimizedSegs.push(graph.table[path60[i2]].node);
90471
90608
  }
90472
90609
  return exports.fromArray(mergeSegments(optimizedSegs));
90473
90610
  };
@@ -92919,7 +93056,7 @@ var require_png2 = __commonJS({
92919
93056
  });
92920
93057
  png.pack();
92921
93058
  };
92922
- exports.renderToFile = function renderToFile(path59, qrData, options, cb) {
93059
+ exports.renderToFile = function renderToFile(path60, qrData, options, cb) {
92923
93060
  if (typeof cb === "undefined") {
92924
93061
  cb = options;
92925
93062
  options = void 0;
@@ -92930,7 +93067,7 @@ var require_png2 = __commonJS({
92930
93067
  called = true;
92931
93068
  cb.apply(null, args);
92932
93069
  };
92933
- const stream = fs43.createWriteStream(path59);
93070
+ const stream = fs43.createWriteStream(path60);
92934
93071
  stream.on("error", done);
92935
93072
  stream.on("close", done);
92936
93073
  exports.renderToFileStream(stream, qrData, options);
@@ -92992,14 +93129,14 @@ var require_utf8 = __commonJS({
92992
93129
  }
92993
93130
  return output;
92994
93131
  };
92995
- exports.renderToFile = function renderToFile(path59, qrData, options, cb) {
93132
+ exports.renderToFile = function renderToFile(path60, qrData, options, cb) {
92996
93133
  if (typeof cb === "undefined") {
92997
93134
  cb = options;
92998
93135
  options = void 0;
92999
93136
  }
93000
93137
  const fs43 = __require("fs");
93001
93138
  const utf8 = exports.render(qrData, options);
93002
- fs43.writeFile(path59, utf8, cb);
93139
+ fs43.writeFile(path60, utf8, cb);
93003
93140
  };
93004
93141
  }
93005
93142
  });
@@ -93120,7 +93257,7 @@ var require_svg_tag = __commonJS({
93120
93257
  return str2;
93121
93258
  }
93122
93259
  function qrToPath(data, size, margin) {
93123
- let path59 = "";
93260
+ let path60 = "";
93124
93261
  let moveBy = 0;
93125
93262
  let newRow = false;
93126
93263
  let lineLength = 0;
@@ -93131,19 +93268,19 @@ var require_svg_tag = __commonJS({
93131
93268
  if (data[i2]) {
93132
93269
  lineLength++;
93133
93270
  if (!(i2 > 0 && col > 0 && data[i2 - 1])) {
93134
- path59 += newRow ? svgCmd("M", col + margin, 0.5 + row + margin) : svgCmd("m", moveBy, 0);
93271
+ path60 += newRow ? svgCmd("M", col + margin, 0.5 + row + margin) : svgCmd("m", moveBy, 0);
93135
93272
  moveBy = 0;
93136
93273
  newRow = false;
93137
93274
  }
93138
93275
  if (!(col + 1 < size && data[i2 + 1])) {
93139
- path59 += svgCmd("h", lineLength);
93276
+ path60 += svgCmd("h", lineLength);
93140
93277
  lineLength = 0;
93141
93278
  }
93142
93279
  } else {
93143
93280
  moveBy++;
93144
93281
  }
93145
93282
  }
93146
- return path59;
93283
+ return path60;
93147
93284
  }
93148
93285
  exports.render = function render2(qrData, options, cb) {
93149
93286
  const opts = Utils.getOptions(options);
@@ -93151,10 +93288,10 @@ var require_svg_tag = __commonJS({
93151
93288
  const data = qrData.modules.data;
93152
93289
  const qrcodesize = size + opts.margin * 2;
93153
93290
  const bg = !opts.color.light.a ? "" : "<path " + getColorAttrib(opts.color.light, "fill") + ' d="M0 0h' + qrcodesize + "v" + qrcodesize + 'H0z"/>';
93154
- const path59 = "<path " + getColorAttrib(opts.color.dark, "stroke") + ' d="' + qrToPath(data, size, opts.margin) + '"/>';
93291
+ const path60 = "<path " + getColorAttrib(opts.color.dark, "stroke") + ' d="' + qrToPath(data, size, opts.margin) + '"/>';
93155
93292
  const viewBox = 'viewBox="0 0 ' + qrcodesize + " " + qrcodesize + '"';
93156
93293
  const width = !opts.width ? "" : 'width="' + opts.width + '" height="' + opts.width + '" ';
93157
- const svgTag = '<svg xmlns="http://www.w3.org/2000/svg" ' + width + viewBox + ' shape-rendering="crispEdges">' + bg + path59 + "</svg>\n";
93294
+ const svgTag = '<svg xmlns="http://www.w3.org/2000/svg" ' + width + viewBox + ' shape-rendering="crispEdges">' + bg + path60 + "</svg>\n";
93158
93295
  if (typeof cb === "function") {
93159
93296
  cb(null, svgTag);
93160
93297
  }
@@ -93168,7 +93305,7 @@ var require_svg = __commonJS({
93168
93305
  "../../node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/renderer/svg.js"(exports) {
93169
93306
  var svgTagRenderer = require_svg_tag();
93170
93307
  exports.render = svgTagRenderer.render;
93171
- exports.renderToFile = function renderToFile(path59, qrData, options, cb) {
93308
+ exports.renderToFile = function renderToFile(path60, qrData, options, cb) {
93172
93309
  if (typeof cb === "undefined") {
93173
93310
  cb = options;
93174
93311
  options = void 0;
@@ -93176,7 +93313,7 @@ var require_svg = __commonJS({
93176
93313
  const fs43 = __require("fs");
93177
93314
  const svgTag = exports.render(qrData, options);
93178
93315
  const xmlStr = '<?xml version="1.0" encoding="utf-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">' + svgTag;
93179
- fs43.writeFile(path59, xmlStr, cb);
93316
+ fs43.writeFile(path60, xmlStr, cb);
93180
93317
  };
93181
93318
  }
93182
93319
  });
@@ -93334,8 +93471,8 @@ var require_server = __commonJS({
93334
93471
  cb
93335
93472
  };
93336
93473
  }
93337
- function getTypeFromFilename(path59) {
93338
- return path59.slice((path59.lastIndexOf(".") - 1 >>> 0) + 2).toLowerCase();
93474
+ function getTypeFromFilename(path60) {
93475
+ return path60.slice((path60.lastIndexOf(".") - 1 >>> 0) + 2).toLowerCase();
93339
93476
  }
93340
93477
  function getRendererFromType(type) {
93341
93478
  switch (type) {
@@ -93399,17 +93536,17 @@ var require_server = __commonJS({
93399
93536
  const renderer2 = getRendererFromType(params.opts.type);
93400
93537
  return render2(renderer2.renderToBuffer, text, params);
93401
93538
  };
93402
- exports.toFile = function toFile3(path59, text, opts, cb) {
93403
- if (typeof path59 !== "string" || !(typeof text === "string" || typeof text === "object")) {
93539
+ exports.toFile = function toFile3(path60, text, opts, cb) {
93540
+ if (typeof path60 !== "string" || !(typeof text === "string" || typeof text === "object")) {
93404
93541
  throw new Error("Invalid argument");
93405
93542
  }
93406
93543
  if (arguments.length < 3 && !canPromise()) {
93407
93544
  throw new Error("Too few arguments provided");
93408
93545
  }
93409
93546
  const params = checkParams(text, opts, cb);
93410
- const type = params.opts.type || getTypeFromFilename(path59);
93547
+ const type = params.opts.type || getTypeFromFilename(path60);
93411
93548
  const renderer2 = getRendererFromType(type);
93412
- const renderToFile = renderer2.renderToFile.bind(null, path59);
93549
+ const renderToFile = renderer2.renderToFile.bind(null, path60);
93413
93550
  return render2(renderToFile, text, params);
93414
93551
  };
93415
93552
  exports.toFileStream = function toFileStream(stream, text, opts) {
@@ -95215,6 +95352,9 @@ var VaultStore = class {
95215
95352
  // whole-file rewrite. open() is also chained through this so two
95216
95353
  // parallel `open()` calls never both derive a fresh salt.
95217
95354
  mutex = createMutex();
95355
+ // stat() fingerprint of the file as of our last load/sync/persist; lets
95356
+ // syncFromDisk() skip the read+parse when nothing else has written.
95357
+ lastSynced = null;
95218
95358
  constructor(opts) {
95219
95359
  this.filePath = opts.filePath;
95220
95360
  this.keySource = opts.keySource;
@@ -95292,6 +95432,49 @@ var VaultStore = class {
95292
95432
  throw new VaultPassphraseError(this.filePath);
95293
95433
  }
95294
95434
  }
95435
+ /**
95436
+ * Fold other writers' entries in from the on-disk file. Historically the
95437
+ * vault persisted a whole-file snapshot of THIS instance's memory, so a
95438
+ * write from any other `VaultStore` (another moxxy process, or a second
95439
+ * instance in this one) was silently clobbered by our next persist —
95440
+ * last-writer-wins, fatal for single-use rotated OAuth refresh tokens.
95441
+ * Now every read and every mutation first re-reads the file (mtime/size
95442
+ * gated, so the common no-other-writer case costs one `stat`) and merges:
95443
+ * - key on both sides → newer `updatedAt` wins (ISO timestamps);
95444
+ * - key only on disk → adopt it (another writer added it);
95445
+ * - key only in memory → drop it (every mutation persists before
95446
+ * returning, so a key missing on disk was deleted by another writer).
95447
+ * Skipped when the on-disk salt differs (vault wiped/recreated — those
95448
+ * entries are undecryptable under our master key) or the file is
95449
+ * unreadable/corrupt (keep memory; the next persist restores a good file).
95450
+ *
95451
+ * Must be called while holding `this.mutex`.
95452
+ */
95453
+ async syncFromDisk() {
95454
+ if (!this.file)
95455
+ return;
95456
+ let st3;
95457
+ try {
95458
+ st3 = await promises.stat(this.filePath);
95459
+ } catch (err) {
95460
+ if (isEnoent(err))
95461
+ return;
95462
+ throw err;
95463
+ }
95464
+ if (this.lastSynced && st3.mtimeMs === this.lastSynced.mtimeMs && st3.size === this.lastSynced.size) {
95465
+ return;
95466
+ }
95467
+ let parsed;
95468
+ try {
95469
+ parsed = JSON.parse(await promises.readFile(this.filePath, "utf8"));
95470
+ } catch {
95471
+ return;
95472
+ }
95473
+ if (parsed.version !== 1 || parsed.kdf !== "scrypt" || parsed.salt !== this.file.salt)
95474
+ return;
95475
+ this.file = { ...this.file, entries: mergeEntries(this.file.entries, parsed.entries ?? {}) };
95476
+ this.lastSynced = { mtimeMs: st3.mtimeMs, size: st3.size };
95477
+ }
95295
95478
  /**
95296
95479
  * Crash-atomic write: serialize to a sibling tmp file, then rename. POSIX
95297
95480
  * rename is atomic, so a crash mid-write leaves the previous vault intact
@@ -95301,12 +95484,19 @@ var VaultStore = class {
95301
95484
  if (!this.file)
95302
95485
  return;
95303
95486
  await writeFileAtomic(this.filePath, JSON.stringify(this.file, null, 2), { mode: 384 });
95487
+ try {
95488
+ const st3 = await promises.stat(this.filePath);
95489
+ this.lastSynced = { mtimeMs: st3.mtimeMs, size: st3.size };
95490
+ } catch {
95491
+ this.lastSynced = null;
95492
+ }
95304
95493
  }
95305
95494
  async set(name, value, tags) {
95306
95495
  await this.open();
95307
95496
  return this.mutex.run(async () => {
95308
95497
  if (!this.file || !this.masterKey)
95309
95498
  throw new Error("vault not open");
95499
+ await this.syncFromDisk();
95310
95500
  const now = (/* @__PURE__ */ new Date()).toISOString();
95311
95501
  const existing = this.file.entries[name];
95312
95502
  const blob = encrypt(value, this.masterKey);
@@ -95327,6 +95517,7 @@ var VaultStore = class {
95327
95517
  }
95328
95518
  async get(name) {
95329
95519
  await this.open();
95520
+ await this.mutex.run(() => this.syncFromDisk());
95330
95521
  if (!this.file || !this.masterKey)
95331
95522
  throw new Error("vault not open");
95332
95523
  const entry = this.file.entries[name];
@@ -95336,6 +95527,7 @@ var VaultStore = class {
95336
95527
  }
95337
95528
  async has(name) {
95338
95529
  await this.open();
95530
+ await this.mutex.run(() => this.syncFromDisk());
95339
95531
  return Boolean(this.file?.entries[name]);
95340
95532
  }
95341
95533
  async delete(name) {
@@ -95343,6 +95535,7 @@ var VaultStore = class {
95343
95535
  return this.mutex.run(async () => {
95344
95536
  if (!this.file)
95345
95537
  return false;
95538
+ await this.syncFromDisk();
95346
95539
  if (!(name in this.file.entries))
95347
95540
  return false;
95348
95541
  const { [name]: _removed, ...rest } = this.file.entries;
@@ -95353,6 +95546,7 @@ var VaultStore = class {
95353
95546
  }
95354
95547
  async list() {
95355
95548
  await this.open();
95549
+ await this.mutex.run(() => this.syncFromDisk());
95356
95550
  if (!this.file)
95357
95551
  return [];
95358
95552
  return Object.entries(this.file.entries).map(([name, e3]) => ({
@@ -95363,6 +95557,18 @@ var VaultStore = class {
95363
95557
  }));
95364
95558
  }
95365
95559
  };
95560
+ function mergeEntries(memory, disk) {
95561
+ const merged = { ...disk };
95562
+ for (const [name, mine] of Object.entries(memory)) {
95563
+ const theirs = merged[name];
95564
+ if (theirs && theirs.updatedAt >= mine.updatedAt)
95565
+ continue;
95566
+ if (!theirs)
95567
+ continue;
95568
+ merged[name] = mine;
95569
+ }
95570
+ return merged;
95571
+ }
95366
95572
  function isEnoent(err) {
95367
95573
  return err instanceof Error && "code" in err && err.code === "ENOENT";
95368
95574
  }
@@ -97174,13 +97380,13 @@ var MultipartBody = class {
97174
97380
  }
97175
97381
  };
97176
97382
  var fileFromPathWarned = false;
97177
- async function fileFromPath3(path59, ...args) {
97383
+ async function fileFromPath3(path60, ...args) {
97178
97384
  const { fileFromPath: _fileFromPath } = await Promise.resolve().then(() => (init_fileFromPath(), fileFromPath_exports));
97179
97385
  if (!fileFromPathWarned) {
97180
- console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(path59)}) instead`);
97386
+ console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(path60)}) instead`);
97181
97387
  fileFromPathWarned = true;
97182
97388
  }
97183
- return await _fileFromPath(path59, ...args);
97389
+ return await _fileFromPath(path60, ...args);
97184
97390
  }
97185
97391
  var defaultHttpAgent = new import_agentkeepalive.default({ keepAlive: true, timeout: 5 * 60 * 1e3 });
97186
97392
  var defaultHttpsAgent = new import_agentkeepalive.default.HttpsAgent({ keepAlive: true, timeout: 5 * 60 * 1e3 });
@@ -97903,29 +98109,29 @@ var APIClient = class {
97903
98109
  defaultIdempotencyKey() {
97904
98110
  return `stainless-node-retry-${uuid4()}`;
97905
98111
  }
97906
- get(path59, opts) {
97907
- return this.methodRequest("get", path59, opts);
98112
+ get(path60, opts) {
98113
+ return this.methodRequest("get", path60, opts);
97908
98114
  }
97909
- post(path59, opts) {
97910
- return this.methodRequest("post", path59, opts);
98115
+ post(path60, opts) {
98116
+ return this.methodRequest("post", path60, opts);
97911
98117
  }
97912
- patch(path59, opts) {
97913
- return this.methodRequest("patch", path59, opts);
98118
+ patch(path60, opts) {
98119
+ return this.methodRequest("patch", path60, opts);
97914
98120
  }
97915
- put(path59, opts) {
97916
- return this.methodRequest("put", path59, opts);
98121
+ put(path60, opts) {
98122
+ return this.methodRequest("put", path60, opts);
97917
98123
  }
97918
- delete(path59, opts) {
97919
- return this.methodRequest("delete", path59, opts);
98124
+ delete(path60, opts) {
98125
+ return this.methodRequest("delete", path60, opts);
97920
98126
  }
97921
- methodRequest(method, path59, opts) {
98127
+ methodRequest(method, path60, opts) {
97922
98128
  return this.request(Promise.resolve(opts).then(async (opts2) => {
97923
98129
  const body = opts2 && isBlobLike(opts2?.body) ? new DataView(await opts2.body.arrayBuffer()) : opts2?.body instanceof DataView ? opts2.body : opts2?.body instanceof ArrayBuffer ? new DataView(opts2.body) : opts2 && ArrayBuffer.isView(opts2?.body) ? new DataView(opts2.body.buffer) : opts2?.body;
97924
- return { method, path: path59, ...opts2, body };
98130
+ return { method, path: path60, ...opts2, body };
97925
98131
  }));
97926
98132
  }
97927
- getAPIList(path59, Page3, opts) {
97928
- return this.requestAPIList(Page3, { method: "get", path: path59, ...opts });
98133
+ getAPIList(path60, Page3, opts) {
98134
+ return this.requestAPIList(Page3, { method: "get", path: path60, ...opts });
97929
98135
  }
97930
98136
  calculateContentLength(body) {
97931
98137
  if (typeof body === "string") {
@@ -97944,10 +98150,10 @@ var APIClient = class {
97944
98150
  }
97945
98151
  buildRequest(inputOptions, { retryCount = 0 } = {}) {
97946
98152
  const options = { ...inputOptions };
97947
- const { method, path: path59, query, headers = {} } = options;
98153
+ const { method, path: path60, query, headers = {} } = options;
97948
98154
  const body = ArrayBuffer.isView(options.body) || options.__binaryRequest && typeof options.body === "string" ? options.body : isMultipartBody(options.body) ? options.body.body : options.body ? JSON.stringify(options.body, null, 2) : null;
97949
98155
  const contentLength = this.calculateContentLength(body);
97950
- const url2 = this.buildURL(path59, query);
98156
+ const url2 = this.buildURL(path60, query);
97951
98157
  if ("timeout" in options)
97952
98158
  validatePositiveInteger("timeout", options.timeout);
97953
98159
  options.timeout = options.timeout ?? this.timeout;
@@ -98071,8 +98277,8 @@ var APIClient = class {
98071
98277
  const request = this.makeRequest(options, null);
98072
98278
  return new PagePromise(this, request, Page3);
98073
98279
  }
98074
- buildURL(path59, query) {
98075
- const url2 = isAbsoluteURL(path59) ? new URL(path59) : new URL(this.baseURL + (this.baseURL.endsWith("/") && path59.startsWith("/") ? path59.slice(1) : path59));
98280
+ buildURL(path60, query) {
98281
+ const url2 = isAbsoluteURL(path60) ? new URL(path60) : new URL(this.baseURL + (this.baseURL.endsWith("/") && path60.startsWith("/") ? path60.slice(1) : path60));
98076
98282
  const defaultQuery = this.defaultQuery();
98077
98283
  if (!isEmptyObj(defaultQuery)) {
98078
98284
  query = { ...defaultQuery, ...query };
@@ -101225,13 +101431,13 @@ var MultipartBody2 = class {
101225
101431
  }
101226
101432
  };
101227
101433
  var fileFromPathWarned2 = false;
101228
- async function fileFromPath5(path59, ...args) {
101434
+ async function fileFromPath5(path60, ...args) {
101229
101435
  const { fileFromPath: _fileFromPath } = await Promise.resolve().then(() => (init_fileFromPath(), fileFromPath_exports));
101230
101436
  if (!fileFromPathWarned2) {
101231
- console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(path59)}) instead`);
101437
+ console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(path60)}) instead`);
101232
101438
  fileFromPathWarned2 = true;
101233
101439
  }
101234
- return await _fileFromPath(path59, ...args);
101440
+ return await _fileFromPath(path60, ...args);
101235
101441
  }
101236
101442
  var defaultHttpAgent2 = new import_agentkeepalive2.default({ keepAlive: true, timeout: 5 * 60 * 1e3 });
101237
101443
  var defaultHttpsAgent2 = new import_agentkeepalive2.default.HttpsAgent({ keepAlive: true, timeout: 5 * 60 * 1e3 });
@@ -102009,29 +102215,29 @@ var APIClient2 = class {
102009
102215
  defaultIdempotencyKey() {
102010
102216
  return `stainless-node-retry-${uuid42()}`;
102011
102217
  }
102012
- get(path59, opts) {
102013
- return this.methodRequest("get", path59, opts);
102218
+ get(path60, opts) {
102219
+ return this.methodRequest("get", path60, opts);
102014
102220
  }
102015
- post(path59, opts) {
102016
- return this.methodRequest("post", path59, opts);
102221
+ post(path60, opts) {
102222
+ return this.methodRequest("post", path60, opts);
102017
102223
  }
102018
- patch(path59, opts) {
102019
- return this.methodRequest("patch", path59, opts);
102224
+ patch(path60, opts) {
102225
+ return this.methodRequest("patch", path60, opts);
102020
102226
  }
102021
- put(path59, opts) {
102022
- return this.methodRequest("put", path59, opts);
102227
+ put(path60, opts) {
102228
+ return this.methodRequest("put", path60, opts);
102023
102229
  }
102024
- delete(path59, opts) {
102025
- return this.methodRequest("delete", path59, opts);
102230
+ delete(path60, opts) {
102231
+ return this.methodRequest("delete", path60, opts);
102026
102232
  }
102027
- methodRequest(method, path59, opts) {
102233
+ methodRequest(method, path60, opts) {
102028
102234
  return this.request(Promise.resolve(opts).then(async (opts2) => {
102029
102235
  const body = opts2 && isBlobLike2(opts2?.body) ? new DataView(await opts2.body.arrayBuffer()) : opts2?.body instanceof DataView ? opts2.body : opts2?.body instanceof ArrayBuffer ? new DataView(opts2.body) : opts2 && ArrayBuffer.isView(opts2?.body) ? new DataView(opts2.body.buffer) : opts2?.body;
102030
- return { method, path: path59, ...opts2, body };
102236
+ return { method, path: path60, ...opts2, body };
102031
102237
  }));
102032
102238
  }
102033
- getAPIList(path59, Page3, opts) {
102034
- return this.requestAPIList(Page3, { method: "get", path: path59, ...opts });
102239
+ getAPIList(path60, Page3, opts) {
102240
+ return this.requestAPIList(Page3, { method: "get", path: path60, ...opts });
102035
102241
  }
102036
102242
  calculateContentLength(body) {
102037
102243
  if (typeof body === "string") {
@@ -102050,10 +102256,10 @@ var APIClient2 = class {
102050
102256
  }
102051
102257
  buildRequest(inputOptions, { retryCount = 0 } = {}) {
102052
102258
  const options = { ...inputOptions };
102053
- const { method, path: path59, query, headers = {} } = options;
102259
+ const { method, path: path60, query, headers = {} } = options;
102054
102260
  const body = ArrayBuffer.isView(options.body) || options.__binaryRequest && typeof options.body === "string" ? options.body : isMultipartBody2(options.body) ? options.body.body : options.body ? JSON.stringify(options.body, null, 2) : null;
102055
102261
  const contentLength = this.calculateContentLength(body);
102056
- const url2 = this.buildURL(path59, query);
102262
+ const url2 = this.buildURL(path60, query);
102057
102263
  if ("timeout" in options)
102058
102264
  validatePositiveInteger2("timeout", options.timeout);
102059
102265
  options.timeout = options.timeout ?? this.timeout;
@@ -102169,8 +102375,8 @@ var APIClient2 = class {
102169
102375
  const request = this.makeRequest(options, null);
102170
102376
  return new PagePromise2(this, request, Page3);
102171
102377
  }
102172
- buildURL(path59, query) {
102173
- const url2 = isAbsoluteURL2(path59) ? new URL(path59) : new URL(this.baseURL + (this.baseURL.endsWith("/") && path59.startsWith("/") ? path59.slice(1) : path59));
102378
+ buildURL(path60, query) {
102379
+ const url2 = isAbsoluteURL2(path60) ? new URL(path60) : new URL(this.baseURL + (this.baseURL.endsWith("/") && path60.startsWith("/") ? path60.slice(1) : path60));
102174
102380
  const defaultQuery = this.defaultQuery();
102175
102381
  if (!isEmptyObj2(defaultQuery)) {
102176
102382
  query = { ...defaultQuery, ...query };
@@ -107550,11 +107756,11 @@ function buildAuthUrl(input) {
107550
107756
  }
107551
107757
  async function runAuthorizationCodeFlow(opts) {
107552
107758
  const port = opts.redirectPort ?? 8765;
107553
- const path59 = opts.redirectPath ?? "/callback";
107759
+ const path60 = opts.redirectPath ?? "/callback";
107554
107760
  const codeVerifier = generateCodeVerifier();
107555
107761
  const codeChallenge = computeCodeChallenge(codeVerifier);
107556
107762
  const state = generateState();
107557
- const redirectUri = `http://localhost:${port}${path59}`;
107763
+ const redirectUri = `http://localhost:${port}${path60}`;
107558
107764
  const authUrl = buildAuthUrl({
107559
107765
  authUrl: opts.authUrl,
107560
107766
  clientId: opts.clientId,
@@ -107566,7 +107772,7 @@ async function runAuthorizationCodeFlow(opts) {
107566
107772
  });
107567
107773
  const codePromise = waitForCallback({
107568
107774
  port,
107569
- path: path59,
107775
+ path: path60,
107570
107776
  expectedState: state,
107571
107777
  timeoutMs: opts.timeoutMs ?? 3e5,
107572
107778
  ...opts.signal ? { signal: opts.signal } : {}
@@ -107914,11 +108120,11 @@ function buildOauthAuthorizeTool(deps) {
107914
108120
  const { computeCodeChallenge: computeCodeChallenge2 } = await Promise.resolve().then(() => (init_pkce(), pkce_exports));
107915
108121
  const challenge = computeCodeChallenge2(verifier);
107916
108122
  const port = input.redirectPort ?? 8765;
107917
- const path59 = input.redirectPath ?? "/callback";
108123
+ const path60 = input.redirectPath ?? "/callback";
107918
108124
  const url2 = buildAuthUrl({
107919
108125
  authUrl: input.authUrl,
107920
108126
  clientId: input.clientId,
107921
- redirectUri: `http://localhost:${port}${path59}`,
108127
+ redirectUri: `http://localhost:${port}${path60}`,
107922
108128
  scopes: input.scopes,
107923
108129
  codeChallenge: challenge,
107924
108130
  state,
@@ -108060,7 +108266,7 @@ async function runOauthLogin(profile, ctx) {
108060
108266
  }
108061
108267
  async function runBrowserFlow(profile, ctx) {
108062
108268
  const port = profile.redirect?.port ?? 8765;
108063
- const path59 = profile.redirect?.path ?? "/callback";
108269
+ const path60 = profile.redirect?.path ?? "/callback";
108064
108270
  const serviceName = profile.displayName ?? profile.id;
108065
108271
  return runAuthorizationCodeFlow({
108066
108272
  authUrl: profile.authUrl,
@@ -108069,7 +108275,7 @@ async function runBrowserFlow(profile, ctx) {
108069
108275
  ...profile.clientSecret ? { clientSecret: profile.clientSecret } : {},
108070
108276
  scopes: profile.scopes,
108071
108277
  redirectPort: port,
108072
- redirectPath: path59,
108278
+ redirectPath: path60,
108073
108279
  ...profile.extraAuthParams ? { extraAuthParams: profile.extraAuthParams } : {},
108074
108280
  timeoutMs: DEFAULT_BROWSER_TIMEOUT_MS,
108075
108281
  ...ctx.signal ? { signal: ctx.signal } : {},
@@ -108081,7 +108287,7 @@ If your browser doesn't open automatically, paste this URL:
108081
108287
 
108082
108288
  ${url2}
108083
108289
 
108084
- Waiting for callback on http://localhost:${port}${path59} (5 min timeout)\u2026
108290
+ Waiting for callback on http://localhost:${port}${path60} (5 min timeout)\u2026
108085
108291
 
108086
108292
  `);
108087
108293
  }
@@ -108125,6 +108331,81 @@ Polling every ${Math.round(init3.intervalMs / 1e3)}s (${Math.round(init3.expires
108125
108331
  ...ctx.signal ? { signal: ctx.signal } : {}
108126
108332
  });
108127
108333
  }
108334
+ var DEFAULT_STALE_MS = 6e4;
108335
+ var DEFAULT_POLL_MS = 150;
108336
+ var DEFAULT_WAIT_MS = 3e4;
108337
+ var inProcessLocks = /* @__PURE__ */ new Map();
108338
+ async function withCredentialLock(key, fn, opts = {}) {
108339
+ const safe2 = sanitizeKey(key);
108340
+ let mutex = inProcessLocks.get(safe2);
108341
+ if (!mutex) {
108342
+ mutex = createMutex();
108343
+ inProcessLocks.set(safe2, mutex);
108344
+ }
108345
+ return mutex.run(async () => {
108346
+ const release = await acquireFileLock(join(opts.dir ?? moxxyPath("locks"), `${safe2}.lock`), opts.staleMs ?? DEFAULT_STALE_MS, opts.pollMs ?? DEFAULT_POLL_MS, opts.waitMs ?? DEFAULT_WAIT_MS);
108347
+ try {
108348
+ return await fn();
108349
+ } finally {
108350
+ await release();
108351
+ }
108352
+ });
108353
+ }
108354
+ function isAuthRejection(err) {
108355
+ return err instanceof MoxxyError && (err.code === "AUTH_INVALID" || err.code === "AUTH_DENIED" || err.code === "AUTH_EXPIRED" || err.code === "PROVIDER_BAD_REQUEST");
108356
+ }
108357
+ function sanitizeKey(key) {
108358
+ return key.replace(/[^a-zA-Z0-9._-]+/g, "_");
108359
+ }
108360
+ async function acquireFileLock(lockPath, staleMs, pollMs, waitMs) {
108361
+ const deadline = Date.now() + waitMs;
108362
+ try {
108363
+ await mkdir(dirname(lockPath), { recursive: true });
108364
+ } catch {
108365
+ return async () => {
108366
+ };
108367
+ }
108368
+ for (; ; ) {
108369
+ try {
108370
+ const handle2 = await open(lockPath, "wx");
108371
+ try {
108372
+ await handle2.writeFile(`${process.pid} ${(/* @__PURE__ */ new Date()).toISOString()}
108373
+ `, "utf8");
108374
+ } finally {
108375
+ await handle2.close().catch(() => {
108376
+ });
108377
+ }
108378
+ return async () => {
108379
+ await rm(lockPath, { force: true }).catch(() => {
108380
+ });
108381
+ };
108382
+ } catch (err) {
108383
+ if (err.code !== "EEXIST") {
108384
+ return async () => {
108385
+ };
108386
+ }
108387
+ }
108388
+ try {
108389
+ const st3 = await stat(lockPath);
108390
+ if (Date.now() - st3.mtimeMs > staleMs) {
108391
+ await rm(lockPath, { force: true }).catch(() => {
108392
+ });
108393
+ continue;
108394
+ }
108395
+ } catch {
108396
+ continue;
108397
+ }
108398
+ if (Date.now() >= deadline)
108399
+ return async () => {
108400
+ };
108401
+ await sleep4(pollMs);
108402
+ }
108403
+ }
108404
+ function sleep4(ms) {
108405
+ return new Promise((resolve12) => setTimeout(resolve12, ms));
108406
+ }
108407
+
108408
+ // ../plugin-oauth/dist/ensure-fresh.js
108128
108409
  async function ensureFreshTokens(profile, vault, opts = {}) {
108129
108410
  const stored = await readStoredCreds(vault, profile.id);
108130
108411
  if (!stored) {
@@ -108138,6 +108419,16 @@ async function ensureFreshTokens(profile, vault, opts = {}) {
108138
108419
  if (!opts.force && !isExpired(stored.tokenSet, opts.skewMs)) {
108139
108420
  return { tokens: stored.tokenSet, extras: stored.extras };
108140
108421
  }
108422
+ return withCredentialLock(`oauth-${profile.id}`, async () => {
108423
+ const current = await readStoredCreds(vault, profile.id) ?? stored;
108424
+ const rotatedMeanwhile = current.tokenSet.accessToken !== stored.tokenSet.accessToken;
108425
+ if (!isExpired(current.tokenSet, opts.skewMs) && (!opts.force || rotatedMeanwhile)) {
108426
+ return { tokens: current.tokenSet, extras: current.extras };
108427
+ }
108428
+ return refreshAndStore(profile, vault, current);
108429
+ });
108430
+ }
108431
+ async function refreshAndStore(profile, vault, stored, retried = false) {
108141
108432
  if (!stored.tokenSet.refreshToken) {
108142
108433
  throw new MoxxyError({
108143
108434
  code: "AUTH_EXPIRED",
@@ -108155,6 +108446,12 @@ async function ensureFreshTokens(profile, vault, opts = {}) {
108155
108446
  refreshToken: stored.tokenSet.refreshToken
108156
108447
  });
108157
108448
  } catch (err) {
108449
+ if (!retried && isAuthRejection(err)) {
108450
+ const latest = await readStoredCreds(vault, profile.id);
108451
+ if (latest?.tokenSet.refreshToken && latest.tokenSet.refreshToken !== stored.tokenSet.refreshToken) {
108452
+ return refreshAndStore(profile, vault, latest, true);
108453
+ }
108454
+ }
108158
108455
  const net3 = classifyNetworkError(err, { url: stored.tokenUrl, provider: profile.id });
108159
108456
  if (net3)
108160
108457
  throw net3;
@@ -108278,8 +108575,6 @@ function buildOauthPlugin(opts) {
108278
108575
  ]
108279
108576
  });
108280
108577
  }
108281
-
108282
- // ../plugin-provider-openai-codex/dist/oauth.js
108283
108578
  var CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
108284
108579
  var ISSUER = "https://auth.openai.com";
108285
108580
  var AUTHORIZE_URL = `${ISSUER}/oauth/authorize`;
@@ -108678,6 +108973,7 @@ var CodexProvider = class {
108678
108973
  models = codexModels;
108679
108974
  tokens;
108680
108975
  onTokensRefreshed;
108976
+ reloadTokens;
108681
108977
  defaultModel;
108682
108978
  fetchImpl;
108683
108979
  sessionIdProvider;
@@ -108686,6 +108982,8 @@ var CodexProvider = class {
108686
108982
  this.tokens = config.tokens;
108687
108983
  if (config.onTokensRefreshed)
108688
108984
  this.onTokensRefreshed = config.onTokensRefreshed;
108985
+ if (config.reloadTokens)
108986
+ this.reloadTokens = config.reloadTokens;
108689
108987
  this.defaultModel = config.defaultModel ?? DEFAULT_CODEX_MODEL;
108690
108988
  this.fetchImpl = config.fetch ?? fetch;
108691
108989
  const defaultSessionId = webcrypto.randomUUID();
@@ -108753,19 +109051,52 @@ var CodexProvider = class {
108753
109051
  return;
108754
109052
  await this.refreshNow();
108755
109053
  }
109054
+ /**
109055
+ * Refresh + persist under the per-credential lock. The refresh token is
109056
+ * SINGLE-USE (rotated + invalidated on every refresh), so concurrent
109057
+ * refreshers — a second stream in this process, the whisper-stt
109058
+ * transcriber sharing this credential, or another moxxy process — must
109059
+ * serialize and coalesce: whoever holds the lock refreshes once, everyone
109060
+ * queued behind it adopts the rotated bundle instead of burning it.
109061
+ */
108756
109062
  async refreshNow() {
108757
- if (!this.tokens) {
109063
+ const entry = this.tokens;
109064
+ if (!entry) {
108758
109065
  throw new Error("Cannot refresh \u2014 no stored tokens.");
108759
109066
  }
108760
- const next = await refreshTokens(this.tokens.refresh, this.fetchImpl);
108761
- const accountId = next.accountId ?? this.tokens.accountId;
108762
- const merged = accountId ? { access: next.access, refresh: next.refresh, expires: next.expires, accountId } : { access: next.access, refresh: next.refresh, expires: next.expires };
108763
- this.tokens = merged;
108764
- if (this.onTokensRefreshed) {
108765
- await this.onTokensRefreshed(merged);
108766
- }
109067
+ await withCredentialLock(`oauth-${this.name}`, async () => {
109068
+ if (this.tokens && this.tokens.access !== entry.access)
109069
+ return;
109070
+ let attempt = this.tokens ?? entry;
109071
+ if (this.reloadTokens) {
109072
+ const latest = await this.reloadTokens().catch(() => null);
109073
+ if (latest && latest.access !== attempt.access && latest.expires > Date.now() + 6e4) {
109074
+ this.tokens = withAccountId(latest, latest.accountId ?? attempt.accountId);
109075
+ return;
109076
+ }
109077
+ if (latest?.refresh)
109078
+ attempt = { ...attempt, refresh: latest.refresh };
109079
+ }
109080
+ let next;
109081
+ try {
109082
+ next = await refreshTokens(attempt.refresh, this.fetchImpl);
109083
+ } catch (err) {
109084
+ const latest = isAuthRejection(err) && this.reloadTokens ? await this.reloadTokens().catch(() => null) : null;
109085
+ if (!latest?.refresh || latest.refresh === attempt.refresh)
109086
+ throw err;
109087
+ next = await refreshTokens(latest.refresh, this.fetchImpl);
109088
+ }
109089
+ const merged = withAccountId(next, next.accountId ?? attempt.accountId);
109090
+ this.tokens = merged;
109091
+ if (this.onTokensRefreshed) {
109092
+ await this.onTokensRefreshed(merged);
109093
+ }
109094
+ });
108767
109095
  }
108768
109096
  };
109097
+ function withAccountId(tokens, accountId) {
109098
+ return accountId ? { access: tokens.access, refresh: tokens.refresh, expires: tokens.expires, accountId } : { access: tokens.access, refresh: tokens.refresh, expires: tokens.expires };
109099
+ }
108769
109100
 
108770
109101
  // ../plugin-provider-openai-codex/dist/profile.js
108771
109102
  var CODEX_PROVIDER_ID = "openai-codex";
@@ -109002,9 +109333,9 @@ async function ensureFreshClaudeTokens(vault) {
109002
109333
  const stored = await readStoredCreds(vault, CLAUDE_CODE_PROVIDER_ID);
109003
109334
  if (!stored)
109004
109335
  return null;
109005
- const { tokenSet, extras } = stored;
109336
+ const { tokenSet } = stored;
109006
109337
  if (tokenSet.refreshToken && isExpired(tokenSet)) {
109007
- const refreshed = await refreshAndPersist(vault, tokenSet.refreshToken, extras.account_email);
109338
+ const refreshed = await refreshClaudeUnderLock(vault, stored);
109008
109339
  return { accessToken: refreshed.accessToken, ...refreshed.expiresAt !== void 0 ? { expiresAt: refreshed.expiresAt } : {}, canRefresh: true };
109009
109340
  }
109010
109341
  return {
@@ -109023,9 +109354,39 @@ async function refreshClaudeAccessToken(vault) {
109023
109354
  context: { provider: CLAUDE_CODE_PROVIDER_ID }
109024
109355
  });
109025
109356
  }
109026
- const refreshed = await refreshAndPersist(vault, stored.tokenSet.refreshToken, stored.extras.account_email);
109357
+ const refreshed = await refreshClaudeUnderLock(vault, stored);
109027
109358
  return { token: refreshed.accessToken, ...refreshed.expiresAt !== void 0 ? { expiresAt: refreshed.expiresAt } : {} };
109028
109359
  }
109360
+ async function refreshClaudeUnderLock(vault, baseline) {
109361
+ return withCredentialLock(`oauth-${CLAUDE_CODE_PROVIDER_ID}`, async () => {
109362
+ const current = await readStoredCreds(vault, CLAUDE_CODE_PROVIDER_ID) ?? baseline;
109363
+ if (current.tokenSet.accessToken !== baseline.tokenSet.accessToken && !isExpired(current.tokenSet)) {
109364
+ return current.tokenSet;
109365
+ }
109366
+ const refreshToken = current.tokenSet.refreshToken ?? baseline.tokenSet.refreshToken;
109367
+ if (!refreshToken) {
109368
+ throw new MoxxyError({
109369
+ code: "AUTH_EXPIRED",
109370
+ message: "Claude token expired and no refresh_token is stored.",
109371
+ hint: "Run `moxxy login claude-code` again, or refresh `CLAUDE_CODE_OAUTH_TOKEN` via `claude setup-token`.",
109372
+ context: { provider: CLAUDE_CODE_PROVIDER_ID }
109373
+ });
109374
+ }
109375
+ const email = current.extras.account_email ?? baseline.extras.account_email;
109376
+ try {
109377
+ return await refreshAndPersist(vault, refreshToken, email);
109378
+ } catch (err) {
109379
+ if (isAuthRejection(err)) {
109380
+ const latest = await readStoredCreds(vault, CLAUDE_CODE_PROVIDER_ID);
109381
+ const latestRefresh = latest?.tokenSet.refreshToken;
109382
+ if (latestRefresh && latestRefresh !== refreshToken) {
109383
+ return refreshAndPersist(vault, latestRefresh, latest.extras.account_email ?? email);
109384
+ }
109385
+ }
109386
+ throw err;
109387
+ }
109388
+ });
109389
+ }
109029
109390
  var fetchImpl = fetch;
109030
109391
  var openBrowserImpl = openInBrowser;
109031
109392
  var sleepImpl = (ms) => new Promise((resolve12) => setTimeout(resolve12, ms));
@@ -109480,6 +109841,49 @@ function globToRegExp(pattern) {
109480
109841
  }
109481
109842
 
109482
109843
  // ../tools-builtin/dist/bash.js
109844
+ var OUTPUT_LIMIT = 2e5;
109845
+ var STREAM_RETAIN_CAP = OUTPUT_LIMIT + 4096;
109846
+ var SIGKILL_GRACE_MS = 2e3;
109847
+ function killTree(child, signal) {
109848
+ if (child.pid === void 0)
109849
+ return;
109850
+ if (process.platform === "win32") {
109851
+ if (child.exitCode === null && child.signalCode === null)
109852
+ child.kill(signal);
109853
+ return;
109854
+ }
109855
+ try {
109856
+ process.kill(-child.pid, signal);
109857
+ } catch (e3) {
109858
+ if (e3.code !== "ESRCH") {
109859
+ if (child.exitCode === null && child.signalCode === null)
109860
+ child.kill(signal);
109861
+ }
109862
+ }
109863
+ }
109864
+ function boundedSink(cap) {
109865
+ let text = "";
109866
+ let dropped = 0;
109867
+ return {
109868
+ push(b3) {
109869
+ const s2 = b3.toString("utf8");
109870
+ const room = cap - text.length;
109871
+ if (room >= s2.length) {
109872
+ text += s2;
109873
+ } else {
109874
+ if (room > 0)
109875
+ text += s2.slice(0, room);
109876
+ dropped += s2.length - Math.max(room, 0);
109877
+ }
109878
+ },
109879
+ get text() {
109880
+ return text;
109881
+ },
109882
+ get dropped() {
109883
+ return dropped;
109884
+ }
109885
+ };
109886
+ }
109483
109887
  var bashTool = defineTool({
109484
109888
  name: "Bash",
109485
109889
  description: "Run a shell command via /bin/sh. Respects the abort signal. Returns combined stdout/stderr with exit code.",
@@ -109516,41 +109920,60 @@ var bashTool = defineTool({
109516
109920
  const child = spawn("/bin/sh", ["-lc", command], {
109517
109921
  cwd: cwd2 ?? ctx.cwd,
109518
109922
  env: env3 ? { ...process.env, ...env3 } : process.env,
109519
- stdio: ["ignore", "pipe", "pipe"]
109520
- });
109521
- let out = "";
109522
- let err = "";
109523
- child.stdout.on("data", (b3) => {
109524
- out += b3.toString("utf8");
109525
- });
109526
- child.stderr.on("data", (b3) => {
109527
- err += b3.toString("utf8");
109923
+ stdio: ["ignore", "pipe", "pipe"],
109924
+ // Own process group on POSIX so timeout/abort can kill the whole
109925
+ // tree via a negative-pid signal. `detached` only detaches the
109926
+ // controlling terminal/group — our piped stdio and exit reporting
109927
+ // are unaffected. Not meaningful on win32 (would open a console).
109928
+ detached: process.platform !== "win32"
109528
109929
  });
109930
+ const out = boundedSink(STREAM_RETAIN_CAP);
109931
+ const err = boundedSink(STREAM_RETAIN_CAP);
109932
+ child.stdout.on("data", out.push);
109933
+ child.stderr.on("data", err.push);
109934
+ let killTimer;
109935
+ const terminate2 = () => {
109936
+ killTree(child, "SIGTERM");
109937
+ killTimer ??= setTimeout(() => {
109938
+ killTree(child, "SIGKILL");
109939
+ }, SIGKILL_GRACE_MS);
109940
+ killTimer.unref();
109941
+ };
109529
109942
  const timer = setTimeout(() => {
109530
- child.kill("SIGTERM");
109943
+ terminate2();
109531
109944
  reject(new MoxxyError({
109532
109945
  code: "ABORTED",
109533
109946
  message: `Bash timed out after ${timeoutMs}ms: ${command}`
109534
109947
  }));
109535
109948
  }, timeoutMs);
109536
109949
  const onAbort = () => {
109537
- child.kill("SIGTERM");
109950
+ terminate2();
109538
109951
  };
109539
109952
  ctx.signal.addEventListener("abort", onAbort, { once: true });
109540
109953
  child.on("error", (e3) => {
109541
109954
  clearTimeout(timer);
109955
+ if (killTimer !== void 0)
109956
+ clearTimeout(killTimer);
109542
109957
  ctx.signal.removeEventListener("abort", onAbort);
109543
109958
  reject(e3);
109544
109959
  });
109545
109960
  child.on("close", (code) => {
109546
109961
  clearTimeout(timer);
109962
+ if (killTimer !== void 0)
109963
+ clearTimeout(killTimer);
109547
109964
  ctx.signal.removeEventListener("abort", onAbort);
109548
- const combined = (out ? `[stdout]
109549
- ${out.trimEnd()}
109550
- ` : "") + (err ? `[stderr]
109551
- ${err.trimEnd()}
109965
+ const combined = (out.text ? `[stdout]
109966
+ ${out.text.trimEnd()}
109967
+ ` : "") + (err.text ? `[stderr]
109968
+ ${err.text.trimEnd()}
109552
109969
  ` : "") + `[exit ${code ?? "null"}]`;
109553
- resolve12(clampString(combined, 2e5));
109970
+ const dropped = out.dropped + err.dropped;
109971
+ if (dropped === 0) {
109972
+ resolve12(clampString(combined, OUTPUT_LIMIT));
109973
+ } else {
109974
+ resolve12(combined.slice(0, OUTPUT_LIMIT) + `
109975
+ ... [truncated ${combined.length + dropped - OUTPUT_LIMIT} chars]`);
109976
+ }
109554
109977
  });
109555
109978
  });
109556
109979
  }
@@ -110238,9 +110661,15 @@ async function* runGoalMode(ctx) {
110238
110661
  });
110239
110662
  return;
110240
110663
  }
110664
+ const sessionResolver = ctx.permissions;
110241
110665
  const autoApprove = {
110242
110666
  name: "goal-auto-approve",
110243
- check: async () => ({ mode: "allow", reason: "goal mode runs tools unattended (auto-approve)" })
110667
+ check: async (call, permCtx) => {
110668
+ const policy = await sessionResolver.policyCheck?.(call, permCtx) ?? null;
110669
+ if (policy)
110670
+ return policy;
110671
+ return { mode: "allow", reason: "goal mode runs tools unattended (auto-approve)" };
110672
+ }
110244
110673
  };
110245
110674
  const goalCtx = {
110246
110675
  ...ctx,
@@ -112497,12 +112926,20 @@ async function performSessionAction(ctx, action, notice, state, deps, cb) {
112497
112926
  if (state.turnController && !state.turnController.signal.aborted) {
112498
112927
  state.turnController.abort("user reset");
112499
112928
  }
112500
- state.session.log.clear();
112501
112929
  deps.framePump.resetRenderer();
112502
112930
  cb.setYolo(false);
112503
112931
  cb.setAwaitingApprovalText(null);
112504
112932
  deps.approvalResolver.abortAll("session reset");
112505
112933
  deps.permissionResolver.abortAll("session reset");
112934
+ try {
112935
+ if (typeof state.session.reset === "function")
112936
+ await state.session.reset();
112937
+ else
112938
+ state.session.log.clear();
112939
+ } catch (err) {
112940
+ await ctx.reply(`\u26A0 /new failed: ${err instanceof Error ? err.message : String(err)} \u2014 history NOT cleared`);
112941
+ return;
112942
+ }
112506
112943
  await ctx.reply(`\u2713 ${notice ?? "new session \u2014 conversation history cleared"}`);
112507
112944
  }
112508
112945
  }
@@ -114302,6 +114739,13 @@ var HttpChannel = class {
114302
114739
  authToken;
114303
114740
  logger;
114304
114741
  server = null;
114742
+ boundPortValue = 0;
114743
+ /** The actual TCP port the server bound to after {@link start}. Equals the
114744
+ * configured port, or the OS-assigned one when `port: 0` (ephemeral) — so
114745
+ * callers (and tests) can address the server without guessing a free port. */
114746
+ get boundPort() {
114747
+ return this.boundPortValue;
114748
+ }
114305
114749
  constructor(opts = {}) {
114306
114750
  this.port = opts.port ?? 3737;
114307
114751
  this.host = opts.host ?? "127.0.0.1";
@@ -114341,9 +114785,11 @@ var HttpChannel = class {
114341
114785
  const listening = new Promise((resolve12, reject) => {
114342
114786
  server.once("error", reject);
114343
114787
  server.listen(this.port, this.host, () => {
114788
+ const addr = server.address();
114789
+ this.boundPortValue = typeof addr === "object" && addr ? addr.port : this.port;
114344
114790
  this.logger?.info?.("http channel listening", {
114345
114791
  host: this.host,
114346
- port: this.port,
114792
+ port: this.boundPortValue,
114347
114793
  authEnabled: this.authToken !== null
114348
114794
  });
114349
114795
  resolve12();
@@ -114472,8 +114918,16 @@ var EventProjector = class {
114472
114918
  }
114473
114919
  }
114474
114920
  };
114475
-
114476
- // ../plugin-channel-web/dist/protocol.js
114921
+ var clientFrameSchema = z.discriminatedUnion("kind", [
114922
+ z.object({ kind: z.literal("prompt"), text: z.string() }),
114923
+ z.object({
114924
+ kind: z.literal("action"),
114925
+ actionId: z.string(),
114926
+ viewId: z.string().nullable(),
114927
+ action: z.object({ name: z.string(), params: z.record(z.unknown()).optional() }),
114928
+ formValues: z.record(z.string())
114929
+ })
114930
+ ]);
114477
114931
  function actionPrompt(action, formValues) {
114478
114932
  const payload = JSON.stringify({ action: action.name, ...action.params ? { params: action.params } : {}, values: formValues });
114479
114933
  return [
@@ -114485,54 +114939,74 @@ function actionPrompt(action, formValues) {
114485
114939
  }
114486
114940
 
114487
114941
  // ../plugin-channel-web/dist/channel.js
114942
+ var MAX_FRAME_BYTES = 256 * 1024;
114943
+ var DROP_WARN_INTERVAL_MS = 1e4;
114488
114944
  function isAddrInUse(err) {
114489
114945
  return !!err && typeof err === "object" && err.code === "EADDRINUSE";
114490
114946
  }
114491
- async function freeTcpPort(port) {
114492
- if (process.platform === "win32")
114493
- return;
114947
+ async function captureStdout(cmd, args) {
114494
114948
  const { spawn: spawn17 } = await import('child_process');
114495
- const pids = await new Promise((resolve12) => {
114949
+ return await new Promise((resolve12) => {
114496
114950
  let out = "";
114497
114951
  try {
114498
- const child = spawn17("lsof", ["-t", `-iTCP:${port}`, "-sTCP:LISTEN"], {
114499
- stdio: ["ignore", "pipe", "ignore"]
114500
- });
114952
+ const child = spawn17(cmd, [...args], { stdio: ["ignore", "pipe", "ignore"] });
114501
114953
  child.stdout.on("data", (b3) => {
114502
114954
  out += b3.toString();
114503
114955
  });
114504
- child.on("error", () => resolve12([]));
114505
- child.on("close", () => {
114506
- const found = /* @__PURE__ */ new Set();
114507
- for (const line of out.split("\n")) {
114508
- const n2 = parseInt(line.trim(), 10);
114509
- if (Number.isFinite(n2) && n2 > 0)
114510
- found.add(n2);
114511
- }
114512
- resolve12([...found]);
114513
- });
114956
+ child.on("error", () => resolve12(""));
114957
+ child.on("close", () => resolve12(out));
114514
114958
  } catch {
114515
- resolve12([]);
114959
+ resolve12("");
114516
114960
  }
114517
114961
  });
114518
- for (const pid of pids) {
114519
- if (pid === process.pid)
114520
- continue;
114962
+ }
114963
+ async function pidsListeningOn(port) {
114964
+ if (process.platform === "win32")
114965
+ return [];
114966
+ const out = await captureStdout("lsof", ["-t", `-iTCP:${port}`, "-sTCP:LISTEN"]);
114967
+ const found = /* @__PURE__ */ new Set();
114968
+ for (const line of out.split("\n")) {
114969
+ const n2 = parseInt(line.trim(), 10);
114970
+ if (Number.isFinite(n2) && n2 > 0)
114971
+ found.add(n2);
114972
+ }
114973
+ return [...found];
114974
+ }
114975
+ async function pidCommand(pid) {
114976
+ return (await captureStdout("ps", ["-p", String(pid), "-o", "command="])).trim();
114977
+ }
114978
+ function looksLikeMoxxy(command) {
114979
+ return command.length > 0 && /moxxy/i.test(command);
114980
+ }
114981
+ async function freeTcpPortIfMoxxy(port, logger) {
114982
+ if (process.platform === "win32")
114983
+ return false;
114984
+ const pids = (await pidsListeningOn(port)).filter((pid) => pid !== process.pid);
114985
+ if (pids.length === 0)
114986
+ return false;
114987
+ const holders = await Promise.all(pids.map(async (pid) => ({ pid, command: await pidCommand(pid) })));
114988
+ const foreign = holders.filter((h3) => !looksLikeMoxxy(h3.command));
114989
+ if (foreign.length > 0) {
114990
+ logger?.warn?.(`port ${port} is held by non-moxxy process(es); not killing them`, {
114991
+ holders: foreign.map((h3) => `${h3.pid}: ${h3.command || "<unknown command>"}`)
114992
+ });
114993
+ return false;
114994
+ }
114995
+ for (const { pid } of holders) {
114521
114996
  try {
114522
114997
  process.kill(pid, "SIGTERM");
114523
114998
  } catch {
114524
114999
  }
114525
115000
  }
114526
115001
  await new Promise((r2) => setTimeout(r2, 400));
114527
- for (const pid of pids) {
114528
- if (pid === process.pid)
114529
- continue;
115002
+ for (const { pid } of holders) {
114530
115003
  try {
114531
115004
  process.kill(pid, 0);
114532
115005
  process.kill(pid, "SIGKILL");
114533
115006
  } catch {
114534
115007
  }
114535
115008
  }
115009
+ return true;
114536
115010
  }
114537
115011
  var PUBLIC_DIR = path3.join(path3.dirname(fileURLToPath(import.meta.url)), "public");
114538
115012
  var WebChannel = class {
@@ -114557,6 +115031,8 @@ var WebChannel = class {
114557
115031
  tunnel = null;
114558
115032
  tunnelBase = null;
114559
115033
  viewSeq = 0;
115034
+ droppedFrames = 0;
115035
+ lastDropWarnAt = 0;
114560
115036
  constructor(opts = {}) {
114561
115037
  this.port = opts.port ?? 4040;
114562
115038
  this.host = opts.host ?? "127.0.0.1";
@@ -114591,14 +115067,19 @@ var WebChannel = class {
114591
115067
  void this.handleHttp(req, res);
114592
115068
  });
114593
115069
  this.server = server;
115070
+ await this.bindServerWithRetry(server);
114594
115071
  const wss = new import_websocket_server.default({
114595
115072
  server,
114596
115073
  path: "/ws",
115074
+ // Frames past this are dropped at the socket layer (ws closes with
115075
+ // 1009) instead of being buffered into memory. onMessage applies a
115076
+ // tighter MAX_FRAME_BYTES cap of its own.
115077
+ maxPayload: 1024 * 1024,
114597
115078
  verifyClient: (info) => this.validToken(info.req.url)
114598
115079
  });
114599
115080
  this.wss = wss;
114600
115081
  wss.on("connection", (ws) => this.onConnection(ws));
114601
- await this.bindServerWithRetry(server);
115082
+ wss.on("error", (err) => this.logger?.warn?.("web socket server error", { err: String(err) }));
114602
115083
  await this.openTunnel();
114603
115084
  this.publishSurface?.({ url: this.shareUrl, nextViewId: () => `v_srv_${++this.viewSeq}` });
114604
115085
  this.publishControls?.({ retunnel: () => this.retunnel() });
@@ -114606,11 +115087,13 @@ var WebChannel = class {
114606
115087
  return { running, stop: () => this.stop() };
114607
115088
  }
114608
115089
  /**
114609
- * Bind the HTTP server, with one round of recovery if the port is
114610
- * already in use. A stale `moxxy serve` from a prior install often
114611
- * leaves 4040 bound even after its unix socket has been released;
114612
- * killing whatever PID holds the port lets the fresh server boot
114613
- * cleanly instead of crashing with EADDRINUSE.
115090
+ * Bind the HTTP server, with recovery if the port is already in use.
115091
+ * A stale `moxxy serve` from a prior install often leaves 4040 bound
115092
+ * even after its unix socket has been released — if (and only if) the
115093
+ * holder is verifiably a moxxy process we kill it and retry. Anything
115094
+ * else (ngrok's local UI also defaults to 4040) is never signalled;
115095
+ * we bind an ephemeral port instead and log loudly which port the
115096
+ * surface actually got (the share URL embeds the real port either way).
114614
115097
  */
114615
115098
  async bindServerWithRetry(server) {
114616
115099
  const tryListen = () => new Promise((resolve12, reject) => {
@@ -114632,13 +115115,26 @@ var WebChannel = class {
114632
115115
  });
114633
115116
  try {
114634
115117
  await tryListen();
115118
+ return;
114635
115119
  } catch (err) {
114636
115120
  if (!isAddrInUse(err))
114637
115121
  throw err;
114638
- this.logger?.warn?.(`web channel port ${this.port} in use; freeing and retrying`);
114639
- await freeTcpPort(this.port).catch(() => void 0);
114640
- await tryListen();
114641
115122
  }
115123
+ const requested = this.port;
115124
+ const freed = await freeTcpPortIfMoxxy(requested, this.logger).catch(() => false);
115125
+ if (freed) {
115126
+ this.logger?.warn?.(`web channel port ${requested} was held by a stale moxxy process; freed it, retrying`);
115127
+ try {
115128
+ await tryListen();
115129
+ return;
115130
+ } catch (err) {
115131
+ if (!isAddrInUse(err))
115132
+ throw err;
115133
+ }
115134
+ }
115135
+ this.port = 0;
115136
+ await tryListen();
115137
+ this.logger?.warn?.(`web channel port ${requested} was in use by another process; bound ephemeral port ${this.port} instead`, { requestedPort: requested, boundPort: this.port, url: this.url });
114642
115138
  }
114643
115139
  /**
114644
115140
  * (Re-)open the tunnel via the active provider, closing any prior one FIRST so
@@ -114744,13 +115240,31 @@ var WebChannel = class {
114744
115240
  for (const frame of this.views.values())
114745
115241
  this.send(ws, frame);
114746
115242
  }
115243
+ /**
115244
+ * Handle a browser → server frame. This is a trust boundary (tunnels put
115245
+ * it on the public internet): every frame is schema-validated before any
115246
+ * field access, and invalid ones are dropped — a thrown error in a ws
115247
+ * 'message' listener escalates to a process-level uncaughtException.
115248
+ */
114747
115249
  onMessage(ws, data) {
114748
- let frame;
115250
+ const raw = String(data);
115251
+ if (raw.length > MAX_FRAME_BYTES) {
115252
+ this.dropFrame("oversized frame");
115253
+ return;
115254
+ }
115255
+ let parsed;
114749
115256
  try {
114750
- frame = JSON.parse(String(data));
115257
+ parsed = JSON.parse(raw);
114751
115258
  } catch {
115259
+ this.dropFrame("invalid JSON");
114752
115260
  return;
114753
115261
  }
115262
+ const result = clientFrameSchema.safeParse(parsed);
115263
+ if (!result.success) {
115264
+ this.dropFrame("schema mismatch");
115265
+ return;
115266
+ }
115267
+ const frame = result.data;
114754
115268
  if (frame.kind === "prompt") {
114755
115269
  if (frame.text.trim())
114756
115270
  void this.drive(frame.text);
@@ -114765,6 +115279,18 @@ var WebChannel = class {
114765
115279
  void this.drive(actionPrompt(frame.action, frame.formValues));
114766
115280
  }
114767
115281
  }
115282
+ /** Count a dropped inbound frame; warn at most once per window (no log spam). */
115283
+ dropFrame(reason) {
115284
+ this.droppedFrames += 1;
115285
+ const now = Date.now();
115286
+ if (now - this.lastDropWarnAt < DROP_WARN_INTERVAL_MS)
115287
+ return;
115288
+ this.lastDropWarnAt = now;
115289
+ this.logger?.warn?.("web channel dropped invalid client frame(s)", {
115290
+ reason,
115291
+ droppedTotal: this.droppedFrames
115292
+ });
115293
+ }
114768
115294
  async drive(prompt) {
114769
115295
  if (!this.session || this.busy)
114770
115296
  return;
@@ -115056,28 +115582,65 @@ buildWebChannelPlugin();
115056
115582
  // ../ipc-server-ws/dist/ws-transport.js
115057
115583
  init_wrapper();
115058
115584
  var BEARER = "Bearer ";
115059
- function checkWsAuth(req, expected) {
115585
+ function checkWsAuth(req, expected, opts = {}) {
115060
115586
  const guard2 = bearerGuard(expected);
115061
115587
  const auth2 = req.headers.authorization;
115062
115588
  if (auth2 && auth2.startsWith(BEARER) && guard2(auth2.slice(BEARER.length)))
115063
115589
  return true;
115064
- try {
115065
- const url2 = new URL(req.url ?? "/", "http://localhost");
115066
- if (guard2(url2.searchParams.get("t")))
115067
- return true;
115068
- } catch {
115590
+ const fromProtocol = tokenFromWsProtocolHeader(req.headers["sec-websocket-protocol"]);
115591
+ if (fromProtocol !== null && guard2(fromProtocol))
115592
+ return true;
115593
+ if (opts.allowQueryToken) {
115594
+ try {
115595
+ const url2 = new URL(req.url ?? "/", "http://localhost");
115596
+ if (guard2(url2.searchParams.get("t")))
115597
+ return true;
115598
+ } catch {
115599
+ }
115069
115600
  }
115070
115601
  return false;
115071
115602
  }
115603
+ function checkWsOrigin(req, allowedOrigins = []) {
115604
+ const origin = req.headers.origin;
115605
+ if (!origin)
115606
+ return true;
115607
+ const lower2 = origin.toLowerCase();
115608
+ return allowedOrigins.some((allowed) => allowed.toLowerCase() === lower2);
115609
+ }
115072
115610
 
115073
115611
  // ../ipc-server-ws/dist/ws-transport.js
115612
+ var DEFAULT_MAX_CONNECTIONS = 8;
115613
+ var DEFAULT_MAX_BUFFERED_BYTES = 4 * 1024 * 1024;
115614
+ var DEFAULT_BUFFER_STALL_GRACE_MS = 1e4;
115615
+ var SlowReaderGuard = class {
115616
+ limitBytes;
115617
+ graceMs;
115618
+ stalledSinceMs = null;
115619
+ constructor(limitBytes = DEFAULT_MAX_BUFFERED_BYTES, graceMs = DEFAULT_BUFFER_STALL_GRACE_MS) {
115620
+ this.limitBytes = limitBytes;
115621
+ this.graceMs = graceMs;
115622
+ }
115623
+ check(bufferedAmount, nowMs) {
115624
+ if (bufferedAmount <= this.limitBytes) {
115625
+ this.stalledSinceMs = null;
115626
+ return "ok";
115627
+ }
115628
+ if (this.stalledSinceMs === null) {
115629
+ this.stalledSinceMs = nowMs;
115630
+ return "ok";
115631
+ }
115632
+ return nowMs - this.stalledSinceMs >= this.graceMs ? "terminate" : "ok";
115633
+ }
115634
+ };
115074
115635
  var WsTransport = class {
115075
115636
  ws;
115637
+ guard;
115076
115638
  frameHandler = null;
115077
115639
  closeHandler = null;
115078
115640
  closeEmitted = false;
115079
- constructor(ws) {
115641
+ constructor(ws, guard2) {
115080
115642
  this.ws = ws;
115643
+ this.guard = guard2;
115081
115644
  ws.on("message", (data) => {
115082
115645
  let parsed;
115083
115646
  try {
@@ -115091,8 +115654,14 @@ var WsTransport = class {
115091
115654
  ws.on("error", (err) => this.emitClose(err));
115092
115655
  }
115093
115656
  send(frame) {
115094
- if (this.ws.readyState === this.ws.OPEN)
115095
- this.ws.send(JSON.stringify(frame));
115657
+ if (this.ws.readyState !== this.ws.OPEN)
115658
+ return;
115659
+ if (this.guard.check(this.ws.bufferedAmount, Date.now()) === "terminate") {
115660
+ console.warn(`[moxxy] ws bridge: evicting slow reader (${this.ws.bufferedAmount} bytes unread past grace)`);
115661
+ this.ws.terminate();
115662
+ return;
115663
+ }
115664
+ this.ws.send(JSON.stringify(frame));
115096
115665
  }
115097
115666
  onFrame(handler) {
115098
115667
  this.frameHandler = handler;
@@ -115115,15 +115684,35 @@ async function createWebSocketTransportServer(opts) {
115115
115684
  throw new Error("createWebSocketTransportServer: authToken is required \u2014 this surface is network-reachable");
115116
115685
  }
115117
115686
  const host = opts.host ?? "127.0.0.1";
115687
+ const maxConnections = opts.maxConnections ?? DEFAULT_MAX_CONNECTIONS;
115688
+ let currentToken = opts.authToken;
115689
+ let connections = 0;
115118
115690
  const wss = new import_websocket_server.default({
115119
115691
  host,
115120
115692
  port: opts.port,
115121
115693
  maxPayload: opts.maxPayloadBytes ?? 64 * 1024 * 1024,
115122
- verifyClient: (info) => checkWsAuth(info.req, opts.authToken)
115694
+ verifyClient: (info) => {
115695
+ if (!checkWsOrigin(info.req, opts.allowedOrigins)) {
115696
+ console.warn(`[moxxy] ws bridge: rejected browser-origin upgrade (Origin: ${String(info.req.headers.origin)})`);
115697
+ return false;
115698
+ }
115699
+ if (connections >= maxConnections) {
115700
+ console.warn(`[moxxy] ws bridge: rejected upgrade \u2014 connection cap (${maxConnections}) reached`);
115701
+ return false;
115702
+ }
115703
+ return checkWsAuth(info.req, currentToken, { allowQueryToken: opts.allowQueryToken });
115704
+ },
115705
+ // When the client offers subprotocols (the moxxy.bearer.* convention),
115706
+ // select the moxxy protocol WITHOUT echoing the token-bearing entry back.
115707
+ handleProtocols: (protocols) => protocols.has(MOXXY_WS_SUBPROTOCOL) ? MOXXY_WS_SUBPROTOCOL : false
115123
115708
  });
115124
115709
  const connectionHandlers = [];
115125
115710
  wss.on("connection", (ws) => {
115126
- const transport = new WsTransport(ws);
115711
+ connections += 1;
115712
+ ws.once("close", () => {
115713
+ connections -= 1;
115714
+ });
115715
+ const transport = new WsTransport(ws, new SlowReaderGuard(opts.maxBufferedBytes, opts.bufferStallGraceMs));
115127
115716
  for (const handler of connectionHandlers)
115128
115717
  handler(transport);
115129
115718
  });
@@ -115131,13 +115720,24 @@ async function createWebSocketTransportServer(opts) {
115131
115720
  wss.once("error", reject);
115132
115721
  wss.once("listening", resolve12);
115133
115722
  });
115723
+ const bound = wss.address();
115724
+ const boundPort = typeof bound === "object" && bound !== null ? bound.port : opts.port;
115134
115725
  return {
115135
- address: `ws://${host}:${opts.port}`,
115726
+ address: `ws://${host}:${boundPort}`,
115136
115727
  onConnection(handler) {
115137
115728
  connectionHandlers.push(handler);
115138
115729
  },
115730
+ rotateAuthToken(next) {
115731
+ currentToken = next;
115732
+ for (const client of wss.clients)
115733
+ client.terminate();
115734
+ },
115139
115735
  close() {
115140
- return new Promise((resolve12) => wss.close(() => resolve12()));
115736
+ return new Promise((resolve12) => {
115737
+ for (const client of wss.clients)
115738
+ client.terminate();
115739
+ wss.close(() => resolve12());
115740
+ });
115141
115741
  }
115142
115742
  };
115143
115743
  }
@@ -115268,6 +115868,12 @@ var JsonRpcPeer = class {
115268
115868
  this.closeHandlers.length = 0;
115269
115869
  }
115270
115870
  };
115871
+ var stderrLogger = {
115872
+ warn: (msg, meta) => process.stderr.write(`[moxxy-runner] WARN ${msg} ${meta ? JSON.stringify(meta) : ""}
115873
+ `),
115874
+ error: (msg, meta) => process.stderr.write(`[moxxy-runner] ERROR ${msg} ${meta ? JSON.stringify(meta) : ""}
115875
+ `)
115876
+ };
115271
115877
  var NdjsonTransport = class {
115272
115878
  socket;
115273
115879
  buffer = "";
@@ -115322,10 +115928,12 @@ var NdjsonTransport = class {
115322
115928
  this.socket.end();
115323
115929
  }
115324
115930
  };
115325
- async function createUnixSocketServer(socketPath) {
115931
+ async function createUnixSocketServer(socketPath, logger = stderrLogger) {
115326
115932
  if (process.platform !== "win32") {
115327
115933
  await reclaimStaleSocket(socketPath);
115328
- fs27__default.mkdirSync(path3__default.dirname(socketPath), { recursive: true });
115934
+ secureSocketDir(path3__default.dirname(socketPath), logger);
115935
+ } else {
115936
+ warnWindowsPipeAclOnce(logger, socketPath);
115329
115937
  }
115330
115938
  const connectionHandlers = [];
115331
115939
  const server = net.createServer((socket) => {
@@ -115343,7 +115951,11 @@ async function createUnixSocketServer(socketPath) {
115343
115951
  if (process.platform !== "win32") {
115344
115952
  try {
115345
115953
  fs27__default.chmodSync(socketPath, 384);
115346
- } catch {
115954
+ } catch (err) {
115955
+ logger.error("failed to chmod runner socket to 0600", {
115956
+ socketPath,
115957
+ error: err instanceof Error ? err.message : String(err)
115958
+ });
115347
115959
  }
115348
115960
  }
115349
115961
  return {
@@ -115380,6 +115992,31 @@ function connectUnixSocket(socketPath) {
115380
115992
  });
115381
115993
  });
115382
115994
  }
115995
+ function secureSocketDir(dir, logger) {
115996
+ fs27__default.mkdirSync(dir, { recursive: true, mode: 448 });
115997
+ try {
115998
+ const st3 = fs27__default.statSync(dir);
115999
+ if ((st3.mode & 63) === 0)
116000
+ return;
116001
+ if (typeof process.getuid === "function" && st3.uid !== process.getuid()) {
116002
+ logger.warn("runner socket directory is accessible to other users and not owned by this process; cannot tighten to 0700", { dir });
116003
+ return;
116004
+ }
116005
+ fs27__default.chmodSync(dir, 448);
116006
+ } catch (err) {
116007
+ logger.error("failed to restrict runner socket directory to 0700", {
116008
+ dir,
116009
+ error: err instanceof Error ? err.message : String(err)
116010
+ });
116011
+ }
116012
+ }
116013
+ var warnedWindowsPipeAcl = false;
116014
+ function warnWindowsPipeAclOnce(logger, pipePath) {
116015
+ if (warnedWindowsPipeAcl)
116016
+ return;
116017
+ warnedWindowsPipeAcl = true;
116018
+ logger.warn("runner named pipe relies on the Windows default DACL (creator/SYSTEM/Administrators: full control; Everyone: read-only) - no explicit ACL is applied", { pipePath });
116019
+ }
115383
116020
  async function reclaimStaleSocket(socketPath) {
115384
116021
  if (!fs27__default.existsSync(socketPath))
115385
116022
  return;
@@ -115425,7 +116062,7 @@ function isRunnerUp(socketPath = runnerSocketPath()) {
115425
116062
 
115426
116063
  // ../runner/dist/server.js
115427
116064
  init_dist();
115428
- var RUNNER_PROTOCOL_VERSION = 2;
116065
+ var RUNNER_PROTOCOL_VERSION = 3;
115429
116066
  var RunnerMethod = {
115430
116067
  /** client->server: handshake; returns the initial info snapshot. */
115431
116068
  Attach: "attach",
@@ -115435,6 +116072,14 @@ var RunnerMethod = {
115435
116072
  RunTurn: "runTurn",
115436
116073
  /** client->server: abort an in-flight turn. */
115437
116074
  Abort: "abort",
116075
+ /**
116076
+ * client->server: `/new` — abort every in-flight turn, clear the runner's
116077
+ * authoritative event log (and, via the log's clear listeners, truncate
116078
+ * the persisted session JSONL so `--resume` can't resurrect the wiped
116079
+ * history). The runner broadcasts the `session.reset` notification to ALL
116080
+ * attached clients so every mirror clears in lockstep.
116081
+ */
116082
+ SessionReset: "session.reset",
115438
116083
  /** client->server: declare which resolvers this client will answer. */
115439
116084
  SetResolver: "setResolver",
115440
116085
  /** client->server: switch the active mode. */
@@ -115472,7 +116117,14 @@ var RunnerNotification = {
115472
116117
  /** A turn finished (cleanly or with an error). */
115473
116118
  TurnComplete: "turn.complete",
115474
116119
  /** The registry snapshot changed (plugin reload, mode switch, …). */
115475
- InfoChanged: "info.changed"
116120
+ InfoChanged: "info.changed",
116121
+ /**
116122
+ * The runner's event log was reset (`/new` from any client, or a
116123
+ * self-hosting channel clearing the local log directly). Mirrors MUST
116124
+ * clear: post-reset events restart at seq 0, which a seq-contiguous
116125
+ * mirror only accepts from an empty log.
116126
+ */
116127
+ SessionReset: "session.reset"
115476
116128
  };
115477
116129
  var attachmentSchema = z.object({
115478
116130
  kind: z.string(),
@@ -115539,6 +116191,7 @@ var RunnerServer = class {
115539
116191
  turnControllers = /* @__PURE__ */ new Map();
115540
116192
  scope = new AsyncLocalStorage();
115541
116193
  logUnsub;
116194
+ logClearUnsub;
115542
116195
  modesUnsub;
115543
116196
  /**
115544
116197
  * Resolvers for unscoped (local) turns - the fall-through path. Seeded from
@@ -115557,6 +116210,7 @@ var RunnerServer = class {
115557
116210
  this.installRoutingResolvers();
115558
116211
  this.transport.onConnection((t2) => this.onConnection(t2));
115559
116212
  this.logUnsub = session.log.subscribe((event) => this.broadcastEvent(event));
116213
+ this.logClearUnsub = session.log.onClear(() => this.broadcast(RunnerNotification.SessionReset, {}));
115560
116214
  this.modesUnsub = session.modes.onActiveChange(() => this.broadcastInfo());
115561
116215
  }
115562
116216
  get address() {
@@ -115567,6 +116221,7 @@ var RunnerServer = class {
115567
116221
  return;
115568
116222
  this.closed = true;
115569
116223
  this.logUnsub();
116224
+ this.logClearUnsub();
115570
116225
  this.modesUnsub();
115571
116226
  for (const client of this.clients)
115572
116227
  client.peer.close();
@@ -115588,7 +116243,8 @@ var RunnerServer = class {
115588
116243
  peer.handle(RunnerMethod.Attach, (raw) => this.handleAttach(client, raw));
115589
116244
  peer.handle(RunnerMethod.GetInfo, () => this.session.getInfo());
115590
116245
  peer.handle(RunnerMethod.RunTurn, (raw) => this.handleRunTurn(client, raw));
115591
- peer.handle(RunnerMethod.Abort, (raw) => this.handleAbort(raw));
116246
+ peer.handle(RunnerMethod.Abort, (raw) => this.handleAbort(client, raw));
116247
+ peer.handle(RunnerMethod.SessionReset, () => this.handleSessionReset());
115592
116248
  peer.handle(RunnerMethod.SetResolver, (raw) => this.handleSetResolver(client, raw));
115593
116249
  peer.handle(RunnerMethod.ModeSetActive, (raw) => this.handleModeSetActive(raw));
115594
116250
  peer.handle(RunnerMethod.ProviderSetActive, (raw) => this.handleProviderSetActive(raw));
@@ -115607,7 +116263,7 @@ var RunnerServer = class {
115607
116263
  onDisconnect(client) {
115608
116264
  this.clients.delete(client);
115609
116265
  for (const turnId of client.turns) {
115610
- this.turnControllers.get(turnId)?.abort("owning client disconnected");
116266
+ this.turnControllers.get(turnId)?.controller.abort("owning client disconnected");
115611
116267
  }
115612
116268
  }
115613
116269
  // --- request handlers ----------------------------------------------------
@@ -115631,7 +116287,7 @@ var RunnerServer = class {
115631
116287
  const params = runTurnParamsSchema.parse(raw);
115632
116288
  const turnId = newTurnId();
115633
116289
  const controller = new AbortController();
115634
- this.turnControllers.set(turnId, controller);
116290
+ this.turnControllers.set(turnId, { controller, owner: client });
115635
116291
  client.turns.add(turnId);
115636
116292
  const opts = {
115637
116293
  turnId,
@@ -115660,9 +116316,39 @@ var RunnerServer = class {
115660
116316
  });
115661
116317
  return { turnId };
115662
116318
  }
115663
- handleAbort(raw) {
116319
+ handleAbort(client, raw) {
115664
116320
  const params = abortParamsSchema.parse(raw);
115665
- this.turnControllers.get(params.turnId)?.abort("client requested abort");
116321
+ const entry = this.turnControllers.get(params.turnId);
116322
+ if (!entry)
116323
+ return {};
116324
+ if (entry.owner !== client) {
116325
+ this.session.logger.warn("cross-client abort", {
116326
+ turnId: params.turnId,
116327
+ ownerRole: entry.owner.role,
116328
+ abortingRole: client.role
116329
+ });
116330
+ if (process.env.MOXXY_RUNNER_STRICT_ABORT === "1") {
116331
+ throw new Error(`turn ${params.turnId} was started by '${entry.owner.role}'; cross-client abort denied (MOXXY_RUNNER_STRICT_ABORT=1)`);
116332
+ }
116333
+ }
116334
+ entry.controller.abort("client requested abort");
116335
+ return {};
116336
+ }
116337
+ /**
116338
+ * `/new` from any attached client. Aborts every in-flight turn (whoever
116339
+ * started it — the wipe is session-global), then clears the authoritative
116340
+ * log. The clear cascades: the log's clear listeners broadcast the
116341
+ * `session.reset` notification to all attached mirrors (wired in the
116342
+ * ctor) and truncate the persistence sidecar's JSONL. An aborted turn may
116343
+ * still flush a final event after the wipe; it lands in the fresh log at
116344
+ * seq 0+ and is broadcast AFTER the reset notification (single ordered
116345
+ * socket), so mirrors stay contiguous either way.
116346
+ */
116347
+ handleSessionReset() {
116348
+ for (const entry of this.turnControllers.values()) {
116349
+ entry.controller.abort("session reset");
116350
+ }
116351
+ this.session.log.clear();
115666
116352
  return {};
115667
116353
  }
115668
116354
  handleSetResolver(client, raw) {
@@ -115873,7 +116559,7 @@ function defaultApproval(request) {
115873
116559
  return { optionId: request.defaultOptionId ?? request.options[0]?.id ?? "" };
115874
116560
  }
115875
116561
  async function startRunnerServer(session, opts = {}) {
115876
- const transport = opts.transport ?? await createUnixSocketServer(opts.socketPath ?? runnerSocketPath());
116562
+ const transport = opts.transport ?? await createUnixSocketServer(opts.socketPath ?? runnerSocketPath(), session.logger);
115877
116563
  return new RunnerServer(session, transport);
115878
116564
  }
115879
116565
 
@@ -115962,6 +116648,9 @@ var RemoteSession = class {
115962
116648
  this.peer.on(RunnerNotification.InfoChanged, (params) => {
115963
116649
  this.info = params.info;
115964
116650
  });
116651
+ this.peer.on(RunnerNotification.SessionReset, () => {
116652
+ this.mirror.clear();
116653
+ });
115965
116654
  this.peer.handle(RunnerMethod.PermissionCheck, (params) => {
115966
116655
  const { call, ctx } = params;
115967
116656
  if (!this.permissionResolver) {
@@ -116019,6 +116708,17 @@ var RemoteSession = class {
116019
116708
  get log() {
116020
116709
  return this.mirror;
116021
116710
  }
116711
+ /**
116712
+ * `SessionLike.reset` — server-authoritative `/new`. The runner aborts
116713
+ * in-flight turns, clears its log + persisted JSONL, and broadcasts
116714
+ * `session.reset`; our mirror clears when that notification lands (it is
116715
+ * sent before this RPC's reply on the same ordered socket, so the mirror
116716
+ * is already empty by the time this resolves). Rejects when the runner is
116717
+ * unreachable — callers must surface that instead of claiming success.
116718
+ */
116719
+ async reset() {
116720
+ await this.peer.request(RunnerMethod.SessionReset, {});
116721
+ }
116022
116722
  getInfo() {
116023
116723
  return this.requireInfo();
116024
116724
  }
@@ -116317,13 +117017,41 @@ async function killAndUnlinkRunner(socketPath, ports = DEFAULT_RUNNER_PORTS) {
116317
117017
  }
116318
117018
  async function killProcessOwning(socketPath) {
116319
117019
  const pids = await pidsListeningOnSocket(socketPath);
116320
- for (const pid of pids)
116321
- await terminate(pid);
117020
+ for (const pid of pids) {
117021
+ if (await isMoxxyProcess(pid))
117022
+ await terminate(pid);
117023
+ }
116322
117024
  }
116323
117025
  async function killProcessOnPort(port) {
116324
117026
  const pids = await pidsListeningOnPort(port);
116325
- for (const pid of pids)
116326
- await terminate(pid);
117027
+ for (const pid of pids) {
117028
+ if (await isMoxxyProcess(pid))
117029
+ await terminate(pid);
117030
+ }
117031
+ }
117032
+ async function isMoxxyProcess(pid) {
117033
+ if (!Number.isFinite(pid) || pid <= 0 || pid === process.pid)
117034
+ return false;
117035
+ const command = await pidCommand2(pid);
117036
+ return command.length > 0 && /moxxy/i.test(command);
117037
+ }
117038
+ async function pidCommand2(pid) {
117039
+ const { spawn: spawn17 } = await import('child_process');
117040
+ return await new Promise((resolve12) => {
117041
+ let out = "";
117042
+ try {
117043
+ const child = spawn17("ps", ["-p", String(pid), "-o", "command="], {
117044
+ stdio: ["ignore", "pipe", "ignore"]
117045
+ });
117046
+ child.stdout.on("data", (b3) => {
117047
+ out += b3.toString();
117048
+ });
117049
+ child.on("error", () => resolve12(""));
117050
+ child.on("close", () => resolve12(out.trim()));
117051
+ } catch {
117052
+ resolve12("");
117053
+ }
117054
+ });
116327
117055
  }
116328
117056
  async function terminate(pid) {
116329
117057
  if (!Number.isFinite(pid) || pid <= 0 || pid === process.pid)
@@ -116767,13 +117495,17 @@ var MobileSessionHost = class {
116767
117495
  resolve12(response);
116768
117496
  }
116769
117497
  };
117498
+ var TOKEN_FILE = "mobile-token";
116770
117499
  function resolveMobileToken(configured) {
116771
117500
  return resolveChannelToken({
116772
117501
  configured,
116773
117502
  envVar: "MOXXY_MOBILE_TOKEN",
116774
- fileName: "mobile-token"
117503
+ fileName: TOKEN_FILE
116775
117504
  });
116776
117505
  }
117506
+ function rotateMobileToken() {
117507
+ return rotateChannelToken({ fileName: TOKEN_FILE });
117508
+ }
116777
117509
  function normalizeTunnelChoice(raw) {
116778
117510
  const v3 = (process.env.MOXXY_MOBILE_TUNNEL ?? raw ?? "localhost").trim().toLowerCase();
116779
117511
  if (v3 === "cloudflared" || v3 === "ngrok")
@@ -116787,6 +117519,18 @@ function tunnelProviderFor(choice) {
116787
117519
  return ngrokTunnel;
116788
117520
  return null;
116789
117521
  }
117522
+ function resolveBindHost(configured) {
117523
+ const v3 = (process.env.MOXXY_MOBILE_HOST ?? configured ?? "").trim();
117524
+ return v3.length > 0 ? v3 : "127.0.0.1";
117525
+ }
117526
+ function isLoopbackHost(host) {
117527
+ const h3 = host.trim().toLowerCase();
117528
+ return h3 === "localhost" || h3 === "::1" || h3 === "[::1]" || h3.startsWith("127.");
117529
+ }
117530
+ function isWildcardHost(host) {
117531
+ const h3 = host.trim().toLowerCase();
117532
+ return h3 === "0.0.0.0" || h3 === "::" || h3 === "[::]";
117533
+ }
116790
117534
  function lanHost(fallback) {
116791
117535
  for (const list of Object.values(os5__default.networkInterfaces())) {
116792
117536
  for (const ni of list ?? []) {
@@ -116796,6 +117540,11 @@ function lanHost(fallback) {
116796
117540
  }
116797
117541
  return fallback;
116798
117542
  }
117543
+ function advertisedHost(bindHost) {
117544
+ if (isWildcardHost(bindHost))
117545
+ return lanHost("127.0.0.1");
117546
+ return bindHost;
117547
+ }
116799
117548
  function buildConnectUrl(opts) {
116800
117549
  const t2 = encodeURIComponent(opts.token);
116801
117550
  if (opts.tunnelUrl) {
@@ -116808,7 +117557,7 @@ function buildConnectUrl(opts) {
116808
117557
 
116809
117558
  // ../plugin-channel-mobile/dist/qr.js
116810
117559
  var import_qrcode = __toESM(require_lib3());
116811
- async function printConnectInfo(url2, token) {
117560
+ async function printConnectInfo(url2, token, hint) {
116812
117561
  let qr2 = "";
116813
117562
  try {
116814
117563
  qr2 = await import_qrcode.default.toString(url2, { type: "terminal", small: true });
@@ -116822,6 +117571,10 @@ async function printConnectInfo(url2, token) {
116822
117571
  qr2,
116823
117572
  ` url: ${url2}`,
116824
117573
  ` token: ${token}`,
117574
+ ...hint ? ["", ` \u2139 ${hint}`] : [],
117575
+ "",
117576
+ " (rotate the pairing token \u2014 invalidating this QR and every paired app \u2014",
117577
+ " by deleting ~/.moxxy/mobile-token and restarting the channel)",
116825
117578
  ""
116826
117579
  ];
116827
117580
  console.log(lines.join("\n"));
@@ -116842,7 +117595,7 @@ var MobileChannel = class {
116842
117595
  tunnel = null;
116843
117596
  constructor(opts = {}) {
116844
117597
  this.port = opts.port ?? DEFAULT_PORT;
116845
- this.bindHost = opts.bindHost ?? "127.0.0.1";
117598
+ this.bindHost = resolveBindHost(opts.bindHost);
116846
117599
  this.token = resolveMobileToken(opts.token);
116847
117600
  this.tunnelChoice = normalizeTunnelChoice(opts.tunnel);
116848
117601
  this.logger = opts.logger;
@@ -116851,6 +117604,19 @@ var MobileChannel = class {
116851
117604
  check: (call, ctx) => this.host ? this.host.permissionResolver.check(call, ctx) : Promise.resolve({ mode: "deny" })
116852
117605
  };
116853
117606
  }
117607
+ /**
117608
+ * Rotate the pairing token: persist a fresh secret, re-key the live server
117609
+ * (every connected app is terminated — a leaked QR/token stops working
117610
+ * immediately), and return the new token so the caller can re-display
117611
+ * pairing info. The printed QR also documents the manual path (delete
117612
+ * `~/.moxxy/mobile-token` + restart). Env/config-supplied tokens take
117613
+ * precedence at resolve time and must be rotated at their source.
117614
+ */
117615
+ rotateToken() {
117616
+ this.token = rotateMobileToken();
117617
+ this.server?.rotateAuthToken(this.token);
117618
+ return this.token;
117619
+ }
116854
117620
  async start(startOpts) {
116855
117621
  const bus = new WebSocketCommandBus();
116856
117622
  const host = new MobileSessionHost(bus, startOpts.session);
@@ -116860,7 +117626,12 @@ var MobileChannel = class {
116860
117626
  const server = await startWsBridge(bus, {
116861
117627
  port: this.port,
116862
117628
  host: this.bindHost,
116863
- authToken: this.token
117629
+ authToken: this.token,
117630
+ // Back-compat ONLY: the QR this channel prints embeds the token as `?t=`
117631
+ // (pairing payload); current apps strip it and authenticate via the
117632
+ // Sec-WebSocket-Protocol bearer entry, but older installed builds still
117633
+ // connect with the token in the WS URL.
117634
+ allowQueryToken: true
116864
117635
  });
116865
117636
  this.server = server;
116866
117637
  this.logger?.info?.("mobile channel listening", { address: server.address });
@@ -116872,7 +117643,7 @@ var MobileChannel = class {
116872
117643
  tunnelUrl = this.tunnel.url;
116873
117644
  this.logger?.info?.("mobile tunnel open", { provider: provider.name, url: tunnelUrl });
116874
117645
  } catch (err) {
116875
- this.logger?.warn?.("mobile tunnel failed; using LAN URL", {
117646
+ this.logger?.warn?.("mobile tunnel failed; using the local URL", {
116876
117647
  provider: provider.name,
116877
117648
  err: String(err)
116878
117649
  });
@@ -116880,11 +117651,12 @@ var MobileChannel = class {
116880
117651
  }
116881
117652
  const connectUrl = buildConnectUrl({
116882
117653
  tunnelUrl,
116883
- localHost: lanHost(this.bindHost),
117654
+ localHost: advertisedHost(this.bindHost),
116884
117655
  port: this.port,
116885
117656
  token: this.token
116886
117657
  });
116887
- await printConnectInfo(connectUrl, this.token);
117658
+ const loopbackOnly = !tunnelUrl && isLoopbackHost(this.bindHost);
117659
+ await printConnectInfo(connectUrl, this.token, loopbackOnly ? "Bound to loopback \u2014 this QR only works on THIS machine (e.g. an iOS/Android\n simulator). For a real phone: opt in to a LAN bind with MOXXY_MOBILE_HOST=0.0.0.0\n (or channels.mobile.bindHost in moxxy.config.ts), or use a tunnel\n (channels.mobile.tunnel: 'cloudflared' | 'ngrok', or MOXXY_MOBILE_TUNNEL)." : void 0);
116888
117660
  let resolveRunning;
116889
117661
  const running = new Promise((resolve12) => {
116890
117662
  resolveRunning = resolve12;
@@ -117023,11 +117795,12 @@ function extractFirstTagBlock(html, selector) {
117023
117795
  function escapeReSelector(s2) {
117024
117796
  return s2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
117025
117797
  }
117026
-
117027
- // ../plugin-browser/dist/web-fetch.js
117028
- var MAX_BYTES_DEFAULT = 2 * 1024 * 1024;
117029
- var MAX_REDIRECTS_DEFAULT = 5;
117030
- var FETCH_TIMEOUT_MS_DEFAULT = 2e4;
117798
+ var SsrfBlockedError = class extends Error {
117799
+ constructor(message) {
117800
+ super(message);
117801
+ this.name = "SsrfBlockedError";
117802
+ }
117803
+ };
117031
117804
  var defaultResolver = async (host) => (await lookup(host, { all: true })).map((a2) => a2.address);
117032
117805
  var resolver = defaultResolver;
117033
117806
  function isBlockedIpv4(ip) {
@@ -117074,32 +117847,23 @@ function isBlockedIp(ip) {
117074
117847
  return isBlockedIpv6(ip);
117075
117848
  return true;
117076
117849
  }
117077
- async function assertPublicUrl(raw) {
117850
+ async function assertPublicUrl(raw, label3 = "request") {
117078
117851
  let u2;
117079
117852
  try {
117080
117853
  u2 = new URL(raw);
117081
117854
  } catch {
117082
- throw new MoxxyError({ code: "INTERNAL", message: `web_fetch: invalid URL: ${raw}` });
117855
+ throw new SsrfBlockedError(`${label3}: invalid URL: ${raw}`);
117083
117856
  }
117084
117857
  if (u2.protocol !== "http:" && u2.protocol !== "https:") {
117085
- throw new MoxxyError({
117086
- code: "INTERNAL",
117087
- message: `web_fetch: refusing non-HTTP(S) scheme "${u2.protocol}"`
117088
- });
117858
+ throw new SsrfBlockedError(`${label3}: refusing non-HTTP(S) scheme "${u2.protocol}"`);
117089
117859
  }
117090
117860
  const host = u2.hostname.replace(/^\[|\]$/g, "");
117091
117861
  if (host === "localhost" || host.endsWith(".localhost")) {
117092
- throw new MoxxyError({
117093
- code: "INTERNAL",
117094
- message: `web_fetch: refusing to fetch loopback host "${host}"`
117095
- });
117862
+ throw new SsrfBlockedError(`${label3}: refusing to fetch loopback host "${host}"`);
117096
117863
  }
117097
117864
  if (isIP(host)) {
117098
117865
  if (isBlockedIp(host))
117099
- throw new MoxxyError({
117100
- code: "INTERNAL",
117101
- message: `web_fetch: refusing private/loopback address "${host}"`
117102
- });
117866
+ throw new SsrfBlockedError(`${label3}: refusing private/loopback address "${host}"`);
117103
117867
  return;
117104
117868
  }
117105
117869
  let addrs;
@@ -117110,11 +117874,23 @@ async function assertPublicUrl(raw) {
117110
117874
  }
117111
117875
  for (const addr of addrs) {
117112
117876
  if (isBlockedIp(addr)) {
117113
- throw new MoxxyError({
117114
- code: "INTERNAL",
117115
- message: `web_fetch: host "${host}" resolves to a private/loopback address (${addr})`
117116
- });
117877
+ throw new SsrfBlockedError(`${label3}: host "${host}" resolves to a private/loopback address (${addr})`);
117878
+ }
117879
+ }
117880
+ }
117881
+
117882
+ // ../plugin-browser/dist/web-fetch.js
117883
+ var MAX_BYTES_DEFAULT = 2 * 1024 * 1024;
117884
+ var MAX_REDIRECTS_DEFAULT = 5;
117885
+ var FETCH_TIMEOUT_MS_DEFAULT = 2e4;
117886
+ async function assertPublicUrlForWebFetch(raw) {
117887
+ try {
117888
+ await assertPublicUrl(raw, "web_fetch");
117889
+ } catch (err) {
117890
+ if (err instanceof SsrfBlockedError) {
117891
+ throw new MoxxyError({ code: "INTERNAL", message: err.message });
117117
117892
  }
117893
+ throw err;
117118
117894
  }
117119
117895
  }
117120
117896
  var webFetchTool = defineTool({
@@ -117182,7 +117958,7 @@ var webFetchTool = defineTool({
117182
117958
  async function fetchFollowRedirects(initialUrl, opts) {
117183
117959
  let current = initialUrl;
117184
117960
  for (let hop = 0; hop <= opts.maxRedirects; hop++) {
117185
- await assertPublicUrl(current);
117961
+ await assertPublicUrlForWebFetch(current);
117186
117962
  const res = await fetch(current, {
117187
117963
  method: opts.method,
117188
117964
  headers: opts.headers,
@@ -117261,8 +118037,11 @@ var actionSchema = z$1.union([
117261
118037
  z$1.object({
117262
118038
  kind: z$1.literal("goto"),
117263
118039
  // `z.string().url()` accepts file:// and javascript: URLs, which would be
117264
- // forwarded verbatim to Playwright `page.goto`. Restrict to http(s) the
117265
- // same scheme guard web_fetch enforces via assertPublicUrl.
118040
+ // forwarded verbatim to Playwright `page.goto`. This refine is only a fast
118041
+ // schema-level scheme check; the full SSRF guard (loopback/private/
118042
+ // link-local/metadata IPs, DNS resolution — same `assertPublicUrl` as
118043
+ // web_fetch) runs in the handler before the RPC AND again inside the
118044
+ // sidecar's goto dispatch.
117266
118045
  url: z$1.string().url().refine((u2) => /^https?:\/\//i.test(u2), "only http(s) URLs allowed"),
117267
118046
  waitUntil: z$1.enum(["load", "domcontentloaded", "networkidle"]).optional(),
117268
118047
  timeoutMs: z$1.number().int().positive().max(12e4).optional()
@@ -117457,7 +118236,7 @@ function defaultSpawn(scriptPath) {
117457
118236
  function buildBrowserSessionTool(deps) {
117458
118237
  return defineTool({
117459
118238
  name: "browser_session",
117460
- description: "Drive a real browser (Playwright). Use for pages that need JS execution, clicks, form fills, or screenshots. For simple GETs prefer web_fetch (no extra deps). Calls within a session share one page.",
118239
+ description: "Drive a real browser (Playwright). Use for pages that need JS execution, clicks, form fills, or screenshots. For simple GETs prefer web_fetch (no extra deps). Calls within a session share one page. Navigation is restricted to public http(s) origins: goto URLs and top-level/iframe navigations (including redirects) to loopback, private (RFC-1918), link-local/metadata, or CGNAT addresses are blocked. Residual risk: subresource requests (img/fetch/script) issued by a loaded page are NOT filtered, so a hostile page can still send blind requests at internal services.",
117461
118240
  inputSchema: z$1.object({
117462
118241
  action: actionSchema
117463
118242
  }),
@@ -117484,6 +118263,14 @@ function buildBrowserSessionTool(deps) {
117484
118263
  try {
117485
118264
  switch (action.kind) {
117486
118265
  case "goto":
118266
+ try {
118267
+ await assertPublicUrl(action.url, "browser_session");
118268
+ } catch (err) {
118269
+ if (err instanceof SsrfBlockedError) {
118270
+ throw new MoxxyError({ code: "INTERNAL", message: err.message });
118271
+ }
118272
+ throw err;
118273
+ }
117487
118274
  return await call("goto", {
117488
118275
  url: action.url,
117489
118276
  waitUntil: action.waitUntil,
@@ -118095,8 +118882,8 @@ var removeProviderInput = z$1.object({
118095
118882
  name: providerNameSchema
118096
118883
  });
118097
118884
  var testProviderInput = z$1.object({
118098
- baseURL: z$1.string().url(),
118099
- apiKey: z$1.string().min(1)
118885
+ baseURL: z$1.string().url().describe("Vendor API base URL to probe, e.g. https://api.deepseek.com."),
118886
+ keyName: z$1.string().regex(/^[A-Z][A-Z0-9_]*$/).describe("NAME of the vault secret holding the API key (e.g. DEEPSEEK_API_KEY). The key is resolved from the vault inside the tool \u2014 never ask the user for the plaintext key and never pass one as a tool argument. Have them store it first: /vault set <NAME> <key>.")
118100
118887
  });
118101
118888
  function buildProviderAdminPlugin(opts) {
118102
118889
  const { providerRegistry, configPath } = opts;
@@ -118137,6 +118924,7 @@ function buildProviderAdminPlugin(opts) {
118137
118924
  providerRegistry.unregister(entry.name);
118138
118925
  throw err;
118139
118926
  }
118927
+ const vaultKeyName2 = entry.envVar ?? `${entry.name.toUpperCase()}_API_KEY`;
118140
118928
  return {
118141
118929
  ok: true,
118142
118930
  name: entry.name,
@@ -118146,7 +118934,7 @@ function buildProviderAdminPlugin(opts) {
118146
118934
  models: entry.models.map((m3) => m3.id),
118147
118935
  path: configPath ?? providersConfigPath(),
118148
118936
  replaced: wasRegistered,
118149
- note: `Provider "${entry.name}" is live in this session. Have the USER store the API key by running: /vault set ${entry.envVar ?? `${entry.name.toUpperCase()}_API_KEY`} <key> \u2014 never ask them to paste the key to you. Switch with the /provider command or set provider.name in moxxy.config.ts.`
118937
+ note: `Provider "${entry.name}" is live in this session. Have the USER store the API key by running: /vault set ${vaultKeyName2} <key> \u2014 never ask them to paste the key to you. Once stored, you can verify it with provider_test (baseURL + keyName "${vaultKeyName2}") \u2014 it resolves the key from the vault itself. Switch with the /provider command or set provider.name in moxxy.config.ts.`
118150
118938
  };
118151
118939
  }
118152
118940
  }),
@@ -118188,10 +118976,28 @@ function buildProviderAdminPlugin(opts) {
118188
118976
  }),
118189
118977
  defineTool({
118190
118978
  name: "provider_test",
118191
- description: "Probe an OpenAI-compatible endpoint with the supplied API key by calling /v1/models. Use BEFORE provider_add to confirm the baseURL + key are valid. Returns { ok: true } on success or { ok: false, message } with the vendor error verbatim.",
118979
+ description: "Probe an OpenAI-compatible endpoint by calling /v1/models. Takes the NAME of a vault secret (e.g. ZAI_API_KEY) and resolves the API key via the vault inside the handler \u2014 the plaintext key never enters the conversation or logs. Have the USER store the key first with /vault set <NAME> <key>, then call this to confirm the baseURL + key are valid (typically before provider_add). Returns { ok: true } on success or { ok: false, message } with the vendor error verbatim.",
118192
118980
  inputSchema: testProviderInput,
118193
118981
  permission: { action: "prompt" },
118194
- handler: async ({ baseURL, apiKey }) => validateKey2(apiKey, { baseURL })
118982
+ // The plaintext key is resolved HERE, at call time, via ctx.getSecret
118983
+ // it never appears as tool input/output, so it stays out of the model
118984
+ // context, the runner session log, and the desktop NDJSON log.
118985
+ handler: async ({ baseURL, keyName: keyName2 }, ctx) => {
118986
+ if (!ctx.getSecret) {
118987
+ return {
118988
+ ok: false,
118989
+ message: "provider_test: this session has no secret vault wired in (ctx.getSecret is unavailable), so the key cannot be resolved. Register the provider with provider_add and have the user verify the key with `moxxy doctor` instead."
118990
+ };
118991
+ }
118992
+ const apiKey = await ctx.getSecret(keyName2);
118993
+ if (!apiKey) {
118994
+ return {
118995
+ ok: false,
118996
+ message: `provider_test: no vault secret named "${keyName2}". Ask the USER to store it by running: /vault set ${keyName2} <key> \u2014 then call provider_test again with keyName "${keyName2}". Never ask them to paste the key into the conversation.`
118997
+ };
118998
+ }
118999
+ return validateKey2(apiKey, { baseURL });
119000
+ }
118195
119001
  })
118196
119002
  ],
118197
119003
  hooks: {
@@ -119828,7 +120634,7 @@ function readHeader(headers, name) {
119828
120634
  return v3[0] ?? null;
119829
120635
  return v3 ?? null;
119830
120636
  }
119831
- function readJsonPath(body, path59) {
120637
+ function readJsonPath(body, path60) {
119832
120638
  let parsed;
119833
120639
  try {
119834
120640
  parsed = JSON.parse(body.toString("utf8"));
@@ -119836,7 +120642,7 @@ function readJsonPath(body, path59) {
119836
120642
  return null;
119837
120643
  }
119838
120644
  let cur = parsed;
119839
- for (const seg of path59.split(".")) {
120645
+ for (const seg of path60.split(".")) {
119840
120646
  if (cur === null || cur === void 0 || typeof cur !== "object")
119841
120647
  return null;
119842
120648
  cur = cur[seg];
@@ -120204,22 +121010,36 @@ var webhookTriggerSchema = z.object({
120204
121010
  lastResult: z.enum(["ok", "error"]).optional(),
120205
121011
  lastError: z.string().optional()
120206
121012
  });
120207
- var fileSchema3 = z.object({
121013
+ var looseFileSchema = z.object({
120208
121014
  version: z.literal(1),
120209
- triggers: z.array(webhookTriggerSchema)
121015
+ triggers: z.array(z.unknown())
120210
121016
  });
120211
121017
  function defaultWebhooksFile() {
120212
121018
  return moxxyPath("webhooks.json");
120213
121019
  }
120214
121020
  var WebhookStore = class {
120215
121021
  file;
121022
+ logger;
120216
121023
  cache = null;
121024
+ loadWarningMsg = null;
120217
121025
  mutex = createMutex();
120218
121026
  constructor(opts = {}) {
120219
121027
  this.file = opts.file ?? defaultWebhooksFile();
121028
+ this.logger = opts.logger;
120220
121029
  }
120221
121030
  invalidate() {
120222
121031
  this.cache = null;
121032
+ this.loadWarningMsg = null;
121033
+ }
121034
+ /**
121035
+ * Human-readable description of any corruption encountered while loading
121036
+ * the store file (corrupt file preserved aside, or invalid entries
121037
+ * quarantined), or `null` when the load was clean. Tools surface this so
121038
+ * the user learns about it instead of silently losing triggers.
121039
+ */
121040
+ async loadWarning() {
121041
+ await this.ensureLoaded();
121042
+ return this.loadWarningMsg;
120223
121043
  }
120224
121044
  async list() {
120225
121045
  await this.ensureLoaded();
@@ -120297,17 +121117,85 @@ var WebhookStore = class {
120297
121117
  async ensureLoaded() {
120298
121118
  if (this.cache)
120299
121119
  return;
121120
+ this.loadWarningMsg = null;
121121
+ let raw;
120300
121122
  try {
120301
- const raw = await readFile(this.file, "utf8");
120302
- const parsed = fileSchema3.safeParse(JSON.parse(raw));
120303
- this.cache = parsed.success ? [...parsed.data.triggers] : [];
121123
+ raw = await readFile(this.file, "utf8");
120304
121124
  } catch (err) {
120305
121125
  if (err.code === "ENOENT") {
120306
121126
  this.cache = [];
120307
- } else {
120308
- this.cache = [];
121127
+ return;
120309
121128
  }
121129
+ throw new Error(`webhooks store: cannot read ${this.file}: ${err instanceof Error ? err.message : String(err)} \u2014 refusing to load (and write) until the file is readable again`);
121130
+ }
121131
+ let json;
121132
+ try {
121133
+ json = JSON.parse(raw);
121134
+ } catch {
121135
+ await this.preserveCorruptFile("is not valid JSON");
121136
+ return;
121137
+ }
121138
+ const file = looseFileSchema.safeParse(json);
121139
+ if (!file.success) {
121140
+ await this.preserveCorruptFile("does not match the expected { version: 1, triggers: [...] } shape");
121141
+ return;
120310
121142
  }
121143
+ const valid = [];
121144
+ const invalid = [];
121145
+ file.data.triggers.forEach((entry, index) => {
121146
+ const parsed = webhookTriggerSchema.safeParse(entry);
121147
+ if (parsed.success) {
121148
+ valid.push(parsed.data);
121149
+ } else {
121150
+ invalid.push({
121151
+ index,
121152
+ entry,
121153
+ issues: parsed.error.issues.map((i2) => `${i2.path.join(".") || "(root)"}: ${i2.message}`).join("; ")
121154
+ });
121155
+ }
121156
+ });
121157
+ if (invalid.length > 0)
121158
+ await this.quarantineEntries(invalid);
121159
+ this.cache = valid;
121160
+ }
121161
+ /**
121162
+ * The file exists but is unparseable/mis-shaped. Rename it aside (rename,
121163
+ * not copy — nothing may remain at the live path that a later persist()
121164
+ * could clobber while it is the only copy), then start empty. If the
121165
+ * rename itself fails the error propagates and the store refuses to
121166
+ * operate, which is the safe direction.
121167
+ */
121168
+ async preserveCorruptFile(reason) {
121169
+ const preserved = `${this.file}.corrupt-${timestampSlug()}`;
121170
+ await rename$1(this.file, preserved);
121171
+ this.loadWarningMsg = `the webhook trigger store (${this.file}) ${reason}; the original file was preserved at ${preserved} and the store restarted empty. Previously configured triggers (and their secrets) are recoverable from that file \u2014 repair it by hand or recreate the triggers.`;
121172
+ this.logger?.error?.("webhooks: store file corrupt \u2014 preserved aside, starting empty", {
121173
+ file: this.file,
121174
+ preserved,
121175
+ reason
121176
+ });
121177
+ this.cache = [];
121178
+ }
121179
+ /**
121180
+ * The file parses but some entries fail validation. Keep the valid ones,
121181
+ * write the rest (verbatim, with their zod issues) to a 0600 sidecar so
121182
+ * they stay recoverable after the next persist() drops them.
121183
+ */
121184
+ async quarantineEntries(invalid) {
121185
+ const quarantine = `${this.file}.quarantine-${timestampSlug()}`;
121186
+ const payload = JSON.stringify({
121187
+ quarantinedAt: (/* @__PURE__ */ new Date()).toISOString(),
121188
+ source: this.file,
121189
+ entries: invalid
121190
+ }, null, 2);
121191
+ await writeFileAtomic(quarantine, payload, { mode: 384 });
121192
+ this.loadWarningMsg = `${invalid.length} trigger entr${invalid.length === 1 ? "y" : "ies"} in ${this.file} failed schema validation and ${invalid.length === 1 ? "was" : "were"} quarantined to ${quarantine} (valid triggers were kept). Inspect that file to repair or recreate them.`;
121193
+ this.logger?.error?.("webhooks: invalid trigger entries quarantined", {
121194
+ file: this.file,
121195
+ quarantine,
121196
+ count: invalid.length,
121197
+ issues: invalid.map((i2) => ({ index: i2.index, issues: i2.issues }))
121198
+ });
120311
121199
  }
120312
121200
  async mutate(fn) {
120313
121201
  await this.mutex.run(async () => {
@@ -120322,6 +121210,9 @@ var WebhookStore = class {
120322
121210
  await writeFileAtomic(this.file, payload);
120323
121211
  }
120324
121212
  };
121213
+ function timestampSlug() {
121214
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
121215
+ }
120325
121216
 
120326
121217
  // ../plugin-webhooks/dist/describe.js
120327
121218
  function describeTrigger(trigger, publicUrl) {
@@ -120450,9 +121341,25 @@ async function startTunnel(opts) {
120450
121341
  }
120451
121342
  };
120452
121343
  }
121344
+ function defaultWebhookSecretsDir() {
121345
+ return moxxyPath("webhooks-secrets");
121346
+ }
120453
121347
  function generateSecret() {
120454
121348
  return randomBytes(32).toString("hex");
120455
121349
  }
121350
+ function maskSecret(secret) {
121351
+ return `${secret.slice(0, 4)}\u2026`;
121352
+ }
121353
+ function secretFilePath(dir, triggerName) {
121354
+ return path3__default.join(dir, `${triggerName}.secret`);
121355
+ }
121356
+ async function writeSecretFile(dir, triggerName, secret) {
121357
+ await mkdir(dir, { recursive: true, mode: 448 });
121358
+ const file = secretFilePath(dir, triggerName);
121359
+ await writeFileAtomic(file, `${secret}
121360
+ `, { mode: 384 });
121361
+ return file;
121362
+ }
120456
121363
  var verificationInputSchema = z$1.discriminatedUnion("type", [
120457
121364
  z$1.object({ type: z$1.literal("none") }),
120458
121365
  z$1.object({
@@ -120505,10 +121412,11 @@ function fullUrl(publicUrl, triggerId) {
120505
121412
  }
120506
121413
  function buildWebhookTools(deps) {
120507
121414
  const { store, config, dispatcher, tunnelHandle } = deps;
121415
+ const secretsDir = deps.secretsDir ?? defaultWebhookSecretsDir();
120508
121416
  return [
120509
121417
  defineTool({
120510
121418
  name: "webhook_create",
120511
- description: 'Create a webhook trigger. When an external system POSTs to the returned URL and verification (and any filters) pass, the configured prompt fires in an isolated session with the listed tools available.\n\nVerification picks the auth model:\n \u2022 `none` \u2014 no auth. Anyone reaching the URL fires the trigger. Local-only.\n \u2022 `bearer` \u2014 secret in `Authorization: Bearer <secret>`. Simplest shared-secret.\n \u2022 `hmac` \u2014 HMAC over the body, compared to a signature header. The user should paste the header name + signature prefix + algorithm from the external system\'s webhook documentation. Use `scheme:"stripe"` for systems that sign `<timestamp>.<body>` and pack it into a comma-separated header.\n\nFor `bearer`/`hmac`, if `secret` is omitted a strong 32-byte random secret is generated and returned ONCE in `secretIssued` \u2014 record it now.\n\n`filters` decides whether a verified delivery actually fires the prompt:\n \u2022 `include` \u2014 fire only if at least one rule matches (or empty = fire on everything)\n \u2022 `exclude` \u2014 never fire if any rule matches\nEach rule reads ONE field (a header or a dot-separated JSON path in the body) and compares it against `equals` (any-of) or `matches` (regex).\n\nPrompt placeholders: `{body}`, `{body_json}`, `{header.<name>}`, `{method}`, `{path}`, `{trigger_name}`, `{fired_at}`. The prompt is the runbook the model reads on every delivery \u2014 write it that way.',
121419
+ description: "Create a webhook trigger. When an external system POSTs to the returned URL and verification (and any filters) pass, the configured prompt fires as a turn on the ACTIVE session (not an isolated one \u2014 output lands in the shared event log). `allowedTools` is enforced per fire: a non-empty list restricts the fire to exactly those tools (any other tool call is denied); an empty list leaves the session's full tool set available under its normal permission rules.\n\nVerification picks the auth model:\n \u2022 `none` \u2014 no auth. Anyone reaching the URL fires the trigger. Local-only.\n \u2022 `bearer` \u2014 secret in `Authorization: Bearer <secret>`. Simplest shared-secret.\n \u2022 `hmac` \u2014 HMAC over the body, compared to a signature header. The user should paste the header name + signature prefix + algorithm from the external system's webhook documentation. Use `scheme:\"stripe\"` for systems that sign `<timestamp>.<body>` and pack it into a comma-separated header.\n\nFor `bearer`/`hmac`, if `secret` is omitted a strong 32-byte random secret is generated. For security the secret is NEVER returned here (tool results persist in session logs): the result carries `generatedSecret` with a masked preview plus the path of an owner-only file holding the full value \u2014 relay that path so the USER opens it themselves and pastes the secret into the external system. Do not read the file into the conversation.\n\n`filters` decides whether a verified delivery actually fires the prompt:\n \u2022 `include` \u2014 fire only if at least one rule matches (or empty = fire on everything)\n \u2022 `exclude` \u2014 never fire if any rule matches\nEach rule reads ONE field (a header or a dot-separated JSON path in the body) and compares it against `equals` (any-of) or `matches` (regex).\n\nPrompt placeholders: `{body}`, `{body_json}`, `{header.<name>}`, `{method}`, `{path}`, `{trigger_name}`, `{fired_at}`. The prompt is the runbook the model reads on every delivery \u2014 write it that way.",
120512
121420
  inputSchema: z$1.object({
120513
121421
  name: z$1.string().min(1).max(120).regex(/^[a-z0-9][a-z0-9-]*$/i, "name must be slug-like"),
120514
121422
  prompt: z$1.string().min(1),
@@ -120541,10 +121449,19 @@ function buildWebhookTools(deps) {
120541
121449
  } else {
120542
121450
  guidance.push(`Paste this URL into the external system's webhook config: ${url2}`);
120543
121451
  }
121452
+ let generatedSecret = null;
120544
121453
  if (secretIssued) {
120545
- guidance.push(`A secret was generated for this trigger \u2014 paste it into the external system's webhook secret field. It will NOT be shown again: record it now. Secret: ${secretIssued}`);
121454
+ const file = await writeSecretFile(secretsDir, trigger.name, secretIssued);
121455
+ generatedSecret = { masked: maskSecret(secretIssued), path: file };
121456
+ guidance.push(`A strong secret was generated for this trigger (preview: ${generatedSecret.masked}). For security it is NOT included in this response \u2014 the full value was written to ${file} (owner-only file). Tell the user to open that file themselves (e.g. \`cat ${file}\`) and paste the value into the external system's webhook secret field, then delete the file once configured. Do NOT read the file into the conversation.`);
120546
121457
  }
120547
- return { trigger: describeTrigger(trigger, cfg.publicUrl), secretIssued, guidance };
121458
+ const storeWarning = await store.loadWarning();
121459
+ return {
121460
+ trigger: describeTrigger(trigger, cfg.publicUrl),
121461
+ generatedSecret,
121462
+ guidance,
121463
+ ...storeWarning ? { storeWarning } : {}
121464
+ };
120548
121465
  }
120549
121466
  }),
120550
121467
  defineTool({
@@ -120557,10 +121474,12 @@ function buildWebhookTools(deps) {
120557
121474
  const triggers = await store.list();
120558
121475
  const cfg = await config.get();
120559
121476
  const filtered = includeDisabled ? triggers : triggers.filter((t2) => t2.enabled);
121477
+ const storeWarning = await store.loadWarning();
120560
121478
  return {
120561
121479
  publicUrl: cfg.publicUrl ?? null,
120562
121480
  listener: { host: cfg.host, port: cfg.port },
120563
- triggers: filtered.map((t2) => describeTrigger(t2, cfg.publicUrl))
121481
+ triggers: filtered.map((t2) => describeTrigger(t2, cfg.publicUrl)),
121482
+ ...storeWarning ? { storeWarning } : {}
120564
121483
  };
120565
121484
  }
120566
121485
  }),
@@ -120569,7 +121488,15 @@ function buildWebhookTools(deps) {
120569
121488
  description: "Permanently remove a webhook trigger by id. Does NOT touch any subscription registered on the external side \u2014 the user must also delete the webhook from the source's dashboard, otherwise it'll keep retrying.",
120570
121489
  inputSchema: z$1.object({ id: z$1.string().min(1) }),
120571
121490
  permission: { action: "prompt" },
120572
- handler: async ({ id }) => ({ deleted: await store.delete(id) })
121491
+ handler: async ({ id }) => {
121492
+ const trigger = await store.get(id);
121493
+ const deleted = await store.delete(id);
121494
+ if (deleted && trigger) {
121495
+ await rm(secretFilePath(secretsDir, trigger.name), { force: true }).catch(() => {
121496
+ });
121497
+ }
121498
+ return { deleted };
121499
+ }
120573
121500
  }),
120574
121501
  defineTool({
120575
121502
  name: "webhook_update",
@@ -120646,7 +121573,8 @@ function buildWebhookTools(deps) {
120646
121573
  } : { running: false },
120647
121574
  cliAvailable: { cloudflared: cloudflaredOk, ngrok: ngrokOk },
120648
121575
  triggerCount: triggers.length,
120649
- enabledCount: triggers.filter((t2) => t2.enabled).length
121576
+ enabledCount: triggers.filter((t2) => t2.enabled).length,
121577
+ ...await store.loadWarning().then((w4) => w4 ? { storeWarning: w4 } : {})
120650
121578
  };
120651
121579
  }
120652
121580
  }),
@@ -120829,8 +121757,8 @@ async function buildSetupGuide(deps) {
120829
121757
  askUser: "If verification is `bearer` or `hmac`, do you already have the secret from the external system, or should I generate one for you?\n \u2022 If they already created the webhook on the source's side and the docs gave them a signing secret, paste it here.\n \u2022 If they're about to create the webhook, I can generate a strong random one and they will paste it into the source.",
120830
121758
  recordAs: "verification.secret",
120831
121759
  hints: [
120832
- "Omit `secret` in `webhook_create` to auto-generate. The response includes `secretIssued` exactly once \u2014 surface it to the user immediately and tell them to record it; we will not show it again.",
120833
- "Never log the secret. Never echo it back in subsequent messages."
121760
+ "Omit `secret` in `webhook_create` to auto-generate. The full value is written to an owner-only file (the response's `generatedSecret.path`) instead of being returned \u2014 relay the path and have the USER open the file and paste the value into the external system themselves.",
121761
+ "Never log the secret. Never read the secret file into the conversation or echo the value in messages."
120834
121762
  ]
120835
121763
  });
120836
121764
  steps.push({
@@ -120859,7 +121787,7 @@ async function buildSetupGuide(deps) {
120859
121787
  steps.push({
120860
121788
  step: 7,
120861
121789
  title: "Prompt + tools",
120862
- askUser: "What should the agent DO when a delivery fires? Describe the runbook. Then: which tools should it have access to (read-only fetch? bash? a specific MCP tool?)? The agent is sandboxed by `allowedTools` \u2014 list only what you trust.",
121790
+ askUser: "What should the agent DO when a delivery fires? Describe the runbook. Then: which tools should it have access to (read-only fetch? bash? a specific MCP tool?)? A non-empty `allowedTools` is enforced \u2014 the fire can only execute the listed tools; everything else is denied. An empty list gives the fire the FULL tool set of the active session (fires are not isolated), so list only what you trust.",
120863
121791
  recordAs: "prompt + allowedTools",
120864
121792
  hints: [
120865
121793
  "Use placeholders like `{body_json}` so the prompt sees the actual delivery payload.",
@@ -120871,8 +121799,8 @@ async function buildSetupGuide(deps) {
120871
121799
  title: "Create the trigger",
120872
121800
  nextToolCall: "webhook_create",
120873
121801
  hints: [
120874
- "Call `webhook_create` with the collected fields. Capture `secretIssued` and `trigger.url` from the response.",
120875
- "Relay both to the user: the URL goes into the external system's webhook config; the secret goes into its signing-secret field."
121802
+ "Call `webhook_create` with the collected fields. Capture `generatedSecret` (masked preview + file path) and `trigger.url` from the response.",
121803
+ "Relay both to the user: the URL goes into the external system's webhook config; the secret lives in the file at `generatedSecret.path` \u2014 the user opens it themselves and pastes the value into the external system's signing-secret field."
120876
121804
  ]
120877
121805
  });
120878
121806
  steps.push({
@@ -120892,7 +121820,7 @@ async function buildSetupGuide(deps) {
120892
121820
 
120893
121821
  // ../plugin-webhooks/dist/index.js
120894
121822
  function buildWebhooksPlugin(opts) {
120895
- const store = opts.store ?? new WebhookStore();
121823
+ const store = opts.store ?? new WebhookStore(opts.logger ? { logger: opts.logger } : {});
120896
121824
  const config = opts.config ?? new WebhookConfigStore();
120897
121825
  const dispatcher = new WebhookDispatcher({
120898
121826
  store,
@@ -122488,8 +123416,8 @@ function findCycle(steps) {
122488
123416
  }
122489
123417
  function formatIssues(error2) {
122490
123418
  return error2.issues.map((iss) => {
122491
- const path59 = iss.path.join(".") || "(root)";
122492
- return `${path59}: ${iss.message}`;
123419
+ const path60 = iss.path.join(".") || "(root)";
123420
+ return `${path60}: ${iss.message}`;
122493
123421
  });
122494
123422
  }
122495
123423
  function validateWorkflow(raw) {
@@ -123558,6 +124486,7 @@ var BUILTIN_WORKFLOWS_DIR = path3.resolve(__dirname2, "../workflows");
123558
124486
 
123559
124487
  // src/setup/workflows.ts
123560
124488
  var PLUGIN_ID3 = asPluginId(WORKFLOWS_PLUGIN_NAME);
124489
+ var MAX_AFTER_WORKFLOW_CHAIN = 8;
123561
124490
  function buildWorkflowsIntegration(args) {
123562
124491
  const { session, scheduleStore, logger } = args;
123563
124492
  const store = new WorkflowStore({
@@ -123567,6 +124496,8 @@ function buildWorkflowsIntegration(args) {
123567
124496
  });
123568
124497
  const watchers = [];
123569
124498
  const inFlight = /* @__PURE__ */ new Set();
124499
+ const cyclicTriggers = /* @__PURE__ */ new Set();
124500
+ const warnedCycles = /* @__PURE__ */ new Set();
123570
124501
  async function runNow(input) {
123571
124502
  const entry = await store.get(input.name);
123572
124503
  if (!entry) return { ok: false, steps: [], output: "", error: `no workflow named "${input.name}"` };
@@ -123602,7 +124533,9 @@ function buildWorkflowsIntegration(args) {
123602
124533
  source: "plugin",
123603
124534
  pluginId: PLUGIN_ID3,
123604
124535
  subtype,
123605
- payload
124536
+ // Stamp the causal chain onto the completion event so the
124537
+ // afterWorkflow subscription can detect cycles/depth per-run.
124538
+ payload: subtype === "workflow_completed" && input.chain && input.chain.length > 0 ? { ...payload, triggerChain: [...input.chain] } : payload
123606
124539
  }),
123607
124540
  ...logger ? { logger } : {}
123608
124541
  },
@@ -123639,6 +124572,12 @@ function buildWorkflowsIntegration(args) {
123639
124572
  };
123640
124573
  async function syncSchedules() {
123641
124574
  const all = await store.list();
124575
+ applyAfterWorkflowCycleGuard({
124576
+ workflows: all.map((w4) => w4.workflow),
124577
+ disabled: cyclicTriggers,
124578
+ warned: warnedCycles,
124579
+ ...logger ? { logger } : {}
124580
+ });
123642
124581
  for (const { workflow } of all) {
123643
124582
  const sched = workflow.enabled ? workflow.on?.schedule : void 0;
123644
124583
  if (sched && (sched.cron || sched.runAt)) {
@@ -123669,23 +124608,24 @@ function buildWorkflowsIntegration(args) {
123669
124608
  }
123670
124609
  const unsubscribe = session.log.subscribe((event) => {
123671
124610
  if (event.type !== "plugin_event" || event.subtype !== "workflow_completed") return;
123672
- const completed = event.payload?.name;
124611
+ const payload = event.payload;
124612
+ const completed = payload?.name;
123673
124613
  if (!completed) return;
124614
+ const chain = Array.isArray(payload?.triggerChain) ? payload.triggerChain.filter((n2) => typeof n2 === "string") : [];
123674
124615
  void (async () => {
123675
- for (const { workflow } of await store.list()) {
123676
- if (!workflow.enabled || !workflow.on?.afterWorkflow) continue;
123677
- const deps = [workflow.on.afterWorkflow].flat();
123678
- if (deps.includes(completed) && workflow.name !== completed) {
123679
- logger?.info?.("workflows: afterWorkflow trigger", { workflow: workflow.name, after: completed });
123680
- await runNow({ name: workflow.name, trigger: `after:${completed}` }).catch(
123681
- (err) => logger?.warn?.("workflows: afterWorkflow run failed", {
123682
- workflow: workflow.name,
123683
- err: err instanceof Error ? err.message : String(err)
123684
- })
123685
- );
123686
- }
123687
- }
123688
- })();
124616
+ await fireAfterWorkflowDependents({
124617
+ completed,
124618
+ chain,
124619
+ workflows: (await store.list()).map((w4) => w4.workflow),
124620
+ disabled: cyclicTriggers,
124621
+ run: runNow,
124622
+ ...logger ? { logger } : {}
124623
+ });
124624
+ })().catch(
124625
+ (err) => logger?.warn?.("workflows: afterWorkflow dispatch failed", {
124626
+ err: err instanceof Error ? err.message : String(err)
124627
+ })
124628
+ );
123689
124629
  });
123690
124630
  async function startFileWatchers() {
123691
124631
  for (const w4 of watchers.splice(0)) w4.close();
@@ -123744,6 +124684,104 @@ function buildWorkflowsIntegration(args) {
123744
124684
  }
123745
124685
  };
123746
124686
  }
124687
+ function detectAfterWorkflowCycles(workflows) {
124688
+ const enabled = /* @__PURE__ */ new Map();
124689
+ for (const w4 of workflows) if (w4.enabled) enabled.set(w4.name, w4);
124690
+ const edges = /* @__PURE__ */ new Map();
124691
+ for (const name of enabled.keys()) edges.set(name, []);
124692
+ for (const w4 of enabled.values()) {
124693
+ for (const dep of [w4.on?.afterWorkflow ?? []].flat()) {
124694
+ if (enabled.has(dep)) edges.get(dep).push(w4.name);
124695
+ }
124696
+ }
124697
+ let counter = 0;
124698
+ const index = /* @__PURE__ */ new Map();
124699
+ const lowlink = /* @__PURE__ */ new Map();
124700
+ const onStack = /* @__PURE__ */ new Set();
124701
+ const stack = [];
124702
+ const cycles = [];
124703
+ const visit = (v3) => {
124704
+ index.set(v3, counter);
124705
+ lowlink.set(v3, counter);
124706
+ counter++;
124707
+ stack.push(v3);
124708
+ onStack.add(v3);
124709
+ for (const w4 of edges.get(v3) ?? []) {
124710
+ if (!index.has(w4)) {
124711
+ visit(w4);
124712
+ lowlink.set(v3, Math.min(lowlink.get(v3), lowlink.get(w4)));
124713
+ } else if (onStack.has(w4)) {
124714
+ lowlink.set(v3, Math.min(lowlink.get(v3), index.get(w4)));
124715
+ }
124716
+ }
124717
+ if (lowlink.get(v3) === index.get(v3)) {
124718
+ const scc = [];
124719
+ for (; ; ) {
124720
+ const w4 = stack.pop();
124721
+ onStack.delete(w4);
124722
+ scc.push(w4);
124723
+ if (w4 === v3) break;
124724
+ }
124725
+ if (scc.length > 1 || (edges.get(v3) ?? []).includes(v3)) cycles.push(scc.reverse());
124726
+ }
124727
+ };
124728
+ for (const name of enabled.keys()) if (!index.has(name)) visit(name);
124729
+ return cycles;
124730
+ }
124731
+ function applyAfterWorkflowCycleGuard(args) {
124732
+ const cycles = detectAfterWorkflowCycles(args.workflows);
124733
+ args.disabled.clear();
124734
+ for (const cycle of cycles) {
124735
+ for (const name of cycle) args.disabled.add(name);
124736
+ const key = [...cycle].sort().join(" -> ");
124737
+ if (args.warned.has(key)) continue;
124738
+ args.warned.add(key);
124739
+ args.logger?.warn?.(
124740
+ `workflows: afterWorkflow trigger cycle detected (${cycle.join(" -> ")} -> ${cycle[0]}); auto-refire is disabled for these workflows \u2014 run them manually or break the cycle`,
124741
+ { cycle: [...cycle] }
124742
+ );
124743
+ }
124744
+ }
124745
+ async function fireAfterWorkflowDependents(args) {
124746
+ const { completed, workflows, disabled, run: run2, logger } = args;
124747
+ const nextChain = [...args.chain, completed];
124748
+ for (const workflow of workflows) {
124749
+ if (!workflow.enabled || !workflow.on?.afterWorkflow) continue;
124750
+ if (![workflow.on.afterWorkflow].flat().includes(completed)) continue;
124751
+ if (disabled.has(workflow.name)) {
124752
+ logger?.info?.("workflows: afterWorkflow auto-refire disabled by the cycle guard; skipping", {
124753
+ workflow: workflow.name,
124754
+ after: completed
124755
+ });
124756
+ continue;
124757
+ }
124758
+ if (nextChain.includes(workflow.name)) {
124759
+ logger?.warn?.(
124760
+ `workflows: refusing afterWorkflow re-fire \u2014 trigger cycle (${[...nextChain, workflow.name].join(" -> ")}); "${workflow.name}" already ran in this chain`,
124761
+ { workflow: workflow.name, chain: [...nextChain] }
124762
+ );
124763
+ continue;
124764
+ }
124765
+ if (nextChain.length >= MAX_AFTER_WORKFLOW_CHAIN) {
124766
+ logger?.warn?.(
124767
+ `workflows: refusing afterWorkflow re-fire \u2014 chain depth cap of ${MAX_AFTER_WORKFLOW_CHAIN} reached (${nextChain.join(" -> ")} -> ${workflow.name})`,
124768
+ { workflow: workflow.name, chain: [...nextChain] }
124769
+ );
124770
+ continue;
124771
+ }
124772
+ logger?.info?.("workflows: afterWorkflow trigger", {
124773
+ workflow: workflow.name,
124774
+ after: completed,
124775
+ depth: nextChain.length
124776
+ });
124777
+ await run2({ name: workflow.name, trigger: `after:${completed}`, chain: nextChain }).catch(
124778
+ (err) => logger?.warn?.("workflows: afterWorkflow run failed", {
124779
+ workflow: workflow.name,
124780
+ err: err instanceof Error ? err.message : String(err)
124781
+ })
124782
+ );
124783
+ }
124784
+ }
123747
124785
  function activeModel(session) {
123748
124786
  return safeActiveProvider(session)?.models[0]?.id ?? "claude-sonnet-4-6";
123749
124787
  }
@@ -124152,11 +125190,12 @@ function buildSchedulerRunner(session) {
124152
125190
  init_dist();
124153
125191
  function buildWebhookRunner(session) {
124154
125192
  return {
124155
- runPrompt: async ({ prompt, model }) => {
125193
+ runPrompt: async ({ prompt, model, allowedTools, triggerName }) => {
125194
+ const target = scopedSessionView(session, allowedTools, triggerName);
124156
125195
  let text = "";
124157
125196
  let lastError = null;
124158
125197
  try {
124159
- for await (const event of runTurn(session, prompt, model ? { model } : {})) {
125198
+ for await (const event of runTurn(target, prompt, model ? { model } : {})) {
124160
125199
  if (event.type === "assistant_message") {
124161
125200
  text = event.content;
124162
125201
  if (event.stopReason === "error") lastError = "turn ended with error stop reason";
@@ -124171,6 +125210,88 @@ function buildWebhookRunner(session) {
124171
125210
  }
124172
125211
  };
124173
125212
  }
125213
+ function scopedSessionView(session, allowedTools, triggerName) {
125214
+ if (allowedTools.length === 0) return session;
125215
+ const allowed = new Set(allowedTools);
125216
+ const denyReason = (name) => `Tool '${name}' is not in webhook trigger '${triggerName}' allowedTools`;
125217
+ const resolver2 = {
125218
+ name: `webhook-allowed-tools(${triggerName})`,
125219
+ async check(call, ctx) {
125220
+ if (!allowed.has(call.name)) {
125221
+ return { mode: "deny", reason: denyReason(call.name) };
125222
+ }
125223
+ return session.resolver.check(call, ctx);
125224
+ },
125225
+ // The allow-list is POLICY, so it must also surface through the
125226
+ // prompt-free `policyCheck` probe — auto-approving modes (goal mode)
125227
+ // consult only this and skip `check`'s prompt path entirely. Without
125228
+ // it, a goal-mode webhook fire would auto-approve tools outside the
125229
+ // trigger's allowedTools.
125230
+ async policyCheck(call, ctx) {
125231
+ if (!allowed.has(call.name)) {
125232
+ return { mode: "deny", reason: denyReason(call.name) };
125233
+ }
125234
+ return await session.resolver.policyCheck?.(call, ctx) ?? null;
125235
+ }
125236
+ };
125237
+ const parent = session.tools;
125238
+ const tools = {
125239
+ list: () => parent.list().filter((t2) => allowed.has(t2.name)),
125240
+ get: (name) => allowed.has(name) ? parent.get(name) : void 0,
125241
+ has: (name) => allowed.has(name) && parent.has(name),
125242
+ register: (tool) => parent.register(tool),
125243
+ unregister: (name) => parent.unregister(name),
125244
+ execute: (name, input, signal, opts) => {
125245
+ if (!allowed.has(name)) return Promise.reject(new Error(denyReason(name)));
125246
+ return parent.execute(name, input, signal, opts);
125247
+ }
125248
+ };
125249
+ return {
125250
+ get id() {
125251
+ return session.id;
125252
+ },
125253
+ get log() {
125254
+ return session.log;
125255
+ },
125256
+ get signal() {
125257
+ return session.signal;
125258
+ },
125259
+ tools,
125260
+ get skills() {
125261
+ return session.skills;
125262
+ },
125263
+ get providers() {
125264
+ return session.providers;
125265
+ },
125266
+ get modes() {
125267
+ return session.modes;
125268
+ },
125269
+ get compactors() {
125270
+ return session.compactors;
125271
+ },
125272
+ get cacheStrategies() {
125273
+ return session.cacheStrategies;
125274
+ },
125275
+ resolver: resolver2,
125276
+ get approvalResolver() {
125277
+ return session.approvalResolver;
125278
+ },
125279
+ get elisionSettings() {
125280
+ return session.elisionSettings;
125281
+ },
125282
+ get lazyTools() {
125283
+ return session.lazyTools;
125284
+ },
125285
+ get dispatcher() {
125286
+ return session.dispatcher;
125287
+ },
125288
+ get pluginHost() {
125289
+ return session.pluginHost;
125290
+ },
125291
+ startTurn: () => session.startTurn(),
125292
+ appContext: () => session.appContext()
125293
+ };
125294
+ }
124174
125295
 
124175
125296
  // src/setup/register-plugins.ts
124176
125297
  init_dist();
@@ -124412,7 +125533,12 @@ async function resolveOAuthCodex(vault) {
124412
125533
  tokens,
124413
125534
  onTokensRefreshed: async (next) => {
124414
125535
  await persistCodexTokens(vault, next);
124415
- }
125536
+ },
125537
+ // Cross-process recovery: when a refresh hits invalid_grant because another
125538
+ // moxxy process already rotated the single-use refresh token, the provider
125539
+ // re-reads the vault through this hook and retries once with the fresher
125540
+ // token instead of forcing a re-login.
125541
+ reloadTokens: () => readStoredTokens(vault)
124416
125542
  };
124417
125543
  }
124418
125544
 
@@ -124689,6 +125815,18 @@ async function setupSessionWithConfig(opts) {
124689
125815
  pluginRegistration
124690
125816
  };
124691
125817
  }
125818
+ async function probeSession(opts, read, boot = setupSessionWithConfig) {
125819
+ const result = await boot({
125820
+ ...opts,
125821
+ skipInitHooks: true,
125822
+ disableSessionPersistence: true
125823
+ });
125824
+ try {
125825
+ return await read(result);
125826
+ } finally {
125827
+ await result.session.close("probe-complete").catch(() => void 0);
125828
+ }
125829
+ }
124692
125830
 
124693
125831
  // src/argv-helpers.ts
124694
125832
  function argvToSetupOptions(argv, overrides = {}) {
@@ -124728,8 +125866,8 @@ function useColor() {
124728
125866
  return Boolean(process.stdout.isTTY);
124729
125867
  }
124730
125868
  var ENABLED = useColor();
124731
- function wrap(open, close) {
124732
- return (s2) => ENABLED ? `\x1B[${open}m${s2}\x1B[${close}m` : s2;
125869
+ function wrap(open2, close) {
125870
+ return (s2) => ENABLED ? `\x1B[${open2}m${s2}\x1B[${close}m` : s2;
124733
125871
  }
124734
125872
  var colors = {
124735
125873
  bold: wrap(1, 22),
@@ -125504,13 +126642,15 @@ async function runSelfHostedTui(argv, tuiOpts, standalone) {
125504
126642
  let needsInit = sources.length === 0;
125505
126643
  if (!needsInit) {
125506
126644
  try {
125507
- const probe = await setupSession({
125508
- ...argvToSetupOptions(argv),
125509
- tolerateNoProvider: true,
125510
- skipKeyPrompt: true,
125511
- disableSessionPersistence: true
125512
- });
125513
- if (!probe.providers.getActiveName()) needsInit = true;
126645
+ const hasProvider = await probeSession(
126646
+ {
126647
+ ...argvToSetupOptions(argv),
126648
+ tolerateNoProvider: true,
126649
+ skipKeyPrompt: true
126650
+ },
126651
+ ({ session }) => Boolean(session.providers.getActiveName())
126652
+ );
126653
+ if (!hasProvider) needsInit = true;
125514
126654
  } catch {
125515
126655
  needsInit = true;
125516
126656
  }
@@ -125887,8 +127027,8 @@ async function runPluginNewCommand(argv) {
125887
127027
  const force = hasBoolFlag(argv, "force");
125888
127028
  const root = here ? path3.join(process.cwd(), name) : path3.join(os5.homedir(), ".moxxy", "plugins", name);
125889
127029
  try {
125890
- const stat = await promises.stat(root);
125891
- if (stat.isDirectory() && !force) {
127030
+ const stat2 = await promises.stat(root);
127031
+ if (stat2.isDirectory() && !force) {
125892
127032
  printError(`refusing to overwrite ${root} (pass --force to allow)`);
125893
127033
  return 1;
125894
127034
  }
@@ -126046,12 +127186,14 @@ ${HELP4}`);
126046
127186
  }
126047
127187
  }
126048
127188
  async function runList(argv) {
126049
- const session = await bootSession(argv, {
126050
- skipKeyPrompt: true,
126051
- tolerateNoProvider: true,
126052
- skipProviderActivation: true
126053
- });
126054
- const loaded = session.pluginHost.list();
127189
+ const loaded = await probeSession(
127190
+ argvToSetupOptions(argv, {
127191
+ skipKeyPrompt: true,
127192
+ tolerateNoProvider: true,
127193
+ skipProviderActivation: true
127194
+ }),
127195
+ ({ session }) => session.pluginHost.list()
127196
+ );
126055
127197
  const disabled = await loadDisabledPackageNames();
126056
127198
  const installed = /* @__PURE__ */ new Set([...loaded.map((p3) => p3.name), ...disabled]);
126057
127199
  const nameCol = Math.max(8, ...loaded.map((p3) => p3.name.length), ...[...disabled].map((n2) => n2.length));
@@ -126324,24 +127466,37 @@ function errMsg2(err) {
126324
127466
  // src/commands/run-channel.ts
126325
127467
  async function runChannelByName(name, argv) {
126326
127468
  if (name === "tui") return runTuiWithBootstrap(argv);
126327
- const { session, vault, config } = await bootSessionWithConfig(argv, {
126328
- skipKeyPrompt: true,
126329
- tolerateNoProvider: true,
126330
- skipProviderActivation: true
126331
- });
126332
- const def = session.channels.get(name);
126333
- if (!def) {
126334
- printError(
126335
- `unknown channel: ${name}
127469
+ const outcome = await probeSession(
127470
+ argvToSetupOptions(argv, {
127471
+ skipKeyPrompt: true,
127472
+ tolerateNoProvider: true,
127473
+ skipProviderActivation: true
127474
+ }),
127475
+ async ({ session, vault, config }) => {
127476
+ const def = session.channels.get(name);
127477
+ if (!def) {
127478
+ printError(
127479
+ `unknown channel: ${name}
126336
127480
  Available:
126337
127481
  ` + session.channels.list().map((d2) => ` ${d2.name} - ${d2.description}
126338
127482
  `).join("")
126339
- );
126340
- return 2;
126341
- }
126342
- if (def.interactiveCommand && process.stdin.isTTY === true && argv.flags["no-wizard"] !== true && argv.flags["__skipWizard"] !== true && !hasBoolFlag(argv, "standalone") && !await isRunnerUp()) {
126343
- return runChannelSubcommand(def, def.interactiveCommand, { session, vault, config, argv });
126344
- }
127483
+ );
127484
+ return { code: 2 };
127485
+ }
127486
+ if (def.interactiveCommand && process.stdin.isTTY === true && argv.flags["no-wizard"] !== true && argv.flags["__skipWizard"] !== true && !hasBoolFlag(argv, "standalone") && !await isRunnerUp()) {
127487
+ return {
127488
+ code: await runChannelSubcommand(def, def.interactiveCommand, {
127489
+ session,
127490
+ vault,
127491
+ config,
127492
+ argv
127493
+ })
127494
+ };
127495
+ }
127496
+ return "start-headless";
127497
+ }
127498
+ );
127499
+ if (outcome !== "start-headless") return outcome.code;
126345
127500
  return startRegisteredChannel(name, argv);
126346
127501
  }
126347
127502
  async function runChannelSubcommand(def, subName, ctx) {
@@ -126393,62 +127548,74 @@ async function runChannelsCommand(argv) {
126393
127548
  if (!name || name === "list") {
126394
127549
  return runList2();
126395
127550
  }
126396
- const { session, vault, config } = await bootSessionWithConfig(argv, {
126397
- skipKeyPrompt: true,
126398
- tolerateNoProvider: true,
126399
- skipProviderActivation: true
126400
- });
126401
- const def = session.channels.get(name);
126402
- if (!def) {
126403
- printError(
126404
- `unknown channel: ${name}
127551
+ const outcome = await probeSession(
127552
+ argvToSetupOptions(argv, {
127553
+ skipKeyPrompt: true,
127554
+ tolerateNoProvider: true,
127555
+ skipProviderActivation: true
127556
+ }),
127557
+ async ({ session, vault, config }) => {
127558
+ const def = session.channels.get(name);
127559
+ if (!def) {
127560
+ printError(
127561
+ `unknown channel: ${name}
126405
127562
  Available:
126406
127563
  ` + session.channels.list().map((d2) => ` ${d2.name} \u2014 ${d2.description}
126407
127564
  `).join("")
126408
- );
126409
- return 2;
126410
- }
126411
- if (!sub) {
126412
- if (helpRequested(argv)) {
126413
- process.stdout.write(formatChannelHelp(def));
126414
- return 0;
126415
- }
126416
- return await runChannelByName(name, argv);
126417
- }
126418
- const subcommand = def.subcommands?.[sub];
126419
- if (!subcommand) {
126420
- const available = def.subcommands ? Object.entries(def.subcommands).map(([n2, c2]) => ` ${name} ${n2} \u2014 ${c2.description}
127565
+ );
127566
+ return { code: 2 };
127567
+ }
127568
+ if (!sub) {
127569
+ if (helpRequested(argv)) {
127570
+ process.stdout.write(formatChannelHelp(def));
127571
+ return { code: 0 };
127572
+ }
127573
+ return "run-channel";
127574
+ }
127575
+ const subcommand = def.subcommands?.[sub];
127576
+ if (!subcommand) {
127577
+ const available = def.subcommands ? Object.entries(def.subcommands).map(([n2, c2]) => ` ${name} ${n2} \u2014 ${c2.description}
126421
127578
  `).join("") : " (none)\n";
126422
- printError(
126423
- `unknown '${name}' subcommand: ${sub}
127579
+ printError(
127580
+ `unknown '${name}' subcommand: ${sub}
126424
127581
  Available subcommands:
126425
127582
  ${available}`
126426
- );
126427
- return 2;
126428
- }
126429
- if (helpRequested(argv)) {
126430
- process.stdout.write(formatSubcommandHelp(name, sub, subcommand));
126431
- return 0;
126432
- }
126433
- return await runChannelSubcommand(def, sub, {
126434
- session,
126435
- vault,
126436
- config,
126437
- argv: { ...argv, positional: rest }
126438
- });
127583
+ );
127584
+ return { code: 2 };
127585
+ }
127586
+ if (helpRequested(argv)) {
127587
+ process.stdout.write(formatSubcommandHelp(name, sub, subcommand));
127588
+ return { code: 0 };
127589
+ }
127590
+ return {
127591
+ code: await runChannelSubcommand(def, sub, {
127592
+ session,
127593
+ vault,
127594
+ config,
127595
+ argv: { ...argv, positional: rest }
127596
+ })
127597
+ };
127598
+ }
127599
+ );
127600
+ if (outcome !== "run-channel") return outcome.code;
127601
+ return runChannelByName(name, argv);
126439
127602
  }
126440
127603
  async function runList2() {
126441
- const { session, vault, config } = await bootSessionWithConfig(
126442
- { flags: {} },
126443
- { skipKeyPrompt: true, tolerateNoProvider: true, skipProviderActivation: true }
127604
+ const { entries, config } = await probeSession(
127605
+ argvToSetupOptions(
127606
+ { flags: {} },
127607
+ { skipKeyPrompt: true, tolerateNoProvider: true, skipProviderActivation: true }
127608
+ ),
127609
+ async ({ session, vault, config: config2 }) => ({
127610
+ config: config2,
127611
+ entries: await session.channels.listWithAvailability({
127612
+ cwd: process.cwd(),
127613
+ vault,
127614
+ logger: session.logger,
127615
+ options: {}
127616
+ })
127617
+ })
126444
127618
  );
126445
- const deps = {
126446
- cwd: process.cwd(),
126447
- vault,
126448
- logger: session.logger,
126449
- options: {}
126450
- };
126451
- const entries = await session.channels.listWithAvailability(deps);
126452
127619
  const nameCol = Math.max(8, ...entries.map((e3) => e3.def.name.length));
126453
127620
  for (const { def, availability } of entries) {
126454
127621
  const namePadded = def.name.padEnd(nameCol);
@@ -126773,12 +127940,12 @@ ${HELP6}`);
126773
127940
  }
126774
127941
  }
126775
127942
  async function statOf(entry) {
126776
- const stat = await promises.stat(entry.path).catch(() => null);
127943
+ const stat2 = await promises.stat(entry.path).catch(() => null);
126777
127944
  return {
126778
127945
  entry,
126779
- size: stat?.size ?? entry.body.length,
126780
- createdAt: new Date(entry.frontmatter.createdAt ?? stat?.birthtime ?? Date.now()),
126781
- updatedAt: new Date(entry.frontmatter.updatedAt ?? stat?.mtime ?? Date.now())
127946
+ size: stat2?.size ?? entry.body.length,
127947
+ createdAt: new Date(entry.frontmatter.createdAt ?? stat2?.birthtime ?? Date.now()),
127948
+ updatedAt: new Date(entry.frontmatter.updatedAt ?? stat2?.mtime ?? Date.now())
126782
127949
  };
126783
127950
  }
126784
127951
  function groupByType(stats) {
@@ -127455,13 +128622,17 @@ async function stepInstallDaemon(skipDaemon) {
127455
128622
  }
127456
128623
  async function stepTelegramStatus() {
127457
128624
  try {
127458
- const { vault } = await setupSessionWithConfig({
127459
- cwd: process.cwd(),
127460
- skipKeyPrompt: true,
127461
- tolerateNoProvider: true
127462
- });
127463
- const hasToken = await vault.has("telegram_bot_token");
127464
- const chatRaw = await vault.get("telegram_authorized_chat_id");
128625
+ const { hasToken, chatRaw } = await probeSession(
128626
+ {
128627
+ cwd: process.cwd(),
128628
+ skipKeyPrompt: true,
128629
+ tolerateNoProvider: true
128630
+ },
128631
+ async ({ vault }) => ({
128632
+ hasToken: await vault.has("telegram_bot_token"),
128633
+ chatRaw: await vault.get("telegram_authorized_chat_id")
128634
+ })
128635
+ );
127465
128636
  const hasChat = !!chatRaw;
127466
128637
  if (hasToken && hasChat) {
127467
128638
  process.stdout.write(
@@ -127631,27 +128802,30 @@ async function runScheduleCommand(argv) {
127631
128802
  }
127632
128803
  if (sub === "setup") return await runScheduleSetup(argv);
127633
128804
  if (sub === "daemon") return await runDaemon(argv);
127634
- const { scheduler } = await setupSessionWithConfig({
127635
- cwd: process.cwd(),
127636
- skipKeyPrompt: true,
127637
- tolerateNoProvider: true
127638
- });
127639
- switch (sub) {
127640
- case "list":
127641
- return listSchedules(scheduler.store);
127642
- case "add":
127643
- return addSchedule(scheduler.store, argv);
127644
- case "remove":
127645
- return removeSchedule(scheduler.store, argv);
127646
- case "enable":
127647
- case "disable":
127648
- return toggleSchedule(scheduler.store, argv, sub);
127649
- case "run":
127650
- return runScheduleNow(argv);
127651
- default:
127652
- process.stderr.write(colors.red(`unknown subcommand: ${sub}`) + "\n" + SCHEDULE_HELP);
127653
- return 2;
127654
- }
128805
+ if (sub === "run") return runScheduleNow(argv);
128806
+ return probeSession(
128807
+ {
128808
+ cwd: process.cwd(),
128809
+ skipKeyPrompt: true,
128810
+ tolerateNoProvider: true
128811
+ },
128812
+ async ({ scheduler }) => {
128813
+ switch (sub) {
128814
+ case "list":
128815
+ return listSchedules(scheduler.store);
128816
+ case "add":
128817
+ return addSchedule(scheduler.store, argv);
128818
+ case "remove":
128819
+ return removeSchedule(scheduler.store, argv);
128820
+ case "enable":
128821
+ case "disable":
128822
+ return toggleSchedule(scheduler.store, argv, sub);
128823
+ default:
128824
+ process.stderr.write(colors.red(`unknown subcommand: ${sub}`) + "\n" + SCHEDULE_HELP);
128825
+ return 2;
128826
+ }
128827
+ }
128828
+ );
127655
128829
  }
127656
128830
 
127657
128831
  // src/commands/doctor.ts
@@ -129265,6 +130439,31 @@ function describeCauseChain(err) {
129265
130439
  return chain.join("\n");
129266
130440
  }
129267
130441
 
130442
+ // src/process-guards.ts
130443
+ function installProcessGuards() {
130444
+ if (guardsInstalled) return;
130445
+ guardsInstalled = true;
130446
+ process.on("unhandledRejection", (reason) => {
130447
+ process.stderr.write(
130448
+ colors.red("[moxxy] unhandled promise rejection (process kept alive):") + "\n" + formatErrorForCli(reason, { debug: debugEnabled() }) + "\n"
130449
+ );
130450
+ });
130451
+ process.on("uncaughtException", (err) => {
130452
+ process.exitCode = 1;
130453
+ const msg = colors.red("[moxxy] uncaught exception \u2014 exiting:") + "\n" + formatErrorForCli(err, { debug: debugEnabled() }) + "\n";
130454
+ const backstop = setTimeout(() => process.exit(1), 250);
130455
+ process.stderr.write(msg, () => {
130456
+ clearTimeout(backstop);
130457
+ process.exit(1);
130458
+ });
130459
+ });
130460
+ }
130461
+ var guardsInstalled = false;
130462
+ function debugEnabled() {
130463
+ const v3 = process.env.MOXXY_DEBUG;
130464
+ return v3 === "1" || v3 === "true";
130465
+ }
130466
+
129268
130467
  // src/bin.ts
129269
130468
  var SECTIONS = [
129270
130469
  {
@@ -129421,16 +130620,15 @@ async function main() {
129421
130620
  if (handler) return handler(argv);
129422
130621
  let isChannel = false;
129423
130622
  try {
129424
- const { session } = await setupSessionWithConfig({
129425
- cwd: process.cwd(),
129426
- skipKeyPrompt: true,
129427
- tolerateNoProvider: true,
129428
- // We only need the channel registry here, never the provider.
129429
- // Activating it can hang or throw on hosts without a configured
129430
- // key, which would mask the real "unknown command" feedback.
129431
- skipProviderActivation: true
129432
- });
129433
- isChannel = session.channels.has(argv.command);
130623
+ isChannel = await probeSession(
130624
+ {
130625
+ cwd: process.cwd(),
130626
+ skipKeyPrompt: true,
130627
+ tolerateNoProvider: true,
130628
+ skipProviderActivation: true
130629
+ },
130630
+ ({ session }) => session.channels.has(argv.command)
130631
+ );
129434
130632
  } catch {
129435
130633
  }
129436
130634
  if (isChannel) {
@@ -129441,6 +130639,8 @@ async function main() {
129441
130639
  );
129442
130640
  return 2;
129443
130641
  }
130642
+ process.title = ["moxxy", ...process.argv.slice(2)].join(" ");
130643
+ installProcessGuards();
129444
130644
  main().then(
129445
130645
  (code) => process.exit(code),
129446
130646
  (err) => {