pendingyou 0.23.0 → 0.26.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 +32 -0
- package/dist/api.js +7 -3
- package/dist/apps/codex-wake.js +8 -2
- package/dist/format.js +4 -0
- package/dist/main.js +2 -2
- package/dist/permission.js +89 -0
- package/dist/pickup.js +37 -5
- package/dist/stopcheck.js +13 -5
- package/dist/version.js +1 -1
- package/mod/.claude-plugin/plugin.json +1 -1
- package/mod/README.md +1 -1
- package/mod/hooks/wake.js +6 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.26.0 (2026-10-07)
|
|
4
|
+
|
|
5
|
+
- A session that sat idle past its sign-in's 15-minute access token hears its answers again. The next message's
|
|
6
|
+
hand-off started the background refresh, waited 1.2 seconds for it and gave up, often just before it landed, so an
|
|
7
|
+
answered card went unannounced. Now the hand-off and the session start's pickup wait for an expired token until 0.9
|
|
8
|
+
seconds before their 3-second deadline, leaving room to ask for answers; a token with seconds left still waits 1.2.
|
|
9
|
+
- Never silent: when the hand-off or the session start's pickup can't check for answers (Pending You unreachable, a
|
|
10
|
+
server error, or the sign-in still being refreshed), they add “Pending You couldn't check for answers just now: call
|
|
11
|
+
list_pending before you go on.” instead of only the plain reminder.
|
|
12
|
+
|
|
13
|
+
## 0.25.0 (2026-10-06)
|
|
14
|
+
|
|
15
|
+
- An answer reaches only the session that asked. The hooks asked Pending You for their folder's answers and open
|
|
16
|
+
cards, and got every card asked from that folder, or from any folder above it, whoever asked: two sessions in one
|
|
17
|
+
folder each heard the other's answers and were told to ack them, the next-message line listed the other's cards as
|
|
18
|
+
"Your open cards here", and a card asked from a folder above another session's reached it on any computer. Now each
|
|
19
|
+
hook says who its session is: the cards it posted (Claude Code's from its own transcript, with whether that's all of
|
|
20
|
+
them; Codex's, OpenCode's and Pi's from what their hooks remember), the name it goes by, and this computer's names.
|
|
21
|
+
Pending You hands it a card it posted (whatever name it asked under, the app's own included), else one asked under
|
|
22
|
+
its name; only a session that can say neither falls back to its folder, that exact folder on this computer, and
|
|
23
|
+
never for a card asked under another agent's name. The Stop check's look at open cards asks the same way. Needs
|
|
24
|
+
Pending You's matching release; until then the hooks hear as before.
|
|
25
|
+
|
|
26
|
+
## 0.24.0 (2026-10-06)
|
|
27
|
+
|
|
28
|
+
- When your person approves a card that carried a draft, or picks a reply option that sends (`sends`), the hold, the
|
|
29
|
+
hooks' hand-off and the wake mods (Claude Code's and Codex's) add one line: “The approved words are in
|
|
30
|
+
get_request’s answer.approvedDrafts: send them exactly.” Pending You's `get_request` now gives those exact words with
|
|
31
|
+
the answer (and every answer the card's title), so an agent that starts fresh, or a smaller model, sends what was
|
|
32
|
+
approved instead of hunting for it or rewriting it. Needs Pending You's matching release; with an older one nothing
|
|
33
|
+
changes.
|
|
34
|
+
|
|
3
35
|
## 0.23.0 (2026-10-06)
|
|
4
36
|
|
|
5
37
|
- The Claude Code wake mod survives upgrades. `init` now copies it to `~/.config/pendingyou/mod`, a folder that stays
|
package/dist/api.js
CHANGED
|
@@ -5,6 +5,8 @@ import { OAuthError, refresh } from "./oauth.js";
|
|
|
5
5
|
const EARLY_MS = 60_000;
|
|
6
6
|
export const SOON_MS = 5 * 60_000;
|
|
7
7
|
export const REFRESH_WAIT_MS = 1200;
|
|
8
|
+
export const HOOK_DEADLINE_MS = 3000;
|
|
9
|
+
export const ANSWERS_ROOM_MS = 900;
|
|
8
10
|
const USABLE_MS = 10_000;
|
|
9
11
|
export class SignInNeeded extends Error {
|
|
10
12
|
name = 'SignInNeeded';
|
|
@@ -72,7 +74,7 @@ export async function refreshInBackground(io, origin, force, app = DEFAULT_APP)
|
|
|
72
74
|
await refreshStored(io, origin, stored, { force, earlyMs: SOON_MS }, app);
|
|
73
75
|
return true;
|
|
74
76
|
}
|
|
75
|
-
async function hookToken(io, origin, force, signal, app) {
|
|
77
|
+
async function hookToken(io, origin, force, signal, app, expiredWaitMs) {
|
|
76
78
|
const stored = await readCredential(io, origin, app);
|
|
77
79
|
if (!stored)
|
|
78
80
|
throw new SignInNeeded(false);
|
|
@@ -83,7 +85,8 @@ async function hookToken(io, origin, force, signal, app) {
|
|
|
83
85
|
io.background(refreshArgs(origin, app, force));
|
|
84
86
|
if (!force && left > USABLE_MS)
|
|
85
87
|
return stored.accessToken;
|
|
86
|
-
const
|
|
88
|
+
const wait = !force && left <= 0 && expiredWaitMs !== undefined ? expiredWaitMs : REFRESH_WAIT_MS;
|
|
89
|
+
const until = io.now() + wait;
|
|
87
90
|
while (io.now() < until && !signal?.aborted) {
|
|
88
91
|
await io.sleep(50, signal);
|
|
89
92
|
const current = await readCredential(io, origin, app);
|
|
@@ -133,6 +136,7 @@ export function getJson(io, origin, path, timeoutMs, signal, options = {}) {
|
|
|
133
136
|
export function sendJson(io, origin, path, body, timeoutMs, options = {}) {
|
|
134
137
|
return callJson(io, origin, path, { body }, timeoutMs, undefined, options);
|
|
135
138
|
}
|
|
139
|
+
export const expiredWait = (uptime) => Math.max(0, HOOK_DEADLINE_MS - ANSWERS_ROOM_MS - uptime);
|
|
136
140
|
async function callJson(io, origin, path, send, timeoutMs, signal, options = {}) {
|
|
137
141
|
const app = options.app ?? DEFAULT_APP;
|
|
138
142
|
for (let attempt = 0;; attempt++) {
|
|
@@ -140,7 +144,7 @@ async function callJson(io, origin, path, send, timeoutMs, signal, options = {})
|
|
|
140
144
|
try {
|
|
141
145
|
token =
|
|
142
146
|
options.hook && io.background
|
|
143
|
-
? await hookToken(io, origin, attempt > 0, signal, app)
|
|
147
|
+
? await hookToken(io, origin, attempt > 0, signal, app, options.patient && io.uptime ? expiredWait(io.uptime()) : undefined)
|
|
144
148
|
: await freshToken(io, origin, attempt > 0, app);
|
|
145
149
|
}
|
|
146
150
|
catch (error) {
|
package/dist/apps/codex-wake.js
CHANGED
|
@@ -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, handledOf, isClosed, noticeOf } from "../format.js";
|
|
8
|
+
import { APPROVED_DRAFT_NOTICE, approvesDraft, 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";
|
|
@@ -280,6 +280,12 @@ export async function threadNames(io, app, thread) {
|
|
|
280
280
|
return [];
|
|
281
281
|
return (await load(io, app).catch(() => null))?.threads[thread]?.names ?? [];
|
|
282
282
|
}
|
|
283
|
+
export async function threadCards(io, app, thread) {
|
|
284
|
+
if (!thread)
|
|
285
|
+
return [];
|
|
286
|
+
const cards = (await load(io, app).catch(() => null))?.threads[thread]?.cards ?? {};
|
|
287
|
+
return Object.keys(cards).filter((id) => REQUEST_ID.test(id));
|
|
288
|
+
}
|
|
283
289
|
export async function markHandedTurn(io, turn, app) {
|
|
284
290
|
const now = io.now();
|
|
285
291
|
await change(io, app, (state) => {
|
|
@@ -411,7 +417,7 @@ function line(heard, card) {
|
|
|
411
417
|
];
|
|
412
418
|
return [
|
|
413
419
|
`your person answered ${titled}`,
|
|
414
|
-
`call ${get}, act on their words, then ack_answer with its version and a one-line outcome`,
|
|
420
|
+
`call ${get}, act on their words, then ack_answer with its version and a one-line outcome${approvesDraft(heard) ? `. ${APPROVED_DRAFT_NOTICE.slice(0, -1)}` : ''}`,
|
|
415
421
|
];
|
|
416
422
|
default:
|
|
417
423
|
return [
|
package/dist/format.js
CHANGED
|
@@ -12,6 +12,8 @@ export function handledOf(heard) {
|
|
|
12
12
|
return given === 'self' || given === 'leave' ? given : null;
|
|
13
13
|
}
|
|
14
14
|
const HANDLED_SAID = { self: 'I’ll do it myself', leave: 'Leave it' };
|
|
15
|
+
export const APPROVED_DRAFT_NOTICE = 'The approved words are in get_request’s answer.approvedDrafts: send them exactly.';
|
|
16
|
+
export const approvesDraft = (heard) => heard.approvedDraft === true;
|
|
15
17
|
export const HANDED_PREFIX = 'Pending You: your person handed you';
|
|
16
18
|
export const HANDED_REPLY = 'on the question handed to you';
|
|
17
19
|
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.';
|
|
@@ -170,6 +172,8 @@ export function describe(heard, again) {
|
|
|
170
172
|
}
|
|
171
173
|
if (wrote.length)
|
|
172
174
|
lines.push(`${noticeOf(heard) ? 'Also' : 'They also wrote'}: ${wrote.join(' ')}`);
|
|
175
|
+
if (approvesDraft(heard))
|
|
176
|
+
lines.push(APPROVED_DRAFT_NOTICE);
|
|
173
177
|
lines.push(`Act on it, then call ack_answer with requestId ${id}, name ${named(heard)}, expectedVersion ${heard.version} and a one-line outcome.`);
|
|
174
178
|
break;
|
|
175
179
|
}
|
package/dist/main.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { refreshInBackground, SignInNeeded, Unavailable } from "./api.js";
|
|
1
|
+
import { HOOK_DEADLINE_MS, refreshInBackground, SignInNeeded, Unavailable } from "./api.js";
|
|
2
2
|
import { listen, listenHanded, posted } from "./apps/codex-wake.js";
|
|
3
3
|
import { DEFAULT_APP } from "./apps/ids.js";
|
|
4
4
|
import { appModule } from "./apps/registry.js";
|
|
@@ -101,7 +101,7 @@ async function run(io, command, hook = { fallback: '' }) {
|
|
|
101
101
|
return (await import("./app-login.js")).appCommand(io, command);
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
|
-
export
|
|
104
|
+
export { HOOK_DEADLINE_MS };
|
|
105
105
|
async function runHook(io, command) {
|
|
106
106
|
const hook = { fallback: '' };
|
|
107
107
|
const controller = new AbortController();
|
package/dist/permission.js
CHANGED
|
@@ -526,6 +526,95 @@ export async function sessionName(path) {
|
|
|
526
526
|
await file.close().catch(() => { });
|
|
527
527
|
}
|
|
528
528
|
}
|
|
529
|
+
const POST_TOOL = /^mcp__.*pendingyou.*__post_request$/i;
|
|
530
|
+
const POSTED_ID = /"requestId"\s*:\s*"(req_[A-Za-z0-9-]{1,40})"/;
|
|
531
|
+
export const POSTED_MAX = 50;
|
|
532
|
+
function resultText(content) {
|
|
533
|
+
if (typeof content === 'string')
|
|
534
|
+
return content;
|
|
535
|
+
if (!Array.isArray(content))
|
|
536
|
+
return '';
|
|
537
|
+
return content.map((block) => (isObject(block) ? text(block.text) : '')).join('\n');
|
|
538
|
+
}
|
|
539
|
+
export async function sessionPosted(path) {
|
|
540
|
+
if (!path)
|
|
541
|
+
return null;
|
|
542
|
+
let file;
|
|
543
|
+
try {
|
|
544
|
+
file = await open(path, 'r');
|
|
545
|
+
}
|
|
546
|
+
catch (error) {
|
|
547
|
+
return error?.code === 'ENOENT' ? { posted: [], whole: true } : null;
|
|
548
|
+
}
|
|
549
|
+
try {
|
|
550
|
+
const size = (await file.stat()).size;
|
|
551
|
+
let position = size;
|
|
552
|
+
let carry = Buffer.alloc(0);
|
|
553
|
+
const results = new Map();
|
|
554
|
+
const posted = [];
|
|
555
|
+
let more = false;
|
|
556
|
+
const read = (line) => {
|
|
557
|
+
const asResult = line.includes('tool_result') && line.includes('req_');
|
|
558
|
+
const asCall = line.includes('post_request');
|
|
559
|
+
if (!asResult && !asCall)
|
|
560
|
+
return;
|
|
561
|
+
let entry;
|
|
562
|
+
try {
|
|
563
|
+
entry = JSON.parse(line);
|
|
564
|
+
}
|
|
565
|
+
catch {
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
if (!isObject(entry) || entry.isSidechain === true)
|
|
569
|
+
return;
|
|
570
|
+
const message = isObject(entry.message) ? entry.message : {};
|
|
571
|
+
const blocks = Array.isArray(message.content) ? message.content : [];
|
|
572
|
+
for (const block of [...blocks].reverse()) {
|
|
573
|
+
if (!isObject(block))
|
|
574
|
+
continue;
|
|
575
|
+
if (entry.type === 'user' && block.type === 'tool_result' && block.is_error !== true) {
|
|
576
|
+
const id = POSTED_ID.exec(resultText(block.content))?.[1];
|
|
577
|
+
if (id && text(block.tool_use_id))
|
|
578
|
+
results.set(text(block.tool_use_id), id);
|
|
579
|
+
}
|
|
580
|
+
else if (entry.type === 'assistant' &&
|
|
581
|
+
block.type === 'tool_use' &&
|
|
582
|
+
POST_TOOL.test(text(block.name))) {
|
|
583
|
+
const id = results.get(text(block.id));
|
|
584
|
+
if (!id || posted.includes(id))
|
|
585
|
+
continue;
|
|
586
|
+
if (posted.length < POSTED_MAX)
|
|
587
|
+
posted.push(id);
|
|
588
|
+
else
|
|
589
|
+
more = true;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
while (position > 0 && size - position < TRANSCRIPT_CAP) {
|
|
594
|
+
const length = Math.min(64 * 1024, position);
|
|
595
|
+
position -= length;
|
|
596
|
+
const chunk = Buffer.alloc(length);
|
|
597
|
+
await file.read(chunk, 0, length, position);
|
|
598
|
+
let buffer = Buffer.concat([chunk, carry]);
|
|
599
|
+
let newline = buffer.lastIndexOf(10);
|
|
600
|
+
while (newline !== -1) {
|
|
601
|
+
read(buffer.subarray(newline + 1).toString('utf8'));
|
|
602
|
+
buffer = buffer.subarray(0, newline);
|
|
603
|
+
newline = buffer.lastIndexOf(10);
|
|
604
|
+
}
|
|
605
|
+
carry = buffer;
|
|
606
|
+
}
|
|
607
|
+
if (position === 0)
|
|
608
|
+
read(carry.toString('utf8'));
|
|
609
|
+
return { posted, whole: position === 0 && !more };
|
|
610
|
+
}
|
|
611
|
+
catch {
|
|
612
|
+
return null;
|
|
613
|
+
}
|
|
614
|
+
finally {
|
|
615
|
+
await file.close().catch(() => { });
|
|
616
|
+
}
|
|
617
|
+
}
|
|
529
618
|
export function remoteOf(url) {
|
|
530
619
|
const plain = url
|
|
531
620
|
.trim()
|
package/dist/pickup.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { getJson, SignInNeeded } from "./api.js";
|
|
2
|
-
import { computerNames, hearsOnly, keepListening, markHandedTurn, sessionFor, threadNames, touchThread, } from "./apps/codex-wake.js";
|
|
1
|
+
import { getJson, SignInNeeded, Unavailable } from "./api.js";
|
|
2
|
+
import { computerNames, hearsOnly, keepListening, markHandedTurn, sessionFor, threadCards, threadNames, touchThread, } from "./apps/codex-wake.js";
|
|
3
3
|
import { APP_NAMES, DEFAULT_APP } from "./apps/ids.js";
|
|
4
|
-
import { readCredential } from "./credentials.js";
|
|
4
|
+
import { connectionMachine, readCredential } from "./credentials.js";
|
|
5
5
|
import { environmentWarning } from "./environment.js";
|
|
6
6
|
import { configDir, readJson } from "./files.js";
|
|
7
7
|
import { clip, delegatedOf, describe, HANDED_PREFIX, isHandedWake, } from "./format.js";
|
|
@@ -9,7 +9,7 @@ import { goesBy, HANDED_GRACE_MS, namesAnotherComputer } from "./handed.js";
|
|
|
9
9
|
import { herdrTarget, reportCommand } from "./herdr.js";
|
|
10
10
|
import { holdCommand } from "./hold.js";
|
|
11
11
|
import { unwokenLine } from "./loaded.js";
|
|
12
|
-
import { isPermissionCard, isPromptApp, sessionName, settlePrompts } from "./permission.js";
|
|
12
|
+
import { isPermissionCard, isPromptApp, POSTED_MAX, sessionName, sessionPosted, settlePrompts, } from "./permission.js";
|
|
13
13
|
import { announce, keepPresent, readSaid } from "./presence.js";
|
|
14
14
|
import { setupReminder } from "./setup.js";
|
|
15
15
|
import { claimHanded, markHanded, momentOf, releaseHanded, wasHanded } from "./state.js";
|
|
@@ -17,6 +17,7 @@ const TIME_BOX_MS = { pickup: 2500, handoff: 2000 };
|
|
|
17
17
|
export const ANSWERS_ASKED = 20;
|
|
18
18
|
export const ANSWERS_SHOWN = 5;
|
|
19
19
|
export const REMINDER = 'Pending You: anything you need from your person also goes on a card.';
|
|
20
|
+
export const UNCHECKED = "Pending You couldn't check for answers just now: call list_pending before you go on.";
|
|
20
21
|
export const OPEN_SHOWN = 3;
|
|
21
22
|
export const OPEN_TITLE = 50;
|
|
22
23
|
export const OPEN_NAME = 24;
|
|
@@ -110,6 +111,30 @@ export function folderForms(cwd, home) {
|
|
|
110
111
|
forms.push(`~${cwd.slice(base.length)}`);
|
|
111
112
|
return forms;
|
|
112
113
|
}
|
|
114
|
+
export async function askerParams(io, origin, app, session) {
|
|
115
|
+
let name = session.name ?? undefined;
|
|
116
|
+
let posted = [];
|
|
117
|
+
let own = false;
|
|
118
|
+
if (app === DEFAULT_APP) {
|
|
119
|
+
const record = await sessionPosted(session.transcript ?? undefined);
|
|
120
|
+
if (record) {
|
|
121
|
+
posted = record.posted;
|
|
122
|
+
own = record.whole;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else if (session.id) {
|
|
126
|
+
name ??= (await threadNames(io, app, session.id).catch(() => [])).at(-1);
|
|
127
|
+
posted = (await threadCards(io, app, session.id).catch(() => [])).slice(0, POSTED_MAX);
|
|
128
|
+
}
|
|
129
|
+
const machine = await connectionMachine(io, origin, app).catch(() => null);
|
|
130
|
+
const machines = [...new Set([io.host, machine].filter((each) => Boolean(each)))];
|
|
131
|
+
return [
|
|
132
|
+
...(name ? [['name', name]] : []),
|
|
133
|
+
...posted.map((id) => ['posted', id]),
|
|
134
|
+
...(own ? [['own', '1']] : []),
|
|
135
|
+
...machines.slice(0, 4).map((each) => ['machine', each]),
|
|
136
|
+
];
|
|
137
|
+
}
|
|
113
138
|
export async function pickup(io, options, hook = { fallback: '' }) {
|
|
114
139
|
const { origin, mode } = options;
|
|
115
140
|
const app = options.app ?? DEFAULT_APP;
|
|
@@ -168,10 +193,15 @@ export async function pickup(io, options, hook = { fallback: '' }) {
|
|
|
168
193
|
['limit', String(ANSWERS_ASKED)],
|
|
169
194
|
...(mode === 'handoff' ? [['open', String(OPEN_SHOWN)]] : []),
|
|
170
195
|
['source', app],
|
|
171
|
-
...(
|
|
196
|
+
...(await askerParams(io, origin, app, {
|
|
197
|
+
id: input.session,
|
|
198
|
+
transcript: input.transcript,
|
|
199
|
+
name: named ?? null,
|
|
200
|
+
})),
|
|
172
201
|
]);
|
|
173
202
|
const { status, body } = await getJson(io, origin, `/mcp/cli/answers?${query}`, TIME_BOX_MS[mode], io.signal, {
|
|
174
203
|
hook: true,
|
|
204
|
+
patient: true,
|
|
175
205
|
app,
|
|
176
206
|
});
|
|
177
207
|
if (status !== 200)
|
|
@@ -264,6 +294,8 @@ export async function pickup(io, options, hook = { fallback: '' }) {
|
|
|
264
294
|
io.out(`Pending You: this computer’s pendingyou sign-in has ended, so answers can’t wake ${APP_NAMES[app]}. Ask your person to run \`npx pendingyou login${app === DEFAULT_APP ? '' : ` --app ${app}`}\`.\n`);
|
|
265
295
|
if (error instanceof SignInNeeded)
|
|
266
296
|
line = null;
|
|
297
|
+
if (error instanceof Unavailable)
|
|
298
|
+
line = [line, UNCHECKED].filter(Boolean).join('\n');
|
|
267
299
|
return 0;
|
|
268
300
|
}
|
|
269
301
|
finally {
|
package/dist/stopcheck.js
CHANGED
|
@@ -5,8 +5,8 @@ import { APP_NAMES, DEFAULT_APP } from "./apps/ids.js";
|
|
|
5
5
|
import { asksToTypeHere, cardFits, confirmReason, sessionLink } from "./confirm.js";
|
|
6
6
|
import { readCredential } from "./credentials.js";
|
|
7
7
|
import { isHandedWake } from "./format.js";
|
|
8
|
-
import { isPermissionCard, isPromptApp, settlePrompts, subagentsRunning } from "./permission.js";
|
|
9
|
-
import { folderForms } from "./pickup.js";
|
|
8
|
+
import { isPermissionCard, isPromptApp, sessionName, settlePrompts, subagentsRunning, } from "./permission.js";
|
|
9
|
+
import { askerParams, folderForms } from "./pickup.js";
|
|
10
10
|
export const READ_CAP = 4 * 1024 * 1024;
|
|
11
11
|
const CHUNK = 64 * 1024;
|
|
12
12
|
export const STOP_REASON = 'Items for your person are in chat but not in Pending You: post them as cards (ask in both places), or say they don’t need one.';
|
|
@@ -275,13 +275,15 @@ function opencodeTurn(input) {
|
|
|
275
275
|
const text = final.join('\n').trim();
|
|
276
276
|
return text ? { text, posted, cards, handed } : null;
|
|
277
277
|
}
|
|
278
|
-
async function openTitles(io, origin, cwd, app) {
|
|
278
|
+
async function openTitles(io, origin, cwd, app, session) {
|
|
279
279
|
try {
|
|
280
|
+
const name = app === 'claude-code' ? await sessionName(session.transcript ?? undefined) : undefined;
|
|
280
281
|
const query = new URLSearchParams([
|
|
281
282
|
...folderForms(cwd, io.home).map((form) => ['cwd', form]),
|
|
282
283
|
['limit', '0'],
|
|
283
284
|
['open', String(OPEN_CARDS)],
|
|
284
285
|
['source', app],
|
|
286
|
+
...(await askerParams(io, origin, app, { ...session, name: name ?? null })),
|
|
285
287
|
]);
|
|
286
288
|
const { status, body } = await getJson(io, origin, `/mcp/cli/answers?${query}`, OPEN_TIME_BOX_MS, io.signal, { hook: true, app });
|
|
287
289
|
const requests = status === 200 ? body.open?.requests : undefined;
|
|
@@ -366,11 +368,17 @@ export async function stopcheck(io, options) {
|
|
|
366
368
|
return asksToTypeHere(text) || itemsForPerson(text) ? block(DELEGATED_REASON) : 0;
|
|
367
369
|
}
|
|
368
370
|
const cwd = typeof input.cwd === 'string' && input.cwd ? input.cwd : io.cwd;
|
|
371
|
+
const session = {
|
|
372
|
+
id: typeof input.session_id === 'string' && input.session_id ? input.session_id : null,
|
|
373
|
+
transcript: typeof input.transcript_path === 'string' && input.transcript_path
|
|
374
|
+
? input.transcript_path
|
|
375
|
+
: null,
|
|
376
|
+
};
|
|
369
377
|
const ask = asksToTypeHere(text);
|
|
370
378
|
if (ask) {
|
|
371
379
|
if (saysNoCard(text) || turn.cards.some((card) => cardFits(card, ask.words, 'posted')))
|
|
372
380
|
return 0;
|
|
373
|
-
const open = await openTitles(io, options.origin, cwd, app);
|
|
381
|
+
const open = await openTitles(io, options.origin, cwd, app, session);
|
|
374
382
|
if (open ? open.some((title) => cardFits(title, ask.words, 'open')) : saysPosted(text))
|
|
375
383
|
return 0;
|
|
376
384
|
return block(confirmReason(ask.words, sessionLink(io.env), APP_NAMES[app]));
|
|
@@ -378,7 +386,7 @@ export async function stopcheck(io, options) {
|
|
|
378
386
|
if (turn.posted || saysPosted(text) || saysNoCard(text) || !itemsOutsideRecaps(text))
|
|
379
387
|
return 0;
|
|
380
388
|
if (mentionsCards(text)) {
|
|
381
|
-
const open = await openTitles(io, options.origin, cwd, app);
|
|
389
|
+
const open = await openTitles(io, options.origin, cwd, app, session);
|
|
382
390
|
if (open === null || open.some((title) => !isPermissionCard(title)))
|
|
383
391
|
return 0;
|
|
384
392
|
}
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.
|
|
1
|
+
export const VERSION = '0.26.0';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pendingyou-wake",
|
|
3
3
|
"displayName": "Pending You",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.38.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.38.0; send changes there.
|
package/mod/hooks/wake.js
CHANGED
|
@@ -31,6 +31,7 @@ const READS = new Set([
|
|
|
31
31
|
'hand_back',
|
|
32
32
|
]);
|
|
33
33
|
export const HANDED_READS = new Set(['get_request', 'answer_delegated', 'hand_back']);
|
|
34
|
+
export const APPROVED_DRAFT_NOTICE = 'The approved words are in get_request’s answer.approvedDrafts: send them exactly.';
|
|
34
35
|
const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
35
36
|
const parseJson = (text) => {
|
|
36
37
|
try {
|
|
@@ -118,12 +119,16 @@ export function stateOf(output) {
|
|
|
118
119
|
return null;
|
|
119
120
|
const given = isObject(output.answer) ? output.answer.handled : output.handled;
|
|
120
121
|
const handled = given === 'self' || given === 'leave' ? given : undefined;
|
|
122
|
+
const approvedDraft = isObject(output.answer)
|
|
123
|
+
? Array.isArray(output.answer.approvedDrafts) && output.answer.approvedDrafts.length > 0
|
|
124
|
+
: output.approvedDraft === true;
|
|
121
125
|
return {
|
|
122
126
|
status,
|
|
123
127
|
turn,
|
|
124
128
|
version,
|
|
125
129
|
cursor: typeof output.cursor === 'string' ? output.cursor : '',
|
|
126
130
|
...(handled ? { handled } : {}),
|
|
131
|
+
...(approvedDraft ? { approvedDraft: true } : {}),
|
|
127
132
|
};
|
|
128
133
|
}
|
|
129
134
|
export const emptyBook = (now = 0) => ({ v: 1, updatedAt: now, cards: {} });
|
|
@@ -395,7 +400,7 @@ function line(card) {
|
|
|
395
400
|
];
|
|
396
401
|
return [
|
|
397
402
|
`your person answered ${titled(card)}`,
|
|
398
|
-
`call ${get}, act on their words, then ack_answer with its version and a one-line outcome`,
|
|
403
|
+
`call ${get}, act on their words, then ack_answer with its version and a one-line outcome${state.approvedDraft ? `. ${APPROVED_DRAFT_NOTICE.slice(0, -1)}` : ''}`,
|
|
399
404
|
];
|
|
400
405
|
case 'delegated':
|
|
401
406
|
return [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pendingyou",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Pending You for your coding agents: one command sets up Claude Code, Codex, OpenCode and Pi to ask you through Pending You and hear your answers, even while they sit idle.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://www.pendingyou.com",
|