squadrant 0.13.1 → 0.13.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.
@@ -543,6 +543,8 @@ function reduce(rec, ev, now) {
543
543
  return { ...stampAttempt(base, {}, now), state: "blocked", question: ev.question, pendingTool: void 0 };
544
544
  case "task.reattached":
545
545
  return stampAttempt(base, {}, now);
546
+ case "task.first-turn.confirmed":
547
+ return { ...rec, firstTurnConfirmedAt: now, lastEvent: ev.type };
546
548
  case "task.stalled":
547
549
  case "task.idle":
548
550
  case "task.quiet":
@@ -580,6 +582,7 @@ function recoverStall(rec, now) {
580
582
  // packages/core/dist/daemon/reduce.js
581
583
  var DEFAULT_TASK_TIMEOUT_MS = 8 * 60 * 60 * 1e3;
582
584
  var TERMINAL_RECORD_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
585
+ var TERMINAL_RECORD_KEEP_PER_PROJECT = 20;
583
586
  var ATTENTION_STATES = /* @__PURE__ */ new Set(["done", "blocked", "failed", "stalled", "awaiting-input"]);
584
587
  var REAPABLE_SURFACE_STATES = /* @__PURE__ */ new Set(["working", "stalled", "awaiting-input", "blocked"]);
585
588
  var IDLE_DEBOUNCE_MS = 12e3;
@@ -689,7 +692,9 @@ var KNOWN_EVENT_TYPES = /* @__PURE__ */ new Set([
689
692
  "task.timeout",
690
693
  "task.reconcile-failed",
691
694
  "task.cancelled",
692
- "task.session.ended"
695
+ "task.session.ended",
696
+ "task.first-turn.confirmed"
697
+ // #466: delivery confirmation
693
698
  ]);
694
699
  function createDaemon(deps) {
695
700
  const { store, now } = deps;
@@ -808,6 +813,13 @@ function createDaemon(deps) {
808
813
  async sweep() {
809
814
  const t = now();
810
815
  const surfaceAlive = deps.isSurfaceAlive ?? (async () => "unknown");
816
+ const projects = new Set(store.listAll().map((r) => r.project));
817
+ for (const project of projects) {
818
+ const terminal = store.list(project).filter((r) => TERMINAL_STATES.has(r.state)).sort((a, b) => b.lastHeartbeat - a.lastHeartbeat);
819
+ for (const r of terminal.slice(TERMINAL_RECORD_KEEP_PER_PROJECT)) {
820
+ store.delete(r.project, r.id);
821
+ }
822
+ }
811
823
  for (const r of store.listAll()) {
812
824
  if (TERMINAL_STATES.has(r.state) && t - r.lastHeartbeat > TERMINAL_RECORD_TTL_MS) {
813
825
  store.delete(r.project, r.id);
@@ -822,7 +834,13 @@ function createDaemon(deps) {
822
834
  const msg = `CREW TIMEOUT ${tag}: wall-clock exceeded ${hrs}h (id: ${r.id}, state: ${prevState})`;
823
835
  const synthEvent = { type: "task.timeout", id: r.id, taskTimeoutMs: ceiling };
824
836
  store.put({ ...r, state: "cancelled", lastEvent: "sweep.task-timeout" });
825
- if (deps.notify) {
837
+ let shouldNotify = true;
838
+ if (r.mode === "interactive") {
839
+ const liveness = await surfaceAlive(r);
840
+ if (liveness === "gone")
841
+ shouldNotify = false;
842
+ }
843
+ if (deps.notify && shouldNotify) {
826
844
  try {
827
845
  const p = deps.notify({ project: r.project, message: msg, record: r, event: synthEvent });
828
846
  if (p && typeof p.catch === "function") {
@@ -856,9 +874,14 @@ function createDaemon(deps) {
856
874
  if (deps.notify && quietNotifiedAt.get(r.id) !== liveness) {
857
875
  quietNotifiedAt.set(r.id, liveness);
858
876
  const tag = crewTag(r);
859
- const mins = Math.max(1, Math.round(quiet / 6e4));
860
- const message = `CREW QUIET ${tag}: working ~${mins}min with no tool activity \u2014 likely deep thinking (no reply expected yet).`;
861
877
  const synthEvent = { type: "task.quiet", id: r.id, quietMs: quiet };
878
+ let message;
879
+ if (!r.firstTurnConfirmedAt) {
880
+ message = `\u26A0\uFE0F CREW UNDELIVERED ${tag}: first turn may not have landed (0 activity) \u2014 re-send the task or check the spawn.`;
881
+ } else {
882
+ const mins = Math.max(1, Math.round(quiet / 6e4));
883
+ message = `CREW QUIET ${tag}: working ~${mins}min with no tool activity \u2014 likely deep thinking (no reply expected yet).`;
884
+ }
862
885
  try {
863
886
  const p = deps.notify({ project: r.project, message, record: r, event: synthEvent });
864
887
  if (p && typeof p.catch === "function")