@paigy/mcp 0.40.11 → 0.40.14
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 +22 -10
- package/dist/{chunk-I5QINUN6.js → chunk-NOZRU5CD.js} +558 -427
- package/dist/{chunk-H5GGWCPS.js → chunk-R2GTL7N3.js} +491 -467
- package/dist/{chunk-XIHVHYDU.js → chunk-SKLVQ73Q.js} +1 -1
- package/dist/{chunk-FQVK7SO4.js → chunk-SO5NRTXG.js} +56 -5
- package/dist/enable.js +9 -4
- package/dist/index.js +29 -5
- package/dist/listen.js +43 -12
- package/dist/onboard.js +4 -4
- package/dist/slot.js +1 -1
- package/dist/statusline.js +1 -1
- package/package.json +1 -1
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AGENT_TOOLS,
|
|
3
3
|
mcpInputSchema,
|
|
4
|
-
serverInstructions
|
|
5
|
-
|
|
4
|
+
serverInstructions,
|
|
5
|
+
sessionStartHook,
|
|
6
|
+
withSessionStartHook
|
|
7
|
+
} from "./chunk-NOZRU5CD.js";
|
|
6
8
|
import {
|
|
7
9
|
agentName
|
|
8
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-R2GTL7N3.js";
|
|
9
11
|
|
|
10
12
|
// src/toolset.ts
|
|
11
13
|
var ONBOARD_DESCRIPTION = "Get this agent talking to Paigy \u2014 call it FIRST, before contact, and any time you're unsure who you are. One call, and it does whatever the situation needs: NOT SET UP \u2192 hatches an identity instantly if this machine holds a device credential (the user ran the Paigy desktop app or harness), otherwise starts the code ceremony; ALREADY SET UP \u2192 returns your current identity and offers the two things left to decide, renaming it or unpairing; 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 (voices: rachel, george, jessica, brian, lily). Safe to call any time: idempotent, and it never writes settings \u2014 the tool-allowlist state it reports is read-only. If it returns a `user_code`, print it to the user immediately and call onboard again with the `device_code`. If it returns `enable_command`, PRINT that command for the user to run \u2014 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.";
|
|
@@ -44,6 +46,15 @@ var IDENTITY_TOOLS = [
|
|
|
44
46
|
name: "unpair",
|
|
45
47
|
description: "Log out / unpair this agent from the user's Paigy account: revokes the token server-side (it stops working everywhere) and deletes the local ~/.paigy/token.json. Takes no arguments. After this, contact won't work until the user pairs again with the pair tool.",
|
|
46
48
|
inputSchema: mcpInputSchema(z.object({}))
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
// A session asked to "start listening" can, in one call (#2265). Nothing can push an
|
|
52
|
+
// answer into a session started in a terminal — an answer waits for its next check_replies —
|
|
53
|
+
// and the only thing that reaches a running session mid-turn is a watched background task.
|
|
54
|
+
// So this tool decides and hands over: it never subscribes, spawns or writes settings.
|
|
55
|
+
name: "listen",
|
|
56
|
+
description: "Am I listening for answers, and if not, how do I start? Call it when the user asks you to start listening (or to stay reachable), and whenever you are unsure whether you are. Takes no arguments. Returns `status`: `listening` \u2014 nothing to do (a session started by the Paigy harness is ALWAYS listening: its pump types each answer in as it lands; or your own paigy-listen is already running, with its pid); `unpaired` \u2014 call onboard first; `start` \u2014 a `command` you MUST run NOW, in this same turn, as a watched BACKGROUND task (your harness's background-task/monitor tool), and leave running for the session. Each line that task prints is a Delivery waiting for you: claim_goal the Goal it names, read it, answer with contact. Start it once. This tool never subscribes or starts anything itself \u2014 the listener has to be YOUR watched task, or its lines reach no one.",
|
|
57
|
+
inputSchema: mcpInputSchema(z.object({}))
|
|
47
58
|
}
|
|
48
59
|
];
|
|
49
60
|
var TOOLS = [...AGENT_TOOLS, ...IDENTITY_TOOLS];
|
|
@@ -52,7 +63,7 @@ var HIDDEN_ALIASES = [];
|
|
|
52
63
|
|
|
53
64
|
// src/clients.ts
|
|
54
65
|
import { execFile } from "child_process";
|
|
55
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
66
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
56
67
|
import { homedir, platform } from "os";
|
|
57
68
|
import { join, dirname } from "path";
|
|
58
69
|
function openBrowser(url) {
|
|
@@ -208,6 +219,45 @@ function enablePaigyTools(scope = "user", cwd = process.cwd()) {
|
|
|
208
219
|
already: PAIGY_TOOL_IDS.filter((t) => prevAllow.includes(t))
|
|
209
220
|
};
|
|
210
221
|
}
|
|
222
|
+
var HOOK_CONFIGS = [
|
|
223
|
+
{ harness: "Claude Code", path: () => join(homedir(), ".claude", "settings.json") },
|
|
224
|
+
{ harness: "Codex", path: () => join(homedir(), ".codex", "hooks.json") }
|
|
225
|
+
];
|
|
226
|
+
function installSessionListening() {
|
|
227
|
+
const script = join(homedir(), ".paigy", "hooks", "session-start.sh");
|
|
228
|
+
try {
|
|
229
|
+
mkdirSync(dirname(script), { recursive: true });
|
|
230
|
+
writeFileSync(script, sessionStartHook(), { mode: 493 });
|
|
231
|
+
chmodSync(script, 493);
|
|
232
|
+
} catch (e) {
|
|
233
|
+
return [{ harness: "all", path: script, ok: false, why: e.message }];
|
|
234
|
+
}
|
|
235
|
+
const out = [];
|
|
236
|
+
for (const { harness, path: at } of HOOK_CONFIGS) {
|
|
237
|
+
const path = at();
|
|
238
|
+
if (!existsSync(dirname(path))) continue;
|
|
239
|
+
let existing = null;
|
|
240
|
+
try {
|
|
241
|
+
existing = existsSync(path) ? readFileSync(path, "utf8") : null;
|
|
242
|
+
} catch (e) {
|
|
243
|
+
out.push({ harness, path, ok: false, why: e.message });
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const already = existing?.includes(script) ?? false;
|
|
247
|
+
const updated = withSessionStartHook(existing, script);
|
|
248
|
+
if (updated === null) {
|
|
249
|
+
out.push({ harness, path, ok: false, why: `${path} didn't parse as JSON \u2014 not overwriting it` });
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
try {
|
|
253
|
+
writeFileSync(path, updated + "\n");
|
|
254
|
+
out.push({ harness, path, ok: true, already });
|
|
255
|
+
} catch (e) {
|
|
256
|
+
out.push({ harness, path, ok: false, why: e.message });
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return out;
|
|
260
|
+
}
|
|
211
261
|
|
|
212
262
|
export {
|
|
213
263
|
SERVER_INSTRUCTIONS,
|
|
@@ -220,5 +270,6 @@ export {
|
|
|
220
270
|
ENABLE_COMMAND,
|
|
221
271
|
PAIGY_TOOL_IDS,
|
|
222
272
|
paigyToolsAllowlisted,
|
|
223
|
-
enablePaigyTools
|
|
273
|
+
enablePaigyTools,
|
|
274
|
+
installSessionListening
|
|
224
275
|
};
|
package/dist/enable.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
PAIGY_TOOL_IDS,
|
|
4
|
-
enablePaigyTools
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import "./chunk-
|
|
4
|
+
enablePaigyTools,
|
|
5
|
+
installSessionListening
|
|
6
|
+
} from "./chunk-SO5NRTXG.js";
|
|
7
|
+
import "./chunk-NOZRU5CD.js";
|
|
8
|
+
import "./chunk-R2GTL7N3.js";
|
|
8
9
|
|
|
9
10
|
// src/enable.ts
|
|
10
11
|
function main() {
|
|
@@ -52,5 +53,9 @@ function main() {
|
|
|
52
53
|
if (result.already.length && result.added.length) {
|
|
53
54
|
console.log(` (${result.already.length} already present)`);
|
|
54
55
|
}
|
|
56
|
+
for (const hook of installSessionListening()) {
|
|
57
|
+
if (!hook.ok) console.log(` ! ${hook.harness}: ${hook.why}`);
|
|
58
|
+
else console.log(`\u2713 ${hook.harness} sessions will be told to listen (${hook.path})${hook.already ? " \u2014 already set" : ""}`);
|
|
59
|
+
}
|
|
55
60
|
}
|
|
56
61
|
main();
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
resolvePairing,
|
|
6
6
|
startPairing,
|
|
7
7
|
suggestedAgentName
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-SKLVQ73Q.js";
|
|
9
9
|
import {
|
|
10
10
|
ENABLE_COMMAND,
|
|
11
11
|
OnboardSchema,
|
|
@@ -14,8 +14,11 @@ import {
|
|
|
14
14
|
SERVER_INSTRUCTIONS,
|
|
15
15
|
TOOLS,
|
|
16
16
|
paigyToolsAllowlisted
|
|
17
|
-
} from "./chunk-
|
|
18
|
-
import
|
|
17
|
+
} from "./chunk-SO5NRTXG.js";
|
|
18
|
+
import {
|
|
19
|
+
decideListen,
|
|
20
|
+
listenerAlive
|
|
21
|
+
} from "./chunk-NOZRU5CD.js";
|
|
19
22
|
import {
|
|
20
23
|
clearSurface,
|
|
21
24
|
writeSurface
|
|
@@ -32,10 +35,11 @@ import {
|
|
|
32
35
|
revokeToken,
|
|
33
36
|
runTool,
|
|
34
37
|
saveToken,
|
|
38
|
+
sessionId,
|
|
35
39
|
setIdentity,
|
|
36
40
|
slotName,
|
|
37
41
|
whoAmI
|
|
38
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-R2GTL7N3.js";
|
|
39
43
|
|
|
40
44
|
// src/index.ts
|
|
41
45
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -44,6 +48,7 @@ import {
|
|
|
44
48
|
CallToolRequestSchema,
|
|
45
49
|
ListToolsRequestSchema
|
|
46
50
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
51
|
+
import { fileURLToPath } from "url";
|
|
47
52
|
import qrcode from "qrcode-generator";
|
|
48
53
|
var JOIN_CAP_MS = 45e3;
|
|
49
54
|
function pairedResult(token, sas, note) {
|
|
@@ -205,7 +210,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
|
205
210
|
return await handleTool(request, extra?.signal);
|
|
206
211
|
} catch (e) {
|
|
207
212
|
if (e instanceof UnpairedError) {
|
|
208
|
-
const IDENTITY_TOOLS = /* @__PURE__ */ new Set(["pair", "onboard", "unpair"]);
|
|
213
|
+
const IDENTITY_TOOLS = /* @__PURE__ */ new Set(["pair", "onboard", "unpair", "listen"]);
|
|
209
214
|
const hatched = IDENTITY_TOOLS.has(request.params.name) ? null : await hatchUnderDevice();
|
|
210
215
|
if (hatched) return await handleTool(request, extra?.signal);
|
|
211
216
|
if (listSlots().includes("Desktop")) return unidentifiedResult();
|
|
@@ -304,6 +309,25 @@ async function handleTool(request, signal) {
|
|
|
304
309
|
}]
|
|
305
310
|
};
|
|
306
311
|
}
|
|
312
|
+
// AM I LISTENING? (#2265) Decides and hands over — `listening.ts` owns the rule and the four
|
|
313
|
+
// answers. The facts are read here, once: the harness's session mark, a token to listen as,
|
|
314
|
+
// a live listener's mark for this slot, and where THIS server's node and daemon live. The
|
|
315
|
+
// daemon is the `listen.js` beside our own entry, so the command never resolves a package.
|
|
316
|
+
case "listen": {
|
|
317
|
+
const slot = agentName();
|
|
318
|
+
const pid = listenerAlive(slot);
|
|
319
|
+
const decision = decideListen({
|
|
320
|
+
env: process.env,
|
|
321
|
+
token: !!readToken(),
|
|
322
|
+
pidAlive: pid !== null,
|
|
323
|
+
...pid !== null ? { pid } : {},
|
|
324
|
+
execPath: process.execPath,
|
|
325
|
+
listenJs: fileURLToPath(new URL("./listen.js", import.meta.url)),
|
|
326
|
+
slot,
|
|
327
|
+
session: sessionId()
|
|
328
|
+
});
|
|
329
|
+
return { content: [{ type: "text", text: JSON.stringify(decision) }] };
|
|
330
|
+
}
|
|
307
331
|
default: {
|
|
308
332
|
const name = request.params.name;
|
|
309
333
|
const result = await runTool(name, request.params.arguments ?? {}, { waits: true, signal });
|
package/dist/listen.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
entryWords
|
|
4
|
-
|
|
3
|
+
entryWords,
|
|
4
|
+
removeListenMark,
|
|
5
|
+
writeListenMark
|
|
6
|
+
} from "./chunk-NOZRU5CD.js";
|
|
5
7
|
import {
|
|
8
|
+
agentName,
|
|
6
9
|
checkReplies,
|
|
7
10
|
contact,
|
|
8
11
|
getGoal,
|
|
12
|
+
slotName,
|
|
9
13
|
subscribeWake
|
|
10
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-R2GTL7N3.js";
|
|
11
15
|
|
|
12
16
|
// src/listen.ts
|
|
13
17
|
import { spawn } from "child_process";
|
|
@@ -112,17 +116,34 @@ function uninstallService() {
|
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
// src/listen.ts
|
|
115
|
-
|
|
119
|
+
function listenOptions(argv) {
|
|
120
|
+
const brief2 = argv.includes("--brief");
|
|
121
|
+
return { brief: brief2, release: !brief2 };
|
|
122
|
+
}
|
|
123
|
+
var { brief: BRIEF, release: RELEASE } = listenOptions(process.argv);
|
|
124
|
+
var line = (text) => void process.stdout.write(text + "\n");
|
|
125
|
+
var emit = (obj) => line(JSON.stringify(obj));
|
|
126
|
+
var listeningAs = () => slotName(agentName()) ?? agentName();
|
|
116
127
|
function saidIn(delivery) {
|
|
117
128
|
return ("entries" in delivery ? delivery.entries : []).filter((entry) => entry.kind === "contribution").map((entry) => entryWords(entry)).filter(Boolean).join("\n");
|
|
118
129
|
}
|
|
130
|
+
function leadOf(deliveries, goalId) {
|
|
131
|
+
return (goalId ? deliveries.find((d) => d.goalIds.includes(goalId)) : void 0) ?? deliveries[0];
|
|
132
|
+
}
|
|
133
|
+
function brief(waiting, reason, as, goalId) {
|
|
134
|
+
const lead = leadOf(waiting.deliveries, goalId);
|
|
135
|
+
if (!lead) return reason === "boot" ? `Paigy \xB7 listening as ${as} \xB7 nothing waiting` : `Paigy \xB7 ${reason} \xB7 nothing waiting`;
|
|
136
|
+
const said = saidIn(lead) || ("about" in lead ? lead.about : "");
|
|
137
|
+
const shown = said.length > 160 ? `${said.slice(0, 160)}\u2026` : said;
|
|
138
|
+
const goal = waiting.goal?.goalId ?? lead.goalIds[0];
|
|
139
|
+
return `Paigy \xB7 ${reason} \xB7 ${waiting.deliveries.length} waiting \xB7 lead ${lead.deliveryId} on Goal ${goal}: "${shown}"`;
|
|
140
|
+
}
|
|
119
141
|
function launchEnv(waiting, reason, goalId) {
|
|
120
142
|
const env = { PAIGY_WORK: JSON.stringify(waiting), PAIGY_EVENT: reason };
|
|
121
143
|
const put = (key, value) => {
|
|
122
144
|
if (value) env[key] = value;
|
|
123
145
|
};
|
|
124
|
-
const
|
|
125
|
-
const lead = named ?? waiting.deliveries[0];
|
|
146
|
+
const lead = leadOf(waiting.deliveries, goalId);
|
|
126
147
|
if (!lead) return env;
|
|
127
148
|
put("PAIGY_DELIVERY_ID", lead.deliveryId);
|
|
128
149
|
put("PAIGY_GOAL_ID", waiting.goal?.goalId ?? lead.goalIds[0]);
|
|
@@ -133,25 +154,31 @@ async function sweep(reason, goalId) {
|
|
|
133
154
|
try {
|
|
134
155
|
const cmd = process.env.PAIGY_ON_WAKE;
|
|
135
156
|
const { deliveries: rows } = await checkReplies();
|
|
136
|
-
const lead = (
|
|
157
|
+
const lead = leadOf(rows, goalId);
|
|
137
158
|
const full = lead ? await contact({ deliveryId: lead.deliveryId }, { waits: false }).catch(() => null) : null;
|
|
138
159
|
const deliveries = rows.map((d) => d === lead && full ? full : d);
|
|
139
160
|
const leadGoalId = lead?.goalIds[0];
|
|
140
161
|
const goal = leadGoalId ? await getGoal(leadGoalId).then((g) => ({ goalId: leadGoalId, progress: g.progress })).catch(() => null) : null;
|
|
141
162
|
const waiting = { deliveries, goal };
|
|
142
|
-
|
|
163
|
+
if (BRIEF) line(brief(waiting, reason, listeningAs(), goalId));
|
|
164
|
+
else emit({ type: "sweep", reason, ...goalId ? { about: goalId } : {}, ...waiting });
|
|
143
165
|
if (cmd && deliveries.length) {
|
|
144
166
|
spawn(cmd, { shell: true, stdio: "inherit", env: { ...process.env, ...launchEnv(waiting, reason, goalId) } });
|
|
145
167
|
}
|
|
146
168
|
} catch (e) {
|
|
147
|
-
|
|
169
|
+
if (BRIEF) line(`Paigy \xB7 error \xB7 ${e.message}`);
|
|
170
|
+
else emit({ type: "error", reason, message: e.message });
|
|
148
171
|
}
|
|
149
172
|
}
|
|
150
173
|
async function main() {
|
|
151
|
-
const sub = await subscribeWake((nudge) => void sweep(`wake:${nudge.kind ?? "?"}`, nudge.parentId));
|
|
152
|
-
|
|
174
|
+
const sub = await subscribeWake((nudge) => void sweep(`wake:${nudge.kind ?? "?"}`, nudge.parentId), { release: RELEASE });
|
|
175
|
+
const slot = agentName();
|
|
176
|
+
writeListenMark(slot, process.pid);
|
|
177
|
+
process.on("exit", () => removeListenMark(slot));
|
|
178
|
+
if (!BRIEF) emit({ type: "listening", channel: sub.channel });
|
|
153
179
|
await sweep("boot");
|
|
154
180
|
const shutdown = async () => {
|
|
181
|
+
removeListenMark(slot);
|
|
155
182
|
await sub.close();
|
|
156
183
|
process.exit(0);
|
|
157
184
|
};
|
|
@@ -175,13 +202,17 @@ if (isEntry()) {
|
|
|
175
202
|
emit({ type: "uninstalled", unit: uninstallService() });
|
|
176
203
|
} else {
|
|
177
204
|
main().catch((e) => {
|
|
178
|
-
|
|
205
|
+
if (BRIEF) line(`Paigy \xB7 error \xB7 ${e.message}`);
|
|
206
|
+
else emit({ type: "fatal", message: e.message });
|
|
179
207
|
process.exit(1);
|
|
180
208
|
});
|
|
181
209
|
}
|
|
182
210
|
}
|
|
183
211
|
export {
|
|
212
|
+
brief,
|
|
184
213
|
launchEnv,
|
|
214
|
+
leadOf,
|
|
215
|
+
listenOptions,
|
|
185
216
|
saidIn,
|
|
186
217
|
sweep
|
|
187
218
|
};
|
package/dist/onboard.js
CHANGED
|
@@ -3,13 +3,13 @@ import {
|
|
|
3
3
|
resolvePairing,
|
|
4
4
|
startPairing,
|
|
5
5
|
suggestedAgentName
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-SKLVQ73Q.js";
|
|
7
7
|
import {
|
|
8
8
|
autoConfigureClients,
|
|
9
9
|
claudeInstallHint,
|
|
10
10
|
openBrowser
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import "./chunk-
|
|
11
|
+
} from "./chunk-SO5NRTXG.js";
|
|
12
|
+
import "./chunk-NOZRU5CD.js";
|
|
13
13
|
import {
|
|
14
14
|
TOKEN_PATH,
|
|
15
15
|
agentName,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
saveToken,
|
|
21
21
|
setIdentity,
|
|
22
22
|
whoAmI
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-R2GTL7N3.js";
|
|
24
24
|
|
|
25
25
|
// src/onboard.ts
|
|
26
26
|
function reportRegistered() {
|
package/dist/slot.js
CHANGED
package/dist/statusline.js
CHANGED
package/package.json
CHANGED