parawatch 0.3.6 → 0.3.7

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/README.md CHANGED
@@ -167,6 +167,8 @@ An agent claims the project the work is for, not the folder it runs in, and clai
167
167
 
168
168
  An approval (`ask --action`) shows the exact command and the text it sends, with Do it and Don't, so the human can say yes without opening the session.
169
169
 
170
+ The human can dismiss a card that no longer matters, with the x on the card in the app or `parawatch dismiss <card> -n "why"`. The card closes without an answer, and the agent that asked gets "dismissed by the human" at its next `wait` or `inbox`.
171
+
170
172
  `parawatch edit <card>` changes a card after the fact: its kind, wording, task, who asked or `--since`. `parawatch goto <card>` opens the session that asked in the Claude desktop app (`--copy` also puts a line about the card on the clipboard, to paste there); sessions outside the desktop app get the `claude --resume` command instead.
171
173
 
172
174
  `wait` prints the answers, and news on linked pull requests, and exits with code 0. Run from an agent session, it reads that session's own cards and pull requests, and the ones no session owns in the project it claimed, with a cursor of its own: four sessions in one project each wake only for their own news. `wait -p <project>` reads the whole project, for a lead. With `--timeout <seconds>` it exits with code 2 when nothing came. An answer given before `wait` starts is not lost: each consumer has its own cursor, and `wait` returns everything after it. While a `wait` runs, cards say an agent is waiting; when none does, an answered card says the answer is kept until the agent next checks.
package/dist/bin/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { execFileSync } from "node:child_process";
3
3
  import fs from "node:fs";
4
4
  import { Command } from "commander";
5
- import { UserError, addProject, answerCard, beatRun, claimSession, closeCard, commentCard, inboxScope, editCard, endRun, hideRun, unhideRun, linkPr, listeners, listenersFor, noteRun, openCard, prEvent, findCard, readInbox, readState, removeProject, startListening, startRun, unlinkPr, waitInbox, } from "../core/ops.js";
5
+ import { UserError, addProject, answerCard, beatRun, claimSession, closeCard, dismissCard, commentCard, inboxScope, editCard, endRun, hideRun, unhideRun, linkPr, listeners, listenersFor, noteRun, openCard, prEvent, findCard, readInbox, readState, removeProject, startListening, startRun, unlinkPr, waitInbox, } from "../core/ops.js";
6
6
  import { githubConfig, readCache, resolvePrRef, syncGithub, prViews } from "../core/github.js";
7
7
  import { digest, digestText, localDate } from "../core/digest.js";
8
8
  import { everywhere, readEvents } from "../core/store.js";
@@ -183,6 +183,11 @@ program
183
183
  .description("close a card once it has been acted on")
184
184
  .option("-n, --note <text>", "what was done")
185
185
  .action((id, o) => closeCard(id, o.note));
186
+ program
187
+ .command("dismiss <card>")
188
+ .description("close a card that no longer matters, without answering it; the agent that asked is told")
189
+ .option("-n, --note <text>", "why, for the agent")
190
+ .action((id, o) => dismissCard(id, o.note));
186
191
  program
187
192
  .command("show <card>")
188
193
  .description("show one card with its answers")
@@ -187,6 +187,8 @@ export function inboxLine(i) {
187
187
  const head = `[${card.project}] ${card.id} "${card.title}"`;
188
188
  if (i.kind === "comment")
189
189
  return `${head} comment: ${i.comment}`;
190
+ if (i.kind === "dismissed")
191
+ return `${head} dismissed by the human: no longer needed, do not act on it${i.comment ? `; said: ${i.comment}` : ""}`;
190
192
  const parts = [];
191
193
  if (i.choice !== undefined)
192
194
  parts.push(`chose ${i.choice}${i.choiceText ? ` (${i.choiceText})` : ""}`);
package/dist/core/ops.js CHANGED
@@ -224,6 +224,16 @@ export function closeCard(id, note) {
224
224
  return;
225
225
  appendEvent(() => ({ type: "card.close", card: card.id, note: note?.trim() || undefined }));
226
226
  }
227
+ /**
228
+ * The human drops a card that no longer matters, without answering it. It closes, and the agent
229
+ * that asked reads that it was dismissed (a wait wakes for it), so nothing waits on it forever.
230
+ */
231
+ export function dismissCard(id, note) {
232
+ const card = requireCard(id);
233
+ if (card.status === "closed")
234
+ throw new UserError(`card ${card.id} is already closed`);
235
+ appendEvent(() => ({ type: "card.close", card: card.id, note: note?.trim() || undefined, dismissed: true }));
236
+ }
227
237
  export function startRun(r) {
228
238
  if (r.heartbeat !== undefined && (!Number.isFinite(r.heartbeat) || r.heartbeat <= 0))
229
239
  throw new UserError("--heartbeat must be a number of seconds above 0");
@@ -385,6 +395,18 @@ export function inboxFrom(events, after, filter, state = readState(events)) {
385
395
  out.push({ seq: ev.seq, dev: ev.dev, at: ev.ts, kind: "pr", by: ev.by ?? ev.source, pr: { ref: ev.pr, change: ev.change, title: ev.title, text: ev.text, project: link.project } });
386
396
  continue;
387
397
  }
398
+ if (ev.type === "card.close") {
399
+ // a card the human dismissed reaches the agent that asked, once
400
+ if (!ev.dismissed || !isAfter(ev, pos))
401
+ continue;
402
+ const card = state.cards.get(ev.card);
403
+ if (!card || !inScope(card.session, card.project, filter, ended))
404
+ continue;
405
+ if (filter.card && card.id !== filter.card)
406
+ continue;
407
+ out.push({ seq: ev.seq, dev: ev.dev, at: ev.ts, kind: "dismissed", card, by: HUMAN, comment: ev.note });
408
+ continue;
409
+ }
388
410
  if (ev.type !== "card.answer" && ev.type !== "card.comment")
389
411
  continue;
390
412
  let late = false;
@@ -78,6 +78,8 @@ export function fold(events, map) {
78
78
  c.status = "closed";
79
79
  c.closedAt = ev.ts;
80
80
  c.closeNote = ev.note;
81
+ if (ev.dismissed)
82
+ c.dismissed = true;
81
83
  break;
82
84
  }
83
85
  case "run.start":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "parawatch",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
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",
@@ -93,6 +93,8 @@ The output looks like `[project] c4 "Ship the top-box ending?" chose 2 (Top box)
93
93
  3. Close the card with what you did: `parawatch close c4 -n "Top box shipped in #212"`.
94
94
  4. If more cards are open, or a linked pull request is not merged yet, arm the wait again.
95
95
 
96
+ A card can also come back as `[project] c4 "…" dismissed by the human: no longer needed, do not act on it`. The human closed it without answering. Drop that step, do not open the card again, and go on with the rest of the work.
97
+
96
98
  To add information to a card without waking anyone, use `parawatch comment <card> "<text>"`. Never answer a card yourself: `answer` is the human's.
97
99
 
98
100
  ## 6. Long jobs that leave no agent log