parawatch 0.3.12 → 0.3.14

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 (2) hide show
  1. package/dist/core/ops.js +31 -5
  2. package/package.json +2 -1
package/dist/core/ops.js CHANGED
@@ -423,10 +423,12 @@ export function leadOf(state, ended) {
423
423
  if (!project)
424
424
  return undefined;
425
425
  if (!memo.has(project)) {
426
+ // the newest claim leads; claims keep log order, so a tie in time goes to the later one
426
427
  const leads = state.claims
427
- .filter((c) => c.role === "lead" && c.project === project && !c.done && !ended(c.session))
428
- .sort((a, b) => (b.at ?? "").localeCompare(a.at ?? ""));
429
- memo.set(project, leads[0]?.session);
428
+ .map((c, i) => ({ c, i }))
429
+ .filter(({ c }) => c.role === "lead" && c.project === project && !c.done && !ended(c.session))
430
+ .sort((a, b) => (b.c.at ?? "").localeCompare(a.c.at ?? "") || b.i - a.i);
431
+ memo.set(project, leads[0]?.c.session);
430
432
  }
431
433
  return memo.get(project);
432
434
  };
@@ -719,7 +721,7 @@ export function inboxScope(opts, session) {
719
721
  // A lead waiting on its project from a session: what other live sessions own is theirs. It
720
722
  // takes over the project cursor once, so it does not hear again what that wait delivered.
721
723
  const mine = readCursor(consumer);
722
- const merged = furthest(mine, readCursor(opts.project));
724
+ const merged = furthest(Object.keys(mine).length ? mine : bornAt(session), readCursor(opts.project));
723
725
  if (JSON.stringify(merged) !== JSON.stringify(mine))
724
726
  writeCursor(consumer, merged);
725
727
  return { consumer, filter: { session, project: opts.project } };
@@ -728,12 +730,36 @@ export function inboxScope(opts, session) {
728
730
  // A session that used to wait on its project starts where that wait stopped, so moving to its
729
731
  // own cursor does not bring back news it has already handled.
730
732
  if (claim && !Object.keys(readCursor(consumer)).length) {
731
- const from = readCursor(claim.project);
733
+ const from = furthest(bornAt(session), readCursor(claim.project));
732
734
  if (Object.keys(from).length)
733
735
  writeCursor(consumer, from);
734
736
  }
735
737
  return { consumer, filter: { session, project: claim?.project } };
736
738
  }
739
+ /**
740
+ * Where a session's first wait starts: just before the session first shows in the log (a claim,
741
+ * a card it opened, a pull request linked to it, something handed to it). Nothing before that was
742
+ * its to act on, and a lead starting at the top of the log would hear every old pull request and
743
+ * answer of the project again. A session the log has not seen starts at the top; it has nothing
744
+ * of its own and no project yet.
745
+ */
746
+ export function bornAt(session, events = readEvents()) {
747
+ const i = events.findIndex((ev) => {
748
+ switch (ev.type) {
749
+ case "run.claim":
750
+ return sameSession(ev.claim.session, session);
751
+ case "card.open":
752
+ return sameSession(ev.card.session, session);
753
+ case "pr.link":
754
+ return sameSession(ev.link.session, session);
755
+ case "card.hand":
756
+ return sameSession(ev.session, session);
757
+ default:
758
+ return false;
759
+ }
760
+ });
761
+ return i < 0 ? {} : advance({}, events.slice(0, i));
762
+ }
737
763
  export function readInbox(consumer, filter, move) {
738
764
  const events = readEvents();
739
765
  const pos = readCursor(consumer);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "parawatch",
3
- "version": "0.3.12",
3
+ "version": "0.3.14",
4
4
  "description": "A control tower for people running many AI coding agents in parallel: see what is running, what needs you, and wake the right agent when you answer.",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -48,6 +48,7 @@
48
48
  },
49
49
  "devDependencies": {
50
50
  "@types/node": "^22.10.1",
51
+ "happy-dom": "^21.0.4",
51
52
  "tsx": "^4.19.2",
52
53
  "typescript": "^5.7.2",
53
54
  "vitest": "^2.1.8"