svamp-cli 0.2.245 → 0.2.247

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/dist/{agentCommands-DvLTS2bk.mjs → agentCommands-DOzMhE_G.mjs} +5 -5
  2. package/dist/{auth-JDjru_js.mjs → auth-B_ZnoKX0.mjs} +1 -1
  3. package/dist/cli.mjs +82 -68
  4. package/dist/{commands-QyDnDry4.mjs → commands-BENx03Oz.mjs} +2 -2
  5. package/dist/{commands-DfTq3rS3.mjs → commands-Bm5q9G35.mjs} +1 -1
  6. package/dist/{commands-CIa6EEAk.mjs → commands-C2u9U3y6.mjs} +2 -2
  7. package/dist/{commands-Bt8pK8MS.mjs → commands-CxOPcoWa.mjs} +1 -1
  8. package/dist/{commands-jFRw1VYy.mjs → commands-F6j9TF4Q.mjs} +6 -6
  9. package/dist/{commands-CRsGX1t1.mjs → commands-_rMwFmFH.mjs} +1 -1
  10. package/dist/{commands-CnVo5S8W.mjs → commands-vCeWXvYJ.mjs} +1 -1
  11. package/dist/{fleet-gkqGOfvU.mjs → fleet-pMXCRilx.mjs} +12 -4
  12. package/dist/{frpc-Dp9dDwDE.mjs → frpc-Di-y4cWK.mjs} +5 -2
  13. package/dist/{headlessCli-BIOe8SSK.mjs → headlessCli-D6k9Pnum.mjs} +2 -2
  14. package/dist/{httpServer-B1KVQJfm.mjs → httpServer-CSMZTpoh.mjs} +25 -3
  15. package/dist/index.mjs +1 -1
  16. package/dist/{package-ZEiIG_qH.mjs → package-BJYmQjS2.mjs} +2 -2
  17. package/dist/{rpc-CdbgL61q.mjs → rpc-BncvIQMO.mjs} +1 -1
  18. package/dist/{rpc-Wp_ufnPT.mjs → rpc-DbY7NUhj.mjs} +2 -2
  19. package/dist/{run-BgPz-Shl.mjs → run-1BBi0_ol.mjs} +104 -23
  20. package/dist/{run-hCQkcg9-.mjs → run-CYx_Rhiu.mjs} +1 -1
  21. package/dist/{runStore-BKgHQGNO.mjs → runStore-C5IyfbON.mjs} +1 -1
  22. package/dist/{scheduler-BUo_8paQ.mjs → scheduler-BkXOKjHn.mjs} +30 -4
  23. package/dist/{serveCommands-bEavErQo.mjs → serveCommands-CESRU6-1.mjs} +5 -5
  24. package/dist/{serveManager-3zR_v1Xj.mjs → serveManager-BZtpClpC.mjs} +36 -19
  25. package/dist/{sideband-l39JbmHM.mjs → sideband-BqD4-WWR.mjs} +1 -1
  26. package/package.json +2 -2
@@ -58,7 +58,7 @@ async function serviceExpose(args) {
58
58
  process.exit(1);
59
59
  }
60
60
  if (foreground) {
61
- const { runFrpcTunnel } = await import('./frpc-Dp9dDwDE.mjs');
61
+ const { runFrpcTunnel } = await import('./frpc-Di-y4cWK.mjs');
62
62
  await runFrpcTunnel(name, ports, void 0, {
63
63
  group,
64
64
  groupKey,
@@ -68,7 +68,7 @@ async function serviceExpose(args) {
68
68
  });
69
69
  return;
70
70
  }
71
- const { connectAndGetMachine } = await import('./commands-CnVo5S8W.mjs');
71
+ const { connectAndGetMachine } = await import('./commands-vCeWXvYJ.mjs');
72
72
  const { server, machine } = await connectAndGetMachine();
73
73
  try {
74
74
  const status = await machine.tunnelStart({
@@ -90,7 +90,7 @@ async function serviceExpose(args) {
90
90
  console.log(` port ${port}: ${url}`);
91
91
  }
92
92
  if (process.env.SVAMP_SESSION_ID) {
93
- const { autoAddSessionLink } = await import('./agentCommands-DvLTS2bk.mjs');
93
+ const { autoAddSessionLink } = await import('./agentCommands-DOzMhE_G.mjs');
94
94
  let added = 0;
95
95
  for (const [port, url] of urlEntries) {
96
96
  const label = urlEntries.length > 1 ? `${name}:${port}` : name;
@@ -136,7 +136,7 @@ async function serviceServe(args) {
136
136
  };
137
137
  process.on("SIGINT", cleanup);
138
138
  process.on("SIGTERM", cleanup);
139
- const { runFrpcTunnel } = await import('./frpc-Dp9dDwDE.mjs');
139
+ const { runFrpcTunnel } = await import('./frpc-Di-y4cWK.mjs');
140
140
  await runFrpcTunnel(name, [caddyPort]);
141
141
  } catch (err) {
142
142
  console.error(`Error serving directory: ${err.message}`);
@@ -145,7 +145,7 @@ async function serviceServe(args) {
145
145
  }
146
146
  async function serviceList(_args) {
147
147
  try {
148
- const { connectAndGetMachine } = await import('./commands-CnVo5S8W.mjs');
148
+ const { connectAndGetMachine } = await import('./commands-vCeWXvYJ.mjs');
149
149
  const { server, machine } = await connectAndGetMachine();
150
150
  try {
151
151
  const tunnels = await machine.tunnelList({});
@@ -185,7 +185,7 @@ async function serviceDelete(args) {
185
185
  process.exit(1);
186
186
  }
187
187
  try {
188
- const { connectAndGetMachine } = await import('./commands-CnVo5S8W.mjs');
188
+ const { connectAndGetMachine } = await import('./commands-vCeWXvYJ.mjs');
189
189
  const { server, machine } = await connectAndGetMachine();
190
190
  try {
191
191
  await machine.tunnelStop({ name });
@@ -1,7 +1,7 @@
1
1
  import { execSync } from 'node:child_process';
2
2
  import { existsSync, readFileSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
- import { m as resolveProjectRoot, w as searchIssues, v as listIssues, o as resumeIssue, p as pauseIssue, q as addComment, u as updateIssue, n as getIssue, x as isVisibleTo, z as summarize, t as addIssue } from './run-BgPz-Shl.mjs';
4
+ import { m as resolveProjectRoot, w as searchIssues, v as listIssues, o as resumeIssue, p as pauseIssue, q as addComment, u as updateIssue, n as getIssue, x as isVisibleTo, z as summarize, t as addIssue } from './run-1BBi0_ol.mjs';
5
5
  import 'os';
6
6
  import 'fs/promises';
7
7
  import 'fs';
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { execSync } from 'node:child_process';
3
3
  import { basename, resolve, join, isAbsolute } from 'node:path';
4
4
  import os from 'node:os';
5
- import { Q as formatHandle, T as normalizeAllowedUser, U as loadSecurityContextConfig, V as resolveSecurityContext, W as buildSecurityContextFromFlags, X as mergeSecurityContexts, c as connectToHypha, Y as buildSessionShareUrl, Z as computeOutboundHop, A as shortId, _ as registerAwaitingReply, $ as buildMachineShareUrl, a0 as parseHandle, a1 as handleMatchesMetadata } from './run-BgPz-Shl.mjs';
5
+ import { Q as formatHandle, T as normalizeAllowedUser, U as loadSecurityContextConfig, V as resolveSecurityContext, W as buildSecurityContextFromFlags, X as mergeSecurityContexts, c as connectToHypha, Y as buildSessionShareUrl, Z as computeOutboundHop, A as shortId, _ as registerAwaitingReply, $ as buildMachineShareUrl, a0 as parseHandle, a1 as handleMatchesMetadata } from './run-1BBi0_ol.mjs';
6
6
  import 'os';
7
7
  import 'fs/promises';
8
8
  import 'fs';
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import os from 'node:os';
4
- import { c as connectToHypha } from './run-BgPz-Shl.mjs';
4
+ import { c as connectToHypha } from './run-1BBi0_ol.mjs';
5
5
  import { PINNED_CLAUDE_CODE_VERSION } from './pinnedClaudeCode-ivV7xAp3.mjs';
6
6
  import 'os';
7
7
  import 'fs/promises';
@@ -259,12 +259,14 @@ async function fleetUpgradeSvamp(opts) {
259
259
  console.log("\u26A0 --exclude-self: could not determine this machine's ID (no local daemon state) \u2014 nothing excluded.");
260
260
  }
261
261
  const { targets, skipped } = partitionSelf(machines, selfId);
262
- console.log(`Upgrading svamp-cli to ${version} on ${targets.length} machine(s)${selfId ? ` (excluding self: ${selfId})` : ""}...
262
+ const restartCmd = opts?.force ? "svamp daemon restart" : "svamp daemon restart --when-idle";
263
+ const mode = opts?.force ? "force (immediate)" : "graceful (restart when idle)";
264
+ console.log(`Upgrading svamp-cli to ${version} on ${targets.length} machine(s)${selfId ? ` (excluding self: ${selfId})` : ""} \u2014 ${mode}...
263
265
  `);
264
266
  try {
265
267
  printHeader(["MACHINE ID", "LABEL", "STATE", "DETAIL"], [20, 24, 6, 60]);
266
268
  for (const m of skipped) printResultRow(m, "SKIP", 'self \u2014 restart separately with "svamp daemon restart"');
267
- const cmd = `bash -lc 'npm install -g svamp-cli@${version} 2>&1 | tail -5 && svamp daemon restart 2>&1 | tail -5'`;
269
+ const cmd = `bash -lc 'npm install -g svamp-cli@${version} 2>&1 | tail -5 && ${restartCmd} 2>&1 | tail -5'`;
268
270
  const rows = await fanOut(targets, async (m) => await m.rpc.bash(cmd, void 0));
269
271
  let failures = 0;
270
272
  for (const r of rows) {
@@ -279,7 +281,13 @@ async function fleetUpgradeSvamp(opts) {
279
281
  if (!ok) failures += 1;
280
282
  }
281
283
  console.log(`
282
- ${rows.length - failures}/${rows.length} machines upgraded.`);
284
+ ${rows.length - failures}/${rows.length} machines updated (new binary installed).`);
285
+ if (opts?.force) {
286
+ console.log("Daemons restarted immediately onto the new binary.");
287
+ } else {
288
+ console.log("Graceful mode: each daemon restarts itself once it has no actively-thinking session");
289
+ console.log("(new binary already installed; takes effect at that restart). Force now with --force.");
290
+ }
283
291
  console.log("Note: daemons will auto-converge Claude Code to the pinned version on restart.");
284
292
  if (failures > 0) process.exitCode = 1;
285
293
  } finally {
@@ -4,7 +4,7 @@ import { mkdirSync, writeFileSync, unlinkSync, existsSync, chmodSync, readFileSy
4
4
  import { join } from 'path';
5
5
  import { homedir, platform, arch } from 'os';
6
6
  import { randomUUID, createHash } from 'crypto';
7
- import { h as getFrpsSubdomainHost, i as getFrpsServerPort, j as getFrpsServerAddr } from './run-BgPz-Shl.mjs';
7
+ import { h as getFrpsSubdomainHost, i as getFrpsServerPort, j as getFrpsServerAddr } from './run-1BBi0_ol.mjs';
8
8
  import 'fs/promises';
9
9
  import 'url';
10
10
  import 'node:crypto';
@@ -339,7 +339,6 @@ class FrpcTunnel {
339
339
  if (!this._connected) {
340
340
  this._connected = true;
341
341
  this._lastConnectedAt = Date.now();
342
- this._restartAttempts = 0;
343
342
  this._consecutiveErrors = 0;
344
343
  this._firstErrorAt = 0;
345
344
  this.options.onConnect?.();
@@ -375,6 +374,10 @@ class FrpcTunnel {
375
374
  reject(new Error(`frpc exited with code ${code}`));
376
375
  }
377
376
  if (!this._destroyed && code !== 0) {
377
+ const STABILITY_WINDOW_MS = 3e4;
378
+ if (this._lastConnectedAt > 0 && Date.now() - this._lastConnectedAt >= STABILITY_WINDOW_MS) {
379
+ this._restartAttempts = 0;
380
+ }
378
381
  this._restartAttempts++;
379
382
  const delay = Math.min(3e3 * Math.pow(2, this._restartAttempts - 1), 6e4);
380
383
  this.log(`frpc exited with code ${code}, restarting in ${Math.round(delay / 1e3)}s (attempt ${this._restartAttempts})...`);
@@ -1,5 +1,5 @@
1
- import { P as resolveModel, a2 as describeMisconfiguration, a3 as buildMachineDeps } from './run-BgPz-Shl.mjs';
2
- import { handleRealtimeEvent, initMachineVoiceSession } from './sideband-l39JbmHM.mjs';
1
+ import { P as resolveModel, a2 as describeMisconfiguration, a3 as buildMachineDeps } from './run-1BBi0_ol.mjs';
2
+ import { handleRealtimeEvent, initMachineVoiceSession } from './sideband-BqD4-WWR.mjs';
3
3
  import { WebSocket } from 'ws';
4
4
  import { execSync, spawn } from 'child_process';
5
5
  import 'os';
@@ -86,7 +86,8 @@ function createChannelHttpServer(deps) {
86
86
  }
87
87
  m = u.pathname.match(/^\/channel\/([\w.-]+)\/events$/);
88
88
  if (m) {
89
- const rpc2 = await findOwner(deps, m[1]);
89
+ const cid = m[1];
90
+ let rpc2 = await findOwner(deps, cid);
90
91
  if (!rpc2?.channelReceive) {
91
92
  res.writeHead(404).end("not found");
92
93
  return;
@@ -100,8 +101,25 @@ function createChannelHttpServer(deps) {
100
101
  req.on("close", () => {
101
102
  closed = true;
102
103
  });
104
+ const MIN_LOOP_MS = 1e3;
103
105
  while (!closed) {
104
- const out = await rpc2.channelReceive({ channel: m[1], key, from, cursor, wait: 25 });
106
+ const started = Date.now();
107
+ let out;
108
+ try {
109
+ out = await rpc2.channelReceive({ channel: cid, key, from, cursor, wait: 25 });
110
+ } catch (e) {
111
+ const re = await findOwner(deps, cid).catch(() => null);
112
+ if (re?.channelReceive && !closed) {
113
+ rpc2 = re;
114
+ await new Promise((r) => setTimeout(r, MIN_LOOP_MS));
115
+ continue;
116
+ }
117
+ res.write(`event: error
118
+ data: ${JSON.stringify({ error: e?.message || String(e) })}
119
+
120
+ `);
121
+ break;
122
+ }
105
123
  if (out?.error) {
106
124
  res.write(`event: error
107
125
  data: ${JSON.stringify({ error: out.error })}
@@ -113,7 +131,11 @@ data: ${JSON.stringify({ error: out.error })}
113
131
 
114
132
  `);
115
133
  cursor = out.cursor ?? cursor;
116
- if (!(out.replies || []).length) res.write(": keepalive\n\n");
134
+ if (!(out.replies || []).length) {
135
+ res.write(": keepalive\n\n");
136
+ const elapsed = Date.now() - started;
137
+ if (elapsed < MIN_LOOP_MS) await new Promise((r) => setTimeout(r, MIN_LOOP_MS - elapsed));
138
+ }
117
139
  }
118
140
  try {
119
141
  res.end();
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { c as connectToHypha, a as createSessionStore, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, s as startDaemon, b as stopDaemon } from './run-BgPz-Shl.mjs';
1
+ export { c as connectToHypha, a as createSessionStore, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, s as startDaemon, b as stopDaemon } from './run-1BBi0_ol.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -1,5 +1,5 @@
1
1
  var name = "svamp-cli";
2
- var version = "0.2.245";
2
+ var version = "0.2.247";
3
3
  var description = "Svamp CLI — AI workspace daemon on Hypha Cloud";
4
4
  var author = "Amun AI AB";
5
5
  var license = "SEE LICENSE IN LICENSE";
@@ -19,7 +19,7 @@ var exports$1 = {
19
19
  var scripts = {
20
20
  build: "rm -rf dist bin/skills bin/commands && mkdir -p bin/skills && cp -r ../../skills/artifact bin/skills/artifact && cp -r ../../skills/crew bin/skills/crew && cp -r ../../commands bin/commands && tsc --noEmit && pkgroll",
21
21
  typecheck: "tsc --noEmit",
22
- test: "npx tsx test/test-context-window.mjs && npx tsx test/test-ratelimit-retry.mjs && npx tsx test/test-instance-config.mjs && npx tsx test/test-authorize.mjs && npx tsx test/test-normalize-allowed-user.mjs && npx tsx test/test-share-url.mjs && npx tsx test/test-update-sharing-normalization.mjs && npx tsx test/test-sharing-notify-sync.mjs && npx tsx test/test-staged-homes-sweep.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-isolation-decision.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-agent-config.mjs && npx tsx test/test-wrap-command.mjs && npx tsx test/test-credential-staging.mjs && npx tsx test/test-claude-auth.mjs && npx tsx test/test-btw-proxy-env.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-inbox-guard.mjs && npx tsx test/test-auto-topic.mjs && npx tsx test/test-project-info.mjs && npx tsx test/test-agent-types.mjs && npx tsx test/test-transport.mjs && npx tsx test/test-session-update-handlers.mjs && npx tsx test/test-session-scanner.mjs && npx tsx test/test-hypha-client.mjs && npx tsx test/test-hook-settings.mjs && npx tsx test/test-session-service-logic.mjs && npx tsx test/test-daemon-persistence.mjs && npx tsx test/test-detect-isolation.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-backend.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-hook-server.mjs && npx tsx test/test-session-commands.mjs && npx tsx test/test-interactive-console.mjs && npx tsx test/test-session-messages.mjs && npx tsx test/test-session-send-query.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.mjs && npx tsx test/test-machine-list-directory.mjs && npx tsx test/test-service-commands.mjs && npx tsx test/test-supervisor.mjs && npx tsx test/test-supervisor-lock.mjs && node test/test-supervisor-restart.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox-store.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-inbox-cross-machine.mjs && npx tsx test/test-issue-store.mjs && npx tsx test/test-issue-close-gate.mjs && npx tsx test/test-loop-verify.mjs && npx tsx test/test-issue-rpc.mjs && npx tsx test/test-issue-pause.mjs && npx tsx test/test-workflow-store.mjs && npx tsx test/test-workflow-rpc.mjs && npx tsx test/test-workflow-scheduler.mjs && npx tsx test/test-workflow-runs.mjs && npx tsx test/test-serve-link-subdomain.mjs && npx tsx test/test-short-id.mjs && npx tsx test/test-transcript-edit.mjs && npx tsx test/test-edit-history.mjs && npx tsx test/test-friendly-name.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only && npx tsx test/test-frpc-status.mjs && node test/pinnedClaudeCode.test.mjs && node test/fleet.test.mjs && npx tsx test/test-session-file.mjs && npx tsx test/test-channel-rpc.mjs && npx tsx test/test-wise-agent.mjs && npx tsx test/test-channel-agent.mjs && npx tsx test/test-channels-service.mjs && npx tsx test/test-hotreload-seam.mjs && npx tsx test/test-hot-reload.mjs && npx tsx test/test-hot-reload-live.mjs && npx tsx test/test-session-core.mjs && npx tsx test/test-channel-async-reply.mjs && npx tsx test/test-channel-binding.mjs && npx tsx test/test-channel-identity.mjs && npx tsx test/test-shared-session-identity.mjs && npx tsx test/test-wise-agent-auth.mjs && npx tsx test/test-channel-http.mjs && npx tsx test/test-channel-upload.mjs && npx tsx test/test-wise-voice.mjs && npx tsx test/test-wise-headless.mjs && npx tsx test/test-wise-machine.mjs && npx tsx test/test-crew-merge.mjs && npx tsx test/test-crew-verdict-routing.mjs && npx tsx test/test-crew-standalone.mjs && npx tsx test/test-session-links.mjs && npx tsx test/test-issue-loop-pause-guard.mjs && npx tsx test/test-artifact-guard.mjs",
22
+ test: "npx tsx test/test-context-window.mjs && npx tsx test/test-ratelimit-retry.mjs && npx tsx test/test-instance-config.mjs && npx tsx test/test-authorize.mjs && npx tsx test/test-normalize-allowed-user.mjs && npx tsx test/test-share-url.mjs && npx tsx test/test-update-sharing-normalization.mjs && npx tsx test/test-sharing-notify-sync.mjs && npx tsx test/test-staged-homes-sweep.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-isolation-decision.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-agent-config.mjs && npx tsx test/test-wrap-command.mjs && npx tsx test/test-credential-staging.mjs && npx tsx test/test-claude-auth.mjs && npx tsx test/test-btw-proxy-env.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-inbox-guard.mjs && npx tsx test/test-auto-topic.mjs && npx tsx test/test-project-info.mjs && npx tsx test/test-agent-types.mjs && npx tsx test/test-transport.mjs && npx tsx test/test-session-update-handlers.mjs && npx tsx test/test-session-scanner.mjs && npx tsx test/test-hypha-client.mjs && npx tsx test/test-hook-settings.mjs && npx tsx test/test-session-service-logic.mjs && npx tsx test/test-daemon-persistence.mjs && npx tsx test/test-detect-isolation.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-backend.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-hook-server.mjs && npx tsx test/test-session-commands.mjs && npx tsx test/test-interactive-console.mjs && npx tsx test/test-session-messages.mjs && npx tsx test/test-session-send-query.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.mjs && npx tsx test/test-machine-list-directory.mjs && npx tsx test/test-service-commands.mjs && npx tsx test/test-supervisor.mjs && npx tsx test/test-supervisor-lock.mjs && node test/test-supervisor-restart.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox-store.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-inbox-cross-machine.mjs && npx tsx test/test-issue-store.mjs && npx tsx test/test-issue-close-gate.mjs && npx tsx test/test-loop-verify.mjs && npx tsx test/test-issue-rpc.mjs && npx tsx test/test-issue-pause.mjs && npx tsx test/test-workflow-store.mjs && npx tsx test/test-workflow-rpc.mjs && npx tsx test/test-workflow-scheduler.mjs && npx tsx test/test-cron.mjs && npx tsx test/test-workflow-runs.mjs && npx tsx test/test-serve-link-subdomain.mjs && npx tsx test/test-short-id.mjs && npx tsx test/test-transcript-edit.mjs && npx tsx test/test-edit-history.mjs && npx tsx test/test-friendly-name.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only && npx tsx test/test-frpc-status.mjs && node test/pinnedClaudeCode.test.mjs && node test/fleet.test.mjs && npx tsx test/test-session-file.mjs && npx tsx test/test-channel-rpc.mjs && npx tsx test/test-wise-agent.mjs && npx tsx test/test-channel-agent.mjs && npx tsx test/test-channels-service.mjs && npx tsx test/test-hotreload-seam.mjs && npx tsx test/test-hot-reload.mjs && npx tsx test/test-hot-reload-live.mjs && npx tsx test/test-session-core.mjs && npx tsx test/test-channel-async-reply.mjs && npx tsx test/test-channel-binding.mjs && npx tsx test/test-channel-identity.mjs && npx tsx test/test-shared-session-identity.mjs && npx tsx test/test-wise-agent-auth.mjs && npx tsx test/test-channel-http.mjs && npx tsx test/test-channel-upload.mjs && npx tsx test/test-wise-voice.mjs && npx tsx test/test-wise-headless.mjs && npx tsx test/test-wise-machine.mjs && npx tsx test/test-crew-merge.mjs && npx tsx test/test-crew-verdict-routing.mjs && npx tsx test/test-crew-standalone.mjs && npx tsx test/test-session-links.mjs && npx tsx test/test-issue-loop-pause-guard.mjs && npx tsx test/test-artifact-guard.mjs && npx tsx test/test-graceful-restart.mjs",
23
23
  "test:hypha": "node --no-warnings test/test-hypha-service.mjs",
24
24
  dev: "tsx src/cli.ts",
25
25
  "dev:daemon": "tsx src/cli.ts daemon start-sync",
@@ -1,4 +1,4 @@
1
- import { m as resolveProjectRoot, u as updateIssue, n as getIssue, o as resumeIssue, p as pauseIssue, q as addComment, t as addIssue, v as listIssues, w as searchIssues, x as isVisibleTo } from './run-BgPz-Shl.mjs';
1
+ import { m as resolveProjectRoot, u as updateIssue, n as getIssue, o as resumeIssue, p as pauseIssue, q as addComment, t as addIssue, v as listIssues, w as searchIssues, x as isVisibleTo } from './run-1BBi0_ol.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -1,6 +1,6 @@
1
- import { m as resolveProjectRoot } from './run-BgPz-Shl.mjs';
1
+ import { m as resolveProjectRoot } from './run-1BBi0_ol.mjs';
2
2
  import { g as getWorkflow, s as setWorkflowEnabled, r as removeWorkflow, a as saveWorkflow, b as rawWorkflow, l as listWorkflows } from './store-BTs0H_y0.mjs';
3
- import { g as getRun, l as listRuns, r as runWorkflow } from './runStore-BKgHQGNO.mjs';
3
+ import { g as getRun, l as listRuns, r as runWorkflow } from './runStore-C5IyfbON.mjs';
4
4
  import 'os';
5
5
  import 'fs/promises';
6
6
  import 'fs';
@@ -1201,10 +1201,15 @@ function parseField(token, [min, max]) {
1201
1201
  for (let i = min; i <= max; i++) set.add(i);
1202
1202
  } else if (m = part.match(/^\*\/(\d+)$/)) {
1203
1203
  const s = +m[1];
1204
+ if (s <= 0) throw new Error(`invalid cron step (must be >= 1): "${token}"`);
1204
1205
  for (let i = min; i <= max; i += s) set.add(i);
1205
1206
  } else if (m = part.match(/^(\d+)-(\d+)\/(\d+)$/)) {
1206
- for (let i = +m[1]; i <= +m[2]; i += +m[3]) set.add(i);
1207
+ const s = +m[3];
1208
+ if (s <= 0) throw new Error(`invalid cron step (must be >= 1): "${token}"`);
1209
+ if (+m[1] > +m[2]) throw new Error(`invalid cron range (start > end): "${token}"`);
1210
+ for (let i = +m[1]; i <= +m[2]; i += s) set.add(i);
1207
1211
  } else if (m = part.match(/^(\d+)-(\d+)$/)) {
1212
+ if (+m[1] > +m[2]) throw new Error(`invalid cron range (start > end): "${token}"`);
1208
1213
  for (let i = +m[1]; i <= +m[2]; i++) set.add(i);
1209
1214
  } else if (m = part.match(/^(\d+)$/)) {
1210
1215
  set.add(+m[1]);
@@ -2991,7 +2996,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
2991
2996
  const tunnels = handlers.tunnels;
2992
2997
  if (!tunnels) throw new Error("Tunnel management not available");
2993
2998
  if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
2994
- const { FrpcTunnel } = await import('./frpc-Dp9dDwDE.mjs');
2999
+ const { FrpcTunnel } = await import('./frpc-Di-y4cWK.mjs');
2995
3000
  const tunnel = new FrpcTunnel({
2996
3001
  name: params.name,
2997
3002
  ports: params.ports,
@@ -3442,7 +3447,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
3442
3447
  }
3443
3448
  const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
3444
3449
  const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
3445
- const { toolsForRole } = await import('./sideband-l39JbmHM.mjs');
3450
+ const { toolsForRole } = await import('./sideband-BqD4-WWR.mjs');
3446
3451
  const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
3447
3452
  return fmt(r2);
3448
3453
  }
@@ -3541,7 +3546,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
3541
3546
  if (r.error || !r.sender) return { error: r.error || "unauthorized" };
3542
3547
  const callId = "call_" + Math.random().toString(16).slice(2, 12);
3543
3548
  const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
3544
- const { queryCore } = await import('./commands-CnVo5S8W.mjs');
3549
+ const { queryCore } = await import('./commands-vCeWXvYJ.mjs');
3545
3550
  const timeout = c.reply?.timeout_sec || 120;
3546
3551
  let result;
3547
3552
  try {
@@ -3748,15 +3753,7 @@ function writeInboundContext(sessionId, ctx) {
3748
3753
  mkdirSync(join(SVAMP_HOME$2, "inbound"), { recursive: true });
3749
3754
  const tmp = p + ".tmp";
3750
3755
  writeFileSync(tmp, JSON.stringify({ ...ctx, deliveredAt: Date.now() }));
3751
- try {
3752
- rmSync(p, { force: true });
3753
- } catch {
3754
- }
3755
- writeFileSync(p, readFileSync(tmp));
3756
- try {
3757
- rmSync(tmp, { force: true });
3758
- } catch {
3759
- }
3756
+ renameSync(tmp, p);
3760
3757
  } catch {
3761
3758
  }
3762
3759
  }
@@ -5402,7 +5399,7 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
5402
5399
  },
5403
5400
  // ── BTW (By The Way) — ephemeral side-channel ──
5404
5401
  btw: async (question, context) => {
5405
- authorizeRequest(context, metadata.sharing, "view");
5402
+ authorizeRequest(context, metadata.sharing, "admin");
5406
5403
  if (!question || typeof question !== "string") {
5407
5404
  return { success: false, error: "Question is required" };
5408
5405
  }
@@ -11000,7 +10997,7 @@ const HOUR_MS = 36e5;
11000
10997
  function sumUsageTokens(usage) {
11001
10998
  if (!usage || typeof usage !== "object") return 0;
11002
10999
  const n = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0;
11003
- return n(usage.input_tokens) + n(usage.output_tokens) + n(usage.cache_creation_input_tokens);
11000
+ return n(usage.input_tokens) + n(usage.output_tokens) + n(usage.cache_creation_input_tokens) + Math.round(n(usage.cache_read_input_tokens) * 0.1);
11004
11001
  }
11005
11002
  function accumulateLedger(prev, sample) {
11006
11003
  const base = prev && typeof prev === "object" ? { turns: prev.turns || 0, tokens: prev.tokens || 0, window: Array.isArray(prev.window) ? prev.window : [] } : { turns: 0, tokens: 0, window: [] };
@@ -11855,6 +11852,30 @@ const SVAMP_HOME = process.env.SVAMP_HOME || join$1(os$1.homedir(), ".svamp");
11855
11852
  const DAEMON_STATE_FILE = join$1(SVAMP_HOME, "daemon.state.json");
11856
11853
  const DAEMON_LOCK_FILE = join$1(SVAMP_HOME, "daemon.lock");
11857
11854
  const DAEMON_STOP_MARKER_FILE = join$1(SVAMP_HOME, "daemon.stop");
11855
+ const PENDING_RESTART_FILE = join$1(SVAMP_HOME, "pending-restart.json");
11856
+ function writePendingRestart(p) {
11857
+ try {
11858
+ if (!existsSync$1(SVAMP_HOME)) mkdirSync$1(SVAMP_HOME, { recursive: true });
11859
+ const rec = { scheduledAt: Date.now(), ...p };
11860
+ writeFileSync$1(PENDING_RESTART_FILE, JSON.stringify(rec), "utf-8");
11861
+ } catch {
11862
+ }
11863
+ }
11864
+ function readPendingRestart() {
11865
+ try {
11866
+ if (!existsSync$1(PENDING_RESTART_FILE)) return null;
11867
+ const rec = JSON.parse(readFileSync$1(PENDING_RESTART_FILE, "utf-8"));
11868
+ return rec && typeof rec.scheduledAt === "number" ? rec : null;
11869
+ } catch {
11870
+ return null;
11871
+ }
11872
+ }
11873
+ function clearPendingRestart() {
11874
+ try {
11875
+ if (existsSync$1(PENDING_RESTART_FILE)) unlinkSync$1(PENDING_RESTART_FILE);
11876
+ } catch {
11877
+ }
11878
+ }
11858
11879
  function writeStopMarker(reason) {
11859
11880
  try {
11860
11881
  writeFileSync$1(DAEMON_STOP_MARKER_FILE, `${(/* @__PURE__ */ new Date()).toISOString()} ${reason}
@@ -12831,7 +12852,7 @@ async function startDaemon(options) {
12831
12852
  saveExposedTunnels(list);
12832
12853
  }
12833
12854
  async function createExposedTunnel(spec) {
12834
- const { FrpcTunnel } = await import('./frpc-Dp9dDwDE.mjs');
12855
+ const { FrpcTunnel } = await import('./frpc-Di-y4cWK.mjs');
12835
12856
  const tunnel = new FrpcTunnel({
12836
12857
  name: spec.name,
12837
12858
  ports: spec.ports,
@@ -12851,7 +12872,7 @@ async function startDaemon(options) {
12851
12872
  return tunnel;
12852
12873
  }
12853
12874
  const tunnelRecreateState = /* @__PURE__ */ new Map();
12854
- const { ServeManager } = await import('./serveManager-3zR_v1Xj.mjs');
12875
+ const { ServeManager } = await import('./serveManager-BZtpClpC.mjs');
12855
12876
  const serveManager = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
12856
12877
  ensureAutoInstalledSkills(logger).catch(() => {
12857
12878
  });
@@ -14876,11 +14897,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
14876
14897
  });
14877
14898
  },
14878
14899
  onIssue: async (params) => {
14879
- const { issueRpc } = await import('./rpc-CdbgL61q.mjs');
14900
+ const { issueRpc } = await import('./rpc-BncvIQMO.mjs');
14880
14901
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
14881
14902
  },
14882
14903
  onWorkflow: async (params) => {
14883
- const { workflowRpc } = await import('./rpc-Wp_ufnPT.mjs');
14904
+ const { workflowRpc } = await import('./rpc-DbY7NUhj.mjs');
14884
14905
  return workflowRpc(params?.cwd || directory, params || {});
14885
14906
  },
14886
14907
  onRipgrep: async (args, cwd) => {
@@ -15413,11 +15434,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
15413
15434
  });
15414
15435
  },
15415
15436
  onIssue: async (params) => {
15416
- const { issueRpc } = await import('./rpc-CdbgL61q.mjs');
15437
+ const { issueRpc } = await import('./rpc-BncvIQMO.mjs');
15417
15438
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
15418
15439
  },
15419
15440
  onWorkflow: async (params) => {
15420
- const { workflowRpc } = await import('./rpc-Wp_ufnPT.mjs');
15441
+ const { workflowRpc } = await import('./rpc-DbY7NUhj.mjs');
15421
15442
  return workflowRpc(params?.cwd || directory, params || {});
15422
15443
  },
15423
15444
  onRipgrep: async (args, cwd) => {
@@ -16034,7 +16055,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
16034
16055
  const channelHttpPort = Number(process.env.SVAMP_CHANNEL_HTTP_PORT) || 0;
16035
16056
  if (channelHttpPort > 0) {
16036
16057
  try {
16037
- const { createChannelHttpServer } = await import('./httpServer-B1KVQJfm.mjs');
16058
+ const { createChannelHttpServer } = await import('./httpServer-CSMZTpoh.mjs');
16038
16059
  const channelHttpServer = createChannelHttpServer({
16039
16060
  getSessionIds: () => {
16040
16061
  const ids = [];
@@ -16211,6 +16232,61 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
16211
16232
  );
16212
16233
  logger.log(`Restored ${persistedSessions.length} session(s) in ${Date.now() - t0}ms (concurrency ${restoreConcurrency})`);
16213
16234
  }
16235
+ {
16236
+ const anySessionBusy = () => {
16237
+ let n = 0;
16238
+ for (const s of pidToTrackedSession.values()) {
16239
+ if (s.stopped || !s.hyphaService) continue;
16240
+ try {
16241
+ if (s.hyphaService.getMetadata()?.lifecycleState === "running") n++;
16242
+ } catch {
16243
+ }
16244
+ }
16245
+ return { busy: n > 0, n };
16246
+ };
16247
+ const triggerSelfRestart = (why) => {
16248
+ logger.log(`[graceful-restart] ${why} \u2014 restarting daemon now`);
16249
+ clearPendingRestart();
16250
+ try {
16251
+ const supPidFile = join$1(SVAMP_HOME, "supervisor.pid");
16252
+ const supPid = existsSync$1(supPidFile) ? parseInt(readFileSync$1(supPidFile, "utf-8").trim(), 10) : NaN;
16253
+ if (supPid && !isNaN(supPid)) {
16254
+ process.kill(supPid, "SIGUSR2");
16255
+ return;
16256
+ }
16257
+ } catch {
16258
+ }
16259
+ logger.log("[graceful-restart] no supervisor to signal \u2014 exiting for KeepAlive respawn");
16260
+ process.exit(0);
16261
+ };
16262
+ let lastDeferLog = 0;
16263
+ const GRACEFUL_RESTART_POLL_MS = 15e3;
16264
+ const DEFAULT_MAX_WAIT_MS = 60 * 60 * 1e3;
16265
+ const gracefulTimer = setInterval(() => {
16266
+ const pending = readPendingRestart();
16267
+ if (!pending) return;
16268
+ if (pending.force) {
16269
+ triggerSelfRestart("force requested");
16270
+ return;
16271
+ }
16272
+ const { busy, n } = anySessionBusy();
16273
+ if (!busy) {
16274
+ triggerSelfRestart("all sessions idle");
16275
+ return;
16276
+ }
16277
+ const maxWait = typeof pending.maxWaitMs === "number" && pending.maxWaitMs > 0 ? pending.maxWaitMs : DEFAULT_MAX_WAIT_MS;
16278
+ if (Date.now() - pending.scheduledAt > maxWait) {
16279
+ triggerSelfRestart(`max-wait ${Math.round(maxWait / 6e4)}m elapsed (${n} still busy)`);
16280
+ return;
16281
+ }
16282
+ const now = Date.now();
16283
+ if (now - lastDeferLog > 6e4) {
16284
+ lastDeferLog = now;
16285
+ logger.log(`[graceful-restart] deferring \u2014 ${n} session(s) actively thinking`);
16286
+ }
16287
+ }, GRACEFUL_RESTART_POLL_MS);
16288
+ gracefulTimer.unref?.();
16289
+ }
16214
16290
  if (sessionsToAutoContinue.length > 0 && !options?.noAutoContinue) {
16215
16291
  logger.log(`Auto-continuing ${sessionsToAutoContinue.length} interrupted session(s)...`);
16216
16292
  for (const sessionId of sessionsToAutoContinue) {
@@ -16294,7 +16370,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
16294
16370
  const PING_TIMEOUT_MS = 15e3;
16295
16371
  const POST_RECONNECT_GRACE_MS = 2e4;
16296
16372
  const RECONNECT_JITTER_MS = 2500;
16297
- const { WorkflowScheduler } = await import('./scheduler-BUo_8paQ.mjs');
16373
+ const { WorkflowScheduler } = await import('./scheduler-BkXOKjHn.mjs');
16298
16374
  const workflowScheduler = new WorkflowScheduler({
16299
16375
  projectRoots: () => {
16300
16376
  const dirs = /* @__PURE__ */ new Set();
@@ -16864,12 +16940,17 @@ function daemonStatus() {
16864
16940
  var run = /*#__PURE__*/Object.freeze({
16865
16941
  __proto__: null,
16866
16942
  DAEMON_STOP_MARKER_FILE: DAEMON_STOP_MARKER_FILE,
16943
+ PENDING_RESTART_FILE: PENDING_RESTART_FILE,
16944
+ clearPendingRestart: clearPendingRestart,
16867
16945
  clearStopMarker: clearStopMarker,
16868
16946
  daemonStatus: daemonStatus,
16947
+ isDaemonAlive: isDaemonAlive,
16948
+ readPendingRestart: readPendingRestart,
16869
16949
  restartDaemon: restartDaemon,
16870
16950
  startDaemon: startDaemon,
16871
16951
  stopDaemon: stopDaemon,
16872
16952
  stopMarkerExists: stopMarkerExists,
16953
+ writePendingRestart: writePendingRestart,
16873
16954
  writeStopMarker: writeStopMarker
16874
16955
  });
16875
16956
 
@@ -1,4 +1,4 @@
1
- import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import { a4 as applyClaudeProxyEnv, a5 as composeSessionId, a6 as generateFriendlyName, c as connectToHypha, a as createSessionStore, r as registerMachineService, a7 as generateHookSettings } from './run-BgPz-Shl.mjs';
1
+ import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import { a4 as applyClaudeProxyEnv, a5 as composeSessionId, a6 as generateFriendlyName, c as connectToHypha, a as createSessionStore, r as registerMachineService, a7 as generateHookSettings } from './run-1BBi0_ol.mjs';
2
2
  import os from 'node:os';
3
3
  import { resolve, join } from 'node:path';
4
4
  import { existsSync, readFileSync, watch } from 'node:fs';
@@ -3,7 +3,7 @@ import { existsSync, readFileSync, mkdirSync, writeFileSync, renameSync } from '
3
3
  import { randomBytes } from 'node:crypto';
4
4
  import { join } from 'node:path';
5
5
  import { c as workflowSteps, d as workflowsDir } from './store-BTs0H_y0.mjs';
6
- import { v as listIssues, t as addIssue } from './run-BgPz-Shl.mjs';
6
+ import { v as listIssues, t as addIssue } from './run-1BBi0_ol.mjs';
7
7
 
8
8
  const MAX_RUNS_PER_WORKFLOW = 50;
9
9
  function maxRunsPerWorkflow() {
@@ -1,6 +1,6 @@
1
- import { m as resolveProjectRoot, y as cronMatches } from './run-BgPz-Shl.mjs';
1
+ import { m as resolveProjectRoot, y as cronMatches } from './run-1BBi0_ol.mjs';
2
2
  import { l as listWorkflows, i as isWorkflowEnabled, w as workflowCrons } from './store-BTs0H_y0.mjs';
3
- import { r as runWorkflow } from './runStore-BKgHQGNO.mjs';
3
+ import { r as runWorkflow } from './runStore-C5IyfbON.mjs';
4
4
  import 'os';
5
5
  import 'fs/promises';
6
6
  import 'fs';
@@ -38,8 +38,18 @@ class WorkflowScheduler {
38
38
  // Wall-clock minute we last processed, so multiple sub-minute ticks fire a workflow at most once
39
39
  // per minute (cron granularity is one minute).
40
40
  lastMinuteKey = null;
41
+ // Epoch ms of the last tick we processed — used to detect and catch up wall-clock minutes that were
42
+ // SKIPPED between two ticks (host sleep / suspend / a >60s event-loop stall), since setInterval does
43
+ // not fire while the process is suspended (#0243).
44
+ lastTickMs = null;
41
45
  // In-flight runs (fire-and-forget). `idle()` awaits them — used by tests; harmless in prod.
42
46
  pending = /* @__PURE__ */ new Set();
47
+ // Default cap on how many skipped minutes we look back over on a single tick. Deliberately small: it
48
+ // rescues genuine short gaps (a >60s event-loop stall, a brief suspend) without resurrecting daily
49
+ // crons hours late after a long sleep — multi-hour catch-up needs persisted last-fire state, out of
50
+ // scope here. Per-workflow dedup means even an every-minute cron fires at most once per sweep (no
51
+ // stampede). Override via SVAMP_SCHEDULER_MAX_CATCHUP_MIN (0 disables catch-up entirely).
52
+ static DEFAULT_MAX_CATCHUP_MINUTES = 5;
43
53
  /** Await all in-flight scheduled runs (test helper; the daemon never needs to block on these). */
44
54
  async idle() {
45
55
  await Promise.all([...this.pending]);
@@ -47,11 +57,27 @@ class WorkflowScheduler {
47
57
  static minuteKey(d) {
48
58
  return `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}-${d.getHours()}-${d.getMinutes()}`;
49
59
  }
50
- /** Fire all scheduled workflows whose cron matches `now`. Returns the names fired (for tests/logs). */
60
+ static maxCatchupMinutes() {
61
+ const raw = Number(process.env.SVAMP_SCHEDULER_MAX_CATCHUP_MIN);
62
+ if (Number.isFinite(raw) && raw >= 0) return Math.min(Math.floor(raw), 1440);
63
+ return WorkflowScheduler.DEFAULT_MAX_CATCHUP_MINUTES;
64
+ }
65
+ /** Fire all scheduled workflows whose cron matches `now` (or any skipped minute since the last tick).
66
+ * Returns the names fired (for tests/logs). */
51
67
  tick(now) {
52
68
  const key = WorkflowScheduler.minuteKey(now);
53
69
  if (key === this.lastMinuteKey) return [];
70
+ const nowMs = now.getTime();
71
+ const curBoundary = Math.floor(nowMs / 6e4) * 6e4;
72
+ const minutes = [];
73
+ if (this.lastTickMs != null) {
74
+ const earliest = nowMs - WorkflowScheduler.maxCatchupMinutes() * 6e4;
75
+ let t = Math.floor(Math.max(this.lastTickMs + 6e4, earliest) / 6e4) * 6e4;
76
+ for (; t < curBoundary; t += 6e4) minutes.push(new Date(t));
77
+ }
78
+ minutes.push(now);
54
79
  this.lastMinuteKey = key;
80
+ this.lastTickMs = nowMs;
55
81
  const fired = [];
56
82
  const seen = /* @__PURE__ */ new Set();
57
83
  for (const dir of this.deps.projectRoots()) {
@@ -73,7 +99,7 @@ class WorkflowScheduler {
73
99
  if (!isWorkflowEnabled(wf)) continue;
74
100
  const crons = workflowCrons(wf);
75
101
  if (!crons.length) continue;
76
- if (crons.some((c) => cronMatchesSafe(c, now))) {
102
+ if (minutes.some((m) => crons.some((c) => cronMatchesSafe(c, m)))) {
77
103
  this.fire(root, wf);
78
104
  fired.push(wf.name);
79
105
  }
@@ -54,7 +54,7 @@ async function handleServeCommand() {
54
54
  }
55
55
  }
56
56
  async function serveAdd(args, machineId) {
57
- const { connectAndGetMachine } = await import('./commands-CnVo5S8W.mjs');
57
+ const { connectAndGetMachine } = await import('./commands-vCeWXvYJ.mjs');
58
58
  const pos = positionalArgs(args);
59
59
  const name = pos[0];
60
60
  if (!name) {
@@ -93,7 +93,7 @@ async function serveAdd(args, machineId) {
93
93
  }
94
94
  }
95
95
  async function serveApply(args, machineId) {
96
- const { connectAndGetMachine } = await import('./commands-CnVo5S8W.mjs');
96
+ const { connectAndGetMachine } = await import('./commands-vCeWXvYJ.mjs');
97
97
  const fs = await import('fs');
98
98
  const yaml = await import('yaml');
99
99
  const file = positionalArgs(args)[0];
@@ -182,7 +182,7 @@ async function serveApply(args, machineId) {
182
182
  }
183
183
  }
184
184
  async function serveRemove(args, machineId) {
185
- const { connectAndGetMachine } = await import('./commands-CnVo5S8W.mjs');
185
+ const { connectAndGetMachine } = await import('./commands-vCeWXvYJ.mjs');
186
186
  const pos = positionalArgs(args);
187
187
  const name = pos[0];
188
188
  if (!name) {
@@ -202,7 +202,7 @@ async function serveRemove(args, machineId) {
202
202
  }
203
203
  }
204
204
  async function serveList(args, machineId) {
205
- const { connectAndGetMachine } = await import('./commands-CnVo5S8W.mjs');
205
+ const { connectAndGetMachine } = await import('./commands-vCeWXvYJ.mjs');
206
206
  const all = hasFlag(args, "--all", "-a");
207
207
  const json = hasFlag(args, "--json");
208
208
  const sessionId = getFlag(args, "--session");
@@ -235,7 +235,7 @@ async function serveList(args, machineId) {
235
235
  }
236
236
  }
237
237
  async function serveInfo(machineId) {
238
- const { connectAndGetMachine } = await import('./commands-CnVo5S8W.mjs');
238
+ const { connectAndGetMachine } = await import('./commands-vCeWXvYJ.mjs');
239
239
  const { machine, server } = await connectAndGetMachine(machineId);
240
240
  try {
241
241
  const info = await machine.serveInfo();