@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.
@@ -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-QE33A5XK.js";
8
+ } from "./chunk-NYSDESST.js";
8
9
  import {
9
10
  CODEX_ENV,
10
11
  agentName,
11
12
  withCodexEnv
12
- } from "./chunk-X4UIGUEX.js";
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 \u2014 the tool-allowlist state it reports is read-only.\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. The leading text block of every result states the code plainly, so it shows even if you emit no prose.\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
+ 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 withPaigyStopHookJson(existing, agent) {
110
- let config = {};
111
- if (existing) {
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
- config = JSON.parse(existing);
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
- const hooks = config.hooks ?? {};
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 = "npx -y -p @paigy/mcp@latest paigy-enable-tools";
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-X4UIGUEX.js";
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-4IAK4ATB.js";
7
- import "./chunk-QE33A5XK.js";
8
- import "./chunk-X4UIGUEX.js";
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-PMZBYLLJ.js";
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-4IAK4ATB.js";
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
- } from "./chunk-QE33A5XK.js";
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-X4UIGUEX.js";
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: "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."
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 Devices \u2192 Connect a computer";
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
- const slot = agentName();
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
- return { content: [{ type: "text", text: JSON.stringify(shaped) }] };
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-QE33A5XK.js";
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-X4UIGUEX.js";
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 cancel each` : "";
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 = saidIn(lead) || ("about" in lead ? lead.about : "");
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
- return `Paigy \xB7 ${reason} \xB7 ${waiting.deliveries.length} waiting \xB7 lead ${lead.deliveryId} on Goal ${goal}: "${shown}"${mine}${fresh}${stalled}`;
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-PMZBYLLJ.js";
6
+ } from "./chunk-AX55W57P.js";
7
7
  import {
8
8
  autoConfigureClients,
9
9
  claudeInstallHint,
10
- openBrowser
11
- } from "./chunk-4IAK4ATB.js";
12
- import "./chunk-QE33A5XK.js";
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-X4UIGUEX.js";
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("\n\nDevice code expired without approval. Please re-run onboarding.\n");
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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  agentName
4
- } from "./chunk-X4UIGUEX.js";
4
+ } from "./chunk-CPDYOHSY.js";
5
5
 
6
6
  // src/slot.ts
7
7
  process.stdout.write(agentName());
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, event = "Stop") {
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 cancel it with manage_goals.`;
19
- return event === "AfterAgent" ? { decision: "block", reason, systemMessage: reason } : { decision: "block", reason };
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-DOFR2565.js");
33
- const reply = stopReply((await receive()).stalled ?? [], input.hook_event_name ?? "Stop");
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
  };
@@ -7,7 +7,7 @@ import {
7
7
  readToken,
8
8
  sessionSlot,
9
9
  slotName
10
- } from "./chunk-X4UIGUEX.js";
10
+ } from "./chunk-CPDYOHSY.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.39",
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",