pendingyou 0.36.1 → 0.37.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 +11 -0
- package/README.md +13 -0
- package/dist/confirm.js +3 -3
- package/dist/step-now.js +133 -0
- package/dist/stopcheck.js +4 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.37.0 (2026-10-22)
|
|
4
|
+
|
|
5
|
+
- A step your agent hands you now goes on a card (guide 2.55). When its last message gives you a step to do now (“Go:
|
|
6
|
+
approve it now”, “you can merge it now”, “now open the run link”, “go ahead and approve it”) and no card from that
|
|
7
|
+
turn has it, the check at the end of its turn asks it once to put it on a card, even while an older card of its waits
|
|
8
|
+
on you. A card from the turn has it when it holds the message’s link, or, without one, two of its words. Not a step
|
|
9
|
+
now: the agent's own act (“I'll go ahead and merge it now”), a question whether it should, an offer (“whenever you
|
|
10
|
+
like”), a look (“now open … to see it”), what a page or a step says, or a heading such as “Open now:”. Patterns
|
|
11
|
+
only, on your computer; it asks Pending You nothing. Run `npx pendingyou@latest init` to switch: the hooks don’t
|
|
12
|
+
change, so Codex keeps trusting them.
|
|
13
|
+
|
|
3
14
|
## 0.36.1 (2026-10-20)
|
|
4
15
|
|
|
5
16
|
- Fixed: when you replied on an answer card, the Claude Code session that posted the answer wasn't woken: the wake mod
|
package/README.md
CHANGED
|
@@ -214,6 +214,19 @@ beside a recap still goes on a card. And anything else for you, in a message tha
|
|
|
214
214
|
recap while this folder has a card waiting on you: only such a message costs a request (this folder's open cards,
|
|
215
215
|
given up after 1.5 seconds), and when Pending You can't say, the turn ends.
|
|
216
216
|
|
|
217
|
+
Since 0.37.0 a step it hands you to do now goes on a card, whatever card is open (guide 2.55). An agent that ends a
|
|
218
|
+
card at a step that waits for it ("on my go, open the link") and then gives the go only in its chat leaves you to find
|
|
219
|
+
it there. So when the last message hands you a step now ("Go: approve it now", "you can merge it now", "now open the
|
|
220
|
+
run link", "go ahead and approve it", "Your turn: …"), a card call from that turn has to carry it: one that holds the
|
|
221
|
+
message's link, or, without one, two of its words. Otherwise the agent is asked once (about 49 tokens): *Your last
|
|
222
|
+
message hands them a step now, and no card from this turn has it: put it on a card (follows: the card they answered;
|
|
223
|
+
one still waiting: update_request it), or say it doesn’t need one.* A card about something else, a recap of open cards
|
|
224
|
+
and the message's word that it's on a card don't count: the card waiting on you is the one whose step said "on my go".
|
|
225
|
+
Not a step now: the agent's own act ("I'll go ahead and merge it now"), a question whether it should ("Merge and deploy
|
|
226
|
+
now?"), an offer ("whenever you like"), a look ("now open … to see it"), news ("you can now run the tests yourself"),
|
|
227
|
+
what a page or a step says, a heading or label ("Open now:", "Open questions: …"), or anything in code or quotes. It
|
|
228
|
+
costs no request.
|
|
229
|
+
|
|
217
230
|
Since 0.9.0 it also catches a message asking you to type or reply something in Claude Code's own chat ("Reply “yes,
|
|
218
231
|
erase disk12” here", "To confirm, type `yes`", "Confirm here"). Some steps an agent takes only on your own words in
|
|
219
232
|
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/confirm.js
CHANGED
|
@@ -10,7 +10,7 @@ const FILLER = /^(?:\s|[,:]|back\b|me\b|it\b|to me\b|with\b|exactly\b|precisely\
|
|
|
10
10
|
const ELSEWHERE = /\b(?:in|into|at|on)\s+(?:your|a|the|that|their)\s+(?:terminal|shell|browser|console|prompt|form|field|dialog|email|inbox|page|app|site|website|portal|dashboard)\b|\bin\s+(?:Terminal|iTerm)\b|\bwhen\s+(?:prompted|asked|it\s+asks)\b|\bat\s+the\s+prompt\b|https?:\/\/(?!claude\.ai\/code\/)/iu;
|
|
11
11
|
const THIS_CHAT = /\bin\s+(?:this|our)\s+(?:chat|conversation|session)\b/iu;
|
|
12
12
|
const OFFER = /\b(?:any\s?time|whenever|at\s+any\s+(?:point|time)|if\s+you\s+change\s+your\s+mind|if\s+(?:anything|something)\s+(?:looks|seems|goes))\b/iu;
|
|
13
|
-
const DESCRIBED = /\b(?:says?|said|reads?|ends?\s+with|starts?\s+with|shows?|prints?|displays?|asks?|prompts?|tells?\s+\w+|text|message|copy|label|button|template)\s*[:,]?$/iu;
|
|
13
|
+
export const DESCRIBED = /\b(?:says?|said|reads?|ends?\s+with|starts?\s+with|shows?|prints?|displays?|asks?|prompts?|tells?\s+\w+|text|message|copy|label|button|template)\s*[:,]?$/iu;
|
|
14
14
|
const COMMAND_LIKE = /^\s*(?:[!/]|\$\s|(?:cd|npm|npx|pnpm|yarn|bun|git|gh|curl|wget|brew|node|python3?|pip|docker|ssh|scp|sudo|claude|codex|wrangler)\s)/u;
|
|
15
15
|
const QUOTED = /“([^“”\n]{1,160})”|"([^"\n]{1,160})"|`([^`\n]{1,160})`|‘([^‘’\n]{1,160})’|(?<![\p{L}\p{N}])'([^'\n]{1,160})'(?![\p{L}\p{N}])/gu;
|
|
16
16
|
export function proseOf(text) {
|
|
@@ -36,7 +36,7 @@ export function proseOf(text) {
|
|
|
36
36
|
}
|
|
37
37
|
return kept.join('\n');
|
|
38
38
|
}
|
|
39
|
-
function marked(text) {
|
|
39
|
+
export function marked(text) {
|
|
40
40
|
const quotes = [];
|
|
41
41
|
const out = text.replace(QUOTED, (...groups) => {
|
|
42
42
|
const inner = groups.slice(1, 6).find((group) => typeof group === 'string');
|
|
@@ -128,7 +128,7 @@ const COMMON = new Set([
|
|
|
128
128
|
'this',
|
|
129
129
|
'that',
|
|
130
130
|
]);
|
|
131
|
-
const wordsOf = (text) => text.toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [];
|
|
131
|
+
export const wordsOf = (text) => text.toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [];
|
|
132
132
|
export const telling = (words) => words === null ? [] : [...new Set(wordsOf(words).filter((word) => !COMMON.has(word)))];
|
|
133
133
|
export function cardFits(text, words, where) {
|
|
134
134
|
const wanted = telling(words);
|
package/dist/step-now.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { DESCRIBED, marked, proseOf, wordsOf } from "./confirm.js";
|
|
2
|
+
const NOW_VERB = String.raw `(?:approve|open(?!\s+(?:to|for|now)\b)|press|click|tap|run|paste|merge|publish|deploy|release|restart|confirm|authori[sz]e|accept|allow|(?:sign|log)\s+in)`;
|
|
3
|
+
const GATE_VERB = String.raw `(?:approve|merge|publish|deploy|release|confirm|authori[sz]e|accept|allow|(?:sign|log)\s+in)`;
|
|
4
|
+
const LEAD = String.raw `(?:(?:ok(?:ay)?|alright|all\s+right|right|so|please|just)[,!]?\s+)*`;
|
|
5
|
+
const NOT_THAT = String.raw `(?!\s+(?:that|on)\b)`;
|
|
6
|
+
const NOT_FOR = String.raw `(?<!\bfor\s+)`;
|
|
7
|
+
export const STEP_NOW = new RegExp([
|
|
8
|
+
String.raw `^${LEAD}go(?:\s+ahead)?(?:\s+now)?\s*(?:[:!—–]|-\s)`,
|
|
9
|
+
String.raw `^${LEAD}(?:it(?:['’]s|\s+is)\s+)?your\s+turn\s*[:!—–]`,
|
|
10
|
+
String.raw `\b(?:that|this|here)(?:['’]s|\s+is)\s+(?:your|the)\s+go\b(?!-)`,
|
|
11
|
+
String.raw `(?:^${LEAD}|[:;—–]\s*|\byou\s+can\s+)go\s+ahead\s+and\s+${NOW_VERB}\b`,
|
|
12
|
+
String.raw `(?:^${LEAD}|[:;,—–]\s*|\b(?:and|so|then)\s+)now[,:]?\s+(?:please\s+)?${NOW_VERB}\b`,
|
|
13
|
+
String.raw `^(?![^\n]*\?\s*$)${LEAD}${NOW_VERB}\b[^.!?\n:]{0,80}?${NOT_FOR}\bnow\b${NOT_THAT}`,
|
|
14
|
+
String.raw `\b(?:you\s+can|can\s+you|could\s+you)\s+(?:go\s+ahead\s+and\s+)?${GATE_VERB}\b[^.!?\n]{0,60}?${NOT_FOR}\bnow\b${NOT_THAT}`,
|
|
15
|
+
String.raw `\byou\s+can\s+now\s+${GATE_VERB}\b`,
|
|
16
|
+
].join('|'), 'iu');
|
|
17
|
+
const STEP_NOW_EACH = new RegExp(STEP_NOW.source, 'giu');
|
|
18
|
+
export const NOT_NOW = /\b(?:any\s?time|whenever|at\s+any\s+(?:point|time)|if\s+you\s+(?:want|like|prefer|['’]d\s+like)|when\s+you\s+get\s+a\s+(?:chance|minute)|no\s+rush|or\s+later|to\s+(?:see|look|check|try|test|compare))\b/iu;
|
|
19
|
+
export const STEP_NOW_REASON = 'Your last message hands them a step now, and no card from this turn has it: put it on a card (follows: the card they answered; one still waiting: update_request it), or say it doesn’t need one.';
|
|
20
|
+
const SENTENCE_END = /(?<=[.!?])\s+|\n+/u;
|
|
21
|
+
const MARK = /⟦(\d+)⟧/gu;
|
|
22
|
+
const LINK = /\bhttps?:\/\/[^\s<>"'`“”‘’⟦⟧]+/giu;
|
|
23
|
+
const LINK_END = /[.,;:!?)]+$/u;
|
|
24
|
+
const LINK_GOES_ON = /[\p{L}\p{N}_-]/u;
|
|
25
|
+
const CARD_PAGE = /\/app\/r\/req_/iu;
|
|
26
|
+
const STEP_COMMON = new Set([
|
|
27
|
+
'go',
|
|
28
|
+
'ahead',
|
|
29
|
+
'now',
|
|
30
|
+
'you',
|
|
31
|
+
'your',
|
|
32
|
+
'yours',
|
|
33
|
+
'can',
|
|
34
|
+
'could',
|
|
35
|
+
'please',
|
|
36
|
+
'just',
|
|
37
|
+
'ok',
|
|
38
|
+
'okay',
|
|
39
|
+
'alright',
|
|
40
|
+
'all',
|
|
41
|
+
'right',
|
|
42
|
+
'so',
|
|
43
|
+
'then',
|
|
44
|
+
'still',
|
|
45
|
+
'card',
|
|
46
|
+
'cards',
|
|
47
|
+
'done',
|
|
48
|
+
'step',
|
|
49
|
+
'steps',
|
|
50
|
+
'turn',
|
|
51
|
+
'it',
|
|
52
|
+
'its',
|
|
53
|
+
's',
|
|
54
|
+
't',
|
|
55
|
+
'll',
|
|
56
|
+
're',
|
|
57
|
+
've',
|
|
58
|
+
'd',
|
|
59
|
+
'm',
|
|
60
|
+
'i',
|
|
61
|
+
'me',
|
|
62
|
+
'my',
|
|
63
|
+
'we',
|
|
64
|
+
'our',
|
|
65
|
+
'the',
|
|
66
|
+
'a',
|
|
67
|
+
'an',
|
|
68
|
+
'and',
|
|
69
|
+
'or',
|
|
70
|
+
'to',
|
|
71
|
+
'of',
|
|
72
|
+
'on',
|
|
73
|
+
'in',
|
|
74
|
+
'at',
|
|
75
|
+
'for',
|
|
76
|
+
'with',
|
|
77
|
+
'from',
|
|
78
|
+
'by',
|
|
79
|
+
'is',
|
|
80
|
+
'are',
|
|
81
|
+
'be',
|
|
82
|
+
'this',
|
|
83
|
+
'that',
|
|
84
|
+
'here',
|
|
85
|
+
'there',
|
|
86
|
+
]);
|
|
87
|
+
function handsOver(sentence) {
|
|
88
|
+
if (NOT_NOW.test(sentence))
|
|
89
|
+
return false;
|
|
90
|
+
for (const found of sentence.matchAll(STEP_NOW_EACH))
|
|
91
|
+
if (!DESCRIBED.test(sentence.slice(0, found.index).trimEnd()))
|
|
92
|
+
return true;
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
function linksIn(said) {
|
|
96
|
+
const links = (said.match(LINK) ?? [])
|
|
97
|
+
.map((link) => link.replace(LINK_END, '').replace(/\/$/u, '').toLowerCase())
|
|
98
|
+
.filter((link) => !CARD_PAGE.test(link));
|
|
99
|
+
return [...new Set(links)];
|
|
100
|
+
}
|
|
101
|
+
export function stepsNow(text) {
|
|
102
|
+
const { text: prose, quotes } = marked(proseOf(text));
|
|
103
|
+
const steps = [];
|
|
104
|
+
for (const each of prose.split(SENTENCE_END)) {
|
|
105
|
+
const sentence = each.trim();
|
|
106
|
+
if (!sentence || !handsOver(sentence))
|
|
107
|
+
continue;
|
|
108
|
+
const said = sentence.replace(MARK, (_, index) => quotes[Number(index)] ?? '');
|
|
109
|
+
steps.push({
|
|
110
|
+
text: said,
|
|
111
|
+
links: linksIn(said),
|
|
112
|
+
words: [
|
|
113
|
+
...new Set(wordsOf(said.replace(LINK, ' ')).filter((word) => !STEP_COMMON.has(word))),
|
|
114
|
+
],
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return steps;
|
|
118
|
+
}
|
|
119
|
+
function holds(said, link) {
|
|
120
|
+
for (let at = said.indexOf(link); at !== -1; at = said.indexOf(link, at + 1))
|
|
121
|
+
if (!LINK_GOES_ON.test(said.charAt(at + link.length)))
|
|
122
|
+
return true;
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
export function carriesStep(card, step) {
|
|
126
|
+
if (step.links.length) {
|
|
127
|
+
const said = card.toLowerCase();
|
|
128
|
+
return step.links.every((link) => holds(said, link));
|
|
129
|
+
}
|
|
130
|
+
const has = new Set(wordsOf(card));
|
|
131
|
+
const shared = step.words.filter((word) => has.has(word)).length;
|
|
132
|
+
return shared >= Math.min(2, step.words.length);
|
|
133
|
+
}
|
package/dist/stopcheck.js
CHANGED
|
@@ -9,6 +9,7 @@ import { readCredential } from "./credentials.js";
|
|
|
9
9
|
import { isHandedWake } from "./format.js";
|
|
10
10
|
import { isPermissionCard, isPromptApp, sessionName, settlePrompts, subagentsRunning, } from "./permission.js";
|
|
11
11
|
import { askerParams, folderForms } from "./pickup.js";
|
|
12
|
+
import { carriesStep, STEP_NOW_REASON, stepsNow } from "./step-now.js";
|
|
12
13
|
export const READ_CAP = 4 * 1024 * 1024;
|
|
13
14
|
const CHUNK = 64 * 1024;
|
|
14
15
|
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.';
|
|
@@ -448,6 +449,9 @@ export async function stopcheck(io, options) {
|
|
|
448
449
|
return 0;
|
|
449
450
|
return block(confirmReason(ask.words, sessionLink(io.env), APP_NAMES[app]));
|
|
450
451
|
}
|
|
452
|
+
const loose = stepsNow(text).filter((step) => !turn.cards.some((card) => carriesStep(card, step)));
|
|
453
|
+
if (loose.length && !saysNoCard(text))
|
|
454
|
+
return block(STEP_NOW_REASON);
|
|
451
455
|
if (turn.posted || saysPosted(text) || saysNoCard(text))
|
|
452
456
|
return 0;
|
|
453
457
|
if (turn.asked && isAnswer(text) && (await seesAnswers(io, app)))
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.
|
|
1
|
+
export const VERSION = '0.37.0';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pendingyou",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.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
|
"keywords": [
|
|
6
6
|
"pendingyou",
|