squadrant 0.16.1 → 0.16.2

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/index.js CHANGED
@@ -1950,7 +1950,7 @@ async function appendToMailbox(opts) {
1950
1950
  }));
1951
1951
  }
1952
1952
  async function appendCaptainMessage(opts) {
1953
- await appendEntry(opts.stateRoot, opts.project, (seq) => ({
1953
+ return appendEntry(opts.stateRoot, opts.project, (seq) => ({
1954
1954
  seq,
1955
1955
  ts: (/* @__PURE__ */ new Date()).toISOString(),
1956
1956
  kind: "captain.message",
@@ -1978,6 +1978,18 @@ async function readCursor(opts) {
1978
1978
  return null;
1979
1979
  }
1980
1980
  }
1981
+ async function waitForCaptainDelivery(opts) {
1982
+ const subscriber = opts.subscriber ?? "captain";
1983
+ const deadline = Date.now() + opts.timeoutMs;
1984
+ for (; ; ) {
1985
+ const cursor = await readCursor({ stateRoot: opts.stateRoot, project: opts.project, subscriber });
1986
+ if (cursor && cursor.lastAckedSeq >= opts.seq)
1987
+ return true;
1988
+ if (Date.now() >= deadline)
1989
+ return false;
1990
+ await new Promise((r) => setTimeout(r, opts.pollMs));
1991
+ }
1992
+ }
1981
1993
  async function writeCursor(opts) {
1982
1994
  await fs9.mkdir(inboxDir(opts.stateRoot), { recursive: true });
1983
1995
  const dest = cursorPath(opts.stateRoot, opts.project, opts.subscriber);
@@ -2389,6 +2401,9 @@ function reconcileLiveness(prev, next) {
2389
2401
  }
2390
2402
  if (next.startedAt >= prev.startedAt || next.lastState === "end")
2391
2403
  return next;
2404
+ const prevAlive = prev.lastState === "start" && prev.pidAlive;
2405
+ if (!prevAlive && next.lastState === "start" && next.pidAlive)
2406
+ return next;
2392
2407
  return prev;
2393
2408
  }
2394
2409
  var CREW_STALE_MS, CREW_GONE_MS, TERMINAL;
@@ -3394,9 +3409,15 @@ async function runLivenessTick(deps) {
3394
3409
  return;
3395
3410
  }
3396
3411
  const seen = /* @__PURE__ */ new Set();
3412
+ const knownCaptainSessions = /* @__PURE__ */ new Map();
3413
+ for (const e of deps.registry.all()) {
3414
+ if (e.role === "captain")
3415
+ knownCaptainSessions.set(e.sessionId, e.project);
3416
+ }
3397
3417
  const byProject = /* @__PURE__ */ new Map();
3398
3418
  for (const r of records) {
3399
- if (r.role !== "captain")
3419
+ const role = r.role === "captain" || knownCaptainSessions.get(r.sessionId) === r.project ? "captain" : r.role;
3420
+ if (role !== "captain")
3400
3421
  continue;
3401
3422
  let arr = byProject.get(r.project);
3402
3423
  if (!arr) {
@@ -3428,10 +3449,14 @@ async function runLivenessTick(deps) {
3428
3449
  logEntry(deps.log, project, deps.registry.get(project));
3429
3450
  }
3430
3451
  for (const e of deps.registry.all()) {
3431
- if (e.role === "captain" && e.lastState === "start" && !seen.has(e.project)) {
3432
- deps.registry.markEnded(e.project, now);
3433
- logEntry(deps.log, e.project, deps.registry.get(e.project));
3452
+ if (e.role !== "captain" || e.lastState !== "start" || seen.has(e.project))
3453
+ continue;
3454
+ if (e.pid == null || deps.isPidAlive(e.pid)) {
3455
+ deps.log?.(`[${e.role}/runtime] ${e.project} pid=${e.pid} missing from snapshot but not confirmed dead \u2014 leaving alive`);
3456
+ continue;
3434
3457
  }
3458
+ deps.registry.markEnded(e.project, now);
3459
+ logEntry(deps.log, e.project, deps.registry.get(e.project));
3435
3460
  }
3436
3461
  if (deps.reap) {
3437
3462
  for (const e of deps.registry.all()) {
@@ -5882,8 +5907,7 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps) {
5882
5907
  throw new Error(blockedByModalMessage());
5883
5908
  }
5884
5909
  if (!delivered) {
5885
- process.stderr.write(`\u26A0\uFE0F Message not delivered to crew '${name}' \u2014 use 'squadrant crew send ${project} ${name}' to re-send.
5886
- `);
5910
+ throw new Error(`Message not delivered to crew '${name}' \u2014 the paste/submit could not be confirmed. Re-send with 'squadrant crew send ${project} ${name}'.`);
5887
5911
  }
5888
5912
  }
5889
5913
  async function runCrewRead(project, name, runtime, workspaceId) {
@@ -6144,6 +6168,7 @@ __export(dist_exports2, {
6144
6168
  topicKey: () => topicKey,
6145
6169
  topicName: () => topicName,
6146
6170
  tryAcquireDaemonLock: () => tryAcquireDaemonLock,
6171
+ waitForCaptainDelivery: () => waitForCaptainDelivery,
6147
6172
  waitForWarmup: () => waitForWarmup,
6148
6173
  writeCursor: () => writeCursor,
6149
6174
  writeTelegramConfig: () => writeTelegramConfig
@@ -13767,7 +13792,9 @@ runtimeCommand.command("status").description("Print 'running' or 'stopped' for a
13767
13792
  process.exit(2);
13768
13793
  }
13769
13794
  });
13770
- async function runRuntimeSend(arg1, arg2, opts) {
13795
+ var SEND_CONFIRM_TIMEOUT_MS = 15e3;
13796
+ var SEND_CONFIRM_POLL_MS = 500;
13797
+ async function runRuntimeSend(arg1, arg2, opts, confirmOpts) {
13771
13798
  const config = loadConfig();
13772
13799
  const registry = buildRegistry();
13773
13800
  if (opts.command && arg2 !== void 0) {
@@ -13777,7 +13804,7 @@ async function runRuntimeSend(arg1, arg2, opts) {
13777
13804
  const message = opts.command ? arg1 : arg2;
13778
13805
  if (!message) throw new Error("Message is required");
13779
13806
  const { requireDaemon: requireDaemon2 } = await Promise.resolve().then(() => (init_require_daemon(), require_daemon_exports));
13780
- const { appendCaptainMessage: appendCaptainMessage2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports2));
13807
+ const { appendCaptainMessage: appendCaptainMessage2, waitForCaptainDelivery: waitForCaptainDelivery2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports2));
13781
13808
  await requireDaemon2();
13782
13809
  const resolved = resolveTarget(registry, config, target, !!opts.command);
13783
13810
  await needRef(resolved);
@@ -13785,16 +13812,30 @@ async function runRuntimeSend(arg1, arg2, opts) {
13785
13812
  const { join: join30, dirname: dirname10 } = await import("path");
13786
13813
  const { DEFAULT_CONFIG_PATH: DEFAULT_CONFIG_PATH2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
13787
13814
  const stateRoot = join30(dirname10(DEFAULT_CONFIG_PATH2), "state");
13788
- await appendCaptainMessage2({
13815
+ const seq = await appendCaptainMessage2({
13789
13816
  stateRoot,
13790
13817
  project: finalProject,
13791
13818
  text: message,
13792
13819
  source: "cli"
13793
13820
  });
13821
+ const timeoutMs = confirmOpts?.timeoutMs ?? SEND_CONFIRM_TIMEOUT_MS;
13822
+ const delivered = await waitForCaptainDelivery2({
13823
+ stateRoot,
13824
+ project: finalProject,
13825
+ seq,
13826
+ timeoutMs,
13827
+ pollMs: confirmOpts?.pollMs ?? SEND_CONFIRM_POLL_MS
13828
+ });
13829
+ if (!delivered) {
13830
+ throw new Error(
13831
+ `Message queued for '${finalProject}' (seq=${seq}) but delivery was not confirmed within ${Math.round(timeoutMs / 1e3)}s. It may still be pending \u2014 check with 'squadrant runtime read-screen ${finalProject}${opts.command ? " --command" : ""}'.`
13832
+ );
13833
+ }
13794
13834
  }
13795
13835
  runtimeCommand.command("send").description("Send a message to a target workspace AND commit with Enter. With --command, the first positional is the message.").argument("<arg1>", "Project name, or the message when --command is used").argument("[arg2]", "Message (when target is a project). Omit when using --command.").option("--command", "Target the command workspace").action(async (arg1, arg2, opts) => {
13796
13836
  try {
13797
13837
  await runRuntimeSend(arg1, arg2, opts);
13838
+ console.log(chalk18.green("\u2714 Delivered (confirmed)"));
13798
13839
  } catch (err) {
13799
13840
  console.error(chalk18.red(err.message));
13800
13841
  process.exit(1);