squadrant 0.13.1 → 0.13.3

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,11 @@ 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
+ if (rec.firstTurnConfirmedAt) {
548
+ return { ...rec, lastEvent: "task.progress" };
549
+ }
550
+ return { ...rec, firstTurnConfirmedAt: now, lastEvent: ev.type };
546
551
  case "task.stalled":
547
552
  case "task.idle":
548
553
  case "task.quiet":
@@ -580,6 +585,7 @@ function recoverStall(rec, now) {
580
585
  // packages/core/dist/daemon/reduce.js
581
586
  var DEFAULT_TASK_TIMEOUT_MS = 8 * 60 * 60 * 1e3;
582
587
  var TERMINAL_RECORD_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
588
+ var TERMINAL_RECORD_KEEP_PER_PROJECT = 20;
583
589
  var ATTENTION_STATES = /* @__PURE__ */ new Set(["done", "blocked", "failed", "stalled", "awaiting-input"]);
584
590
  var REAPABLE_SURFACE_STATES = /* @__PURE__ */ new Set(["working", "stalled", "awaiting-input", "blocked"]);
585
591
  var IDLE_DEBOUNCE_MS = 12e3;
@@ -689,7 +695,9 @@ var KNOWN_EVENT_TYPES = /* @__PURE__ */ new Set([
689
695
  "task.timeout",
690
696
  "task.reconcile-failed",
691
697
  "task.cancelled",
692
- "task.session.ended"
698
+ "task.session.ended",
699
+ "task.first-turn.confirmed"
700
+ // #466: delivery confirmation
693
701
  ]);
694
702
  function createDaemon(deps) {
695
703
  const { store, now } = deps;
@@ -808,6 +816,13 @@ function createDaemon(deps) {
808
816
  async sweep() {
809
817
  const t = now();
810
818
  const surfaceAlive = deps.isSurfaceAlive ?? (async () => "unknown");
819
+ const projects = new Set(store.listAll().map((r) => r.project));
820
+ for (const project of projects) {
821
+ const terminal = store.list(project).filter((r) => TERMINAL_STATES.has(r.state)).sort((a, b) => b.lastHeartbeat - a.lastHeartbeat);
822
+ for (const r of terminal.slice(TERMINAL_RECORD_KEEP_PER_PROJECT)) {
823
+ store.delete(r.project, r.id);
824
+ }
825
+ }
811
826
  for (const r of store.listAll()) {
812
827
  if (TERMINAL_STATES.has(r.state) && t - r.lastHeartbeat > TERMINAL_RECORD_TTL_MS) {
813
828
  store.delete(r.project, r.id);
@@ -822,7 +837,13 @@ function createDaemon(deps) {
822
837
  const msg = `CREW TIMEOUT ${tag}: wall-clock exceeded ${hrs}h (id: ${r.id}, state: ${prevState})`;
823
838
  const synthEvent = { type: "task.timeout", id: r.id, taskTimeoutMs: ceiling };
824
839
  store.put({ ...r, state: "cancelled", lastEvent: "sweep.task-timeout" });
825
- if (deps.notify) {
840
+ let shouldNotify = true;
841
+ if (r.mode === "interactive") {
842
+ const liveness = await surfaceAlive(r);
843
+ if (liveness === "gone")
844
+ shouldNotify = false;
845
+ }
846
+ if (deps.notify && shouldNotify) {
826
847
  try {
827
848
  const p = deps.notify({ project: r.project, message: msg, record: r, event: synthEvent });
828
849
  if (p && typeof p.catch === "function") {
@@ -842,6 +863,25 @@ function createDaemon(deps) {
842
863
  continue;
843
864
  }
844
865
  }
866
+ if (r.mode === "interactive" && r.state === "submitted" && !r.firstTurnConfirmedAt) {
867
+ const elapsed = t - r.lastHeartbeat;
868
+ if (elapsed > r.heartbeatBudgetMs) {
869
+ if (deps.notify && quietNotifiedAt.get(r.id) !== r.lastHeartbeat) {
870
+ quietNotifiedAt.set(r.id, r.lastHeartbeat);
871
+ const tag = crewTag(r);
872
+ const synthEvent = { type: "task.quiet", id: r.id, quietMs: elapsed };
873
+ const message = `\u26A0\uFE0F CREW UNDELIVERED ${tag}: first turn may not have landed (crew never started) \u2014 re-send the task or check the spawn.`;
874
+ try {
875
+ const p = deps.notify({ project: r.project, message, record: r, event: synthEvent });
876
+ if (p && typeof p.catch === "function")
877
+ p.catch(() => {
878
+ });
879
+ } catch {
880
+ }
881
+ }
882
+ continue;
883
+ }
884
+ }
845
885
  const idle = evaluateStall(r, t);
846
886
  if (idle) {
847
887
  store.put(idle);
@@ -856,9 +896,14 @@ function createDaemon(deps) {
856
896
  if (deps.notify && quietNotifiedAt.get(r.id) !== liveness) {
857
897
  quietNotifiedAt.set(r.id, liveness);
858
898
  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
899
  const synthEvent = { type: "task.quiet", id: r.id, quietMs: quiet };
900
+ let message;
901
+ if (!r.firstTurnConfirmedAt) {
902
+ message = `\u26A0\uFE0F CREW UNDELIVERED ${tag}: first turn may not have landed (0 activity) \u2014 re-send the task or check the spawn.`;
903
+ } else {
904
+ const mins = Math.max(1, Math.round(quiet / 6e4));
905
+ message = `CREW QUIET ${tag}: working ~${mins}min with no tool activity \u2014 likely deep thinking (no reply expected yet).`;
906
+ }
862
907
  try {
863
908
  const p = deps.notify({ project: r.project, message, record: r, event: synthEvent });
864
909
  if (p && typeof p.catch === "function")