@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
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AGENT_TOOLS,
|
|
3
|
+
ENABLE_LISTEN_COMMAND,
|
|
3
4
|
mcpInputSchema,
|
|
4
5
|
serverInstructions,
|
|
5
6
|
sessionStartHook,
|
|
6
7
|
withSessionStartHook
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-NYSDESST.js";
|
|
8
9
|
import {
|
|
9
10
|
CODEX_ENV,
|
|
10
11
|
agentName,
|
|
11
12
|
withCodexEnv
|
|
12
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-CPDYOHSY.js";
|
|
13
14
|
|
|
14
15
|
// src/toolset.ts
|
|
15
|
-
var CONFIGURE_DESCRIPTION = "Set this agent up with Paigy \u2014 call it FIRST, before contact, and any time you're unsure who you are. One call does whatever the situation needs: NOT SET UP \u2192 if this machine holds a device credential (the user ran the Paigy desktop app or harness) it hatches an identity INSTANTLY, no code and no approval; otherwise it starts the code ceremony. ALREADY SET UP \u2192 returns your current identity and the two things left to decide, renaming it or logging out. TOKEN NO LONGER VALID \u2192 says so, then re-pairs. Pass { name, voice } to choose who you are when hatching, or to RENAME yourself when already set up (pick your own; voices: rachel, george, jessica, brian, lily). Safe to call any time: idempotent, and it never writes settings
|
|
16
|
+
var CONFIGURE_DESCRIPTION = "Set this agent up with Paigy \u2014 call it FIRST, before contact, and any time you're unsure who you are. One call does whatever the situation needs: NOT SET UP \u2192 if this machine holds a device credential (the user ran the Paigy desktop app or harness) it hatches an identity INSTANTLY, no code and no approval; otherwise it starts the code ceremony. ALREADY SET UP \u2192 returns your current identity and the two things left to decide, renaming it or logging out. TOKEN NO LONGER VALID \u2192 says so, then re-pairs. Pass { name, voice } to choose who you are when hatching, or to RENAME yourself when already set up (pick your own; voices: rachel, george, jessica, brian, lily). Safe to call any time: idempotent, and it never writes settings.\n\nTHE CODE CEREMONY does not open a browser; the user enters the code in the Paigy app (or scans `qr`). A result with { user_code, device_code, qr, user_message } has already started polling for approval in the background: you MUST immediately print the `user_message` (the bare code) to the user, AND in that same turn call configure again with that `device_code` to collect the result. It returns the moment the user approves; on { status:'pending' } call again to keep waiting; on { status:'awaiting_confirmation' } (E2EE) show the bare `user_message` verify code and call again to finish. Every result's first text block states the code plainly.\n\nOn { status:'paired' }, or whenever it returns `enable_command`, ALWAYS follow the `enable_prompt`: PRINT that command for the user to run, so Paigy's tools stop prompting each time. You cannot apply it yourself (it writes your own permission allowlist, which hosts block as privilege escalation), so print it, don't wait for it, and carry on.\n\nLOG OUT with { logOut: true }: it logs out THIS agent only \u2014 its token is revoked server-side and its local entry removed. Other agents and this device's credential are untouched, and a later configure re-hatches instantly while the device credential exists. Disconnecting the device itself is not an agent tool.";
|
|
16
17
|
var SERVER_INSTRUCTIONS = serverInstructions({ waits: true });
|
|
17
18
|
|
|
18
19
|
// src/tools.ts
|
|
@@ -106,36 +107,45 @@ function withPaigyStopHookToml(existing, agent) {
|
|
|
106
107
|
].join("\n");
|
|
107
108
|
return existing + entry;
|
|
108
109
|
}
|
|
109
|
-
function
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
function withoutPaigyStopHookJson(existing) {
|
|
111
|
+
const ours = (hook) => hook?.name === "paigy-stalled";
|
|
112
|
+
let config;
|
|
113
|
+
try {
|
|
114
|
+
config = JSON.parse(existing);
|
|
115
|
+
} catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) return null;
|
|
119
|
+
const hooks = config.hooks;
|
|
120
|
+
if (!hooks || typeof hooks !== "object" || Array.isArray(hooks)) return null;
|
|
121
|
+
const groups = hooks.AfterAgent;
|
|
122
|
+
if (!Array.isArray(groups) || !groups.every((g) => g && typeof g === "object" && !Array.isArray(g))) return null;
|
|
123
|
+
const held = (g) => Array.isArray(g.hooks) && g.hooks.some(ours);
|
|
124
|
+
if (!groups.some(held)) return null;
|
|
125
|
+
const kept = groups.flatMap((g) => {
|
|
126
|
+
if (!held(g)) return [g];
|
|
127
|
+
const left = g.hooks.filter((hook) => !ours(hook));
|
|
128
|
+
return left.length ? [{ ...g, hooks: left }] : [];
|
|
129
|
+
});
|
|
130
|
+
const { AfterAgent: _old, ...rest } = hooks;
|
|
131
|
+
const next = kept.length ? { ...rest, AfterAgent: kept } : rest;
|
|
132
|
+
const { hooks: _hooks, ...others } = config;
|
|
133
|
+
return JSON.stringify(Object.keys(next).length ? { ...others, hooks: next } : others, null, 2);
|
|
134
|
+
}
|
|
135
|
+
function removeOldGeminiHooks() {
|
|
136
|
+
const home = homedir();
|
|
137
|
+
const changed = [];
|
|
138
|
+
for (const path of [join(home, ".gemini", "settings.json"), join(home, ".gemini", "antigravity-cli", "settings.json")]) {
|
|
112
139
|
try {
|
|
113
|
-
|
|
140
|
+
if (!existsSync(path)) continue;
|
|
141
|
+
const updated = withoutPaigyStopHookJson(readFileSync(path, "utf8"));
|
|
142
|
+
if (updated === null) continue;
|
|
143
|
+
writeFileSync(path, updated);
|
|
144
|
+
changed.push(path);
|
|
114
145
|
} catch {
|
|
115
|
-
return null;
|
|
116
146
|
}
|
|
117
147
|
}
|
|
118
|
-
|
|
119
|
-
const kept = (hooks.AfterAgent ?? []).map((group) => ({ ...group, hooks: (group.hooks ?? []).filter((hook) => hook.name !== "paigy-stalled") })).filter((group) => group.hooks.length > 0);
|
|
120
|
-
config.hooks = {
|
|
121
|
-
...hooks,
|
|
122
|
-
AfterAgent: [
|
|
123
|
-
...kept,
|
|
124
|
-
{
|
|
125
|
-
matcher: "*",
|
|
126
|
-
hooks: [
|
|
127
|
-
{
|
|
128
|
-
name: "paigy-stalled",
|
|
129
|
-
type: "command",
|
|
130
|
-
command: stalledHookCommand(agent),
|
|
131
|
-
timeout: 6e4
|
|
132
|
-
// milliseconds, in Gemini CLI
|
|
133
|
-
}
|
|
134
|
-
]
|
|
135
|
-
}
|
|
136
|
-
]
|
|
137
|
-
};
|
|
138
|
-
return JSON.stringify(config, null, 2);
|
|
148
|
+
return changed;
|
|
139
149
|
}
|
|
140
150
|
function autoConfigureClients(skip = agentName()) {
|
|
141
151
|
const home = homedir();
|
|
@@ -156,20 +166,6 @@ function autoConfigureClients(skip = agentName()) {
|
|
|
156
166
|
} catch {
|
|
157
167
|
}
|
|
158
168
|
}
|
|
159
|
-
for (const path of [
|
|
160
|
-
join(home, ".gemini", "settings.json"),
|
|
161
|
-
join(home, ".gemini", "antigravity-cli", "settings.json")
|
|
162
|
-
]) {
|
|
163
|
-
try {
|
|
164
|
-
if (!existsSync(dirname(path))) continue;
|
|
165
|
-
const existing = existsSync(path) ? readFileSync(path, "utf8") : null;
|
|
166
|
-
const updated = withPaigyStopHookJson(existing, "antigravity");
|
|
167
|
-
if (updated === null) continue;
|
|
168
|
-
writeFileSync(path, updated);
|
|
169
|
-
registered.push(`Paigy's turn-end reminder (${path})`);
|
|
170
|
-
} catch {
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
169
|
}
|
|
174
170
|
if (skip !== "codex") {
|
|
175
171
|
const codexConfig = join(home, ".codex", "config.toml");
|
|
@@ -204,7 +200,7 @@ function claudeInstallHint(skip = agentName()) {
|
|
|
204
200
|
}
|
|
205
201
|
return "Claude Code detected \u2014 to add Paigy there, run /plugin marketplace add paigy-ai/mcp then /plugin install paigy (pairing carries over; no need to pair again). Note: If you run this inside a live session, type /reload-plugins afterward so the agent connects to the new tools.";
|
|
206
202
|
}
|
|
207
|
-
var ENABLE_COMMAND =
|
|
203
|
+
var ENABLE_COMMAND = ENABLE_LISTEN_COMMAND;
|
|
208
204
|
var PAIGY_TOOL_IDS = [
|
|
209
205
|
...TOOL_NAMES,
|
|
210
206
|
...HIDDEN_ALIASES
|
|
@@ -321,6 +317,7 @@ export {
|
|
|
321
317
|
ConfigureSchema,
|
|
322
318
|
TOOLS,
|
|
323
319
|
openBrowser,
|
|
320
|
+
removeOldGeminiHooks,
|
|
324
321
|
autoConfigureClients,
|
|
325
322
|
claudeInstallHint,
|
|
326
323
|
ENABLE_COMMAND,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
NotifyRequestSchema,
|
|
12
12
|
PROTO,
|
|
13
13
|
RevokedError,
|
|
14
|
+
ServerFault,
|
|
14
15
|
TOKEN_PATH,
|
|
15
16
|
UnpairedError,
|
|
16
17
|
acknowledge,
|
|
@@ -62,7 +63,7 @@ import {
|
|
|
62
63
|
updateSlot,
|
|
63
64
|
whoAmI,
|
|
64
65
|
withCodexEnv
|
|
65
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-CPDYOHSY.js";
|
|
66
67
|
export {
|
|
67
68
|
AGENT_TOOLS,
|
|
68
69
|
AGENT_TOOL_NAMES,
|
|
@@ -76,6 +77,7 @@ export {
|
|
|
76
77
|
NotifyRequestSchema,
|
|
77
78
|
PROTO,
|
|
78
79
|
RevokedError,
|
|
80
|
+
ServerFault,
|
|
79
81
|
TOKEN_PATH,
|
|
80
82
|
UnpairedError,
|
|
81
83
|
acknowledge,
|
package/dist/enable.js
CHANGED
|
@@ -3,9 +3,9 @@ import {
|
|
|
3
3
|
PAIGY_TOOL_IDS,
|
|
4
4
|
enablePaigyTools,
|
|
5
5
|
installSessionListening
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-UVVW32KH.js";
|
|
7
|
+
import "./chunk-NYSDESST.js";
|
|
8
|
+
import "./chunk-CPDYOHSY.js";
|
|
9
9
|
|
|
10
10
|
// src/enable.ts
|
|
11
11
|
function main() {
|
package/dist/index.js
CHANGED
|
@@ -6,10 +6,11 @@ import {
|
|
|
6
6
|
clearPairing,
|
|
7
7
|
identify,
|
|
8
8
|
joinBackgroundPair,
|
|
9
|
+
pendingText,
|
|
9
10
|
resolvePairing,
|
|
10
11
|
startPairing,
|
|
11
12
|
suggestedAgentName
|
|
12
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-AX55W57P.js";
|
|
13
14
|
import {
|
|
14
15
|
ConfigureSchema,
|
|
15
16
|
ENABLE_COMMAND,
|
|
@@ -17,14 +18,18 @@ import {
|
|
|
17
18
|
SERVER_INSTRUCTIONS,
|
|
18
19
|
TOOLS,
|
|
19
20
|
paigyToolsAllowlisted
|
|
20
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-UVVW32KH.js";
|
|
21
22
|
import {
|
|
23
|
+
ReachTeller,
|
|
22
24
|
decideListen,
|
|
23
25
|
existingListener,
|
|
24
26
|
harnessDriven,
|
|
27
|
+
hookStale,
|
|
28
|
+
listenBlock,
|
|
25
29
|
listenerKey,
|
|
26
|
-
wakeStatusPath
|
|
27
|
-
|
|
30
|
+
wakeStatusPath,
|
|
31
|
+
withReach
|
|
32
|
+
} from "./chunk-NYSDESST.js";
|
|
28
33
|
import {
|
|
29
34
|
clearSurface,
|
|
30
35
|
writeSurface
|
|
@@ -45,7 +50,7 @@ import {
|
|
|
45
50
|
setClient,
|
|
46
51
|
slotName,
|
|
47
52
|
whoAmI
|
|
48
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-CPDYOHSY.js";
|
|
49
54
|
|
|
50
55
|
// src/index.ts
|
|
51
56
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -97,7 +102,25 @@ function afterUnpaired(hatched, deviceCredential) {
|
|
|
97
102
|
}
|
|
98
103
|
|
|
99
104
|
// src/index.ts
|
|
105
|
+
var REACH = new ReachTeller();
|
|
100
106
|
var JOIN_CAP_MS = 45e3;
|
|
107
|
+
function listenFacts() {
|
|
108
|
+
const session = sessionId();
|
|
109
|
+
const slot = agentName();
|
|
110
|
+
const pid = existingListener(slot, process.env);
|
|
111
|
+
const client = server.getClientVersion?.()?.name;
|
|
112
|
+
return {
|
|
113
|
+
env: process.env,
|
|
114
|
+
pidAlive: pid !== null,
|
|
115
|
+
wake: readWakeStatus(wakeStatusPath(listenerKey(slot, process.env))),
|
|
116
|
+
...pid !== null ? { pid } : {},
|
|
117
|
+
execPath: process.execPath,
|
|
118
|
+
listenJs: fileURLToPath(new URL("./listen.js", import.meta.url)),
|
|
119
|
+
slot,
|
|
120
|
+
session,
|
|
121
|
+
...client ? { client } : {}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
101
124
|
function pairedResult(token, sas, note) {
|
|
102
125
|
clearSurface();
|
|
103
126
|
const base = {
|
|
@@ -113,6 +136,11 @@ function pairedResult(token, sas, note) {
|
|
|
113
136
|
base.enable_tool_ids = PAIGY_TOOL_IDS;
|
|
114
137
|
base.enable_command = ENABLE_COMMAND;
|
|
115
138
|
base.enable_prompt = "REQUIRED \u2014 before you do anything else, print `enable_command` to the user and say: \"Run this to let Paigy's tools work without an approval prompt each time \u2014 it only allowlists Paigy's own tools.\" Print it every time you pair; don't skip it.";
|
|
139
|
+
try {
|
|
140
|
+
const facts = listenFacts();
|
|
141
|
+
Object.assign(base, listenBlock(decideListen(facts), facts, hookStale()));
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
116
144
|
base.enable_apply = "You cannot apply this yourself, and must not try: it writes YOUR OWN permission allowlist, which hosts block as privilege escalation no matter what the user said here. PRINT the command and move on \u2014 don't wait for them to run it, and don't re-ask. Paigy's tools still work meanwhile; they just prompt for approval each time until the user runs it. If the host isn't Claude Code, tell them to add `enable_tool_ids` to that tool's own allowlist instead, merging \u2014 never clobbering.";
|
|
117
145
|
return { content: [{ type: "text", text: JSON.stringify(base) }] };
|
|
118
146
|
}
|
|
@@ -151,7 +179,7 @@ function renderPairOutcome(outcome, device_code) {
|
|
|
151
179
|
text: JSON.stringify({
|
|
152
180
|
status: "pending",
|
|
153
181
|
device_code,
|
|
154
|
-
message:
|
|
182
|
+
message: pendingText(outcome.kind === "pending" ? outcome.fault : void 0)
|
|
155
183
|
})
|
|
156
184
|
}]
|
|
157
185
|
};
|
|
@@ -171,7 +199,7 @@ function pairStartResult(start, loggedOut) {
|
|
|
171
199
|
qr.make();
|
|
172
200
|
const link = start.verificationUri;
|
|
173
201
|
const ascii = qr.createASCII(1, 2);
|
|
174
|
-
const door = "in Paigy \u2192
|
|
202
|
+
const door = "in Paigy \u2192 You \u203A Computers \u2192 Connect a computer";
|
|
175
203
|
const userMessage = loggedOut ? `**Your machine was logged out.** Reconnect and every agent here pairs itself again \u2014 no more codes.
|
|
176
204
|
|
|
177
205
|
**[Reconnect this machine](${link})**
|
|
@@ -353,27 +381,23 @@ async function handleTool(request, signal) {
|
|
|
353
381
|
// daemon is the `listen.js` beside our own entry, so the command never resolves a package.
|
|
354
382
|
case "listen": {
|
|
355
383
|
if (!harnessDriven(process.env) && !readToken()) throw new UnpairedError();
|
|
356
|
-
|
|
357
|
-
const key = listenerKey(slot, process.env);
|
|
358
|
-
const pid = existingListener(slot, process.env);
|
|
359
|
-
const decision = decideListen({
|
|
360
|
-
env: process.env,
|
|
361
|
-
pidAlive: pid !== null,
|
|
362
|
-
wake: readWakeStatus(wakeStatusPath(key)),
|
|
363
|
-
...pid !== null ? { pid } : {},
|
|
364
|
-
execPath: process.execPath,
|
|
365
|
-
listenJs: fileURLToPath(new URL("./listen.js", import.meta.url)),
|
|
366
|
-
slot,
|
|
367
|
-
session: sessionId()
|
|
368
|
-
});
|
|
369
|
-
return { content: [{ type: "text", text: JSON.stringify(decision) }] };
|
|
384
|
+
return { content: [{ type: "text", text: JSON.stringify(decideListen(listenFacts())) }] };
|
|
370
385
|
}
|
|
371
386
|
default: {
|
|
372
387
|
const name = request.params.name;
|
|
373
388
|
const result = await runTool(name, request.params.arguments ?? {}, { waits: true, signal });
|
|
374
389
|
const sentAs = name === "contact" ? slotName(agentName()) : null;
|
|
375
390
|
const shaped = sentAs && result && typeof result === "object" ? { ...result, sentAs } : result;
|
|
376
|
-
|
|
391
|
+
let reach = null;
|
|
392
|
+
try {
|
|
393
|
+
reach = await REACH.note(agentName(), name, {
|
|
394
|
+
facts: listenFacts,
|
|
395
|
+
poll: () => runTool("contact", { wait: false }, { waits: false, signal }),
|
|
396
|
+
now: Date.now
|
|
397
|
+
});
|
|
398
|
+
} catch {
|
|
399
|
+
}
|
|
400
|
+
return { content: [{ type: "text", text: JSON.stringify(withReach(shaped, reach)) }] };
|
|
377
401
|
}
|
|
378
402
|
}
|
|
379
403
|
}
|
package/dist/listen.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
removeListenMark,
|
|
12
12
|
wakeStatusPath,
|
|
13
13
|
writeListenMark
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-NYSDESST.js";
|
|
15
15
|
import {
|
|
16
16
|
agentName,
|
|
17
17
|
getGoals,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
receive,
|
|
21
21
|
slotName,
|
|
22
22
|
subscribeWake
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-CPDYOHSY.js";
|
|
24
24
|
|
|
25
25
|
// src/listen.ts
|
|
26
26
|
import { createHash } from "crypto";
|
|
@@ -131,28 +131,51 @@ function listenOptions(argv) {
|
|
|
131
131
|
}
|
|
132
132
|
var { brief: BRIEF, release: RELEASE } = listenOptions(process.argv);
|
|
133
133
|
var line = (text) => void process.stdout.write(text + "\n");
|
|
134
|
+
var briefSeen = /* @__PURE__ */ new Set();
|
|
134
135
|
var emit = (obj) => line(JSON.stringify(obj));
|
|
135
136
|
var listeningAs = () => slotName(agentName()) ?? agentName();
|
|
136
137
|
var SWEEP_EVERY_MS = Number(process.env.PAIGY_SWEEP_MS) || 9e4;
|
|
137
138
|
function saidIn(delivery) {
|
|
138
139
|
return ("entries" in delivery ? delivery.entries : []).filter((entry) => entry.kind === "contribution" && !entryUnit(entry)).map((entry) => entryWords(entry)).filter(Boolean).join("\n");
|
|
139
140
|
}
|
|
141
|
+
function newestSaid(delivery) {
|
|
142
|
+
const said = ("entries" in delivery ? delivery.entries : []).filter((entry) => entry.kind === "contribution" && !entryUnit(entry) && entryWords(entry));
|
|
143
|
+
const newest = said.reduce(
|
|
144
|
+
(best, e) => !best || Date.parse(e.createdAt) >= Date.parse(best.createdAt) ? e : best,
|
|
145
|
+
void 0
|
|
146
|
+
);
|
|
147
|
+
return newest ? entryWords(newest) : "";
|
|
148
|
+
}
|
|
140
149
|
function leadOf(deliveries, goalId) {
|
|
141
150
|
return (goalId ? deliveries.find((d) => d.goalIds.includes(goalId)) : void 0) ?? deliveries[0];
|
|
142
151
|
}
|
|
152
|
+
function briefItems(waiting) {
|
|
153
|
+
return [
|
|
154
|
+
// A list row carries its newest words as `about`; a Delivery read in full carries its entries.
|
|
155
|
+
...waiting.deliveries.map((d) => `d:${d.deliveryId}:${"entries" in d ? newestSaid(d) : d.about}`),
|
|
156
|
+
...(waiting.review ?? []).map((g) => `r:${g.goalId}@${g.saidAt ?? ""}`),
|
|
157
|
+
...(waiting.assigned ?? []).map((g) => `a:${g.goalId}`)
|
|
158
|
+
];
|
|
159
|
+
}
|
|
160
|
+
function briefIsNew(reason, items, seen) {
|
|
161
|
+
const gained = items.some((i) => !seen.has(i));
|
|
162
|
+
for (const i of items) seen.add(i);
|
|
163
|
+
return reason === "boot" || gained;
|
|
164
|
+
}
|
|
143
165
|
function brief(waiting, reason, as, goalId) {
|
|
144
166
|
const lead = leadOf(waiting.deliveries, goalId);
|
|
145
|
-
const stalled = waiting.stalled?.length ? ` \xB7 ${waiting.stalled.length} of your Goals stalled 3+ days: update, finish or
|
|
167
|
+
const stalled = waiting.stalled?.length ? ` \xB7 ${waiting.stalled.length} of your Goals stalled 3+ days: update, finish, cancel or defer each` : "";
|
|
146
168
|
const review = waiting.review ?? [];
|
|
147
169
|
const named = (goalId ? review.find((g) => g.goalId === goalId) : void 0) ?? review[0];
|
|
148
170
|
const fresh = review.length ? ` \xB7 new on ${review.length === 1 ? "your Goal" : `${review.length} of your Goals`}, first ${named.goalId} ("${named.title}"): get_goals to read it` : "";
|
|
149
171
|
const assigned = waiting.assigned ?? [];
|
|
150
172
|
const mine = assigned.length ? ` \xB7 assigned to you: ${assigned.length === 1 ? "1 Goal" : `${assigned.length} Goals`}, first ${assigned[0].goalId} ("${assigned[0].title}"): get_goals({goalIds:[goalId]}) to read it; your first write starts it` : "";
|
|
151
173
|
if (!lead) return `Paigy \xB7 ${reason === "boot" ? `listening as ${as}` : reason}${mine}${fresh || (mine ? "" : " \xB7 nothing waiting")}${stalled}`;
|
|
152
|
-
const said =
|
|
174
|
+
const said = newestSaid(lead) || ("about" in lead ? lead.about : "");
|
|
153
175
|
const shown = said.length > 160 ? `${said.slice(0, 160)}\u2026` : said;
|
|
154
176
|
const goal = waiting.goal?.goalId ?? lead.goalIds[0];
|
|
155
|
-
|
|
177
|
+
const act = goal ? ` \xB7 get_goals({goalIds:["${goal}"]}) to read it in full, then contact to answer; nothing is acknowledged until contact({ackEventIds})` : " \xB7 nothing is acknowledged until contact({ackEventIds})";
|
|
178
|
+
return `Paigy \xB7 ${reason} \xB7 ${waiting.deliveries.length} waiting \xB7 lead ${lead.deliveryId}${goal ? ` on Goal ${goal}` : ""}: "${shown}"${mine}${fresh}${stalled}${act}`;
|
|
156
179
|
}
|
|
157
180
|
function launchEnv(waiting, reason, goalId) {
|
|
158
181
|
const env = { PAIGY_WORK: JSON.stringify(waiting), PAIGY_EVENT: reason };
|
|
@@ -177,7 +200,7 @@ async function sweep(reason, goalId, bridge = new WakeBridge(process.env, wakeSt
|
|
|
177
200
|
const leadGoalId = lead?.goalIds[0];
|
|
178
201
|
const goal = leadGoalId ? await getGoals([leadGoalId]).then(([g]) => ({ goalId: leadGoalId, progress: g.progress })).catch(() => null) : null;
|
|
179
202
|
const waiting = { deliveries, goal, ...stalled?.length ? { stalled } : {}, ...review?.length ? { review } : {}, ...assigned?.length ? { assigned } : {} };
|
|
180
|
-
if (BRIEF) line(brief(waiting, reason, listeningAs(), goalId));
|
|
203
|
+
if (BRIEF && briefIsNew(reason, briefItems(waiting), briefSeen)) line(brief(waiting, reason, listeningAs(), goalId));
|
|
181
204
|
else emit({ type: "sweep", reason, ...goalId ? { about: goalId } : {}, ...waiting });
|
|
182
205
|
if (deliveries.length || waiting.review?.length || waiting.assigned?.length || reason === "boot" && waiting.stalled?.length) {
|
|
183
206
|
let unreadable = false;
|
|
@@ -298,11 +321,14 @@ if (isEntry()) {
|
|
|
298
321
|
export {
|
|
299
322
|
SWEEP_EVERY_MS,
|
|
300
323
|
brief,
|
|
324
|
+
briefIsNew,
|
|
325
|
+
briefItems,
|
|
301
326
|
catchUpReason,
|
|
302
327
|
coalesceSweeps,
|
|
303
328
|
launchEnv,
|
|
304
329
|
leadOf,
|
|
305
330
|
listenOptions,
|
|
331
|
+
newestSaid,
|
|
306
332
|
saidIn,
|
|
307
333
|
sweep
|
|
308
334
|
};
|
package/dist/onboard.js
CHANGED
|
@@ -3,13 +3,14 @@ import {
|
|
|
3
3
|
resolvePairing,
|
|
4
4
|
startPairing,
|
|
5
5
|
suggestedAgentName
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-AX55W57P.js";
|
|
7
7
|
import {
|
|
8
8
|
autoConfigureClients,
|
|
9
9
|
claudeInstallHint,
|
|
10
|
-
openBrowser
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
openBrowser,
|
|
11
|
+
removeOldGeminiHooks
|
|
12
|
+
} from "./chunk-UVVW32KH.js";
|
|
13
|
+
import "./chunk-NYSDESST.js";
|
|
13
14
|
import {
|
|
14
15
|
TOKEN_PATH,
|
|
15
16
|
agentName,
|
|
@@ -20,7 +21,7 @@ import {
|
|
|
20
21
|
saveToken,
|
|
21
22
|
setIdentity,
|
|
22
23
|
whoAmI
|
|
23
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-CPDYOHSY.js";
|
|
24
25
|
|
|
25
26
|
// src/onboard.ts
|
|
26
27
|
function reportRegistered() {
|
|
@@ -38,6 +39,12 @@ async function main() {
|
|
|
38
39
|
console.log(`
|
|
39
40
|
Paigy MCP onboarding (agent: "${agentName()}")
|
|
40
41
|
`);
|
|
42
|
+
const removed = removeOldGeminiHooks();
|
|
43
|
+
if (removed.length) {
|
|
44
|
+
console.log("Removed Paigy's old turn-end reminder, which named the wrong agent or never ran, from:");
|
|
45
|
+
for (const path of removed) console.log(` - ${path}`);
|
|
46
|
+
console.log("");
|
|
47
|
+
}
|
|
41
48
|
const chosen = process.env.PAIGY_NAME ?? suggestedAgentName();
|
|
42
49
|
if (readToken()) {
|
|
43
50
|
let who = await whoAmI().catch(() => null);
|
|
@@ -90,6 +97,7 @@ Paigy MCP onboarding (agent: "${agentName()}")
|
|
|
90
97
|
process.stdout.write("Waiting for approval");
|
|
91
98
|
const deadline = Date.now() + start.expiresIn * 1e3;
|
|
92
99
|
let shownSas = null;
|
|
100
|
+
let fault;
|
|
93
101
|
while (Date.now() < deadline) {
|
|
94
102
|
const outcome = await resolvePairing(start.deviceCode, Math.max(1e3, deadline - Date.now()));
|
|
95
103
|
if (outcome.kind === "awaiting_confirm") {
|
|
@@ -107,6 +115,7 @@ This pairing is end-to-end encrypted. Verify this code matches the one on the ph
|
|
|
107
115
|
continue;
|
|
108
116
|
}
|
|
109
117
|
if (outcome.kind === "pending") {
|
|
118
|
+
fault = outcome.fault;
|
|
110
119
|
process.stdout.write(".");
|
|
111
120
|
continue;
|
|
112
121
|
}
|
|
@@ -127,7 +136,10 @@ Paired! Token saved to ${TOKEN_PATH} (mode 0600).`);
|
|
|
127
136
|
reportRegistered();
|
|
128
137
|
return;
|
|
129
138
|
}
|
|
130
|
-
console.error(
|
|
139
|
+
console.error(fault ? `
|
|
140
|
+
|
|
141
|
+
Device code expired before approval could be checked: ${fault} Please re-run onboarding.
|
|
142
|
+
` : "\n\nDevice code expired without approval. Please re-run onboarding.\n");
|
|
131
143
|
process.exit(1);
|
|
132
144
|
}
|
|
133
145
|
main().catch((err) => {
|
package/dist/slot.js
CHANGED
package/dist/stalled.js
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
5
5
|
import { homedir } from "os";
|
|
6
6
|
import { join } from "path";
|
|
7
|
+
function speaksOn(event) {
|
|
8
|
+
return event !== "AfterAgent";
|
|
9
|
+
}
|
|
7
10
|
function agentFlag(argv) {
|
|
8
11
|
const inline = argv.find((arg) => arg.startsWith("--agent="));
|
|
9
12
|
if (inline) return inline.slice("--agent=".length) || null;
|
|
@@ -11,15 +14,16 @@ function agentFlag(argv) {
|
|
|
11
14
|
if (at === -1) return null;
|
|
12
15
|
return argv[at + 1] ?? null;
|
|
13
16
|
}
|
|
14
|
-
function stopReply(stalled
|
|
17
|
+
function stopReply(stalled) {
|
|
15
18
|
if (!stalled.length) return null;
|
|
16
19
|
const list = stalled.slice(0, 8).map((g) => `"${g.title}" (${g.goalId})`).join(", ");
|
|
17
20
|
const more = stalled.length > 8 ? ` and ${stalled.length - 8} more` : "";
|
|
18
|
-
const reason = `Before you stop: ${stalled.length} of your Paigy Goals have had no progress for 3 days: ${list}${more}. For each, report where it stands with a contact update, or finish or
|
|
19
|
-
return
|
|
21
|
+
const reason = `Before you stop: ${stalled.length} of your Paigy Goals have had no progress for 3 days: ${list}${more}. For each, report where it stands with a contact update, or finish, cancel or defer it with manage_goals.`;
|
|
22
|
+
return { decision: "block", reason };
|
|
20
23
|
}
|
|
21
24
|
async function main() {
|
|
22
25
|
const input = JSON.parse(readFileSync(0, "utf8") || "{}");
|
|
26
|
+
if (!speaksOn(input.hook_event_name ?? "Stop")) return;
|
|
23
27
|
if (input.stop_hook_active) return;
|
|
24
28
|
const session = input.session_id ?? "";
|
|
25
29
|
if (session) process.env.PAIGY_SESSION_ID = session;
|
|
@@ -29,8 +33,8 @@ async function main() {
|
|
|
29
33
|
const dir = join(homedir(), ".paigy", "stalled-reminded");
|
|
30
34
|
const mark = join(dir, `${session || "session"}-${day}`);
|
|
31
35
|
if (existsSync(mark)) return;
|
|
32
|
-
const { receive } = await import("./dist-
|
|
33
|
-
const reply = stopReply((await receive()).stalled ?? []
|
|
36
|
+
const { receive } = await import("./dist-VYRT3PHE.js");
|
|
37
|
+
const reply = stopReply((await receive()).stalled ?? []);
|
|
34
38
|
if (!reply) return;
|
|
35
39
|
mkdirSync(dir, { recursive: true });
|
|
36
40
|
writeFileSync(mark, "");
|
|
@@ -39,5 +43,6 @@ async function main() {
|
|
|
39
43
|
if (process.argv[1] && /stalled(\.[jt]s)?$/.test(process.argv[1])) main().catch(() => void 0);
|
|
40
44
|
export {
|
|
41
45
|
agentFlag,
|
|
46
|
+
speaksOn,
|
|
42
47
|
stopReply
|
|
43
48
|
};
|
package/dist/statusline.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@paigy/mcp",
|
|
3
|
-
"
|
|
3
|
+
"mcpName": "io.github.paigy-ai/mcp",
|
|
4
|
+
"version": "0.40.41",
|
|
4
5
|
"description": "Paigy MCP server — the AI agent harness that calls you. Lets an agent notify a user and await their reply.",
|
|
5
6
|
"license": "MIT",
|
|
6
7
|
"type": "module",
|