pendingyou 0.19.0 → 0.20.1

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,32 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.20.1 (2026-10-06)
4
+
5
+ - An older answer is no longer hidden behind newer ones. The session-start and next-message hooks asked for a folder's
6
+ five newest answers and left out the ones already handed over, so a session with five newer answers it hadn't
7
+ acknowledged yet never heard an older one, even on the person's own message (seen 2026-10-06: two answers waited
8
+ over an hour). They now ask for 20 and hand over the newest five not handed over yet; the rest come with the next
9
+ message.
10
+ - The wake mod (guide 2.33.1) makes room in a session's list of 50 cards with the answers its agent already heard,
11
+ never a card still waiting on you while there's one of those: a session that ran many agents lost its oldest open
12
+ cards, and their answers were never told.
13
+
14
+ ## 0.20.0 (2026-10-06)
15
+
16
+ - “I’ll handle it”: a card you ended yourself in Pending You says so, and tells the agent to stand down. An answer can
17
+ now carry `handled`: `self` (you'll do it yourself) or `leave` (nobody does it), with or without your note, and its
18
+ words in `said` (“I’ll do it myself”, “Leave it”, then the note). Wherever the command line hands an answer over
19
+ (`hold`, the session-start and next-message hooks, the wake mod in Claude Code, and the listeners that wake Codex,
20
+ OpenCode and Pi), it says which instead of “Act on it”:
21
+ - `self`: don't act on it, don't draft or send anything for it, and don't follow up.
22
+ - `leave`: nobody does it, so an email or a task gets no reply and no follow-up, and nothing is archived or deleted
23
+ unless the person's rules say so.
24
+ - Either way: if the note asks for one thing, do just that; then `ack_answer` with an outcome like “Left to you”, and
25
+ never post it again. No “reply_in_thread with reopen” hint for these.
26
+ `watch` passes `handled` on with the rest of the request. An answer without it, from an older Pending You, reads as
27
+ before.
28
+ - Hooks run the copy `init` installed: run `npx pendingyou@latest init` once to switch.
29
+
3
30
  ## 0.19.0 (2026-10-06)
4
31
 
5
32
  - 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
package/README.md CHANGED
@@ -349,6 +349,11 @@ next message brings any answer with it too.
349
349
  When you're at the terminal, Claude Code asks you there and puts the same question in Pending You at once; the
350
350
  first answer wins. If you answer in the terminal, it withdraws the card and the hold ends by itself.
351
351
 
352
+ **When you'll handle it yourself (0.20.0).** A card you end with “I’ll handle it” in Pending You (“I’ll do it myself”,
353
+ or “Leave it”) reaches the agent the same ways, saying which: instead of acting on it, it's told to stand down (nothing
354
+ done, drafted, sent or followed up for it; with “Leave it”, an email or a task gets no reply), to pick it up with
355
+ `ack_answer` and an outcome like “Left to you”, and never to post it again.
356
+
352
357
  **Finishing setup by itself (0.11.0).** In `init`'s copy of the mod, a few seconds after a session you're at opens
353
358
  (never `claude -p`), it asks Pending You whether Claude Code is set up here (`whoami`, once Pending You's tools are
354
359
  allowed and the server has connected). When it isn't, it starts one turn once the session is idle: “Finish setting up
@@ -5,7 +5,7 @@ import { getJson, SignInNeeded, Unavailable } from "../api.js";
5
5
  import { DEFAULT_ORIGIN } from "../args.js";
6
6
  import { connectionMachine, readCredential } from "../credentials.js";
7
7
  import { configDir, readJson, withLock, writeWhole } from "../files.js";
8
- import { clip, delegatedOf, isClosed, noticeOf } from "../format.js";
8
+ import { clip, delegatedOf, handledOf, isClosed, noticeOf } from "../format.js";
9
9
  import { chooseThread, goesBy, HANDED_FOR_MS, handedCard, handedMessage, namesAnotherComputer, } from "../handed.js";
10
10
  import { clearHerdr, herdrTarget, isUrgency, PANE_ID, reportCommand, writeHerdr, } from "../herdr.js";
11
11
  import { macName, tailscaleName } from "../remote.js";
@@ -399,6 +399,16 @@ function line(heard, card) {
399
399
  case 'resolved':
400
400
  return null;
401
401
  case 'answered':
402
+ if (handledOf(heard) === 'self')
403
+ return [
404
+ `your person will handle ${titled} themselves`,
405
+ `call ${get} to read it, don’t act on it, then ack_answer with its version and an outcome like “Left to you”`,
406
+ ];
407
+ if (handledOf(heard) === 'leave')
408
+ return [
409
+ `your person said to leave ${titled}`,
410
+ `call ${get} to read it, do nothing for it (no reply, no follow-up), then ack_answer with its version and an outcome like “Left it”`,
411
+ ];
402
412
  return [
403
413
  `your person answered ${titled}`,
404
414
  `call ${get}, act on their words, then ack_answer with its version and a one-line outcome`,
@@ -418,7 +428,7 @@ export function wakeMessage(ready) {
418
428
  });
419
429
  if (lines.length === 0)
420
430
  return null;
421
- const asked = lines.some(({ heard }) => heard.turn === 'agent' && heard.status !== 'expired' && !noticeOf(heard));
431
+ const asked = lines.some(({ heard }) => heard.turn === 'agent' && heard.status !== 'expired' && !noticeOf(heard) && !handledOf(heard));
422
432
  const reopen = asked
423
433
  ? ' If they asked you something or you need more from them, reply_in_thread with reopen instead.'
424
434
  : '';
package/dist/format.js CHANGED
@@ -7,6 +7,11 @@ export function noticeOf(heard) {
7
7
  return null;
8
8
  return { kind, from };
9
9
  }
10
+ export function handledOf(heard) {
11
+ const given = heard.handled;
12
+ return given === 'self' || given === 'leave' ? given : null;
13
+ }
14
+ const HANDLED_SAID = { self: 'I’ll do it myself', leave: 'Leave it' };
10
15
  export const HANDED_PREFIX = 'Pending You: your person handed you';
11
16
  export const HANDED_REPLY = 'on the question handed to you';
12
17
  export const HANDED_TALK_TODO = 'If you know now, answer_delegated with your answer and how you know; if you need more, reply_in_thread to “asker”; if it’s your person’s to decide, reply_in_thread with escalate true; if not, hand_back with what you checked.';
@@ -113,6 +118,14 @@ function describeNotice(heard, notice) {
113
118
  ...(cut ? [`(Cut short: get_request ${id} has all of it.)`] : []),
114
119
  ];
115
120
  }
121
+ function standDown(heard, handled) {
122
+ const ack = `ack_answer with requestId ${heard.requestId}, name ${named(heard)}, expectedVersion ${heard.version}`;
123
+ const noted = Boolean(heard.said?.trim() && heard.said.trim() !== HANDLED_SAID[handled]);
124
+ const note = noted ? '; if their note asks you for one thing, do just that' : '';
125
+ return handled === 'self'
126
+ ? `Stand down: don’t act on it, don’t draft or send anything for it, and don’t follow up${note}. Then call ${ack} and an outcome like “Left to you”. Never post it again.`
127
+ : `Stand down: nobody does it, so an email or a task gets no reply and no follow-up, and nothing is archived or deleted unless their rules say so${note}. Then call ${ack} and an outcome like “Left it”. Never post it again.`;
128
+ }
116
129
  export function describe(heard, again) {
117
130
  const id = heard.requestId;
118
131
  const delegated = delegatedOf(heard);
@@ -131,6 +144,16 @@ export function describe(heard, again) {
131
144
  const lines = [];
132
145
  switch (heard.status) {
133
146
  case 'answered': {
147
+ const handled = handledOf(heard);
148
+ if (handled) {
149
+ const answer = clip(heard.said ?? HANDLED_SAID[handled], MAX_ANSWER);
150
+ cut ||= answer.cut;
151
+ lines.push(`Pending You: ${id} is answered: ${handled === 'self' ? 'your person will do it themselves' : 'your person said to leave it'}.`, question, `Answer: ${answer.text}`);
152
+ if (wrote.length)
153
+ lines.push(`They also wrote: ${wrote.join(' ')}`);
154
+ lines.push(standDown(heard, handled));
155
+ break;
156
+ }
134
157
  if (heard.answers?.length) {
135
158
  lines.push(`Pending You: ${id} is answered, all ${heard.answers.length} questions.`, question);
136
159
  for (const [index, each] of heard.answers.entries()) {
package/dist/pickup.js CHANGED
@@ -13,6 +13,8 @@ import { announce, keepPresent } from "./presence.js";
13
13
  import { setupReminder } from "./setup.js";
14
14
  import { claimHanded, markHanded, momentOf, releaseHanded, wasHanded } from "./state.js";
15
15
  const TIME_BOX_MS = { pickup: 2500, handoff: 2000 };
16
+ export const ANSWERS_ASKED = 20;
17
+ export const ANSWERS_SHOWN = 5;
16
18
  export const REMINDER = 'Pending You: anything you need from your person also goes on a card.';
17
19
  export const OPEN_SHOWN = 3;
18
20
  export const OPEN_TITLE = 50;
@@ -152,7 +154,7 @@ export async function pickup(io, options, hook = { fallback: '' }) {
152
154
  await keepListening(io, origin, input.session);
153
155
  const query = new URLSearchParams([
154
156
  ...folderForms(cwd, io.home).map((form) => ['cwd', form]),
155
- ['limit', '5'],
157
+ ['limit', String(ANSWERS_ASKED)],
156
158
  ...(mode === 'handoff' ? [['open', String(OPEN_SHOWN)]] : []),
157
159
  ['source', app],
158
160
  ]);
@@ -203,8 +205,10 @@ export async function pickup(io, options, hook = { fallback: '' }) {
203
205
  const delegated = delegatedOf(heard);
204
206
  if (await wasHanded(io, origin, heard.requestId, momentOf(heard)))
205
207
  continue;
206
- if (!delegated)
207
- fresh.push(heard);
208
+ if (!delegated) {
209
+ if (fresh.length < ANSWERS_SHOWN)
210
+ fresh.push(heard);
211
+ }
208
212
  else if (woke?.includes(`(${heard.requestId})`))
209
213
  told.push(heard);
210
214
  else if (await takesHanded(io, context, heard, delegated))
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.19.0';
1
+ export const VERSION = '0.20.1';
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pendingyou-wake",
3
3
  "displayName": "Pending You",
4
- "version": "2.32.0",
4
+ "version": "2.33.1",
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.32.0; send changes there.
7
+ Generated from recordplane/pendingyou (packages/claude-plugin/src/wake) with Pending You’s guide 2.33.1; send changes there.
package/mod/hooks/wake.js CHANGED
@@ -116,7 +116,15 @@ export function stateOf(output) {
116
116
  const version = count(output.version);
117
117
  if (!status || !turn || version === undefined)
118
118
  return null;
119
- return { status, turn, version, cursor: typeof output.cursor === 'string' ? output.cursor : '' };
119
+ const given = isObject(output.answer) ? output.answer.handled : output.handled;
120
+ const handled = given === 'self' || given === 'leave' ? given : undefined;
121
+ return {
122
+ status,
123
+ turn,
124
+ version,
125
+ cursor: typeof output.cursor === 'string' ? output.cursor : '',
126
+ ...(handled ? { handled } : {}),
127
+ };
120
128
  }
121
129
  export const emptyBook = (now = 0) => ({ v: 1, updatedAt: now, cards: {} });
122
130
  export function readBook(value) {
@@ -160,12 +168,19 @@ export function readBook(value) {
160
168
  cards,
161
169
  };
162
170
  }
171
+ const listening = (card) => {
172
+ const state = card.state;
173
+ if (!state || isWaitingOnYou(state))
174
+ return true;
175
+ const moment = momentOf(state);
176
+ return moment !== card.told && moment !== card.seen;
177
+ };
163
178
  export function put(book, card) {
164
179
  const cards = { ...book.cards, [card.requestId]: card };
165
180
  const all = Object.values(cards);
166
181
  if (all.length > MAX_CARDS)
167
182
  for (const old of all
168
- .sort((a, b) => a.changedAt - b.changedAt)
183
+ .sort((a, b) => Number(listening(a)) - Number(listening(b)) || a.changedAt - b.changedAt)
169
184
  .slice(0, all.length - MAX_CARDS))
170
185
  delete cards[old.requestId];
171
186
  return { ...book, cards };
@@ -368,6 +383,16 @@ function line(card) {
368
383
  case 'resolved':
369
384
  return [`${titled(card)} is already handled`, 'nothing to do'];
370
385
  case 'answered':
386
+ if (state.handled === 'self')
387
+ return [
388
+ `your person will handle ${titled(card)} themselves`,
389
+ `call ${get} to read it, don’t act on it, then ack_answer with its version and an outcome like “Left to you”`,
390
+ ];
391
+ if (state.handled === 'leave')
392
+ return [
393
+ `your person said to leave ${titled(card)}`,
394
+ `call ${get} to read it, do nothing for it (no reply, no follow-up), then ack_answer with its version and an outcome like “Left it”`,
395
+ ];
371
396
  return [
372
397
  `your person answered ${titled(card)}`,
373
398
  `call ${get}, act on their words, then ack_answer with its version and a one-line outcome`,
@@ -388,7 +413,8 @@ const capital = (sentence) => sentence.charAt(0).toUpperCase() + sentence.slice(
388
413
  export function wakeText(cards) {
389
414
  const asked = cards.some((card) => card.state?.turn === 'agent' &&
390
415
  !isClosed(card.state.status) &&
391
- card.state.status !== 'delegated');
416
+ card.state.status !== 'delegated' &&
417
+ !card.state.handled);
392
418
  const reopen = asked
393
419
  ? ' If they asked you something or you need more from them, reply_in_thread with reopen instead.'
394
420
  : '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pendingyou",
3
- "version": "0.19.0",
3
+ "version": "0.20.1",
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",