pendingyou 0.14.0 → 0.16.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 +97 -0
- package/README.md +58 -15
- package/dist/api.js +27 -0
- package/dist/apps/claude-code.js +17 -12
- package/dist/apps/codex-wake.js +15 -3
- package/dist/apps/codex.js +102 -32
- package/dist/apps/opencode-plugin.js +52 -2
- package/dist/apps/opencode.js +13 -10
- package/dist/apps/pi-extension.js +41 -1
- package/dist/apps/pi.js +13 -10
- package/dist/apps/switch.js +15 -0
- package/dist/args.js +69 -1
- package/dist/claude.js +18 -4
- package/dist/format.js +65 -1
- package/dist/handed.js +28 -12
- package/dist/hooks.js +2 -1
- package/dist/io.js +1 -0
- package/dist/main.js +26 -4
- package/dist/permission.js +274 -72
- package/dist/pickup.js +16 -5
- package/dist/presence.js +330 -0
- package/dist/stopcheck.js +4 -4
- 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 +41 -3
- package/mod/hooks/wake.js +79 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,102 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.16.0 (2026-10-05)
|
|
4
|
+
|
|
5
|
+
- A word on one of your own cards that's neither an answer nor your person's (guide 2.32): `/mcp/cli/answers` marks it
|
|
6
|
+
`notice` (`{ kind, from }`), and the hold, the pickup, the hooks and `watch` say what it is instead of “they wrote
|
|
7
|
+
back”:
|
|
8
|
+
- `follow_up`: the helper that answered it for your person followed up afterwards (“shipped in #231”): “Pending You:
|
|
9
|
+
pending-you-chief followed up on req_…, which it answered for your person.”, the question, its words, then “Act on
|
|
10
|
+
it if it needs anything, then call ack_answer … to mark it heard.” On an answer you haven't picked up yet, its
|
|
11
|
+
words come with the answer (“Also: …”).
|
|
12
|
+
- `handed_over`: your person handed your open cards to another assistant: “Pending You: your person handed your open
|
|
13
|
+
cards to pending-you-chief, req_… among them.” and “Stop working on them: pending-you-chief answers them and
|
|
14
|
+
follows up. Call ack_answer … to say you’ve stopped.”
|
|
15
|
+
`watch` gives both as `message_ready`. A command line before 0.16.0 hears the same words as a message from them.
|
|
16
|
+
- Claude Code's permission prompts get a card in bypassPermissions mode too: a dangerous request from a background
|
|
17
|
+
agent there waited minutes for an OK with no card. Claude Code still asks for a few things in that mode (a dangerous
|
|
18
|
+
`rm`, an ask rule, a safety check it can't verify), but the PermissionRequest hook skipped bypassPermissions and
|
|
19
|
+
dontAsk mode outright. It also runs for a call Claude Code then denies by itself, showing
|
|
20
|
+
nothing, so in those two modes it now writes the prompt down quietly, and its card goes up only once a dialog is on
|
|
21
|
+
screen. A new Notification hook says so: `pendingyou-hook notify --app claude-code`, with the matcher
|
|
22
|
+
`permission_prompt`, so Claude Code runs it only for a dialog that has waited about six seconds with nobody typing,
|
|
23
|
+
and never for its other notifications. A call denied without a dialog never gets a card: its PostToolUseFailure, the
|
|
24
|
+
same agent's next call, your next message or the end of the turn settles it. In the other modes a card still goes up
|
|
25
|
+
after 10 seconds, or as soon as the Notification comes.
|
|
26
|
+
- A dialog no PermissionRequest hook ran for (a sandboxed command's network request; one a Claude Code ran none for)
|
|
27
|
+
gets a card from its Notification alone: “Claude Code is waiting for your OK”, with Claude Code's own words, redacted,
|
|
28
|
+
in its summary (“It says: “Claude needs your permission”.”). Whose dialog it was isn't known, so only a turn that ends
|
|
29
|
+
with no subagent running, or the session's end, takes it down.
|
|
30
|
+
- A background agent's prompt says so: “Claude Code is waiting for your OK: pnpm lint && git push (a background
|
|
31
|
+
agent)”, and its summary names the agent's type (“A background agent (general-purpose) of Claude Code
|
|
32
|
+
(billing-webhooks), in ~/… on work-mbp, is waiting for your OK to run a command.”). It stays on the session's one card,
|
|
33
|
+
and only that agent's own calls settle it, not the session's or another agent's.
|
|
34
|
+
- `init` adds the Notification hook at the end of Claude Code's Notification hooks, after any of your own, and says so
|
|
35
|
+
on a computer set up before (“Added a Notification hook to the permission-prompt hooks: …”); `status` marks the
|
|
36
|
+
Permission prompts line missing without it; `uninstall` and `--no-permission-cards` take it out with the others.
|
|
37
|
+
Codex is unchanged: it has no hook that says a dialog is on screen (its `notify` runs only when a turn completes).
|
|
38
|
+
|
|
39
|
+
## 0.15.0 (2026-10-05)
|
|
40
|
+
|
|
41
|
+
- Presence: Pending You hears which of your agents' sessions are open right now. Each app tells it with its own
|
|
42
|
+
connection here, `POST <origin>/mcp/cli/presence` with `{"source", "name", "sessionId", "cwd", "state"}` (`name` the
|
|
43
|
+
name the session goes by with Pending You, else null; `cwd` only as `~/…`, else null; `state` `live` or `closed`).
|
|
44
|
+
Pending You counts a session live for 15 minutes after its last `live`, so an open session says so every 5 minutes,
|
|
45
|
+
with no model involved:
|
|
46
|
+
- Claude Code: its session-start hook says live; the wake mod (Claude Code 2.1.287 or later) says live as the
|
|
47
|
+
session starts, after /clear or /resume, and every 5 minutes while it's open, by running the hooks' script; a new
|
|
48
|
+
SessionEnd hook, `presence`, through the same script, says closed. Without the wake mod, the next-message hook
|
|
49
|
+
says live again once the last was 4 minutes ago. Only `init`'s copy of the mod does it, never the plugin's.
|
|
50
|
+
- Codex: its session-start and next-message hooks start a keeper for the thread (`presence --keep`, detached, a lease
|
|
51
|
+
of its own), which says live every 5 minutes while the Codex process the hooks ran under is running (Codex CLI, or
|
|
52
|
+
the Codex app's own codex), and closed once it's gone. A new SessionEnd hook (Codex fires it when a conversation
|
|
53
|
+
is archived or deleted, when Codex closes, or after 30 idle minutes) says closed and stops the keeper; its timeout
|
|
54
|
+
is 3 seconds, the most Codex gives one. Where the Codex process can't be found, the hooks say live as they run.
|
|
55
|
+
- OpenCode's plugin and Pi's extension say live as a session starts (OpenCode: when it's created, or its first
|
|
56
|
+
message) and every 5 minutes while the app runs, and closed when OpenCode deletes the session or goes, or Pi shuts
|
|
57
|
+
the session down.
|
|
58
|
+
A hook only starts the send in the background; a send gives up after 3 seconds; a Pending You from before presence
|
|
59
|
+
answers 404, which is taken quietly. A sign-in that only hears (`--oauth`) sends nothing. Events are written down in
|
|
60
|
+
`~/.config/pendingyou/presence/<app>.json` (ids, states and times), so a late `live` never overrides a newer
|
|
61
|
+
`closed`, and a reload's `live` goes after its `closed`. `status` has a line for each app: “Presence: tells Pending
|
|
62
|
+
You when this session is open”.
|
|
63
|
+
- Codex's permission prompts on a card, as Claude Code's since 0.13.0. `init` adds Codex's PermissionRequest hook (it
|
|
64
|
+
fires when Codex is about to ask for approval: a shell command that needs more than the sandbox gives, an
|
|
65
|
+
`apply_patch`, an MCP tool; `tool_name` Bash, apply_patch or the tool's, and `tool_input.description` when Codex has
|
|
66
|
+
a reason), through the shim with `--app codex`. Codex reads that hook's output as its decision, so it prints nothing
|
|
67
|
+
and exits 0: it declines, and Codex asks as before. Once a prompt has waited 10 seconds, the same detached worker
|
|
68
|
+
posts one card with Codex's own connection: “Codex is waiting for your OK: git push origin main”, “…: edit
|
|
69
|
+
src/app.ts and 1 more” for a patch (the files its `*** Update File:` lines name), “…: create_issue on github” for an
|
|
70
|
+
MCP tool, with Codex's reason (redacted) in the summary, under the name the thread went by on Pending You's card tools,
|
|
71
|
+
else “Codex”, and the same redaction. It's withdrawn when the call runs (a PostToolUse hook for every tool, which
|
|
72
|
+
also settles a prompt for the same tool when Codex gives the call that ran its input in another form), with your
|
|
73
|
+
next message, at the end of the turn, or when the thread ends (SessionEnd). `--no-permission-cards` leaves Codex's
|
|
74
|
+
out too. The Codex app's browser-use prompts run no PermissionRequest hook, so they get no card.
|
|
75
|
+
- Presence keeps to Pending You's contract: the name trimmed to one line of at most 60 characters, a folder over 300
|
|
76
|
+
characters sent as null, a 401 tried once more after a refresh in the background, a 429 waited out once (its
|
|
77
|
+
`retry-after`, 60 seconds) by a send in its own process (never a hook), and 400, 403 and 404 taken quietly; Codex's
|
|
78
|
+
keeper stops once a send is refused, its sign-in is gone, or the server has no presence.
|
|
79
|
+
- A question you handed an agent, when the assistant that asked answers what the helper asked it on the card (or you
|
|
80
|
+
write in to them both, guide 2.30), wakes the helper with those words, not the handed question again: “Pending You:
|
|
81
|
+
billing-webhooks replied on the question handed to you, “Which queue?” (req_…): “It's the jobs queue.”. Call
|
|
82
|
+
get_request (requestId req_…, name “infra”) to read what you've said to each other. If you know now,
|
|
83
|
+
answer_delegated …; if you need more, reply_in_thread to “asker”; if it's your person's to decide, reply_in_thread
|
|
84
|
+
with escalate true; if not, hand_back …” (“your person wrote to you both …” for yours). The same from the hooks, the
|
|
85
|
+
wake mod, OpenCode's plugin and Pi's extension; Codex's `codex queue` leaves the words out of its arguments, where any
|
|
86
|
+
program could read them, and get_request has them. The wake mod now wakes a session for such a reply after it read
|
|
87
|
+
the question itself (before, a question it had read never woke it again), and the Stop check and the hooks know a
|
|
88
|
+
turn such a wake started.
|
|
89
|
+
- Switching an agent's own sign-in to this computer's says what becomes of the old one: once the agent shows up on this
|
|
90
|
+
computer's sign-in, Pending You moves the old sign-in's agents over, with their cards and the questions you delegated
|
|
91
|
+
to them, and signs it out. Where there's no browser (an SSH session) init says what it switched instead of switching
|
|
92
|
+
without a word.
|
|
93
|
+
- Codex runs a hook only once you trust it, so the three new hooks (PermissionRequest, PostToolUse for every tool,
|
|
94
|
+
SessionEnd) need trusting once. They go at the end of their lists, so the ones you trusted stay trusted. `init` says
|
|
95
|
+
so when it adds them to a Codex set up before (“Codex has new Pending You hooks (session end and permission prompts),
|
|
96
|
+
which it skips until you trust them once: …”), and `status` has a line for each, with each hook's trust:
|
|
97
|
+
“Permission prompts: a card when Codex waits for your OK” and “Presence: …”. Neither keeps `status` from saying
|
|
98
|
+
Ready. Codex's record of its hooks (`codex.json`) now names each by its event and command.
|
|
99
|
+
|
|
3
100
|
## 0.14.0 (2026-10-05)
|
|
4
101
|
|
|
5
102
|
- Questions your person hands one of your agents from another assistant (Delegate: Pending You's card link, or D on the
|
package/README.md
CHANGED
|
@@ -24,8 +24,9 @@ Run it once on each computer, in a terminal. It finds the agents on the computer
|
|
|
24
24
|
Pi reads Codex's, else `~/.pi/agent/skills/pendingyou/SKILL.md`),
|
|
25
25
|
- adds the hooks that hand it your answers: a new session picks up answers that arrived while nothing was running,
|
|
26
26
|
your next message brings a waiting answer with it, and a check when it finishes a turn catches what it left you only
|
|
27
|
-
in chat ([below](#nothing-left-only-in-chat));
|
|
28
|
-
([below](#when-claude-code-waits-for-your-ok))
|
|
27
|
+
in chat ([below](#nothing-left-only-in-chat)); for Claude Code and Codex, a card when it waits for your OK to run
|
|
28
|
+
something ([below](#when-claude-code-waits-for-your-ok)); and they tell Pending You which sessions are open
|
|
29
|
+
([below](#which-sessions-are-open)),
|
|
29
30
|
- and wakes it when you answer: Claude Code 2.1.287 or later through the wake mod, Codex through `codex queue`, OpenCode
|
|
30
31
|
through its plugin and Pi through its extension (which also do the hooks' work there); and when you hand it a
|
|
31
32
|
question from another of your assistants ([below](#when-you-hand-an-agent-a-question)).
|
|
@@ -54,10 +55,11 @@ in each, every other byte as it was), and running it again changes nothing it do
|
|
|
54
55
|
**A pendingyou server that's there already** is decided on before anyone signs in. One at another Pending You
|
|
55
56
|
(staging, say) is named and replaced only once you say so (`--yes` replaces it without asking); keep it and that agent
|
|
56
57
|
is left as it is. One at this Pending You that signs in by itself (Claude Code's `/mcp` → Authenticate, Codex's
|
|
57
|
-
`codex mcp login`, or the Pending You plugin for Claude) is moved to this computer's sign-in:
|
|
58
|
-
|
|
59
|
-
ask it's kept, and `init` prints the command that switches it.
|
|
60
|
-
|
|
58
|
+
`codex mcp login`, or the Pending You plugin for Claude) is moved to this computer's sign-in: where there's no browser
|
|
59
|
+
(it can't finish there anyway) without asking, saying what it switched; on Yes (the default) at a terminal; or with
|
|
60
|
+
`--yes`; with nobody to ask it's kept, and `init` prints the command that switches it. Once the agent shows up on this
|
|
61
|
+
computer's sign-in, Pending You moves the old sign-in's agents over to it, with their cards and the questions you
|
|
62
|
+
delegated to them, and signs the old one out. A kept plugin
|
|
61
63
|
carries the skill, so none is saved. `--oauth` keeps every agent signing in by itself, as before 0.11.0.
|
|
62
64
|
|
|
63
65
|
The hooks run a copy of this version kept in `~/.config/pendingyou/cli/<version>/`, through a small script with a path
|
|
@@ -94,10 +96,13 @@ http_headers_helper = "/Users/ann/.config/pendingyou/bin/pendingyou-mcp-headers
|
|
|
94
96
|
default_tools_approval_mode = "approve"
|
|
95
97
|
```
|
|
96
98
|
|
|
97
|
-
and
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
and hooks in `~/.codex/hooks.json`: a session start, each message, Pending You's card tools (so the Stop check knows a
|
|
100
|
+
turn posted, and so the thread is woken), and the Stop check; and (0.15.0) Codex's permission prompt and every tool
|
|
101
|
+
call that ran (a card when Codex waits for your OK, [below](#when-claude-code-waits-for-your-ok)) and the session's end
|
|
102
|
+
([below](#which-sessions-are-open)). **Codex runs a hook only once you trust it**: in Codex CLI, start `codex` and
|
|
103
|
+
choose “Trust all and continue” (or trust them in `/hooks`); in the Codex app, click Review hooks › Allow all (or
|
|
104
|
+
Settings › Hooks › Trust all). `status` says which are trusted. Upgrading from 0.14.0 adds three hooks, which Codex
|
|
105
|
+
skips until you trust them once; `init` says so, and the ones you trusted stay trusted. A `pendingyou` server or
|
|
101
106
|
hooks Codex's `/import` copied from Claude Code (without its sign-in) are replaced.
|
|
102
107
|
|
|
103
108
|
**The Codex app** (on a Mac, in the ChatGPT app) shares all of this with Codex CLI: the same `~/.codex`, server, hooks
|
|
@@ -241,10 +246,44 @@ a key are blanked (`curl -H "Authorization: …" https://api.example.com/items?t
|
|
|
241
246
|
shown safely (several lines, inline code like `node -e`, one that takes a secret) shows only as “Bash: node …”. No card
|
|
242
247
|
for `claude -p` or the Agent SDK, where nobody sees a prompt.
|
|
243
248
|
|
|
249
|
+
In bypassPermissions mode (`--dangerously-skip-permissions`) Claude Code still asks for a few things: a dangerous `rm`,
|
|
250
|
+
an ask rule of yours, a safety check it can't verify. Since 0.16.0 those get a card too, once Claude Code says the
|
|
251
|
+
dialog has waited a few seconds with nobody typing (its Notification hook, which `init` adds for permission dialogs
|
|
252
|
+
only): a call it denies by itself, showing you nothing, never gets one. The same hook puts up a card for a dialog no
|
|
253
|
+
other hook sees (a sandboxed command asking for the network), in Claude Code's own words, and in the other modes puts
|
|
254
|
+
a card up as soon as Claude Code says so, without waiting the full 10 seconds. A background agent's prompt says so:
|
|
255
|
+
“Claude Code is waiting for your OK: pnpm lint && git push (a background agent)”.
|
|
256
|
+
|
|
244
257
|
It needs Claude Code 2.1.119 or later, signing in through this computer's sign-in (not `--oauth`), and isn't on
|
|
245
258
|
Windows yet. Don't want it? `npx pendingyou init --no-permission-cards` (later runs keep it off; `--permission-cards`
|
|
246
259
|
turns it back on). `npx pendingyou status` says which.
|
|
247
260
|
|
|
261
|
+
**Codex** (0.15.0) gets the same: when Codex stops to ask for your approval (a command that needs more than its sandbox
|
|
262
|
+
gives, a patch, an MCP tool) and you don't answer within 10 seconds, a card says “Codex is waiting for your OK: git push
|
|
263
|
+
origin main” (“…: edit src/app.ts and 1 more” for a patch), with Codex's reason for asking, blanked like the rest. Its
|
|
264
|
+
PermissionRequest hook prints nothing, so Codex asks exactly as before; the card goes once the call runs, you write to
|
|
265
|
+
Codex, the turn ends or the thread closes. It needs Codex signing in through this computer's sign-in, and trusting the
|
|
266
|
+
new hooks once. The Codex app's browser-use prompts don't run that hook, so they get no card.
|
|
267
|
+
|
|
268
|
+
## Which sessions are open
|
|
269
|
+
|
|
270
|
+
Since 0.15.0 Pending You hears which of your agents' sessions are open right now, so it can say so and offer an open
|
|
271
|
+
one a question. Each app tells it with its own connection here: the session's id, the name it goes by with Pending You,
|
|
272
|
+
its folder (under `~` only) and `live` or `closed`. Pending You counts a session open for 15 minutes after its last
|
|
273
|
+
`live`, so an open session says so every 5 minutes, with no model involved:
|
|
274
|
+
|
|
275
|
+
- **Claude Code**: as a session starts (its session-start hook, and the wake mod), then every 5 minutes from the wake
|
|
276
|
+
mod while it's open (without the mod, with your messages); closed as it ends (a SessionEnd hook).
|
|
277
|
+
- **Codex**: as a thread starts or you write to it, a small process in the background says so every 5 minutes while
|
|
278
|
+
Codex (or the Codex app) runs, and closed once it's gone; its SessionEnd hook (a conversation archived or closed, or
|
|
279
|
+
idle 30 minutes) says closed.
|
|
280
|
+
- **OpenCode and Pi**: the plugin and the extension, as a session starts and every 5 minutes while the app runs, and
|
|
281
|
+
closed as it goes.
|
|
282
|
+
|
|
283
|
+
It's never in your way: the hooks only start it in the background, each send gives up after 3 seconds, and a Pending
|
|
284
|
+
You from before presence is taken quietly. A sign-in that only hears (`--oauth`) sends nothing. `npx pendingyou status`
|
|
285
|
+
has a Presence line for each agent.
|
|
286
|
+
|
|
248
287
|
## How Claude Code hears your answer
|
|
249
288
|
|
|
250
289
|
**Claude Code 2.1.287 or later: the wake mod.** There's nothing to run. In each session the mod learns the cards
|
|
@@ -386,8 +425,9 @@ Pending You (Claude Code says which server it's for; Codex's is the one `init` w
|
|
|
386
425
|
sign-in each agent's connection used, so signing in again keeps it.
|
|
387
426
|
|
|
388
427
|
`state.json` beside them remembers which answers were already handed over (ids only) and how many session starts
|
|
389
|
-
reminded an unfinished setup, and when; `permission-cards/` each Claude Code session's
|
|
390
|
-
and its card (ids, hashes and the card's redacted words: never what the tool was given), gone once nothing waits;
|
|
428
|
+
reminded an unfinished setup, and when; `permission-cards/` each Claude Code session's (and Codex thread's) prompts
|
|
429
|
+
still waiting on your OK and its card (ids, hashes and the card's redacted words: never what the tool was given), gone once nothing waits;
|
|
430
|
+
`presence/` the last presence each session said (ids, states and times), and Codex threads' keepers;
|
|
391
431
|
`claude-code.json`, `codex.json`, `opencode.json` and `pi.json` what `init`
|
|
392
432
|
added; `codex-threads.json`, `opencode-threads.json` and `pi-threads.json` which cards each Codex thread or OpenCode or
|
|
393
433
|
Pi session waits on, and (0.14.0) the names it went by and its folder, kept 12 hours for a question you may hand it
|
|
@@ -398,8 +438,9 @@ whether the agent has a `pendingyou` MCP server and where it points. With a sign
|
|
|
398
438
|
listeners tell this computer's handed questions from another's by its names: its hostname, the names its sign-ins by
|
|
399
439
|
code were given, a Mac's own name (`scutil --get ComputerName`) and, outside a hook, Tailscale's (`tailscale status`). For a permission prompt's card, the worker reads
|
|
400
440
|
the end of the Claude Code session's transcript only for the name the session gave Pending You, and `git remote
|
|
401
|
-
get-url origin` in its folder (sent without any user name or password in it).
|
|
402
|
-
|
|
441
|
+
get-url origin` in its folder (sent without any user name or password in it). To tell when a Codex thread's Codex has quit, its
|
|
442
|
+
keeper asks `ps` about the Codex process its hooks ran under (its id and program name, nothing else). Nothing is sent
|
|
443
|
+
anywhere but Pending You. OpenCode's plugin reads its session's messages only when it goes idle, for the check that
|
|
403
444
|
nothing was left in chat, and hands the hooks' script just that turn's words and tool names.
|
|
404
445
|
|
|
405
446
|
The wake mod runs inside Claude Code and reaches Pending You only through the session's own connection, with
|
|
@@ -407,7 +448,9 @@ The wake mod runs inside Claude Code and reaches Pending You only through the se
|
|
|
407
448
|
`list_pending` with the session's name and `get_request` on a question you handed it. It keeps each session's cards (id,
|
|
408
449
|
title, the agent's name, where each stands), and which handed questions a session was told of (ids and when), in Claude
|
|
409
450
|
Code's store for mods, `~/.claude/plugins/store/`, and forgets them once they close or after 7 days. It marks the copy
|
|
410
|
-
acting for a session in that session's environment (`PENDINGYOU_WAKE_OWNER`).
|
|
451
|
+
acting for a session in that session's environment (`PENDINGYOU_WAKE_OWNER`). `init`'s copy (0.15.0) also reads
|
|
452
|
+
`~/.config/pendingyou/claude-code.json` for the Pending You it's for, and runs the hooks' script to say the session is
|
|
453
|
+
open.
|
|
411
454
|
|
|
412
455
|
## Development
|
|
413
456
|
|
package/dist/api.js
CHANGED
|
@@ -172,3 +172,30 @@ export async function getJson(io, origin, path, timeoutMs, signal, options = {})
|
|
|
172
172
|
return { status: response.status, body };
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
|
+
export async function postJson(io, origin, path, body, timeoutMs, options = {}) {
|
|
176
|
+
const app = options.app ?? DEFAULT_APP;
|
|
177
|
+
const token = await hookToken(io, origin, options.force === true, options.signal, app);
|
|
178
|
+
let response;
|
|
179
|
+
try {
|
|
180
|
+
response = await io.fetch(`${origin}${path}`, {
|
|
181
|
+
method: 'POST',
|
|
182
|
+
headers: {
|
|
183
|
+
authorization: `Bearer ${token}`,
|
|
184
|
+
'content-type': 'application/json',
|
|
185
|
+
accept: 'application/json',
|
|
186
|
+
},
|
|
187
|
+
body: JSON.stringify(body),
|
|
188
|
+
signal: either(options.signal, timeoutMs),
|
|
189
|
+
redirect: 'manual',
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
throw new Unavailable('Pending You couldn’t be reached.');
|
|
194
|
+
}
|
|
195
|
+
await response.body?.cancel().catch(() => { });
|
|
196
|
+
const retry = Number(response.headers.get('retry-after'));
|
|
197
|
+
return {
|
|
198
|
+
status: response.status,
|
|
199
|
+
...(Number.isFinite(retry) && retry > 0 ? { retryAfter: retry } : {}),
|
|
200
|
+
};
|
|
201
|
+
}
|
package/dist/apps/claude-code.js
CHANGED
|
@@ -7,16 +7,11 @@ import { checkHelper, foundBy, headlessReason, helperCommand, helperPath, isOurH
|
|
|
7
7
|
import { connectionTitle, FINISH_SAY, setupOf, setupStatus } from "../setup.js";
|
|
8
8
|
import { shimPath } from "../shim.js";
|
|
9
9
|
import { APP_NAMES } from "./ids.js";
|
|
10
|
+
import { switches } from "./switch.js";
|
|
10
11
|
const NAME = APP_NAMES['claude-code'];
|
|
11
12
|
const RESTART = 'restart Claude Code here (/exit, then claude --continue), or start it in the folder you work in';
|
|
12
|
-
async function
|
|
13
|
-
|
|
14
|
-
return true;
|
|
15
|
-
if (!io.interactive)
|
|
16
|
-
return false;
|
|
17
|
-
io.out(`${what} init can move Claude Code to this computer’s own sign-in, which the hooks use too: one sign-in for both, and nothing to Authenticate. The connection it has now stays on your Assistants page until you remove it, and answers to cards it asked through that one won’t reach Claude Code by themselves.\n`);
|
|
18
|
-
const answer = await io.ask('Switch Claude Code to this computer’s sign-in? [Y/n] ');
|
|
19
|
-
return !/^\s*no?\s*$/i.test(answer);
|
|
13
|
+
async function switchesFor(io, ctx, what) {
|
|
14
|
+
return switches(io, ctx, { name: 'Claude Code', what, uses: 'the hooks use', headless: true });
|
|
20
15
|
}
|
|
21
16
|
async function replaces(io, ctx, server, url) {
|
|
22
17
|
ctx.progress(`Claude Code’s pendingyou MCP server points at ${server.url}, not ${url}.\n`);
|
|
@@ -117,7 +112,7 @@ export const claudeCode = {
|
|
|
117
112
|
};
|
|
118
113
|
}
|
|
119
114
|
else if (helperMode &&
|
|
120
|
-
(await
|
|
115
|
+
(await switchesFor(io, ctx, 'Claude Code signs in to Pending You by itself here (/mcp → Authenticate).')))
|
|
121
116
|
plan = { action: 'switch', server };
|
|
122
117
|
else {
|
|
123
118
|
mode = 'oauth';
|
|
@@ -132,7 +127,7 @@ export const claudeCode = {
|
|
|
132
127
|
}
|
|
133
128
|
else if (plugin) {
|
|
134
129
|
if (helperMode &&
|
|
135
|
-
(await
|
|
130
|
+
(await switchesFor(io, ctx, 'The Pending You plugin signs Claude Code in to Pending You by itself here.')))
|
|
136
131
|
plan = { action: 'add', plugin: true };
|
|
137
132
|
else {
|
|
138
133
|
mode = 'oauth';
|
|
@@ -204,8 +199,10 @@ export const claudeCode = {
|
|
|
204
199
|
};
|
|
205
200
|
function permissionLine(io, claude, manifest, helper, flag) {
|
|
206
201
|
const label = 'Permission prompts:';
|
|
207
|
-
if (claude.permission)
|
|
202
|
+
if (claude.permission && claude.notify)
|
|
208
203
|
return ` ok ${label} a card when Claude Code waits for your OK`;
|
|
204
|
+
if (claude.permission)
|
|
205
|
+
return ` missing ${label} no card for a dialog in bypassPermissions mode, or one the other hooks don’t see; run npx pendingyou@latest init${flag}`;
|
|
209
206
|
if (manifest?.permissionCards === false)
|
|
210
207
|
return ` ${label} off, as you chose; for a card when Claude Code waits for your OK, run npx pendingyou init --permission-cards${flag}`;
|
|
211
208
|
const blocker = claude.claude
|
|
@@ -215,6 +212,14 @@ function permissionLine(io, claude, manifest, helper, flag) {
|
|
|
215
212
|
? ` ${label} no card when Claude Code waits for your OK: ${blocker}`
|
|
216
213
|
: ` missing ${label} no card when Claude Code waits for your OK; run npx pendingyou@latest init${flag}`;
|
|
217
214
|
}
|
|
215
|
+
function presenceLine(claude, connected, mod, flag) {
|
|
216
|
+
const label = 'Presence:';
|
|
217
|
+
if (!connected)
|
|
218
|
+
return ` ${label} off: it needs Claude Code signed in through this computer’s own connection`;
|
|
219
|
+
if (!claude.presence || !claude.hooks)
|
|
220
|
+
return ` missing ${label} Pending You isn’t told when a session closes; run npx pendingyou@latest init${flag}`;
|
|
221
|
+
return ` ok ${label} tells Pending You when this session is open (${mod ? 'as it starts, then every 5 minutes from the wake mod' : 'as it starts and with your messages'}; closed as it ends)`;
|
|
222
|
+
}
|
|
218
223
|
async function claudeStatus(io, ctx) {
|
|
219
224
|
const { origin, signIn } = ctx;
|
|
220
225
|
const [claude, here, manifest] = await Promise.all([
|
|
@@ -322,7 +327,7 @@ async function claudeStatus(io, ctx) {
|
|
|
322
327
|
? [
|
|
323
328
|
` ok Plugin: ${plugin} is enabled${pluginServer ? '' : mcp !== false && mcp !== 'unknown' ? '; Claude Code uses the server added by hand instead of its own' : ''}`,
|
|
324
329
|
]
|
|
325
|
-
: []), ` ${mark(skillOk)} Skill: ${skillText}`, ` ${mark(claude.hooks && hooksRun)} Hooks: ${claude.hooks ? `session-start pickup and next-message hand-off; ${formText}` : `not installed; run npx pendingyou init${flag}`}`, ` ${mark(claude.stopcheck)} Stop check: ${claude.stopcheck ? 'asks Claude Code to post what it leaves you in chat' : `not installed; run npx pendingyou init${flag}`}`, ` ${mark(modOk)} Wake mod: ${modText}`, permissionLine(io, claude, manifest, helper, flag));
|
|
330
|
+
: []), ` ${mark(skillOk)} Skill: ${skillText}`, ` ${mark(claude.hooks && hooksRun)} Hooks: ${claude.hooks ? `session-start pickup and next-message hand-off; ${formText}` : `not installed; run npx pendingyou init${flag}`}`, ` ${mark(claude.stopcheck)} Stop check: ${claude.stopcheck ? 'asks Claude Code to post what it leaves you in chat' : `not installed; run npx pendingyou init${flag}`}`, ` ${mark(modOk)} Wake mod: ${modText}`, permissionLine(io, claude, manifest, helper, flag), presenceLine(claude, helper && connected, modOk, flag));
|
|
326
331
|
const setup = setupStatus(NAME, signIn.state === 'ok' ? setupOf(signIn.for) : null);
|
|
327
332
|
if (setup)
|
|
328
333
|
lines.push(` ${setup === 'finished' ? 'ok ' : ' '} Setup: ${setup}`);
|
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, isClosed } from "../format.js";
|
|
8
|
+
import { clip, delegatedOf, isClosed, noticeOf } from "../format.js";
|
|
9
9
|
import { chooseThread, goesBy, HANDED_FOR_MS, handedCard, handedMessage, namesAnotherComputer, } from "../handed.js";
|
|
10
10
|
import { macName, tailscaleName } from "../remote.js";
|
|
11
11
|
import { claimHanded, markHanded, momentOf, releaseHanded, wasHanded } from "../state.js";
|
|
@@ -310,6 +310,18 @@ function line(heard, card) {
|
|
|
310
310
|
.replaceAll('”', '’')}” (${heard.requestId})`;
|
|
311
311
|
const named = card.name ?? heard.sessionLabel;
|
|
312
312
|
const get = `get_request (requestId ${heard.requestId}, ${named ? `name “${named}”` : 'your name'})`;
|
|
313
|
+
const notice = noticeOf(heard);
|
|
314
|
+
const from = notice ? clip(notice.from, 60).text : '';
|
|
315
|
+
if (notice?.kind === 'handed_over')
|
|
316
|
+
return [
|
|
317
|
+
`your person handed your open cards to ${from}, ${titled} among them`,
|
|
318
|
+
`stop working on them: ${from} answers them and follows up; ack_answer ${heard.requestId} with its version to say you’ve stopped`,
|
|
319
|
+
];
|
|
320
|
+
if (notice?.kind === 'follow_up' && heard.status !== 'answered')
|
|
321
|
+
return [
|
|
322
|
+
`${from} followed up on ${titled}, which it answered for your person`,
|
|
323
|
+
`call ${get} to read it, act on it if it needs anything, then ack_answer with its version to mark it heard`,
|
|
324
|
+
];
|
|
313
325
|
switch (heard.status) {
|
|
314
326
|
case 'expired':
|
|
315
327
|
return [
|
|
@@ -339,7 +351,7 @@ export function wakeMessage(ready) {
|
|
|
339
351
|
});
|
|
340
352
|
if (lines.length === 0)
|
|
341
353
|
return null;
|
|
342
|
-
const asked = lines.some(({ heard }) => heard.turn === 'agent' && heard.status !== 'expired');
|
|
354
|
+
const asked = lines.some(({ heard }) => heard.turn === 'agent' && heard.status !== 'expired' && !noticeOf(heard));
|
|
343
355
|
const reopen = asked
|
|
344
356
|
? ' If they asked you something or you need more from them, reply_in_thread with reopen instead.'
|
|
345
357
|
: '';
|
|
@@ -636,7 +648,7 @@ export async function listenHanded(io, options) {
|
|
|
636
648
|
]);
|
|
637
649
|
}
|
|
638
650
|
for (const [thread, list] of byThread) {
|
|
639
|
-
const message = handedMessage(list.map(({ card }) => card));
|
|
651
|
+
const message = handedMessage(list.map(({ card }) => card), { words: false });
|
|
640
652
|
if (message && (await queue(thread, message)))
|
|
641
653
|
continue;
|
|
642
654
|
await giveBack(io, origin, list);
|