@paigy/mcp 0.40.11 → 0.40.15

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.
@@ -4,7 +4,7 @@ import {
4
4
  requestCode,
5
5
  saveToken,
6
6
  sleep
7
- } from "./chunk-H5GGWCPS.js";
7
+ } from "./chunk-PHTQITTC.js";
8
8
 
9
9
  // src/identity.ts
10
10
  var CLIENT_LABELS = {
@@ -0,0 +1,110 @@
1
+ import {
2
+ AGENT_TOOLS,
3
+ AGENT_TOOL_NAMES,
4
+ AWAIT_WINDOW_MS,
5
+ ApiError,
6
+ BACKEND_URL,
7
+ KEY_PATH,
8
+ NETWORK_MSG,
9
+ NotifyRequestSchema,
10
+ PROTO,
11
+ TOKEN_PATH,
12
+ UnpairedError,
13
+ acceptTriage,
14
+ agentName,
15
+ authToken,
16
+ checkReplies,
17
+ claimGoal,
18
+ claimSessions,
19
+ contact,
20
+ createGoal,
21
+ currentRepo,
22
+ deleteToken,
23
+ dismissTriage,
24
+ getGoal,
25
+ getTriage,
26
+ hatch,
27
+ heartbeat,
28
+ isNetworkError,
29
+ listConnections,
30
+ listNotes,
31
+ listSlots,
32
+ overrideToken,
33
+ parseDue,
34
+ pollToken,
35
+ reach,
36
+ reachAs,
37
+ readToken,
38
+ registerDelivery,
39
+ repoFromRemote,
40
+ requestCode,
41
+ revokeToken,
42
+ runTool,
43
+ saveToken,
44
+ sessionId,
45
+ sessionSlot,
46
+ setIdentity,
47
+ sleep,
48
+ slotIdentity,
49
+ slotName,
50
+ submitTriage,
51
+ subscribeWake,
52
+ updateGoal,
53
+ updateSlot,
54
+ whoAmI
55
+ } from "./chunk-PHTQITTC.js";
56
+ export {
57
+ AGENT_TOOLS,
58
+ AGENT_TOOL_NAMES,
59
+ AWAIT_WINDOW_MS,
60
+ ApiError,
61
+ BACKEND_URL,
62
+ KEY_PATH,
63
+ NETWORK_MSG,
64
+ NotifyRequestSchema,
65
+ PROTO,
66
+ TOKEN_PATH,
67
+ UnpairedError,
68
+ acceptTriage,
69
+ agentName,
70
+ authToken,
71
+ checkReplies,
72
+ claimGoal,
73
+ claimSessions,
74
+ contact,
75
+ createGoal,
76
+ currentRepo,
77
+ deleteToken,
78
+ dismissTriage,
79
+ getGoal,
80
+ getTriage,
81
+ hatch,
82
+ heartbeat,
83
+ isNetworkError,
84
+ listConnections,
85
+ listNotes,
86
+ listSlots,
87
+ overrideToken,
88
+ parseDue,
89
+ pollToken,
90
+ reach,
91
+ reachAs,
92
+ readToken,
93
+ registerDelivery,
94
+ repoFromRemote,
95
+ requestCode,
96
+ revokeToken,
97
+ runTool,
98
+ saveToken,
99
+ sessionId,
100
+ sessionSlot,
101
+ setIdentity,
102
+ sleep,
103
+ slotIdentity,
104
+ slotName,
105
+ submitTriage,
106
+ subscribeWake,
107
+ updateGoal,
108
+ updateSlot,
109
+ whoAmI
110
+ };
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
- } from "./chunk-FQVK7SO4.js";
6
- import "./chunk-I5QINUN6.js";
7
- import "./chunk-H5GGWCPS.js";
4
+ enablePaigyTools,
5
+ installSessionListening
6
+ } from "./chunk-37FS33UP.js";
7
+ import "./chunk-SE2JCKDI.js";
8
+ import "./chunk-PHTQITTC.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-XIHVHYDU.js";
8
+ } from "./chunk-X6G7XXCP.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-FQVK7SO4.js";
18
- import "./chunk-I5QINUN6.js";
17
+ } from "./chunk-37FS33UP.js";
18
+ import {
19
+ decideListen,
20
+ listenerAlive
21
+ } from "./chunk-SE2JCKDI.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-H5GGWCPS.js";
42
+ } from "./chunk-PHTQITTC.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
- } from "./chunk-I5QINUN6.js";
3
+ entryWords,
4
+ removeListenMark,
5
+ writeListenMark
6
+ } from "./chunk-SE2JCKDI.js";
5
7
  import {
8
+ agentName,
6
9
  checkReplies,
7
10
  contact,
8
11
  getGoal,
12
+ slotName,
9
13
  subscribeWake
10
- } from "./chunk-H5GGWCPS.js";
14
+ } from "./chunk-PHTQITTC.js";
11
15
 
12
16
  // src/listen.ts
13
17
  import { spawn } from "child_process";
@@ -112,17 +116,35 @@ function uninstallService() {
112
116
  }
113
117
 
114
118
  // src/listen.ts
115
- var emit = (obj) => void process.stdout.write(JSON.stringify(obj) + "\n");
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
+ const stalled = waiting.stalled?.length ? ` \xB7 ${waiting.stalled.length} of your Goals stalled 3+ days: update, finish or cancel each` : "";
136
+ if (!lead) return (reason === "boot" ? `Paigy \xB7 listening as ${as} \xB7 nothing waiting` : `Paigy \xB7 ${reason} \xB7 nothing waiting`) + stalled;
137
+ const said = saidIn(lead) || ("about" in lead ? lead.about : "");
138
+ const shown = said.length > 160 ? `${said.slice(0, 160)}\u2026` : said;
139
+ const goal = waiting.goal?.goalId ?? lead.goalIds[0];
140
+ return `Paigy \xB7 ${reason} \xB7 ${waiting.deliveries.length} waiting \xB7 lead ${lead.deliveryId} on Goal ${goal}: "${shown}"${stalled}`;
141
+ }
119
142
  function launchEnv(waiting, reason, goalId) {
120
143
  const env = { PAIGY_WORK: JSON.stringify(waiting), PAIGY_EVENT: reason };
121
144
  const put = (key, value) => {
122
145
  if (value) env[key] = value;
123
146
  };
124
- const named = goalId ? waiting.deliveries.find((d) => d.goalIds.includes(goalId)) : void 0;
125
- const lead = named ?? waiting.deliveries[0];
147
+ const lead = leadOf(waiting.deliveries, goalId);
126
148
  if (!lead) return env;
127
149
  put("PAIGY_DELIVERY_ID", lead.deliveryId);
128
150
  put("PAIGY_GOAL_ID", waiting.goal?.goalId ?? lead.goalIds[0]);
@@ -132,26 +154,32 @@ function launchEnv(waiting, reason, goalId) {
132
154
  async function sweep(reason, goalId) {
133
155
  try {
134
156
  const cmd = process.env.PAIGY_ON_WAKE;
135
- const { deliveries: rows } = await checkReplies();
136
- const lead = (goalId ? rows.find((d) => d.goalIds.includes(goalId)) : void 0) ?? rows[0];
137
- const full = lead ? await contact({ deliveryId: lead.deliveryId }, { waits: false }).catch(() => null) : null;
157
+ const { deliveries: rows, stalled } = await checkReplies();
158
+ const lead = leadOf(rows, goalId);
159
+ const full = lead ? await contact({ deliveryId: lead.deliveryId }, { waits: false }).then((d) => "recorded" in d ? null : d).catch(() => null) : null;
138
160
  const deliveries = rows.map((d) => d === lead && full ? full : d);
139
161
  const leadGoalId = lead?.goalIds[0];
140
162
  const goal = leadGoalId ? await getGoal(leadGoalId).then((g) => ({ goalId: leadGoalId, progress: g.progress })).catch(() => null) : null;
141
- const waiting = { deliveries, goal };
142
- emit({ type: "sweep", reason, ...goalId ? { about: goalId } : {}, ...waiting });
143
- if (cmd && deliveries.length) {
163
+ const waiting = { deliveries, goal, ...stalled?.length ? { stalled } : {} };
164
+ if (BRIEF) line(brief(waiting, reason, listeningAs(), goalId));
165
+ else emit({ type: "sweep", reason, ...goalId ? { about: goalId } : {}, ...waiting });
166
+ if (cmd && (deliveries.length || reason === "boot" && waiting.stalled?.length)) {
144
167
  spawn(cmd, { shell: true, stdio: "inherit", env: { ...process.env, ...launchEnv(waiting, reason, goalId) } });
145
168
  }
146
169
  } catch (e) {
147
- emit({ type: "error", reason, message: e.message });
170
+ if (BRIEF) line(`Paigy \xB7 error \xB7 ${e.message}`);
171
+ else emit({ type: "error", reason, message: e.message });
148
172
  }
149
173
  }
150
174
  async function main() {
151
- const sub = await subscribeWake((nudge) => void sweep(`wake:${nudge.kind ?? "?"}`, nudge.parentId));
152
- emit({ type: "listening", channel: sub.channel });
175
+ const sub = await subscribeWake((nudge) => void sweep(`wake:${nudge.kind ?? "?"}`, nudge.parentId), { release: RELEASE });
176
+ const slot = agentName();
177
+ writeListenMark(slot, process.pid);
178
+ process.on("exit", () => removeListenMark(slot));
179
+ if (!BRIEF) emit({ type: "listening", channel: sub.channel });
153
180
  await sweep("boot");
154
181
  const shutdown = async () => {
182
+ removeListenMark(slot);
155
183
  await sub.close();
156
184
  process.exit(0);
157
185
  };
@@ -175,13 +203,17 @@ if (isEntry()) {
175
203
  emit({ type: "uninstalled", unit: uninstallService() });
176
204
  } else {
177
205
  main().catch((e) => {
178
- emit({ type: "fatal", message: e.message });
206
+ if (BRIEF) line(`Paigy \xB7 error \xB7 ${e.message}`);
207
+ else emit({ type: "fatal", message: e.message });
179
208
  process.exit(1);
180
209
  });
181
210
  }
182
211
  }
183
212
  export {
213
+ brief,
184
214
  launchEnv,
215
+ leadOf,
216
+ listenOptions,
185
217
  saidIn,
186
218
  sweep
187
219
  };
package/dist/onboard.js CHANGED
@@ -3,13 +3,13 @@ import {
3
3
  resolvePairing,
4
4
  startPairing,
5
5
  suggestedAgentName
6
- } from "./chunk-XIHVHYDU.js";
6
+ } from "./chunk-X6G7XXCP.js";
7
7
  import {
8
8
  autoConfigureClients,
9
9
  claudeInstallHint,
10
10
  openBrowser
11
- } from "./chunk-FQVK7SO4.js";
12
- import "./chunk-I5QINUN6.js";
11
+ } from "./chunk-37FS33UP.js";
12
+ import "./chunk-SE2JCKDI.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-H5GGWCPS.js";
23
+ } from "./chunk-PHTQITTC.js";
24
24
 
25
25
  // src/onboard.ts
26
26
  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-H5GGWCPS.js";
4
+ } from "./chunk-PHTQITTC.js";
5
5
 
6
6
  // src/slot.ts
7
7
  process.stdout.write(agentName());
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/stalled.ts
4
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
5
+ import { homedir } from "os";
6
+ import { join } from "path";
7
+ function stopReply(stalled) {
8
+ if (!stalled.length) return null;
9
+ const list = stalled.slice(0, 8).map((g) => `"${g.title}" (${g.goalId})`).join(", ");
10
+ const more = stalled.length > 8 ? ` and ${stalled.length - 8} more` : "";
11
+ return {
12
+ decision: "block",
13
+ reason: `Before you stop: ${stalled.length} of your Paigy Goals have had no progress for 3 days: ${list}${more}. For each, update_goal with progress, finish it, or cancel it.`
14
+ };
15
+ }
16
+ async function main() {
17
+ const input = JSON.parse(readFileSync(0, "utf8") || "{}");
18
+ if (input.stop_hook_active) return;
19
+ const session = input.session_id ?? "";
20
+ if (session) process.env.PAIGY_SESSION_ID = session;
21
+ const day = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
22
+ const dir = join(homedir(), ".paigy", "stalled-reminded");
23
+ const mark = join(dir, `${session || "session"}-${day}`);
24
+ if (existsSync(mark)) return;
25
+ const { checkReplies } = await import("./dist-DKP3DJQJ.js");
26
+ const reply = stopReply((await checkReplies()).stalled ?? []);
27
+ if (!reply) return;
28
+ mkdirSync(dir, { recursive: true });
29
+ writeFileSync(mark, "");
30
+ process.stdout.write(JSON.stringify(reply));
31
+ }
32
+ if (process.argv[1] && /stalled(\.[jt]s)?$/.test(process.argv[1])) main().catch(() => void 0);
33
+ export {
34
+ stopReply
35
+ };
@@ -7,7 +7,7 @@ import {
7
7
  readToken,
8
8
  sessionSlot,
9
9
  slotName
10
- } from "./chunk-H5GGWCPS.js";
10
+ } from "./chunk-PHTQITTC.js";
11
11
 
12
12
  // src/statusline.ts
13
13
  import { realpathSync } from "fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paigy/mcp",
3
- "version": "0.40.11",
3
+ "version": "0.40.15",
4
4
  "description": "Paigy MCP server — the AI agent harness that calls you. Lets an agent notify a user and await their reply.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -11,7 +11,8 @@
11
11
  "paigy-listen": "dist/listen.js",
12
12
  "paigy-statusline": "dist/statusline.js",
13
13
  "paigy-enable-tools": "dist/enable.js",
14
- "paigy-slot": "dist/slot.js"
14
+ "paigy-slot": "dist/slot.js",
15
+ "paigy-stalled": "dist/stalled.js"
15
16
  },
16
17
  "files": [
17
18
  "dist"