pendingyou 0.32.0 → 0.32.2
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 +20 -0
- package/dist/permission.js +170 -20
- package/dist/version.js +1 -1
- package/mod/.claude-plugin/plugin.json +1 -1
- package/mod/README.md +1 -1
- package/mod/hooks/register.js +12 -3
- package/mod/hooks/wake.js +15 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.32.2 (2026-10-08)
|
|
4
|
+
|
|
5
|
+
- A Claude Code dialog in bypassPermissions or dontAsk mode always gets its card. A subagent's `rm -rf $S/*` met Claude
|
|
6
|
+
Code's “Dangerous rm operation” check and its dialog waited an hour with no card: its prompt was written down, but
|
|
7
|
+
its card waited for a Notification hook that never named it (one comes per dialog, it doesn't say which agent's, and
|
|
8
|
+
with several subagents at work it could mean another's prompt). Now the session's worker also looks at every such
|
|
9
|
+
prompt after 20 seconds: if the asking agent's transcript has no result for that call yet, its dialog is on screen
|
|
10
|
+
and the card goes up; if it has one (a call Claude Code denied by itself), it's settled with no card. A Notification
|
|
11
|
+
names the newest prompt whose call is still unanswered, and a card it makes due goes up within a second. A
|
|
12
|
+
subagent's card says “A subagent (general-purpose) of Claude Code … needs your permission to run a command”, its
|
|
13
|
+
title ends “(a subagent)”, and it goes once the person answers in Claude Code.
|
|
14
|
+
|
|
15
|
+
## 0.32.1 (2026-10-08)
|
|
16
|
+
|
|
17
|
+
- The Claude Code wake mod never announces a card that's already picked up. It checks cards while a turn runs but tells
|
|
18
|
+
only once the session is idle, and it told what it had read then: a card the agent picked up in that turn (by posting
|
|
19
|
+
the next card with `follows`, or with `ack_answer`) was announced minutes later as “your person answered …”. It now
|
|
20
|
+
reads each card again just before telling it, unless it read it in the last 10 seconds; never tells a card picked up,
|
|
21
|
+
whichever way; and takes the card a new card follows as heard. A card withdrawn under the agent is still told once.
|
|
22
|
+
|
|
3
23
|
## 0.32.0 (2026-10-08)
|
|
4
24
|
|
|
5
25
|
- Follow-ups are new cards that follow the one before (threads, guide 2.48). The Codex wake and the Claude Code wake mod
|
package/dist/permission.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
2
|
import { open, readdir, rm, stat } from 'node:fs/promises';
|
|
3
|
-
import { basename, join } from 'node:path';
|
|
3
|
+
import { basename, dirname, join } from 'node:path';
|
|
4
4
|
import { threadNames } from "./apps/codex-wake.js";
|
|
5
5
|
import { APP_NAMES } from "./apps/ids.js";
|
|
6
6
|
import { DEFAULT_ORIGIN } from "./args.js";
|
|
@@ -13,8 +13,9 @@ import { machineOf } from "./remote.js";
|
|
|
13
13
|
export const GRACE_MS = 10_000;
|
|
14
14
|
export const TITLE_MAX = 90;
|
|
15
15
|
export const WAITING = 'Claude Code is waiting for your OK';
|
|
16
|
-
export const
|
|
16
|
+
export const SUBAGENT = ' (a subagent)';
|
|
17
17
|
const QUIET_MODES = new Set(['bypassPermissions', 'dontAsk']);
|
|
18
|
+
export const QUIET_GRACE_MS = 20_000;
|
|
18
19
|
export const NOTICE_MS = 4000;
|
|
19
20
|
const BATCH_MS = 1000;
|
|
20
21
|
export const PROMPT_APPS = ['claude-code', 'codex'];
|
|
@@ -28,7 +29,7 @@ export const DEFAULT_NAME = 'Claude Code';
|
|
|
28
29
|
const defaultName = (app) => APP_NAMES[app];
|
|
29
30
|
const LEASE_STALE_MS = 120_000;
|
|
30
31
|
const WORKER_MS = 60 * 60_000;
|
|
31
|
-
const NAP_MS =
|
|
32
|
+
const NAP_MS = 1000;
|
|
32
33
|
const CALL_MS = 15_000;
|
|
33
34
|
const BACKOFF_MS = [2000, 5000, 10_000, 20_000];
|
|
34
35
|
const MAX_PROMPTS = 20;
|
|
@@ -139,7 +140,7 @@ export function promptWords(tool, input, home, app = CLAUDE) {
|
|
|
139
140
|
}
|
|
140
141
|
return said(toolName(tool), `to use ${toolName(tool)}`);
|
|
141
142
|
}
|
|
142
|
-
export const fromAgent = (title) => `${cut(title, TITLE_MAX - [...
|
|
143
|
+
export const fromAgent = (title) => `${cut(title, TITLE_MAX - [...SUBAGENT].length)}${SUBAGENT}`;
|
|
143
144
|
const noticeWords = (message) => redactText(text(message), 120);
|
|
144
145
|
function noticeOf(message, now) {
|
|
145
146
|
return {
|
|
@@ -171,7 +172,8 @@ async function change(io, session, edit) {
|
|
|
171
172
|
});
|
|
172
173
|
}
|
|
173
174
|
const alive = (state, now) => Boolean(state.worker && now - state.worker.at < LEASE_STALE_MS);
|
|
174
|
-
const seen = (prompt) => !prompt.quiet || prompt.shown !== undefined;
|
|
175
|
+
const seen = (prompt) => !prompt.quiet || prompt.shown !== undefined || prompt.waited !== undefined;
|
|
176
|
+
const unchecked = (prompt) => !seen(prompt) && !prompt.notice;
|
|
175
177
|
function claim(state, now) {
|
|
176
178
|
if (alive(state, now))
|
|
177
179
|
return null;
|
|
@@ -234,7 +236,7 @@ async function asked(io, origin, input, app) {
|
|
|
234
236
|
const words = promptWords(tool, isObject(input.tool_input) ? input.tool_input : {}, io.home, app);
|
|
235
237
|
const prompt = {
|
|
236
238
|
key: promptKey(tool, keyInput(app, input.tool_input), subagent || undefined),
|
|
237
|
-
|
|
239
|
+
tool: tool.slice(0, 100),
|
|
238
240
|
...(reasonOf(app, input.tool_input)
|
|
239
241
|
? { reason: reasonOf(app, input.tool_input) }
|
|
240
242
|
: {}),
|
|
@@ -271,15 +273,13 @@ async function asked(io, origin, input, app) {
|
|
|
271
273
|
const kept = app === 'claude-code'
|
|
272
274
|
? next.prompts.filter((each) => !wentOn(agent, now)(each))
|
|
273
275
|
: next.prompts;
|
|
274
|
-
const dropped = kept.length < next.prompts.length;
|
|
275
276
|
next.prompts = kept;
|
|
276
277
|
if (!next.prompts.some((each) => each.key === prompt.key))
|
|
277
278
|
next.prompts.push(prompt);
|
|
278
279
|
next.prompts = next.prompts.slice(-MAX_PROMPTS);
|
|
279
280
|
delete next.settled;
|
|
280
281
|
next.at = now;
|
|
281
|
-
|
|
282
|
-
worker = claim(next, now);
|
|
282
|
+
worker = claim(next, now);
|
|
283
283
|
return next;
|
|
284
284
|
});
|
|
285
285
|
if (worker)
|
|
@@ -302,8 +302,8 @@ async function settle(io, session, which, how) {
|
|
|
302
302
|
state.prompts = left;
|
|
303
303
|
state.settled = how;
|
|
304
304
|
state.at = now;
|
|
305
|
-
if (!state.card &&
|
|
306
|
-
return
|
|
305
|
+
if (!state.card && left.length === 0)
|
|
306
|
+
return alive(state, now) ? state : null;
|
|
307
307
|
worker = claim(state, now);
|
|
308
308
|
return state;
|
|
309
309
|
});
|
|
@@ -368,6 +368,16 @@ async function notified(io, origin, input) {
|
|
|
368
368
|
const message = noticeWords(input.message);
|
|
369
369
|
const cwd = text(input.cwd) || io.cwd;
|
|
370
370
|
const link = sessionLink(io.env);
|
|
371
|
+
const known = await load(io, where.session);
|
|
372
|
+
let picked = null;
|
|
373
|
+
for (const prompt of (known?.prompts ?? []).slice().reverse()) {
|
|
374
|
+
if (prompt.shown !== undefined || prompt.notice || prompt.at > before)
|
|
375
|
+
continue;
|
|
376
|
+
if (await stillAsked(known, prompt)) {
|
|
377
|
+
picked = promptId(prompt);
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
371
381
|
let worker = null;
|
|
372
382
|
await change(io, where.session, (state) => {
|
|
373
383
|
const next = state ?? {
|
|
@@ -385,9 +395,9 @@ async function notified(io, origin, input) {
|
|
|
385
395
|
next.transcript = where.transcript;
|
|
386
396
|
if (link)
|
|
387
397
|
next.link = link;
|
|
388
|
-
const shows =
|
|
389
|
-
.
|
|
390
|
-
|
|
398
|
+
const shows = picked
|
|
399
|
+
? next.prompts.find((prompt) => prompt.shown === undefined && promptId(prompt) === picked)
|
|
400
|
+
: undefined;
|
|
391
401
|
if (shows) {
|
|
392
402
|
shows.shown = now;
|
|
393
403
|
delete shows.failed;
|
|
@@ -434,9 +444,23 @@ export async function permissionHook(io, options) {
|
|
|
434
444
|
catch { }
|
|
435
445
|
return 0;
|
|
436
446
|
}
|
|
437
|
-
const dueOf = (prompt, grace) => Math.min(prompt.quiet ? Number.POSITIVE_INFINITY : prompt.at + grace, prompt.shown ?? Number.POSITIVE_INFINITY);
|
|
447
|
+
const dueOf = (prompt, grace) => Math.min(prompt.quiet ? (prompt.waited ?? Number.POSITIVE_INFINITY) : prompt.at + grace, prompt.shown ?? Number.POSITIVE_INFINITY);
|
|
438
448
|
const shownOf = (prompts) => (prompts.filter((prompt) => !prompt.notice).at(-1) ?? prompts.at(-1));
|
|
439
|
-
export function planFor(state, now, grace) {
|
|
449
|
+
export function planFor(state, now, grace, quietGrace = QUIET_GRACE_MS) {
|
|
450
|
+
const plan = cardPlan(state, now, grace);
|
|
451
|
+
const pending = state.prompts.filter(unchecked);
|
|
452
|
+
if (pending.length === 0)
|
|
453
|
+
return plan;
|
|
454
|
+
const check = Math.min(...pending.map((prompt) => prompt.at + quietGrace)) - now;
|
|
455
|
+
if (check <= 0)
|
|
456
|
+
return { kind: 'check' };
|
|
457
|
+
if (plan.kind === 'done')
|
|
458
|
+
return { kind: 'wait', ms: check };
|
|
459
|
+
if (plan.kind === 'wait')
|
|
460
|
+
return { kind: 'wait', ms: Math.min(plan.ms, check) };
|
|
461
|
+
return plan;
|
|
462
|
+
}
|
|
463
|
+
function cardPlan(state, now, grace) {
|
|
440
464
|
const prompts = state.prompts.filter(seen);
|
|
441
465
|
if (prompts.length === 0)
|
|
442
466
|
return state.card ? { kind: 'cancel' } : { kind: 'done' };
|
|
@@ -615,6 +639,104 @@ export async function sessionPosted(path) {
|
|
|
615
639
|
await file.close().catch(() => { });
|
|
616
640
|
}
|
|
617
641
|
}
|
|
642
|
+
const AGENT_ID = /^[A-Za-z0-9_-]{1,100}$/;
|
|
643
|
+
function callsOf(state, prompt) {
|
|
644
|
+
const transcript = state.transcript;
|
|
645
|
+
if (!transcript)
|
|
646
|
+
return [];
|
|
647
|
+
const agent = prompt.agent;
|
|
648
|
+
if (!agent)
|
|
649
|
+
return [{ path: transcript, mine: (entry) => entry.isSidechain !== true }];
|
|
650
|
+
if (!AGENT_ID.test(agent))
|
|
651
|
+
return [];
|
|
652
|
+
const folder = join(dirname(transcript), basename(transcript, '.jsonl'));
|
|
653
|
+
return [
|
|
654
|
+
{ path: join(folder, 'subagents', `agent-${agent}.jsonl`), mine: () => true },
|
|
655
|
+
{ path: transcript, mine: (entry) => entry.isSidechain === true && entry.agentId === agent },
|
|
656
|
+
];
|
|
657
|
+
}
|
|
658
|
+
async function callState(calls, prompt) {
|
|
659
|
+
let file;
|
|
660
|
+
try {
|
|
661
|
+
file = await open(calls.path, 'r');
|
|
662
|
+
}
|
|
663
|
+
catch {
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
try {
|
|
667
|
+
const size = (await file.stat()).size;
|
|
668
|
+
let position = size;
|
|
669
|
+
let carry = Buffer.alloc(0);
|
|
670
|
+
const results = new Set();
|
|
671
|
+
let sameTool = null;
|
|
672
|
+
let found = null;
|
|
673
|
+
const read = (line) => {
|
|
674
|
+
if (!line.includes('tool_'))
|
|
675
|
+
return;
|
|
676
|
+
let entry;
|
|
677
|
+
try {
|
|
678
|
+
entry = JSON.parse(line);
|
|
679
|
+
}
|
|
680
|
+
catch {
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
if (!isObject(entry) || !calls.mine(entry))
|
|
684
|
+
return;
|
|
685
|
+
const message = isObject(entry.message) ? entry.message : {};
|
|
686
|
+
const blocks = Array.isArray(message.content) ? message.content : [];
|
|
687
|
+
for (const block of [...blocks].reverse()) {
|
|
688
|
+
if (!isObject(block))
|
|
689
|
+
continue;
|
|
690
|
+
const id = text(block.tool_use_id) || text(block.id);
|
|
691
|
+
if (block.type === 'tool_result' && id)
|
|
692
|
+
results.add(id);
|
|
693
|
+
else if (block.type === 'tool_use' && entry.type === 'assistant' && found === null) {
|
|
694
|
+
const tool = text(block.name);
|
|
695
|
+
if (promptKey(tool, block.input, prompt.agent) === prompt.key)
|
|
696
|
+
found = results.has(id) ? 'answered' : 'waiting';
|
|
697
|
+
else if (sameTool === null && prompt.tool && tool === prompt.tool)
|
|
698
|
+
sameTool = id;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
while (found === null && position > 0 && size - position < TRANSCRIPT_CAP) {
|
|
703
|
+
const length = Math.min(64 * 1024, position);
|
|
704
|
+
position -= length;
|
|
705
|
+
const chunk = Buffer.alloc(length);
|
|
706
|
+
await file.read(chunk, 0, length, position);
|
|
707
|
+
let buffer = Buffer.concat([chunk, carry]);
|
|
708
|
+
let newline = buffer.lastIndexOf(10);
|
|
709
|
+
while (found === null && newline !== -1) {
|
|
710
|
+
read(buffer.subarray(newline + 1).toString('utf8'));
|
|
711
|
+
buffer = buffer.subarray(0, newline);
|
|
712
|
+
newline = buffer.lastIndexOf(10);
|
|
713
|
+
}
|
|
714
|
+
carry = buffer;
|
|
715
|
+
}
|
|
716
|
+
if (found === null && position === 0)
|
|
717
|
+
read(carry.toString('utf8'));
|
|
718
|
+
if (found !== null)
|
|
719
|
+
return found;
|
|
720
|
+
if (sameTool !== null)
|
|
721
|
+
return results.has(sameTool) ? 'answered' : 'waiting';
|
|
722
|
+
return 'waiting';
|
|
723
|
+
}
|
|
724
|
+
catch {
|
|
725
|
+
return null;
|
|
726
|
+
}
|
|
727
|
+
finally {
|
|
728
|
+
await file.close().catch(() => { });
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
async function stillAsked(state, prompt) {
|
|
732
|
+
for (const calls of callsOf(state, prompt)) {
|
|
733
|
+
const found = await callState(calls, prompt);
|
|
734
|
+
if (found)
|
|
735
|
+
return found === 'waiting';
|
|
736
|
+
}
|
|
737
|
+
return true;
|
|
738
|
+
}
|
|
739
|
+
const promptId = (prompt) => `${prompt.key}:${prompt.at}`;
|
|
618
740
|
export function remoteOf(url) {
|
|
619
741
|
const plain = url
|
|
620
742
|
.trim()
|
|
@@ -675,7 +797,7 @@ const byAgent = (state, prompt) => appOf(state) === 'claude-code' && Boolean(pro
|
|
|
675
797
|
function changeNoteOf(state, prompt) {
|
|
676
798
|
if (prompt.doing === 'to answer its question')
|
|
677
799
|
return cut(`Now waiting for your answer: ${prompt.what}`, 120);
|
|
678
|
-
const agent = byAgent(state, prompt) ?
|
|
800
|
+
const agent = byAgent(state, prompt) ? SUBAGENT : '';
|
|
679
801
|
return `${cut(`Now waiting for your OK: ${prompt.what}`, 120 - [...agent].length)}${agent}`;
|
|
680
802
|
}
|
|
681
803
|
function cardWords(io, state, prompt, name, machine) {
|
|
@@ -686,10 +808,16 @@ function cardWords(io, state, prompt, name, machine) {
|
|
|
686
808
|
: `is waiting for your OK${prompt.doing ? ` ${prompt.doing}` : ''}`;
|
|
687
809
|
const why = prompt.reason ? ` It says: “${prompt.reason}”.` : '';
|
|
688
810
|
const where = `in ${tilde(io, state.cwd)} on ${machine}`;
|
|
689
|
-
const
|
|
690
|
-
|
|
811
|
+
const subagent = byAgent(state, prompt);
|
|
812
|
+
const asker = subagent
|
|
813
|
+
? `A subagent${prompt.agentType ? ` (${prompt.agentType})` : ''} of ${who}, ${where},`
|
|
691
814
|
: `${who} ${where}`;
|
|
692
|
-
const
|
|
815
|
+
const needs = !subagent
|
|
816
|
+
? waiting
|
|
817
|
+
: prompt.doing === 'to answer its question'
|
|
818
|
+
? 'needs your answer to its question'
|
|
819
|
+
: `needs your permission${prompt.doing ? ` ${prompt.doing}` : ''}`;
|
|
820
|
+
const summary = cut(`${asker} ${needs}.${why} It goes on once you answer it there.`, 400);
|
|
693
821
|
const place = `${APP_NAMES[app]} on ${machine}`;
|
|
694
822
|
const linked = state.link ? `${place}, ${state.link}` : place;
|
|
695
823
|
return {
|
|
@@ -817,6 +945,24 @@ async function withdraw(io, client, card, state, how) {
|
|
|
817
945
|
.catch(() => { });
|
|
818
946
|
}
|
|
819
947
|
}
|
|
948
|
+
async function check(io, state, worker) {
|
|
949
|
+
const now = io.now();
|
|
950
|
+
const due = state.prompts.filter((prompt) => unchecked(prompt) && prompt.at + QUIET_GRACE_MS <= now);
|
|
951
|
+
const waiting = new Map();
|
|
952
|
+
for (const prompt of due)
|
|
953
|
+
waiting.set(promptId(prompt), await stillAsked(state, prompt));
|
|
954
|
+
await record(io, state.session, worker, (state) => {
|
|
955
|
+
const left = state.prompts.filter((prompt) => waiting.get(promptId(prompt)) !== false);
|
|
956
|
+
if (left.length < state.prompts.length) {
|
|
957
|
+
state.prompts = left;
|
|
958
|
+
state.settled = 'answered';
|
|
959
|
+
state.at = now;
|
|
960
|
+
}
|
|
961
|
+
for (const prompt of left)
|
|
962
|
+
if (unchecked(prompt) && waiting.get(promptId(prompt)))
|
|
963
|
+
prompt.waited = now;
|
|
964
|
+
});
|
|
965
|
+
}
|
|
820
966
|
const renew = (io, session, worker) => record(io, session, worker, () => { });
|
|
821
967
|
async function letGo(io, session, worker, grace) {
|
|
822
968
|
let done = true;
|
|
@@ -871,6 +1017,10 @@ export async function permissionWorker(io, options) {
|
|
|
871
1017
|
await io.sleep(Math.min(plan.ms, NAP_MS), io.signal);
|
|
872
1018
|
continue;
|
|
873
1019
|
}
|
|
1020
|
+
if (plan.kind === 'check') {
|
|
1021
|
+
await check(io, state, worker);
|
|
1022
|
+
continue;
|
|
1023
|
+
}
|
|
874
1024
|
try {
|
|
875
1025
|
client ??= await mcpClient(io, state.origin, appOf(state), CALL_MS);
|
|
876
1026
|
if (plan.kind === 'post')
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.32.
|
|
1
|
+
export const VERSION = '0.32.2';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pendingyou-wake",
|
|
3
3
|
"displayName": "Pending You",
|
|
4
|
-
"version": "2.48.
|
|
4
|
+
"version": "2.48.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.48.
|
|
7
|
+
Generated from recordplane/pendingyou (packages/claude-plugin/src/wake) with Pending You’s guide 2.48.1; send changes there.
|
package/mod/hooks/register.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Generated from packages/claude-plugin/src/wake/register.ts in recordplane/pendingyou: edit that, then run
|
|
2
2
|
// `pnpm --filter @pendingyou/claude-plugin generate`.
|
|
3
3
|
|
|
4
|
-
import { argumentsOf, blockedNote, CLAIM_PREFIX, CLI_COPY, cardTool, checked, claimLease, cliConfigOf, connectionOf, dueCards, emptyBook, failed, folderOf, HANDED_FAST_MS, HANDED_READS, HERDR_EVERY_MS, HERDR_RUN_MS, handedDue, handedNext, handedOf, handedText, handedView, herdrArgv, herdrPayload, holdOf, holdReply, inHerdr, isRefusal, isStale, isWaitingOnYou, KEEP_MS, learn, leaseHolder, loadedNote, loadedPath, momentOf, originOf, PRESENCE_EVERY_MS, PRESENCE_RUN_MS, presenceArgv, prompted, put, readBook, readClaim, readSetup, readSince, resultObject, SETUP_AFTER_MS, SETUP_RETRY_MS, SETUP_SERVER, SETUP_TRIES, settle, setUpOf, setupNeeded, setupPrompt, stateOf, statusText, TICK_MS, takesHanded, told, toTell, untold, wakeText, } from "./wake.js";
|
|
4
|
+
import { argumentsOf, blockedNote, CLAIM_PREFIX, CLI_COPY, cardTool, checked, claimLease, cliConfigOf, connectionOf, dueCards, emptyBook, failed, folderOf, HANDED_FAST_MS, HANDED_READS, HERDR_EVERY_MS, HERDR_RUN_MS, handedDue, handedNext, handedOf, handedText, handedView, herdrArgv, herdrPayload, holdOf, holdReply, inHerdr, isFresh, isRefusal, isStale, isWaitingOnYou, KEEP_MS, learn, leaseHolder, loadedNote, loadedPath, momentOf, originOf, PRESENCE_EVERY_MS, PRESENCE_RUN_MS, presenceArgv, prompted, put, readBook, readClaim, readSetup, readSince, resultObject, SETUP_AFTER_MS, SETUP_RETRY_MS, SETUP_SERVER, SETUP_TRIES, settle, setUpOf, setupNeeded, setupPrompt, stateOf, statusText, TICK_MS, takesHanded, told, toTell, untold, wakeText, } from "./wake.js";
|
|
5
5
|
const RETRY_MS = 60_000;
|
|
6
6
|
const live = {
|
|
7
7
|
me: '',
|
|
@@ -141,11 +141,20 @@ async function check($, requestId) {
|
|
|
141
141
|
async function deliver($) {
|
|
142
142
|
if (live.busy)
|
|
143
143
|
return false;
|
|
144
|
-
|
|
145
|
-
if (cards.length === 0 || (await $.clock.now()) < live.quietUntil)
|
|
144
|
+
if (toTell(live.book).length === 0 || (await $.clock.now()) < live.quietUntil)
|
|
146
145
|
return false;
|
|
147
146
|
if (!(await claim($)))
|
|
148
147
|
return false;
|
|
148
|
+
const before = await $.clock.now();
|
|
149
|
+
for (const card of toTell(live.book))
|
|
150
|
+
if (!isFresh(card, before))
|
|
151
|
+
await check($, card.requestId);
|
|
152
|
+
if (live.busy)
|
|
153
|
+
return false;
|
|
154
|
+
const now = await $.clock.now();
|
|
155
|
+
const cards = toTell(live.book).filter((card) => isFresh(card, now));
|
|
156
|
+
if (cards.length === 0)
|
|
157
|
+
return false;
|
|
149
158
|
live.book = told(live.book, cards);
|
|
150
159
|
await save($);
|
|
151
160
|
$.prompt.submit({ text: wakeText(cards) }).catch(async () => {
|
package/mod/hooks/wake.js
CHANGED
|
@@ -8,6 +8,7 @@ export const RECENT_MS = 30 * 60_000;
|
|
|
8
8
|
export const MAX_BACKOFF_MS = 15 * 60_000;
|
|
9
9
|
export const HINT_FLOOR_MS = 5000;
|
|
10
10
|
export const BLOCKED_MS = 60_000;
|
|
11
|
+
export const FRESH_MS = 10_000;
|
|
11
12
|
export const KEEP_MS = 7 * 24 * 60 * 60_000;
|
|
12
13
|
export const MAX_CARDS = 50;
|
|
13
14
|
export const LEASE_MS = 60_000;
|
|
@@ -231,6 +232,13 @@ function tracked(book, facts, now) {
|
|
|
231
232
|
nextAt: now + FAST_MS,
|
|
232
233
|
});
|
|
233
234
|
}
|
|
235
|
+
function followedUp(book, follows, now) {
|
|
236
|
+
const card = follows ? book.cards[follows] : undefined;
|
|
237
|
+
if (!card)
|
|
238
|
+
return book;
|
|
239
|
+
const heard = card.state && isReady(card.state) ? { seen: momentOf(card.state) } : {};
|
|
240
|
+
return put(book, { ...card, ...heard, nextAt: now });
|
|
241
|
+
}
|
|
234
242
|
export function learn(book, call, now) {
|
|
235
243
|
const { server, tool, input, output } = call;
|
|
236
244
|
if (!output)
|
|
@@ -251,7 +259,7 @@ export function learn(book, call, now) {
|
|
|
251
259
|
if (!status || !OPEN.has(status) || version === undefined)
|
|
252
260
|
return next;
|
|
253
261
|
const title = text(input.title, 400);
|
|
254
|
-
|
|
262
|
+
const posted = tracked(next, {
|
|
255
263
|
requestId,
|
|
256
264
|
server,
|
|
257
265
|
...(name ? { name } : {}),
|
|
@@ -259,6 +267,7 @@ export function learn(book, call, now) {
|
|
|
259
267
|
pressing: pressingFrom(tool, input, card),
|
|
260
268
|
state: { status, turn: 'you', version, cursor: card?.state?.cursor ?? '' },
|
|
261
269
|
}, now);
|
|
270
|
+
return tool === 'post_request' ? followedUp(posted, requestIdOf(input.follows), now) : posted;
|
|
262
271
|
}
|
|
263
272
|
case 'reply_in_thread': {
|
|
264
273
|
const reopen = isObject(input.reopen) ? input.reopen : null;
|
|
@@ -311,7 +320,7 @@ export function checked(card, output, now) {
|
|
|
311
320
|
if (!isReady(state) && pollAfter !== undefined && pollAfter * 1000 < wait)
|
|
312
321
|
wait = Math.max(HINT_FLOOR_MS, pollAfter * 1000 + 1000);
|
|
313
322
|
const { failures: _failures, ...rest } = card;
|
|
314
|
-
return { ...rest, state, changedAt, nextAt: now + wait };
|
|
323
|
+
return { ...rest, state, changedAt, nextAt: now + wait, checkedAt: now };
|
|
315
324
|
}
|
|
316
325
|
export function failed(card, now, blocked) {
|
|
317
326
|
if (blocked)
|
|
@@ -332,13 +341,16 @@ export function needsTelling(card) {
|
|
|
332
341
|
const state = card.state;
|
|
333
342
|
if (!state || !isReady(state))
|
|
334
343
|
return false;
|
|
344
|
+
if (state.status === 'resolved')
|
|
345
|
+
return false;
|
|
335
346
|
const moment = momentOf(state);
|
|
336
347
|
if (moment === card.told || moment === card.seen)
|
|
337
348
|
return false;
|
|
338
|
-
if (
|
|
349
|
+
if (state.status === 'cancelled' && (card.told || card.seen))
|
|
339
350
|
return false;
|
|
340
351
|
return true;
|
|
341
352
|
}
|
|
353
|
+
export const isFresh = (card, now) => card.checkedAt !== undefined && now - card.checkedAt <= FRESH_MS;
|
|
342
354
|
export const toTell = (book) => Object.values(book.cards)
|
|
343
355
|
.filter(needsTelling)
|
|
344
356
|
.sort((a, b) => a.changedAt - b.changedAt);
|
|
@@ -385,8 +397,6 @@ function line(card) {
|
|
|
385
397
|
];
|
|
386
398
|
case 'cancelled':
|
|
387
399
|
return [`${titled(card)} was withdrawn`, 'nothing to do'];
|
|
388
|
-
case 'resolved':
|
|
389
|
-
return [`${titled(card)} is already handled`, 'nothing to do'];
|
|
390
400
|
case 'answered':
|
|
391
401
|
if (state.handled === 'self')
|
|
392
402
|
return [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pendingyou",
|
|
3
|
-
"version": "0.32.
|
|
3
|
+
"version": "0.32.2",
|
|
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",
|