@paigy/mcp 0.40.40 → 0.40.42

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.
@@ -22,6 +22,7 @@ import {
22
22
  claimSessions,
23
23
  contact,
24
24
  currentRepo,
25
+ decided,
25
26
  deleteToken,
26
27
  finishSessionEnd,
27
28
  getGoals,
@@ -61,9 +62,10 @@ import {
61
62
  slotName,
62
63
  subscribeWake,
63
64
  updateSlot,
65
+ viaLine,
64
66
  whoAmI,
65
67
  withCodexEnv
66
- } from "./chunk-V4OVLFMP.js";
68
+ } from "./chunk-W3FK4FD6.js";
67
69
  export {
68
70
  AGENT_TOOLS,
69
71
  AGENT_TOOL_NAMES,
@@ -88,6 +90,7 @@ export {
88
90
  claimSessions,
89
91
  contact,
90
92
  currentRepo,
93
+ decided,
91
94
  deleteToken,
92
95
  finishSessionEnd,
93
96
  getGoals,
@@ -127,6 +130,7 @@ export {
127
130
  slotName,
128
131
  subscribeWake,
129
132
  updateSlot,
133
+ viaLine,
130
134
  whoAmI,
131
135
  withCodexEnv
132
136
  };
package/dist/enable.js CHANGED
@@ -3,9 +3,11 @@ import {
3
3
  PAIGY_TOOL_IDS,
4
4
  enablePaigyTools,
5
5
  installSessionListening
6
- } from "./chunk-PKJJSHLW.js";
7
- import "./chunk-H2EBILBE.js";
8
- import "./chunk-V4OVLFMP.js";
6
+ } from "./chunk-I3WDI2JV.js";
7
+ import {
8
+ LISTEN_COMMAND
9
+ } from "./chunk-NNB3HQTF.js";
10
+ import "./chunk-W3FK4FD6.js";
9
11
 
10
12
  // src/enable.ts
11
13
  function main() {
@@ -57,5 +59,6 @@ function main() {
57
59
  if (!hook.ok) console.log(` ! ${hook.harness}: ${hook.why}`);
58
60
  else console.log(`\u2713 ${hook.harness} sessions will be told to listen (${hook.path})${hook.already ? " \u2014 already set" : ""}`);
59
61
  }
62
+ console.log(` To listen in a session already running: ${LISTEN_COMMAND} \u2014 under your harness's Monitor tool.`);
60
63
  }
61
64
  main();
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  resolvePairing,
11
11
  startPairing,
12
12
  suggestedAgentName
13
- } from "./chunk-6FG3PZR2.js";
13
+ } from "./chunk-6V4X3EFA.js";
14
14
  import {
15
15
  ConfigureSchema,
16
16
  ENABLE_COMMAND,
@@ -18,8 +18,9 @@ import {
18
18
  SERVER_INSTRUCTIONS,
19
19
  TOOLS,
20
20
  paigyToolsAllowlisted
21
- } from "./chunk-PKJJSHLW.js";
21
+ } from "./chunk-I3WDI2JV.js";
22
22
  import {
23
+ AGENT_PROMPTS,
23
24
  ReachTeller,
24
25
  decideListen,
25
26
  existingListener,
@@ -27,9 +28,11 @@ import {
27
28
  hookStale,
28
29
  listenBlock,
29
30
  listenerKey,
31
+ promptResult,
32
+ refreshSessionHook,
30
33
  wakeStatusPath,
31
34
  withReach
32
- } from "./chunk-H2EBILBE.js";
35
+ } from "./chunk-NNB3HQTF.js";
33
36
  import {
34
37
  clearSurface,
35
38
  writeSurface
@@ -50,13 +53,15 @@ import {
50
53
  setClient,
51
54
  slotName,
52
55
  whoAmI
53
- } from "./chunk-V4OVLFMP.js";
56
+ } from "./chunk-W3FK4FD6.js";
54
57
 
55
58
  // src/index.ts
56
59
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
57
60
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
58
61
  import {
59
62
  CallToolRequestSchema,
63
+ GetPromptRequestSchema,
64
+ ListPromptsRequestSchema,
60
65
  ListToolsRequestSchema
61
66
  } from "@modelcontextprotocol/sdk/types.js";
62
67
  import { fileURLToPath } from "url";
@@ -101,6 +106,14 @@ function afterUnpaired(hatched, deviceCredential) {
101
106
  return deviceCredential ? "unidentified" : "pair";
102
107
  }
103
108
 
109
+ // src/prompts.ts
110
+ function stdioPrompts(identity) {
111
+ return {
112
+ list: () => ({ prompts: AGENT_PROMPTS }),
113
+ get: async (name, args = {}) => promptResult(name, { ...args.topic ? { topic: args.topic } : {}, identity: name === "welcome" ? await identity().catch(() => null) : null, listens: true })
114
+ };
115
+ }
116
+
104
117
  // src/index.ts
105
118
  var REACH = new ReachTeller();
106
119
  var JOIN_CAP_MS = 45e3;
@@ -199,7 +212,7 @@ function pairStartResult(start, loggedOut) {
199
212
  qr.make();
200
213
  const link = start.verificationUri;
201
214
  const ascii = qr.createASCII(1, 2);
202
- const door = "in Paigy \u2192 Devices \u2192 Connect a computer";
215
+ const door = "in Paigy \u2192 You \u203A Computers \u2192 Connect a computer";
203
216
  const userMessage = loggedOut ? `**Your machine was logged out.** Reconnect and every agent here pairs itself again \u2014 no more codes.
204
217
 
205
218
  **[Reconnect this machine](${link})**
@@ -240,7 +253,7 @@ setClient(`mcp@${VERSION}`);
240
253
  var server = new Server(
241
254
  { name: "paigy", version: VERSION },
242
255
  {
243
- capabilities: { tools: {} },
256
+ capabilities: { tools: {}, prompts: {} },
244
257
  instructions: SERVER_INSTRUCTIONS
245
258
  }
246
259
  );
@@ -282,6 +295,9 @@ async function runPair(device_code, name, voice, note) {
282
295
  return renderPairOutcome(outcome, device_code);
283
296
  }
284
297
  server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
298
+ var PROMPTS = stdioPrompts(async () => readToken() ? await whoAmI() : null);
299
+ server.setRequestHandler(ListPromptsRequestSchema, async () => PROMPTS.list());
300
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => PROMPTS.get(request.params.name, request.params.arguments ?? {}));
285
301
  server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
286
302
  try {
287
303
  return await handleTool(request, extra?.signal);
@@ -425,3 +441,4 @@ guardTheProcess(process, (line) => console.error(line));
425
441
  var transport = new StdioServerTransport();
426
442
  traceTheTransport(transport, (line) => console.error(line));
427
443
  await server.connect(transport);
444
+ setImmediate(() => refreshSessionHook());
package/dist/listen.js CHANGED
@@ -11,16 +11,18 @@ import {
11
11
  removeListenMark,
12
12
  wakeStatusPath,
13
13
  writeListenMark
14
- } from "./chunk-H2EBILBE.js";
14
+ } from "./chunk-NNB3HQTF.js";
15
15
  import {
16
16
  agentName,
17
+ decided,
17
18
  getGoals,
18
19
  heartbeat,
19
20
  readDelivery,
20
21
  receive,
21
22
  slotName,
22
- subscribeWake
23
- } from "./chunk-V4OVLFMP.js";
23
+ subscribeWake,
24
+ viaLine
25
+ } from "./chunk-W3FK4FD6.js";
24
26
 
25
27
  // src/listen.ts
26
28
  import { createHash } from "crypto";
@@ -132,9 +134,38 @@ function listenOptions(argv) {
132
134
  var { brief: BRIEF, release: RELEASE } = listenOptions(process.argv);
133
135
  var line = (text) => void process.stdout.write(text + "\n");
134
136
  var briefSeen = /* @__PURE__ */ new Set();
137
+ var briefUnreadable = /* @__PURE__ */ new Set();
135
138
  var emit = (obj) => line(JSON.stringify(obj));
136
139
  var listeningAs = () => slotName(agentName()) ?? agentName();
137
140
  var SWEEP_EVERY_MS = Number(process.env.PAIGY_SWEEP_MS) || 9e4;
141
+ var ANSWER_SLACK_MS = 5e3;
142
+ function answersOn(review, g) {
143
+ const since = g.reviewSince ? Date.parse(g.reviewSince) : NaN;
144
+ if (!Number.isFinite(since)) return [];
145
+ const entries = g.entries ?? [];
146
+ return (g.answers ?? []).flatMap((a) => {
147
+ if (Date.parse(a.acceptedAt) < since - ANSWER_SLACK_MS) return [];
148
+ const asked = entries.find((e) => e.entryId === a.requestEntryId);
149
+ if (asked?.authorParticipant.startsWith("human:")) return [];
150
+ const words = decided(a.result) ?? ("kind" in a.result && a.result.kind === "text" ? a.result.text : "");
151
+ const question = asked ? entryWords(asked).trim().split("\n")[0].slice(0, 120) : "";
152
+ const via = viaLine(a.via);
153
+ return [{
154
+ goalId: review.goalId,
155
+ title: review.title,
156
+ decisionNeedId: a.decisionNeedId,
157
+ acceptedAt: a.acceptedAt,
158
+ ...question ? { question } : {},
159
+ answer: words.length > 160 ? `${words.slice(0, 160)}\u2026` : words,
160
+ ...via ? { via } : {}
161
+ }];
162
+ });
163
+ }
164
+ var NAMED = /* @__PURE__ */ new Set(["deliveries", "review", "assigned", "stalled", "calls", "rings", "others", "houseRules", "claimable", "ringPolicy", "unnamed"]);
165
+ function otherKinds(read) {
166
+ const other = Object.entries(read).filter((e) => !NAMED.has(e[0]) && Array.isArray(e[1]) && e[1].length > 0);
167
+ return other.length ? Object.fromEntries(other) : void 0;
168
+ }
138
169
  function saidIn(delivery) {
139
170
  return ("entries" in delivery ? delivery.entries : []).filter((entry) => entry.kind === "contribution" && !entryUnit(entry)).map((entry) => entryWords(entry)).filter(Boolean).join("\n");
140
171
  }
@@ -153,14 +184,24 @@ function briefItems(waiting) {
153
184
  return [
154
185
  // A list row carries its newest words as `about`; a Delivery read in full carries its entries.
155
186
  ...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}`)
187
+ // BY ITS EVENT (2026-10-27): the review's id moves with every new signal on the Goal, its `saidAt` only with
188
+ // words — and a tapped choice has none, so a second answer on a Goal already printed made no new item.
189
+ ...(waiting.review ?? []).map((g) => `r:${g.goalId}@${g.eventId ?? g.saidAt ?? ""}`),
190
+ ...(waiting.assigned ?? []).map((g) => `a:${g.goalId}`),
191
+ ...(waiting.answers ?? []).map(answerItem),
192
+ ...Object.entries(waiting.other ?? {}).flatMap(([kind, rows]) => rows.map((row) => `x:${kind}:${createHash("sha256").update(JSON.stringify(row) ?? "").digest("hex").slice(0, 16)}`))
158
193
  ];
159
194
  }
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;
195
+ var answerItem = (a) => `ans:${a.decisionNeedId}@${a.acceptedAt}`;
196
+ function answerLine(a) {
197
+ return `Paigy \xB7 answer on Goal ${a.goalId} ("${a.title}"): ${a.question ? `"${a.question}" \u2192 ` : ""}${a.answer || "(no words)"}${a.via ? ` \xB7 ${a.via}` : ""} \xB7 get_goals({goalIds:["${a.goalId}"]}) to read it, then acknowledge the review with contact({ackEventIds})`;
198
+ }
199
+ function briefLines(waiting, reason, as, goalId, seen) {
200
+ const gained = new Set(briefItems(waiting).filter((i) => !seen.has(i)));
201
+ for (const i of gained) seen.add(i);
202
+ const answers = (waiting.answers ?? []).filter((a) => gained.has(answerItem(a)));
203
+ const more = [...gained].some((i) => !i.startsWith("ans:"));
204
+ return [...reason === "boot" || more ? [brief(waiting, reason, as, goalId)] : [], ...answers.map(answerLine)];
164
205
  }
165
206
  function brief(waiting, reason, as, goalId) {
166
207
  const lead = leadOf(waiting.deliveries, goalId);
@@ -169,13 +210,15 @@ function brief(waiting, reason, as, goalId) {
169
210
  const named = (goalId ? review.find((g) => g.goalId === goalId) : void 0) ?? review[0];
170
211
  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` : "";
171
212
  const assigned = waiting.assigned ?? [];
213
+ const kinds = Object.entries(waiting.other ?? {});
214
+ const other = kinds.length ? ` \xB7 also waiting: ${kinds.map(([kind, rows]) => `${kind} (${rows.length})`).join(", ")}: contact({ wait: false }) to read it` : "";
172
215
  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` : "";
173
- if (!lead) return `Paigy \xB7 ${reason === "boot" ? `listening as ${as}` : reason}${mine}${fresh || (mine ? "" : " \xB7 nothing waiting")}${stalled}`;
216
+ if (!lead) return `Paigy \xB7 ${reason === "boot" ? `listening as ${as}` : reason}${mine}${fresh || (mine || other ? "" : " \xB7 nothing waiting")}${other}${stalled}`;
174
217
  const said = newestSaid(lead) || ("about" in lead ? lead.about : "");
175
218
  const shown = said.length > 160 ? `${said.slice(0, 160)}\u2026` : said;
176
219
  const goal = waiting.goal?.goalId ?? lead.goalIds[0];
177
- const act = ` \xB7 get_goals({goalIds:["${goal}"]}) to read it in full, then contact to answer; nothing is acknowledged until contact({ackEventIds})`;
178
- return `Paigy \xB7 ${reason} \xB7 ${waiting.deliveries.length} waiting \xB7 lead ${lead.deliveryId} on Goal ${goal}: "${shown}"${mine}${fresh}${stalled}${act}`;
220
+ 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})";
221
+ return `Paigy \xB7 ${reason} \xB7 ${waiting.deliveries.length} waiting \xB7 lead ${lead.deliveryId}${goal ? ` on Goal ${goal}` : ""}: "${shown}"${mine}${fresh}${other}${stalled}${act}`;
179
222
  }
180
223
  function launchEnv(waiting, reason, goalId) {
181
224
  const env = { PAIGY_WORK: JSON.stringify(waiting), PAIGY_EVENT: reason };
@@ -193,35 +236,58 @@ async function sweep(reason, goalId, bridge = new WakeBridge(process.env, wakeSt
193
236
  try {
194
237
  void Promise.resolve().then(() => heartbeat(void 0, { listening: true })).catch(() => {
195
238
  });
196
- const { deliveries: rows, stalled, review, assigned } = await receive();
239
+ const read = await receive();
240
+ const { deliveries: rows, stalled, review, assigned } = read;
197
241
  const lead = leadOf(rows, goalId);
198
242
  const full = lead ? await readDelivery(lead.deliveryId).catch(() => null) : null;
199
243
  const deliveries = rows.map((d) => d === lead && full ? full : d);
200
244
  const leadGoalId = lead?.goalIds[0];
201
245
  const goal = leadGoalId ? await getGoals([leadGoalId]).then(([g]) => ({ goalId: leadGoalId, progress: g.progress })).catch(() => null) : null;
202
- const waiting = { deliveries, goal, ...stalled?.length ? { stalled } : {}, ...review?.length ? { review } : {}, ...assigned?.length ? { assigned } : {} };
203
- if (BRIEF && briefIsNew(reason, briefItems(waiting), briefSeen)) line(brief(waiting, reason, listeningAs(), goalId));
246
+ const reads = await Promise.all((review ?? []).map(async ({ goalId: id }) => {
247
+ try {
248
+ const [g] = await getGoals([id]);
249
+ return { id, g };
250
+ } catch (error) {
251
+ return { id, error };
252
+ }
253
+ }));
254
+ const answers = reads.flatMap((r) => "g" in r ? answersOn(review.find((v) => v.goalId === r.id), r.g) : []);
255
+ const other = otherKinds(read);
256
+ const waiting = {
257
+ deliveries,
258
+ goal,
259
+ ...stalled?.length ? { stalled } : {},
260
+ ...review?.length ? { review } : {},
261
+ ...assigned?.length ? { assigned } : {},
262
+ ...answers.length ? { answers } : {},
263
+ ...other ? { other } : {}
264
+ };
265
+ if (BRIEF) for (const text of briefLines(waiting, reason, listeningAs(), goalId, briefSeen)) line(text);
204
266
  else emit({ type: "sweep", reason, ...goalId ? { about: goalId } : {}, ...waiting });
205
- if (deliveries.length || waiting.review?.length || waiting.assigned?.length || reason === "boot" && waiting.stalled?.length) {
267
+ if (deliveries.length || waiting.review?.length || waiting.assigned?.length || waiting.other || reason === "boot" && waiting.stalled?.length) {
206
268
  let unreadable = false;
207
- const reviews = await Promise.all((review ?? []).map(async ({ goalId: id }) => {
208
- try {
209
- const [g] = await getGoals([id]);
210
- return [
211
- `review:${id}`,
212
- ...(g.entries ?? []).filter((e) => participant ? e.authorParticipant !== participant : e.authorParticipant.startsWith("human:")).map((e) => `entry:${e.entryId}:${e.createdAt}`),
213
- ...(g.answers ?? []).map((a) => `answer:${a.decisionNeedId}:${a.acceptedAt}`)
214
- ];
215
- } catch (error) {
269
+ const reviews = reads.map((r) => {
270
+ if (!("g" in r)) {
216
271
  unreadable = true;
217
- emit({ type: "error", reason: "review_read", goalId: id, message: error.message });
218
- return [`review:${id}`];
272
+ if (!BRIEF) emit({ type: "error", reason: "review_read", goalId: r.id, message: r.error.message });
273
+ else if (!briefUnreadable.has(r.id)) {
274
+ briefUnreadable.add(r.id);
275
+ line(`Paigy \xB7 error \xB7 could not read Goal ${r.id}: ${r.error.message}`);
276
+ }
277
+ return [`review:${r.id}`];
219
278
  }
220
- }));
279
+ briefUnreadable.delete(r.id);
280
+ return [
281
+ `review:${r.id}`,
282
+ ...(r.g.entries ?? []).filter((e) => participant ? e.authorParticipant !== participant : e.authorParticipant.startsWith("human:")).map((e) => `entry:${e.entryId}:${e.createdAt}`),
283
+ ...(r.g.answers ?? []).map((a) => `answer:${a.decisionNeedId}:${a.acceptedAt}`)
284
+ ];
285
+ });
221
286
  const fingerprints = unreadable ? void 0 : [
222
287
  ...rows.map((d) => createHash("sha256").update(JSON.stringify(d)).digest("hex")),
223
288
  ...reviews.flat(),
224
289
  ...(assigned ?? []).map((g) => `assigned:${g.goalId}`),
290
+ ...briefItems({ deliveries: [], goal: null, ...other ? { other } : {} }),
225
291
  ...!rows.length && !reviews.length && !assigned?.length ? (stalled ?? []).map((g) => `stalled:${g.goalId}:${g.since}`) : []
226
292
  ];
227
293
  const message = `Paigy has waiting work${goalId ? ` on Goal ${goalId}` : ""}. Read contact({ wait: false }) and the relevant Goal conversation, handle new user input, and acknowledge (contact({ ackEventIds })) only events actually handled. Preserve this session's current task; do not start unrelated old work. This wake is not new authorization.`;
@@ -320,15 +386,18 @@ if (isEntry()) {
320
386
  }
321
387
  export {
322
388
  SWEEP_EVERY_MS,
389
+ answerLine,
390
+ answersOn,
323
391
  brief,
324
- briefIsNew,
325
392
  briefItems,
393
+ briefLines,
326
394
  catchUpReason,
327
395
  coalesceSweeps,
328
396
  launchEnv,
329
397
  leadOf,
330
398
  listenOptions,
331
399
  newestSaid,
400
+ otherKinds,
332
401
  saidIn,
333
402
  sweep
334
403
  };
package/dist/onboard.js CHANGED
@@ -3,14 +3,14 @@ import {
3
3
  resolvePairing,
4
4
  startPairing,
5
5
  suggestedAgentName
6
- } from "./chunk-6FG3PZR2.js";
6
+ } from "./chunk-6V4X3EFA.js";
7
7
  import {
8
8
  autoConfigureClients,
9
9
  claudeInstallHint,
10
10
  openBrowser,
11
11
  removeOldGeminiHooks
12
- } from "./chunk-PKJJSHLW.js";
13
- import "./chunk-H2EBILBE.js";
12
+ } from "./chunk-I3WDI2JV.js";
13
+ import "./chunk-NNB3HQTF.js";
14
14
  import {
15
15
  TOKEN_PATH,
16
16
  agentName,
@@ -21,7 +21,7 @@ import {
21
21
  saveToken,
22
22
  setIdentity,
23
23
  whoAmI
24
- } from "./chunk-V4OVLFMP.js";
24
+ } from "./chunk-W3FK4FD6.js";
25
25
 
26
26
  // src/onboard.ts
27
27
  function reportRegistered() {
package/dist/slot.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  agentName
4
- } from "./chunk-V4OVLFMP.js";
4
+ } from "./chunk-W3FK4FD6.js";
5
5
 
6
6
  // src/slot.ts
7
7
  process.stdout.write(agentName());
package/dist/stalled.js CHANGED
@@ -33,7 +33,7 @@ async function main() {
33
33
  const dir = join(homedir(), ".paigy", "stalled-reminded");
34
34
  const mark = join(dir, `${session || "session"}-${day}`);
35
35
  if (existsSync(mark)) return;
36
- const { receive } = await import("./dist-EJWDUF5J.js");
36
+ const { receive } = await import("./dist-ZBYGV7QX.js");
37
37
  const reply = stopReply((await receive()).stalled ?? []);
38
38
  if (!reply) return;
39
39
  mkdirSync(dir, { recursive: true });
@@ -7,7 +7,7 @@ import {
7
7
  readToken,
8
8
  sessionSlot,
9
9
  slotName
10
- } from "./chunk-V4OVLFMP.js";
10
+ } from "./chunk-W3FK4FD6.js";
11
11
 
12
12
  // src/statusline.ts
13
13
  import { realpathSync } from "fs";
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@paigy/mcp",
3
- "version": "0.40.40",
3
+ "mcpName": "io.github.paigy-ai/mcp",
4
+ "version": "0.40.42",
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",