pendingyou 0.36.1 → 0.38.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 +28 -0
- package/README.md +27 -1
- package/dist/app-login.js +2 -0
- package/dist/args.js +1 -0
- package/dist/confirm.js +3 -3
- package/dist/herdr/command.js +35 -8
- package/dist/herdr/show.js +35 -0
- package/dist/herdr/watch.js +75 -13
- package/dist/sdk/client.js +12 -0
- package/dist/sdk/index.js +1 -0
- package/dist/sdk/shows.js +86 -0
- package/dist/step-now.js +133 -0
- package/dist/stopcheck.js +4 -0
- package/dist/version.js +1 -1
- package/mod/.claude-plugin/plugin.json +1 -1
- package/mod/README.md +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.38.0 (2026-10-25)
|
|
4
|
+
|
|
5
|
+
- Show in Herdr: press it on a card in Pending You, at your desk or on your phone, and Herdr moves to the pane of the
|
|
6
|
+
agent that asked, on every client attached to it. The Pending You plugin for Herdr's watcher listens for your
|
|
7
|
+
presses once Herdr's sign-in allows it (`assistants:show`), one wait at a time beside what it already does, and
|
|
8
|
+
checks each before anything moves: the pane must be on its own Herdr server, be there now, and be the agent's (its
|
|
9
|
+
badge lists the card, or it runs that kind of agent: `claude` for Claude Code). Otherwise Pending You hears "not
|
|
10
|
+
found" and nothing moves. It only ever brings a pane to the front: it never types into one, runs or opens anything,
|
|
11
|
+
and logs nothing. What it hears is ids and the agent's name, never a card's words.
|
|
12
|
+
- `npx pendingyou app login herdr` asks for `assistants:show` too, and `app status` lists it: "bring the assistant
|
|
13
|
+
that asked to the front when you press Show". A sign-in from before doesn't hold it: the plugin's setup and doctor
|
|
14
|
+
say "Sign Herdr in again to use Show in Herdr: npx pendingyou app login herdr", and setup offers to.
|
|
15
|
+
- Setup starts this version's watcher in place of one from an older pendingyou, which doesn't listen for Show in
|
|
16
|
+
Herdr; doctor says when the one running is older. The watcher's lease now says which pendingyou it is.
|
|
17
|
+
- Fixed: Herdr's toast "Pending You · Herdr is signed out" could show twice when the sign-in ended just after its
|
|
18
|
+
tokens were renewed. The watcher now tells sign-ins apart by their key, which renewing doesn't change.
|
|
19
|
+
|
|
20
|
+
## 0.37.0 (2026-10-22)
|
|
21
|
+
|
|
22
|
+
- 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:
|
|
23
|
+
approve it now”, “you can merge it now”, “now open the run link”, “go ahead and approve it”) and no card from that
|
|
24
|
+
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
|
|
25
|
+
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
|
|
26
|
+
now: the agent's own act (“I'll go ahead and merge it now”), a question whether it should, an offer (“whenever you
|
|
27
|
+
like”), a look (“now open … to see it”), what a page or a step says, or a heading such as “Open now:”. Patterns
|
|
28
|
+
only, on your computer; it asks Pending You nothing. Run `npx pendingyou@latest init` to switch: the hooks don’t
|
|
29
|
+
change, so Codex keeps trusting them.
|
|
30
|
+
|
|
3
31
|
## 0.36.1 (2026-10-20)
|
|
4
32
|
|
|
5
33
|
- 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
|
|
@@ -399,6 +412,17 @@ as it does while Pending You is open on your desk.
|
|
|
399
412
|
last use here; it warns in the last 3), and `npx pendingyou app logout herdr` ends Herdr's sign-in. Settings › Apps and devices in Pending You
|
|
400
413
|
lists it too, with Remove. It needs `init` to have run here first: this computer's key vouches for the sign-in.
|
|
401
414
|
|
|
415
|
+
### Show in Herdr (0.38.0)
|
|
416
|
+
|
|
417
|
+
Press Show in Herdr on a card in Pending You, at your desk or on your phone, and Herdr moves to the pane of the agent
|
|
418
|
+
that asked, on every client attached to it. The plugin's watcher listens for your presses once Herdr's sign-in allows it
|
|
419
|
+
(`assistants:show`: `npx pendingyou app login herdr` asks for it; a sign-in from before 0.38.0 doesn't hold it, and the
|
|
420
|
+
plugin's setup and doctor say to sign in again). It checks each press before anything moves: the pane must be on its own
|
|
421
|
+
Herdr server, be there now, and be the agent's (its badge lists the card, or it runs that kind of agent, `claude` for
|
|
422
|
+
Claude Code). Otherwise Pending You hears "not found", and nothing moves. It only ever brings a pane to the front: it
|
|
423
|
+
never types into one, runs or opens anything, and logs nothing. Setup starts this version's watcher in place of one
|
|
424
|
+
from an older pendingyou, which doesn't listen.
|
|
425
|
+
|
|
402
426
|
## How Claude Code hears your answer
|
|
403
427
|
|
|
404
428
|
**Claude Code 2.1.287 or later: the wake mod.** There's nothing to run. In each session the mod learns the cards
|
|
@@ -580,7 +604,9 @@ a command that acts for one agent acts for Claude Code. Options can come before
|
|
|
580
604
|
hands over a card, and no agent's sign-in can. An app you sign in with `pendingyou app login` (the Pending You plugin
|
|
581
605
|
for Herdr) answers only from its own sign-in and its own key, kept apart in `apps/<app>.json`, which nothing an agent
|
|
582
606
|
runs here reads, and only when you press a key in it; never a high-stakes card, and Pending You labels every answer
|
|
583
|
-
with the app and the computer.
|
|
607
|
+
with the app and the computer. Signed in with `assistants:show` (0.38.0), Herdr's watcher also keeps one wait open with
|
|
608
|
+
Pending You for your presses of Show in Herdr, which name a card, an agent and its pane, never a card's words, and it
|
|
609
|
+
only ever brings that pane to the front.
|
|
584
610
|
|
|
585
611
|
Each agent's sign-in is that agent's own connection on Pending You, signed in from this computer: it can post cards,
|
|
586
612
|
change or cancel its own, read the answers to them and reply in their threads, and it can't answer anything for you.
|
package/dist/app-login.js
CHANGED
|
@@ -18,6 +18,7 @@ export const APP_SCOPES = [
|
|
|
18
18
|
'cards:later',
|
|
19
19
|
'cards:delegate',
|
|
20
20
|
'assistants:read',
|
|
21
|
+
'assistants:show',
|
|
21
22
|
'presence:desk',
|
|
22
23
|
];
|
|
23
24
|
export const FIRST_PARTY_APPS = {
|
|
@@ -54,6 +55,7 @@ const MAY = {
|
|
|
54
55
|
'cards:later': 'put them in Later',
|
|
55
56
|
'cards:delegate': 'hand them to another assistant',
|
|
56
57
|
'assistants:read': 'see your assistants',
|
|
58
|
+
'assistants:show': 'bring the assistant that asked to the front when you press Show',
|
|
57
59
|
'presence:desk': 'keep your phone quiet while you use it',
|
|
58
60
|
};
|
|
59
61
|
const listed = (items) => items.length <= 1 ? (items[0] ?? '') : `${items.slice(0, -1).join(', ')} and ${items.at(-1)}`;
|
package/dist/args.js
CHANGED
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/herdr/command.js
CHANGED
|
@@ -5,6 +5,7 @@ import { reportThreads } from "../apps/codex-wake.js";
|
|
|
5
5
|
import { APP_IDS, APP_NAMES } from "../apps/ids.js";
|
|
6
6
|
import { configDir, readText, writeWhole } from "../files.js";
|
|
7
7
|
import { badgesOn, clearHerdr, HERDR_SOURCE, herdrBin, herdrPane, herdrTarget, readPayload, setBadges, TOKEN_NAMES, writeHerdr, } from "../herdr.js";
|
|
8
|
+
import { originArgs } from "../hooks.js";
|
|
8
9
|
import { sessionName } from "../permission.js";
|
|
9
10
|
import { presenceName } from "../presence.js";
|
|
10
11
|
import { headlessReason } from "../remote.js";
|
|
@@ -14,7 +15,7 @@ import { badgedPanes, nextWaiting, readWaiting } from "./panes.js";
|
|
|
14
15
|
import { agentView, CARD_ROW, hasBlock, herdrConfigPath, KEYS_SNIPPET, ownSidebar, pluginOff, pluginPlace, SIDEBAR_BLOCK, saveView, setPluginOff, TOAST_SNIPPET, toastDelivery, viewOn, withBlock, withoutBlock, } from "./plugin.js";
|
|
15
16
|
import { cardsPopup, originFor } from "./popup.js";
|
|
16
17
|
import { HerdrError, herdrRequest } from "./socket.js";
|
|
17
|
-
import { pluginEnabled, stopWatcher, watch, watching } from "./watch.js";
|
|
18
|
+
import { olderWatcher, pluginEnabled, stopWatcher, watch, watching } from "./watch.js";
|
|
18
19
|
async function toast(place, title, body) {
|
|
19
20
|
if (!place.socket)
|
|
20
21
|
return;
|
|
@@ -157,6 +158,16 @@ async function originHere(io, fallback) {
|
|
|
157
158
|
return fallback;
|
|
158
159
|
}
|
|
159
160
|
const line = (mark, text) => `${mark} ${text}\n`;
|
|
161
|
+
const showsHere = (stored) => stored.scopes.includes('assistants:show');
|
|
162
|
+
const signInAgainForShow = (origin) => `Sign Herdr in again to use Show in Herdr: npx pendingyou app login herdr${originArgs(origin)}`;
|
|
163
|
+
async function startWatcher(io, place) {
|
|
164
|
+
if (!place.socket)
|
|
165
|
+
return;
|
|
166
|
+
if (await olderWatcher(place))
|
|
167
|
+
await stopWatcher(place);
|
|
168
|
+
if (!(await watching(place)))
|
|
169
|
+
io.background(['herdr', 'watch']);
|
|
170
|
+
}
|
|
160
171
|
async function setup(io, place, options) {
|
|
161
172
|
const ask = async (question) => {
|
|
162
173
|
if (options.yes)
|
|
@@ -202,8 +213,7 @@ async function setup(io, place, options) {
|
|
|
202
213
|
io.out(line('✓', 'Agents waiting on you come first in the Agents panel.'));
|
|
203
214
|
}
|
|
204
215
|
}
|
|
205
|
-
|
|
206
|
-
io.background(['herdr', 'watch']);
|
|
216
|
+
await startWatcher(io, place);
|
|
207
217
|
if (io.interactive && !options.yes)
|
|
208
218
|
await io.ask('\nPress Enter to close. ');
|
|
209
219
|
return 0;
|
|
@@ -212,6 +222,14 @@ async function signInStep(io, options, ask) {
|
|
|
212
222
|
const stored = await readAppSignIn(io, 'herdr').catch(() => null);
|
|
213
223
|
if (stored) {
|
|
214
224
|
io.out(`${line('✓', `Herdr is signed in to Pending You at ${stored.origin}: its popup (prefix+y) answers your cards with a key.`)} npx pendingyou app status herdr says what it may do and for how long.\n\n`);
|
|
225
|
+
if (showsHere(stored))
|
|
226
|
+
return;
|
|
227
|
+
io.out(line('!', signInAgainForShow(stored.origin)));
|
|
228
|
+
if (options.yes || !io.interactive || !(await ask('Sign Herdr in again now?'))) {
|
|
229
|
+
io.out('\n');
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
await signInHerdr(io, stored.origin);
|
|
215
233
|
return;
|
|
216
234
|
}
|
|
217
235
|
io.out(`\nAnswer from Herdr: once Herdr is signed in to Pending You, its popup (prefix+y) answers your cards with a key (a choice, yes or no, your words, Later, or handing it to another agent), only when you press one. High-stakes cards still open in Pending You. It sees only this computer's agents' cards unless you tick "all" when you allow it.\n`);
|
|
@@ -219,7 +237,9 @@ async function signInStep(io, options, ask) {
|
|
|
219
237
|
io.out(`${line('·', 'Herdr isn’t signed in: its popup lists what’s waiting. Sign it in any time: setup again, or npx pendingyou app login herdr')}\n`);
|
|
220
238
|
return;
|
|
221
239
|
}
|
|
222
|
-
|
|
240
|
+
await signInHerdr(io, await originHere(io, options.origin));
|
|
241
|
+
}
|
|
242
|
+
async function signInHerdr(io, origin) {
|
|
223
243
|
const result = await signInApp(io, {
|
|
224
244
|
app: 'herdr',
|
|
225
245
|
origin,
|
|
@@ -271,9 +291,14 @@ async function doctor(io, place, options) {
|
|
|
271
291
|
io.out(shim
|
|
272
292
|
? line('✓', `Your agents' hooks run ${shimPath(io)}.`)
|
|
273
293
|
: line('!', 'Pending You isn’t set up for your agents here: run npx -y pendingyou@latest init'));
|
|
274
|
-
|
|
294
|
+
const stored = await readAppSignIn(io, 'herdr').catch(() => undefined);
|
|
295
|
+
if (stored !== null) {
|
|
275
296
|
const status = await appStatusLines(io, 'herdr');
|
|
276
297
|
io.out(line(status.ok ? '✓' : '!', status.lines[0] ?? ''));
|
|
298
|
+
if (stored && status.ok)
|
|
299
|
+
io.out(showsHere(stored)
|
|
300
|
+
? line('✓', 'Show in Herdr: press it on a card in Pending You, and Herdr goes to the pane of the agent that asked.')
|
|
301
|
+
: line('!', signInAgainForShow(stored.origin)));
|
|
277
302
|
}
|
|
278
303
|
else
|
|
279
304
|
io.out(line('·', 'Herdr isn’t signed in to Pending You: its popup lists what’s waiting. Setup signs it in.'));
|
|
@@ -294,9 +319,11 @@ async function doctor(io, place, options) {
|
|
|
294
319
|
const { snapshot, waiting } = await readWaiting(place.socket);
|
|
295
320
|
const badged = badgedPanes(snapshot).length;
|
|
296
321
|
io.out(line('·', `${badged} pane${badged === 1 ? '' : 's'} with Pending You's badges, ${waiting.length} waiting on you.`));
|
|
297
|
-
io.out((await watching(place))
|
|
298
|
-
? line('
|
|
299
|
-
:
|
|
322
|
+
io.out(!(await watching(place))
|
|
323
|
+
? line('!', 'Its watcher isn’t running: it starts with Herdr, or with setup.')
|
|
324
|
+
: (await olderWatcher(place))
|
|
325
|
+
? line('!', `Its watcher runs an older pendingyou than this one (${VERSION}): setup restarts it.`)
|
|
326
|
+
: line('✓', 'Its watcher is running (toasts when a pane gains a card).'));
|
|
300
327
|
io.out(line('·', `Its Agents view is ${(await viewOn(place)) ? 'on' : 'off'}.`));
|
|
301
328
|
}
|
|
302
329
|
catch (error) {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { isAppId } from "../apps/ids.js";
|
|
2
|
+
import { HERDR_AGENTS, TOKENS } from "../herdr.js";
|
|
3
|
+
import { idsOf, readSnapshot } from "./panes.js";
|
|
4
|
+
import { herdrRequest } from "./socket.js";
|
|
5
|
+
export const SHOW_PLACES = ['herdr'];
|
|
6
|
+
export const placeHere = (server) => (place) => place.app === 'herdr' && place.server === server;
|
|
7
|
+
const herdrAgentOf = (app) => (isAppId(app) ? HERDR_AGENTS[app] : null);
|
|
8
|
+
export async function showPane(socket, show) {
|
|
9
|
+
const result = await herdrRequest(socket, 'session.snapshot');
|
|
10
|
+
const { panes } = readSnapshot(result?.snapshot);
|
|
11
|
+
const agent = herdrAgentOf(show.agent.app);
|
|
12
|
+
for (const place of show.places) {
|
|
13
|
+
const pane = panes.find((each) => each.paneId === place.paneId);
|
|
14
|
+
if (!pane)
|
|
15
|
+
continue;
|
|
16
|
+
if (idsOf(pane.tokens[TOKENS.cards]).includes(show.card.id))
|
|
17
|
+
return { paneId: pane.paneId };
|
|
18
|
+
if (agent !== null && pane.agent === agent)
|
|
19
|
+
return { paneId: pane.paneId };
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
export async function focusPane(socket, pane) {
|
|
24
|
+
await herdrRequest(socket, 'pane.focus', { pane_id: pane.paneId });
|
|
25
|
+
}
|
|
26
|
+
export function followHerdrShows(client, options) {
|
|
27
|
+
const { socket, server, signal } = options;
|
|
28
|
+
return client.followShows({
|
|
29
|
+
places: [...SHOW_PLACES],
|
|
30
|
+
mine: placeHere(server),
|
|
31
|
+
find: (show) => showPane(socket, show),
|
|
32
|
+
focus: (pane) => focusPane(socket, pane),
|
|
33
|
+
signal,
|
|
34
|
+
});
|
|
35
|
+
}
|
package/dist/herdr/watch.js
CHANGED
|
@@ -2,11 +2,15 @@ import { randomBytes } from 'node:crypto';
|
|
|
2
2
|
import { mkdir, readFile, rm, stat, utimes, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { appClient, RENEW_WITHIN_MS, readAppSignIn } from "../app-login.js";
|
|
5
|
+
import { herdrServer } from "../herdr.js";
|
|
6
|
+
import { atLeast } from "../mod.js";
|
|
5
7
|
import { SignedOutError } from "../sdk/index.js";
|
|
8
|
+
import { VERSION } from "../version.js";
|
|
6
9
|
import { due, emptyNotices, look } from "./notice.js";
|
|
7
10
|
import { readWaiting } from "./panes.js";
|
|
8
11
|
import { agentView, pluginOff, viewOn } from "./plugin.js";
|
|
9
12
|
import { awayDesk, deskStep, lastActive } from "./presence.js";
|
|
13
|
+
import { followHerdrShows } from "./show.js";
|
|
10
14
|
import { HerdrError, herdrRequest, herdrSubscribe } from "./socket.js";
|
|
11
15
|
export const LEASE_STALE_MS = 60_000;
|
|
12
16
|
export const RENEW_MS = 20_000;
|
|
@@ -32,7 +36,10 @@ async function takeLease(place) {
|
|
|
32
36
|
const id = randomBytes(8).toString('hex');
|
|
33
37
|
await rm(path, { force: true });
|
|
34
38
|
try {
|
|
35
|
-
await writeFile(path, JSON.stringify({ id, pid: process.pid
|
|
39
|
+
await writeFile(path, JSON.stringify({ id, pid: process.pid, version: VERSION }), {
|
|
40
|
+
flag: 'wx',
|
|
41
|
+
mode: 0o600,
|
|
42
|
+
});
|
|
36
43
|
return id;
|
|
37
44
|
}
|
|
38
45
|
catch {
|
|
@@ -43,13 +50,23 @@ async function holder(place) {
|
|
|
43
50
|
try {
|
|
44
51
|
const held = JSON.parse(await readFile(leasePath(place), 'utf8'));
|
|
45
52
|
return typeof held.id === 'string' && typeof held.pid === 'number'
|
|
46
|
-
? {
|
|
53
|
+
? {
|
|
54
|
+
id: held.id,
|
|
55
|
+
pid: held.pid,
|
|
56
|
+
version: typeof held.version === 'string' ? held.version : null,
|
|
57
|
+
}
|
|
47
58
|
: null;
|
|
48
59
|
}
|
|
49
60
|
catch {
|
|
50
61
|
return null;
|
|
51
62
|
}
|
|
52
63
|
}
|
|
64
|
+
export async function olderWatcher(place) {
|
|
65
|
+
if (!(await watching(place)))
|
|
66
|
+
return false;
|
|
67
|
+
const held = await holder(place);
|
|
68
|
+
return held !== null && (held.version === null || !atLeast(held.version, VERSION));
|
|
69
|
+
}
|
|
53
70
|
async function renew(place, id) {
|
|
54
71
|
if ((await holder(place))?.id !== id)
|
|
55
72
|
return false;
|
|
@@ -121,6 +138,8 @@ const FOCUS_EVENTS = new Set([
|
|
|
121
138
|
'workspace_focused',
|
|
122
139
|
]);
|
|
123
140
|
const EXPIRY_CHECK_MS = 6 * 60 * 60_000;
|
|
141
|
+
export const SHOWS_AGAIN_MS = 10 * 60_000;
|
|
142
|
+
const signInOf = (stored) => `${stored.key.x}.${stored.key.y}`;
|
|
124
143
|
export const SIGNED_OUT_TOAST = {
|
|
125
144
|
title: 'Pending You · Herdr is signed out',
|
|
126
145
|
body: 'Removed in Settings, signed in again, or ended. To answer from Herdr again: Pending You: set up.',
|
|
@@ -131,19 +150,50 @@ export const endingToast = (days) => ({
|
|
|
131
150
|
});
|
|
132
151
|
export function signInKeeper(io, socket, place) {
|
|
133
152
|
let client = null;
|
|
134
|
-
let
|
|
153
|
+
let current = null;
|
|
135
154
|
let presence = false;
|
|
155
|
+
let shows = false;
|
|
136
156
|
let desk = awayDesk();
|
|
137
157
|
let checkedAt = Number.NEGATIVE_INFINITY;
|
|
138
|
-
let
|
|
158
|
+
let endedSignIn = null;
|
|
139
159
|
let warnedAt = Number.NEGATIVE_INFINITY;
|
|
140
160
|
let focusAt = null;
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
161
|
+
const server = herdrServer(io.env, io.host);
|
|
162
|
+
let following = null;
|
|
163
|
+
let refused = null;
|
|
164
|
+
const stopShows = () => {
|
|
165
|
+
following?.stop.abort();
|
|
166
|
+
following = null;
|
|
167
|
+
};
|
|
168
|
+
const ended = async (which = current) => {
|
|
169
|
+
if (which === null || which !== current)
|
|
170
|
+
return;
|
|
171
|
+
client = null;
|
|
172
|
+
stopShows();
|
|
173
|
+
if (endedSignIn !== which) {
|
|
174
|
+
endedSignIn = which;
|
|
144
175
|
await showToast(socket, SIGNED_OUT_TOAST);
|
|
145
176
|
}
|
|
146
|
-
|
|
177
|
+
};
|
|
178
|
+
const followShows = () => {
|
|
179
|
+
if (!client || !current || !shows || !server || following || io.signal.aborted)
|
|
180
|
+
return;
|
|
181
|
+
if (refused?.signIn === current && io.now() - refused.at < SHOWS_AGAIN_MS)
|
|
182
|
+
return;
|
|
183
|
+
const run = { signIn: current, stop: new AbortController() };
|
|
184
|
+
following = run;
|
|
185
|
+
void followHerdrShows(client, { socket, server, signal: run.stop.signal })
|
|
186
|
+
.catch(async (error) => {
|
|
187
|
+
if (error instanceof SignedOutError)
|
|
188
|
+
await ended(run.signIn);
|
|
189
|
+
else
|
|
190
|
+
refused = { signIn: run.signIn, at: io.now() };
|
|
191
|
+
})
|
|
192
|
+
.catch(() => { })
|
|
193
|
+
.finally(() => {
|
|
194
|
+
if (following === run)
|
|
195
|
+
following = null;
|
|
196
|
+
});
|
|
147
197
|
};
|
|
148
198
|
return {
|
|
149
199
|
heard(event) {
|
|
@@ -152,16 +202,27 @@ export function signInKeeper(io, socket, place) {
|
|
|
152
202
|
},
|
|
153
203
|
async renew() {
|
|
154
204
|
const stored = await readAppSignIn(io, 'herdr').catch(() => null);
|
|
155
|
-
|
|
205
|
+
const signIn = stored ? signInOf(stored) : null;
|
|
206
|
+
if (!stored || signIn === endedSignIn) {
|
|
156
207
|
client = null;
|
|
157
|
-
|
|
208
|
+
current = signIn;
|
|
209
|
+
stopShows();
|
|
158
210
|
return;
|
|
159
211
|
}
|
|
160
|
-
if (
|
|
161
|
-
|
|
212
|
+
if (signIn !== current || !client) {
|
|
213
|
+
current = signIn;
|
|
162
214
|
client = appClient(io, 'herdr');
|
|
163
|
-
|
|
215
|
+
stopShows();
|
|
164
216
|
}
|
|
217
|
+
presence = stored.scopes.includes('presence:desk');
|
|
218
|
+
shows = stored.scopes.includes('assistants:show');
|
|
219
|
+
if (shows)
|
|
220
|
+
followShows();
|
|
221
|
+
else
|
|
222
|
+
stopShows();
|
|
223
|
+
},
|
|
224
|
+
stop() {
|
|
225
|
+
stopShows();
|
|
165
226
|
},
|
|
166
227
|
async tick() {
|
|
167
228
|
if (!client)
|
|
@@ -268,6 +329,7 @@ export async function watch(io, place, options) {
|
|
|
268
329
|
return 0;
|
|
269
330
|
}
|
|
270
331
|
finally {
|
|
332
|
+
signIn.stop();
|
|
271
333
|
if ((await holder(place))?.id === lease)
|
|
272
334
|
await rm(leasePath(place), { force: true }).catch(() => { });
|
|
273
335
|
}
|
package/dist/sdk/client.js
CHANGED
|
@@ -2,9 +2,11 @@ import { changeFeed } from "./changes.js";
|
|
|
2
2
|
import { importKey, proof, wantsNonce } from "./dpop.js";
|
|
3
3
|
import { ApiError, SignedOutError } from "./errors.js";
|
|
4
4
|
import { nonces, tokenRequest } from "./oauth.js";
|
|
5
|
+
import { followShowsWith, showFeed } from "./shows.js";
|
|
5
6
|
export const REFRESH_SKEW_MS = 60_000;
|
|
6
7
|
const CACHE_MAX = 200;
|
|
7
8
|
const path = (id, action) => `/v1/cards/${encodeURIComponent(id)}${action ? `/${action}` : ''}`;
|
|
9
|
+
const showPath = (id) => `/v1/shows/${encodeURIComponent(id)}`;
|
|
8
10
|
export function queryString(query) {
|
|
9
11
|
const params = new URLSearchParams();
|
|
10
12
|
for (const [name, value] of Object.entries(query ?? {})) {
|
|
@@ -218,6 +220,16 @@ export function createClient(options) {
|
|
|
218
220
|
...(query ? { query: query } : {}),
|
|
219
221
|
}),
|
|
220
222
|
changes: (opts) => changeFeed((query, signal) => client.changesPage(query, signal ? { signal } : {}), { now: http.now, sleep }, opts),
|
|
223
|
+
showsPage: (query, opts) => request('GET', '/v1/shows', {
|
|
224
|
+
...opts,
|
|
225
|
+
query: query,
|
|
226
|
+
}),
|
|
227
|
+
shows: (opts) => showFeed((query, signal) => client.showsPage(query, signal ? { signal } : {}), { now: http.now, sleep }, opts),
|
|
228
|
+
answerShow: (id, result, opts) => request('POST', showPath(id), {
|
|
229
|
+
body: { result },
|
|
230
|
+
...keyed(opts?.idempotencyKey),
|
|
231
|
+
}),
|
|
232
|
+
followShows: (opts) => followShowsWith((follow) => client.shows(follow), (id, result) => client.answerShow(id, result), opts),
|
|
221
233
|
request,
|
|
222
234
|
};
|
|
223
235
|
return client;
|
package/dist/sdk/index.js
CHANGED
|
@@ -5,5 +5,6 @@ export { deviceSignIn, } from "./device.js";
|
|
|
5
5
|
export { generateKey, importKey, thumbprint } from "./dpop.js";
|
|
6
6
|
export { ApiError, SignedOutError, SignInError } from "./errors.js";
|
|
7
7
|
export * from "./openapi.generated.js";
|
|
8
|
+
export { REPEAT_MS } from "./shows.js";
|
|
8
9
|
export { signOut } from "./signout.js";
|
|
9
10
|
export { memoryStore } from "./store.js";
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { backoffMs, WAIT_SECONDS } from "./changes.js";
|
|
2
|
+
import { ApiError, SignedOutError } from "./errors.js";
|
|
3
|
+
export const REPEAT_MS = 1000;
|
|
4
|
+
const KEPT_MS = 60_000;
|
|
5
|
+
export async function* showFeed(look, clock, options) {
|
|
6
|
+
const { signal } = options;
|
|
7
|
+
const wait = Math.max(0, Math.min(WAIT_SECONDS, options.wait ?? WAIT_SECONDS));
|
|
8
|
+
const yielded = new Map();
|
|
9
|
+
let failures = 0;
|
|
10
|
+
while (!signal?.aborted) {
|
|
11
|
+
let page;
|
|
12
|
+
try {
|
|
13
|
+
page = await look({ places: [...options.places], wait }, signal);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
if (signal?.aborted)
|
|
17
|
+
return;
|
|
18
|
+
if (error instanceof SignedOutError)
|
|
19
|
+
throw error;
|
|
20
|
+
if (error instanceof ApiError) {
|
|
21
|
+
if (error.status === 429) {
|
|
22
|
+
failures++;
|
|
23
|
+
await clock.sleep((error.retryAfterSeconds ?? 60) * 1000, signal);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (error.status < 500)
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
failures++;
|
|
30
|
+
await clock.sleep(backoffMs(failures), signal);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
failures = 0;
|
|
34
|
+
const now = clock.now();
|
|
35
|
+
for (const [id, until] of yielded)
|
|
36
|
+
if (until < now)
|
|
37
|
+
yielded.delete(id);
|
|
38
|
+
const fresh = page.shows.filter((show) => !yielded.has(show.id));
|
|
39
|
+
for (const show of fresh) {
|
|
40
|
+
const ends = Date.parse(show.expiresAt);
|
|
41
|
+
yielded.set(show.id, (Number.isFinite(ends) ? ends : now) + KEPT_MS);
|
|
42
|
+
yield show;
|
|
43
|
+
if (signal?.aborted)
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (page.shows.length > 0 && fresh.length === 0)
|
|
47
|
+
await clock.sleep(REPEAT_MS, signal);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function isMine(options, place, show) {
|
|
51
|
+
if (!options.mine)
|
|
52
|
+
return true;
|
|
53
|
+
try {
|
|
54
|
+
return options.mine(place, show);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export async function followShowsWith(shows, answer, options) {
|
|
61
|
+
const { places, wait, signal, find, focus } = options;
|
|
62
|
+
for await (const show of shows({ places, wait, signal })) {
|
|
63
|
+
const theirs = show.places.filter((place) => isMine(options, place, show));
|
|
64
|
+
if (!theirs.length)
|
|
65
|
+
continue;
|
|
66
|
+
const ours = { ...show, places: theirs };
|
|
67
|
+
let result = 'not_found';
|
|
68
|
+
try {
|
|
69
|
+
const window = await find(ours);
|
|
70
|
+
if (window !== null && window !== undefined) {
|
|
71
|
+
await focus(window, ours);
|
|
72
|
+
result = 'shown';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
result = 'not_found';
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
await answer(show.id, result);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
if (error instanceof SignedOutError)
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
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.38.0';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pendingyou-wake",
|
|
3
3
|
"displayName": "Pending You",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.56.0",
|
|
5
5
|
"description": "Wakes Claude Code when your person answers a Pending You card it posted, even while it sits idle. Installed by `npx pendingyou init`.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "RecordPlane",
|
package/mod/README.md
CHANGED
|
@@ -4,4 +4,4 @@ The pendingyou command line’s copy of the Pending You plugin’s wake mod, for
|
|
|
4
4
|
|
|
5
5
|
In each session it learns the cards the session posts to Pending You, checks them through the session’s own Pending You connection, and starts a turn when your person answers, writes back or a fallback comes due, or hands the session a question from another of their assistants (CLI 0.14.0), even while it sits idle. It answers `npx pendingyou hold` itself, so nothing waits in the background, and shows “2 waiting on you” under the prompt, after the plugin’s name. With the Pending You plugin installed too, only one copy acts.
|
|
6
6
|
|
|
7
|
-
Generated from recordplane/pendingyou (packages/claude-plugin/src/wake) with Pending You’s guide 2.
|
|
7
|
+
Generated from recordplane/pendingyou (packages/claude-plugin/src/wake) with Pending You’s guide 2.56.0; send changes there.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pendingyou",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.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",
|