pendingyou 0.18.0 → 0.19.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.19.0 (2026-10-06)
4
+
5
+ - The Stop check lets a turn end on a recap of cards already waiting on you. It asked the agent to go on whenever its
6
+ last message had something for you and the turn posted no card, so a closing line like “Two cards are waiting on
7
+ you: CLI 0.18.0 (publish it) and the Herdr check (does the amber title show in the sidebar?).” was asked to go on,
8
+ and Claude Code showed each of those as a “Stop hook error”. Now:
9
+ - A recap counts as posted, for what it says: a sentence that says its cards wait on you (“Two cards are waiting on
10
+ you: …”, “the card is still open for your answer”, “both cards are in your queue”, “… (both cards).”), or a heading
11
+ that does, or names your cards (“**Cards waiting for you:**”, “Your open cards:”), with the list under it.
12
+ Anything else in the message for you is checked as before, so a new ask beside a recap still goes on a card. Cards
13
+ that aren't said to wait on you (“the two cards line up”, “add a credit card”) are no recap.
14
+ - So does a card said to be coming, or one an item already is: “a card is coming”, “there'll be a card”, “I'll
15
+ create a card”, “that one is already a card”, as “I'll send you a card” and “it's on a card” already did.
16
+ - Anything else for you, in a message that talks about cards (not a credit card, a SIM card or a card number), is
17
+ taken as a recap while this folder has a card waiting on you: one request (this folder's open cards, the one the
18
+ next-message hook makes), made only for such a message and given up after 1.5 seconds. None open: the agent is
19
+ asked to go on, as before. A permission prompt's card doesn't count. When Pending You can't say (offline, slow, an
20
+ older server), the turn ends.
21
+ - Everything else is as before: an ask to type something here, a question handed to the agent, once a turn at most,
22
+ quiet when not signed in, silent on any error, the 3-second deadline. Codex's, OpenCode's and Pi's Stop checks do
23
+ the same, each asking for its own app's cards.
24
+ - Hooks run the copy `init` installed: run `npx pendingyou@latest init` once to switch.
25
+
3
26
  ## 0.18.0 (2026-10-05)
4
27
 
5
28
  - Presence says which terminal: in a Herdr pane (`HERDR_ENV=1` with `HERDR_BIN_PATH` and `HERDR_PANE_ID`), every
package/README.md CHANGED
@@ -205,6 +205,13 @@ can", "waiting on you" and the like; or a list under a heading like "Your part",
205
205
  you" or "Left for you". It lets the turn end when the message says nothing needs a card ("No card needed.", "Nothing
206
206
  needs your attention.", "There's nothing waiting on you.").
207
207
 
208
+ Since 0.19.0 it also lets the turn end on a recap of cards already waiting on you. A sentence that says its cards wait
209
+ on you ("Two cards are waiting on you: …", "… (both cards).") covers what it asks, and so does a heading that does
210
+ ("Cards waiting for you:") over its list; anything else for you in the message is checked as before, so a new ask
211
+ beside a recap still goes on a card. And anything else for you, in a message that talks about cards, is taken as a
212
+ recap while this folder has a card waiting on you: only such a message costs a request (this folder's open cards,
213
+ given up after 1.5 seconds), and when Pending You can't say, the turn ends.
214
+
208
215
  Since 0.9.0 it also catches a message asking you to type or reply something in Claude Code's own chat ("Reply “yes,
209
216
  erase disk12” here", "To confirm, type `yes`", "Confirm here"). Some steps an agent takes only on your own words in
210
217
  its conversation, never on a card answer; that's fine, but then a card has to send you there. Unless a card posted in
package/dist/stopcheck.js CHANGED
@@ -5,17 +5,35 @@ import { APP_NAMES, DEFAULT_APP } from "./apps/ids.js";
5
5
  import { asksToTypeHere, cardFits, confirmReason, sessionLink } from "./confirm.js";
6
6
  import { readCredential } from "./credentials.js";
7
7
  import { isHandedWake } from "./format.js";
8
- import { isPromptApp, settlePrompts, subagentsRunning } from "./permission.js";
8
+ import { isPermissionCard, isPromptApp, settlePrompts, subagentsRunning } from "./permission.js";
9
9
  import { folderForms } from "./pickup.js";
10
10
  export const READ_CAP = 4 * 1024 * 1024;
11
11
  const CHUNK = 64 * 1024;
12
12
  export const STOP_REASON = 'Items for your person are in chat but not in Pending You: post them as cards (ask in both places), or say they don’t need one.';
13
13
  export const DELEGATED_REASON = 'Your person handed you that question to answer, not to ask them back: answer_delegated if you know, or hand_back with what you checked, unless you already did. Never post it as a card of your own.';
14
14
  const POSTING_TOOL = /^mcp__.+__(post_request|update_request|reply_in_thread|answer_delegated|hand_back)$/;
15
- const OPEN_TIME_BOX_MS = 1500;
15
+ export const OPEN_TIME_BOX_MS = 1500;
16
16
  const OPEN_CARDS = 5;
17
17
  const CARD_TEXT = 20_000;
18
- const SAYS_POSTED = /\b(posted|put|added|raised|filed)\b[^.\n]{0,60}\b(cards?|pending ?you)\b|\bon (a|the|its|their|your|that|this) cards?\b|\b(in|to|through|via|on) pending ?you\b|\bpending ?you cards?\b|\b(i|we|you)(?:(?:'|’)ll| will)\s+(?:\w+\s+)?(post|send|put|add|raise|file|get|see|find)\b[^.\n]{0,40}\bcards?\b/i;
18
+ const SAYS_POSTED = new RegExp([
19
+ String.raw `\b(posted|put|added|raised|filed)\b[^.\n]{0,60}\b(cards?|pending ?you)\b`,
20
+ String.raw `\bon (a|the|its|their|your|that|this) cards?\b`,
21
+ String.raw `\b(in|to|through|via|on) pending ?you\b`,
22
+ String.raw `\bpending ?you cards?\b`,
23
+ String.raw `\b(i|we|you)(?:(?:'|’)ll| will)\s+(?:\w+\s+)?(post|send|put|add|raise|file|get|see|find|make|create|open)\b[^.\n]{0,40}\bcards?\b`,
24
+ String.raw `\balready (?:a |on (?:a )?)?cards?\b`,
25
+ String.raw `\bcards? (?:is|are|will be) (?:coming|on (?:its|their|the) way)\b(?! (?:together|along))`,
26
+ String.raw `\bcards? (?:will|to) follow\b`,
27
+ String.raw `\bthere(?:(?:'|’)ll| will) be (?:a |another |one )?cards?\b`,
28
+ ].join('|'), 'i');
29
+ const RECAP = new RegExp([
30
+ String.raw `\bcards?\b[^.!?\n]{0,40}?\b(?:(?:waiting|pending|open|parked|queued|left)\b[^.!?\n]{0,20}?\b(?:on|for)\s+your?|in\s+your\s+queue)\b`,
31
+ String.raw `\b(?:waiting|pending|parked)\s+(?:on|for)\s+you\b[^.!?\n]{0,30}?\b(?:as|in)\s+(?:(?:a|two|both|three|its|their|the|separate)\s+)?cards?\b`,
32
+ String.raw `\((?:(?:both|all|two|three|each)\s+)?(?:(?:are|is|on|as)\s+)?(?:a\s+|(?:its|their)\s+own\s+)?cards?(?:\s+(?:already|too))?\)`,
33
+ ].join('|'), 'i');
34
+ const CARDS_HEADING = /\b(?:your|open|pending|waiting)\s+(?:(?:open|pending|waiting)\s+)?cards\b/i;
35
+ const SENTENCE_END = /(?<=[.!?])\s+/;
36
+ const OTHER_CARDS = /\b(?:credit|debit|payment|gift|sim|sd|business|graphics|video|sound|network|memory|smart)\s+cards?\b|\bcards?\s+(?:numbers?|readers?|details|holders?)\b/gi;
19
37
  const STEPS_FOR_THEM = /\b(you(?:'|’)ll need to|you will need to|you(?:'|’)ll have to|you will have to|your (?:next )?steps?\b|can you|could you|would you mind|please (?:run|confirm|approve|check|add|set|sign|create|open|paste|reply|send|choose|pick|decide|let me know)|in your terminal|only you can|(?<!\b(?:no|nothing|none|not)\b[^.,;:!?\n]{0,40})waiting (?:on|for) you|(?<!\bif (?:it|he|she|they|that|this|anything|something|someone|anyone|one) )needs? you to|over to you|parked for you|when you(?:'|’)re ready|up to you|your call\b|is yours\b|say the word|let me know (?:which|whether)|tell me (?:which|whether)|needs? your (?:ok|okay|approval|decision|sign-?off|go-ahead))/i;
20
38
  const SAYS_NO_CARD = /\bno (?:new |more |further )?cards? (?:is |are )?(?:needed|necessary|required)\b|\b(?:doesn(?:'|’)t|don(?:'|’)t|does not|do not|won(?:'|’)t|will not) need (?:a |any )?(?:new )?cards?\b|\bnothing (?:here |else |more |now )?(?:needs|requires) your (?:attention|input|action|answer)\b|\b(?:nothing|no cards?)\b[^.,;:!?\n]{0,40}\b(?:waiting|pending) (?:on|for) you\b/i;
21
39
  const FOR_THEM_HEADING = /\b(your part|your steps?|your to-?dos?|your turn|your call|for you|waiting on you|left for you|parked for you|needs you|over to you|what you need to do|you(?:'|’)ll need)\b/i;
@@ -47,8 +65,7 @@ function headingOf(line) {
47
65
  return null;
48
66
  return /^\s*#/.test(line) || bare.endsWith(':') || /^\s*\*\*.+\*\*\s*$/.test(line) ? bare : null;
49
67
  }
50
- export function itemsForPerson(text) {
51
- const plain = plainText(text);
68
+ function itemsIn(plain) {
52
69
  const lines = plain.split('\n');
53
70
  for (let index = 0; index < lines.length; index++) {
54
71
  const heading = headingOf(lines[index]);
@@ -65,8 +82,50 @@ export function itemsForPerson(text) {
65
82
  return 'question';
66
83
  return null;
67
84
  }
85
+ export function itemsForPerson(text) {
86
+ return itemsIn(plainText(text));
87
+ }
88
+ function listEnd(lines, index) {
89
+ let end = index;
90
+ for (let next = index + 1; next < lines.length; next++) {
91
+ const line = lines[next];
92
+ if (!line.trim()) {
93
+ if (end === index && next - index >= 3)
94
+ return index;
95
+ }
96
+ else if (LIST_ITEM.test(line) || (end > index && /^\s/.test(line)))
97
+ end = next;
98
+ else
99
+ break;
100
+ }
101
+ return end;
102
+ }
103
+ function withoutRecaps(plain) {
104
+ const lines = plain.split('\n');
105
+ const kept = [];
106
+ for (let index = 0; index < lines.length; index++) {
107
+ const line = lines[index];
108
+ const heading = headingOf(line);
109
+ if (heading && (RECAP.test(heading) || CARDS_HEADING.test(heading))) {
110
+ const end = listEnd(lines, index);
111
+ if (end > index) {
112
+ index = end;
113
+ continue;
114
+ }
115
+ }
116
+ kept.push(line
117
+ .split(SENTENCE_END)
118
+ .filter((sentence) => !RECAP.test(sentence))
119
+ .join(' '));
120
+ }
121
+ return kept.join('\n');
122
+ }
123
+ export function itemsOutsideRecaps(text) {
124
+ return itemsIn(withoutRecaps(plainText(text)));
125
+ }
68
126
  export const saysPosted = (text) => SAYS_POSTED.test(plainText(text));
69
127
  export const saysNoCard = (text) => SAYS_NO_CARD.test(plainText(text));
128
+ export const mentionsCards = (text) => /\bcards?\b/i.test(plainText(text).replace(OTHER_CARDS, ''));
70
129
  function wordsIn(value, max = CARD_TEXT) {
71
130
  const found = [];
72
131
  let left = max;
@@ -306,18 +365,23 @@ export async function stopcheck(io, options) {
306
365
  return 0;
307
366
  return asksToTypeHere(text) || itemsForPerson(text) ? block(DELEGATED_REASON) : 0;
308
367
  }
368
+ const cwd = typeof input.cwd === 'string' && input.cwd ? input.cwd : io.cwd;
309
369
  const ask = asksToTypeHere(text);
310
370
  if (ask) {
311
371
  if (saysNoCard(text) || turn.cards.some((card) => cardFits(card, ask.words, 'posted')))
312
372
  return 0;
313
- const cwd = typeof input.cwd === 'string' && input.cwd ? input.cwd : io.cwd;
314
373
  const open = await openTitles(io, options.origin, cwd, app);
315
374
  if (open ? open.some((title) => cardFits(title, ask.words, 'open')) : saysPosted(text))
316
375
  return 0;
317
376
  return block(confirmReason(ask.words, sessionLink(io.env), APP_NAMES[app]));
318
377
  }
319
- if (turn.posted || saysPosted(text) || saysNoCard(text) || !itemsForPerson(text))
378
+ if (turn.posted || saysPosted(text) || saysNoCard(text) || !itemsOutsideRecaps(text))
320
379
  return 0;
380
+ if (mentionsCards(text)) {
381
+ const open = await openTitles(io, options.origin, cwd, app);
382
+ if (open === null || open.some((title) => !isPermissionCard(title)))
383
+ return 0;
384
+ }
321
385
  return block(STOP_REASON);
322
386
  }
323
387
  catch {
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.18.0';
1
+ export const VERSION = '0.19.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pendingyou",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "Pending You for your coding agents: one command sets up Claude Code, Codex, OpenCode and Pi to ask you through Pending You and hear your answers, even while they sit idle.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://www.pendingyou.com",