@paigy/mcp 0.40.39 → 0.40.41
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/README.md +25 -26
- package/dist/{chunk-PMZBYLLJ.js → chunk-AX55W57P.js} +30 -7
- package/dist/{chunk-X4UIGUEX.js → chunk-CPDYOHSY.js} +737 -551
- package/dist/{chunk-QE33A5XK.js → chunk-NYSDESST.js} +804 -547
- package/dist/{chunk-4IAK4ATB.js → chunk-UVVW32KH.js} +41 -44
- package/dist/{dist-DOFR2565.js → dist-VYRT3PHE.js} +3 -1
- package/dist/enable.js +3 -3
- package/dist/index.js +46 -22
- package/dist/listen.js +32 -6
- package/dist/onboard.js +18 -6
- package/dist/slot.js +1 -1
- package/dist/stalled.js +10 -5
- package/dist/statusline.js +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -109,26 +109,27 @@ Or add the entry to `~/.gemini/settings.json`:
|
|
|
109
109
|
|
|
110
110
|
Then pair: `PAIGY_AGENT=gemini npx -p @paigy/mcp@latest paigy-mcp-onboard`.
|
|
111
111
|
|
|
112
|
-
> `PAIGY_AGENT` names the agent AND keys its token slot
|
|
113
|
-
> `mcp-agent`
|
|
114
|
-
> `PAIGY_AGENT` the client's config uses
|
|
115
|
-
>
|
|
112
|
+
> `PAIGY_AGENT` names the agent AND keys its token slot. Without it, each session gets a private slot
|
|
113
|
+
> (`session:<id>`); the shared `mcp-agent` default went on 2026-08-07. Each client reads only its own slot, so pair with the same
|
|
114
|
+
> `PAIGY_AGENT` the client's config uses. A fixed `PAIGY_AGENT` makes every session of that client one agent: two running at once
|
|
115
|
+
> collide, and the copy set aside is refused `holder_conflict` when it acts (#2933). `paigy-harness setup` wires clients without it,
|
|
116
|
+
> so each session hatches its own identity.
|
|
116
117
|
|
|
117
118
|
## Tools
|
|
118
119
|
|
|
119
120
|
One catalog, on every transport (2026-09-11): the agent tools are `AGENT_TOOLS` in
|
|
120
|
-
`packages/schema/src/tools.ts`
|
|
121
|
-
send_feedback
|
|
121
|
+
`packages/schema/src/tools.ts` (configure, contact, manage_goals, get_delivery, get_goals, search, check_activity,
|
|
122
|
+
send_feedback), and both this server and the hosted MCP
|
|
122
123
|
(`apps/api/src/mcp`) publish that list and dispatch it through the SDK's one `runTool`.
|
|
123
|
-
This server
|
|
124
|
-
renames and logs this agent out (`logOut: true`) over the token file this machine holds
|
|
124
|
+
This server extends `configure`, the one identity tool, which on the hosted MCP only reports who you are: here it also pairs,
|
|
125
|
+
renames and logs this agent out (`logOut: true`) over the token file this machine holds. It adds `listen`.
|
|
125
126
|
`onboard` is now `configure`; `pair` and `unpair` are folded into it (2026-10-07). See the [SDK contract](../../packages/sdk/README.md) for the
|
|
126
127
|
contact shape. Sending returns immediately unless it asks to `wait`: `wait: true` holds one
|
|
127
128
|
cancellable ~45-second window here for a response to what was sent (the hosted transport
|
|
128
129
|
returns after one read), and `contact({})` holds one for anything addressed to you; after an
|
|
129
130
|
`expired` window, wait again without sending again. Every Goal a contact names must already
|
|
130
131
|
exist (create it with manage_goals); a question with `workItBlocks` asks for a call; a
|
|
131
|
-
question, an update or an answer is at most
|
|
132
|
+
question, an update or an answer is at most 80 characters with its context in `units`; and
|
|
132
133
|
`withdrawQuestionIds` takes back a question of yours. `update_goal` is gone (2026-10-07): progress
|
|
133
134
|
is a contact update (one the person does not need yet is kept as the Goal's progress), a review is
|
|
134
135
|
acknowledged with `ackEventIds`, and a Goal is postponed with manage_goals `defer` (you are woken
|
|
@@ -145,9 +146,8 @@ time. Bookkeeping (`operationId`, the manage request id, the revision an edit wa
|
|
|
145
146
|
minted here, never asked of the model. Retired
|
|
146
147
|
reply-lease/ACK/work/callback tools and hidden notification aliases are rejected.
|
|
147
148
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
not a compatibility fallback for target MCP tools.
|
|
149
|
+
Until 2026-09-13 the standalone listener below was a legacy host with its own reply intake. Since #1957 it reads
|
|
150
|
+
the same `receive` as `contact({})`, as the next section says.
|
|
151
151
|
|
|
152
152
|
## Configuration
|
|
153
153
|
|
|
@@ -242,12 +242,12 @@ script and branch on `$PAIGY_EVENT` there:
|
|
|
242
242
|
codex exec "Paigy goal $PAIGY_GOAL_ID. Read it with get_goals first. The user said: $PAIGY_TEXT. Reply with contact when done."
|
|
243
243
|
```
|
|
244
244
|
|
|
245
|
-
## Stalled work (turn-end hook — Claude Code, Codex
|
|
245
|
+
## Stalled work (turn-end hook — Claude Code, Codex)
|
|
246
246
|
|
|
247
247
|
An agent that goes quiet leaves its Goals behind. Three things hand it that work back, all
|
|
248
248
|
reading one rule (no progress for 3 days, `COLD_AFTER_MS`): `contact({})` lists it under
|
|
249
249
|
`stalled`, `paigy-listen` carries it in `PAIGY_WORK` (and launches for it on boot), and this
|
|
250
|
-
hook holds a session's turn end once a day to list it. One program,
|
|
250
|
+
hook holds a session's turn end once a day to list it. One program, two tools:
|
|
251
251
|
|
|
252
252
|
**Claude Code** (`~/.claude/settings.json`) — installed by the paigy plugin:
|
|
253
253
|
|
|
@@ -267,19 +267,18 @@ command = "npx -y -p @paigy/mcp@latest paigy-stalled --agent codex"
|
|
|
267
267
|
timeout = 60
|
|
268
268
|
```
|
|
269
269
|
|
|
270
|
-
**Gemini CLI
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
```
|
|
270
|
+
**Antigravity and Gemini CLI have no turn-end reminder (#3648).** Onboarding used to write one on
|
|
271
|
+
`AfterAgent` into their `settings.json`; agy 1.4.1 reads hooks only from `hooks.json` and ends a turn
|
|
272
|
+
on `Stop`, so it never fired, and in Gemini CLI's own file it named the wrong slot. `paigy-stalled`
|
|
273
|
+
answers nothing on `AfterAgent` from this release on, so a copy goes quiet once its `npx` fetches
|
|
274
|
+
it (the hook still starts `npx` each turn until removed), and onboarding (on every run) and
|
|
275
|
+
`scripts/paigy-wipe.sh` take it out. A `Stop` hook in agy's `hooks.json` waits on a check of its
|
|
276
|
+
payload on a real turn. The listener's wake and `contact({})`'s `stalled` still reach these agents.
|
|
278
277
|
|
|
279
278
|
`--agent <slot>` is not optional outside Claude Code: a hook is not the MCP server, so it never
|
|
280
279
|
inherits the `PAIGY_AGENT` that server was registered with, and without the flag it reads this
|
|
281
280
|
session's own slot — a different identity, with no stalled work of its own. Pass the same value as
|
|
282
|
-
the tool's MCP registration (`codex`,
|
|
281
|
+
the tool's MCP registration (`codex`, or whatever you set `PAIGY_AGENT` to).
|
|
283
282
|
|
|
284
283
|
It is silent when nothing is stalled, when the stop is already being continued by a hook, and
|
|
285
284
|
after it has reminded that session once today. Codex's older `notify` program is not a place for
|
|
@@ -306,12 +305,12 @@ machine is paired and this session's identity is minted by its first tool call;
|
|
|
306
305
|
|
|
307
306
|
## Publishing (maintainers)
|
|
308
307
|
|
|
309
|
-
Tool input schemas are generated from zod
|
|
308
|
+
Tool input schemas are generated from zod by `mcpInputSchema` (`packages/schema/src/mcp-schema.ts`) as draft-2020-12 JSON
|
|
310
309
|
Schema, and `src/schema.test.ts` guards that every tool stays valid (strict clients
|
|
311
310
|
like the Anthropic API reject anything else). **A schema fix only reaches agents once
|
|
312
311
|
a new version is published to npm** — 0.8.0 once shipped *without* a committed fix and
|
|
313
|
-
400'd strict clients for weeks. So after any change
|
|
314
|
-
definitions: **bump the version
|
|
312
|
+
400'd strict clients for weeks. So after any change to the tool schemas (`packages/schema`) or the tool
|
|
313
|
+
definitions: **bump the version in `apps/mcp/package.json`**; publishing is `pnpm release` from the repo root, which the owner runs (`docs/infra/releasing.md`). Don't rely on the commit alone.
|
|
315
314
|
Install recipes pin `@paigy/mcp@latest` so a fresh `npx` picks up the new version; if a
|
|
316
315
|
stale one sticks, `rm -rf ~/.npm/_npx` and restart the client.
|
|
317
316
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
NameTakenError,
|
|
3
|
+
ServerFault,
|
|
3
4
|
isNetworkError,
|
|
4
5
|
pollToken,
|
|
5
6
|
requestCode,
|
|
6
7
|
saveToken,
|
|
7
8
|
setIdentity,
|
|
8
9
|
sleep
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-CPDYOHSY.js";
|
|
10
11
|
|
|
11
12
|
// src/identity.ts
|
|
12
13
|
var CLIENT_LABELS = {
|
|
@@ -44,8 +45,9 @@ async function identify(who, ask) {
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
// src/pairing.ts
|
|
47
|
-
async function resolvePairing(deviceCode, capMs, pollMs = 2e3) {
|
|
48
|
+
async function resolvePairing(deviceCode, capMs, pollMs = 2e3, onFault) {
|
|
48
49
|
const start = Date.now();
|
|
50
|
+
let fault;
|
|
49
51
|
while (Date.now() - start < capMs) {
|
|
50
52
|
try {
|
|
51
53
|
const token = await pollToken(deviceCode);
|
|
@@ -53,8 +55,12 @@ async function resolvePairing(deviceCode, capMs, pollMs = 2e3) {
|
|
|
53
55
|
saveToken(token);
|
|
54
56
|
return { kind: "paired", token };
|
|
55
57
|
}
|
|
58
|
+
fault = void 0;
|
|
59
|
+
onFault?.(fault);
|
|
56
60
|
} catch (e) {
|
|
57
|
-
if (isNetworkError(e)) {
|
|
61
|
+
if (isNetworkError(e) || e instanceof ServerFault) {
|
|
62
|
+
fault = e.message;
|
|
63
|
+
onFault?.(fault);
|
|
58
64
|
await sleep(pollMs);
|
|
59
65
|
continue;
|
|
60
66
|
}
|
|
@@ -62,12 +68,25 @@ async function resolvePairing(deviceCode, capMs, pollMs = 2e3) {
|
|
|
62
68
|
}
|
|
63
69
|
await sleep(pollMs);
|
|
64
70
|
}
|
|
65
|
-
return { kind: "pending" };
|
|
71
|
+
return fault ? { kind: "pending", fault } : { kind: "pending" };
|
|
72
|
+
}
|
|
73
|
+
function pendingText(fault) {
|
|
74
|
+
if (fault) {
|
|
75
|
+
const said = /[.!?]$/.test(fault) ? fault : `${fault}.`;
|
|
76
|
+
return `Approval can't be checked right now: ${said} This is not the user being slow. Tell them once that Paigy is having trouble reaching its server, then call configure again with this device_code to keep trying; the code stays valid until it expires.`;
|
|
77
|
+
}
|
|
78
|
+
return "Still awaiting approval \u2014 the user hasn't entered the code yet. Call configure again with this device_code RIGHT NOW to keep waiting; each call returns after ~45s so it can't be killed by a host tool-call timeout. Nothing is lost between calls: the code stays valid and approval keeps being polled in the background, so a re-call returns the instant they approve.";
|
|
66
79
|
}
|
|
67
80
|
var bg = null;
|
|
68
81
|
var started = null;
|
|
82
|
+
var startedUntil = 0;
|
|
83
|
+
function activePairing() {
|
|
84
|
+
if (started && Date.now() >= startedUntil) cancelBackgroundPair();
|
|
85
|
+
return started;
|
|
86
|
+
}
|
|
69
87
|
async function startPairing(agent) {
|
|
70
|
-
|
|
88
|
+
const live = activePairing();
|
|
89
|
+
if (live) return { ...live, expiresIn: Math.max(1, Math.round((startedUntil - Date.now()) / 1e3)) };
|
|
71
90
|
const code = await requestCode(agent);
|
|
72
91
|
started = {
|
|
73
92
|
verificationUri: code.verification_uri_complete,
|
|
@@ -75,11 +94,14 @@ async function startPairing(agent) {
|
|
|
75
94
|
deviceCode: code.device_code,
|
|
76
95
|
expiresIn: code.expires_in
|
|
77
96
|
};
|
|
97
|
+
startedUntil = Date.now() + code.expires_in * 1e3;
|
|
78
98
|
startBackgroundPair(code.device_code, code.expires_in * 1e3);
|
|
79
99
|
return started;
|
|
80
100
|
}
|
|
81
101
|
function startBackgroundPair(deviceCode, budgetMs) {
|
|
82
|
-
const promise = resolvePairing(deviceCode, budgetMs
|
|
102
|
+
const promise = resolvePairing(deviceCode, budgetMs, void 0, (fault) => {
|
|
103
|
+
if (bg?.deviceCode === deviceCode) bg.fault = fault;
|
|
104
|
+
}).catch((e) => ({ kind: "error", message: e.message })).then((o) => {
|
|
83
105
|
if (bg?.deviceCode === deviceCode) bg.settled = o;
|
|
84
106
|
return o;
|
|
85
107
|
});
|
|
@@ -106,7 +128,7 @@ async function joinBackgroundPair(deviceCode, capMs) {
|
|
|
106
128
|
if (s.kind === "paired" || s.kind === "error") started = null;
|
|
107
129
|
return s;
|
|
108
130
|
}
|
|
109
|
-
return { kind: "pending" };
|
|
131
|
+
return bg?.fault ? { kind: "pending", fault: bg.fault } : { kind: "pending" };
|
|
110
132
|
}
|
|
111
133
|
function cancelBackgroundPair() {
|
|
112
134
|
bg = null;
|
|
@@ -121,6 +143,7 @@ export {
|
|
|
121
143
|
suggestedAgentName,
|
|
122
144
|
identify,
|
|
123
145
|
resolvePairing,
|
|
146
|
+
pendingText,
|
|
124
147
|
startPairing,
|
|
125
148
|
joinBackgroundPair,
|
|
126
149
|
clearPairing
|