@paigy/mcp 0.40.9 → 0.40.11

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.
@@ -2,10 +2,10 @@ import {
2
2
  AGENT_TOOLS,
3
3
  mcpInputSchema,
4
4
  serverInstructions
5
- } from "./chunk-UFOR2QEP.js";
5
+ } from "./chunk-I5QINUN6.js";
6
6
  import {
7
- AGENT_NAME
8
- } from "./chunk-5OY6ED2Q.js";
7
+ agentName
8
+ } from "./chunk-H5GGWCPS.js";
9
9
 
10
10
  // src/toolset.ts
11
11
  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.";
@@ -88,7 +88,7 @@ function withPaigyMcpToml(existing, agent) {
88
88
  ].join("\n");
89
89
  return existing + entry;
90
90
  }
91
- function autoConfigureClients(skip = AGENT_NAME) {
91
+ function autoConfigureClients(skip = agentName()) {
92
92
  const home = homedir();
93
93
  const registered = [];
94
94
  if (skip !== "antigravity") {
@@ -124,7 +124,7 @@ function autoConfigureClients(skip = AGENT_NAME) {
124
124
  }
125
125
  return registered;
126
126
  }
127
- function claudeInstallHint(skip = AGENT_NAME) {
127
+ function claudeInstallHint(skip = agentName()) {
128
128
  if (skip.startsWith("claude")) return null;
129
129
  const claudeDir = join(homedir(), ".claude");
130
130
  if (!existsSync(claudeDir)) return null;
@@ -1,6 +1,10 @@
1
1
  // ../../packages/sdk/dist/index.js
2
2
  import { createRequire as __sdkCreateRequire } from "module";
3
+ import { execFileSync } from "child_process";
3
4
  import { createHash, randomUUID } from "crypto";
5
+ import { readFileSync } from "fs";
6
+ import { homedir } from "os";
7
+ import { join } from "path";
4
8
  import { randomUUID as randomUUID3 } from "crypto";
5
9
  import { setTimeout as sleep2 } from "timers/promises";
6
10
  import { z as z3 } from "zod";
@@ -10,9 +14,9 @@ import { ZodFirstPartyTypeKind } from "zod/v3";
10
14
  import { ZodFirstPartyTypeKind as ZodFirstPartyTypeKind2 } from "zod/v3";
11
15
  import { z } from "zod";
12
16
  import { randomUUID as randomUUID2 } from "crypto";
13
- import { closeSync, existsSync, mkdirSync, openSync, readFileSync, rmSync, statSync, writeFileSync } from "fs";
14
- import { homedir } from "os";
15
- import { join } from "path";
17
+ import { closeSync, existsSync, mkdirSync, openSync, readFileSync as readFileSync2, rmSync, statSync, writeFileSync } from "fs";
18
+ import { homedir as homedir2 } from "os";
19
+ import { join as join2 } from "path";
16
20
  import { randomUUID as randomUUID4 } from "crypto";
17
21
  import { WebSocket } from "undici";
18
22
  var require2 = __sdkCreateRequire(import.meta.url);
@@ -36,15 +40,42 @@ var envSession = (key) => {
36
40
  const v = process.env[key];
37
41
  return v && v.trim() ? v : void 0;
38
42
  };
39
- var SESSION_ID = envSession("PAIGY_SESSION_ID") ?? envSession("CLAUDE_CODE_SESSION_ID") ?? envSession("CODEX_THREAD_ID") ?? envSession("CODEX_SESSION_ID") ?? WORKSPACE_ID;
43
+ function parentOf(pid) {
44
+ try {
45
+ if (process.platform === "win32") return void 0;
46
+ const ppid = process.platform === "linux" ? readFileSync(`/proc/${pid}/stat`, "utf8").split(") ")[1]?.split(" ")[1] : execFileSync("ps", ["-o", "ppid=", "-p", String(pid)], { encoding: "utf8", timeout: 1e3 });
47
+ return Number(ppid?.trim()) || void 0;
48
+ } catch {
49
+ return void 0;
50
+ }
51
+ }
52
+ var lineage;
53
+ function ancestors() {
54
+ if (lineage) return lineage;
55
+ lineage = [];
56
+ for (let pid = process.ppid; pid && pid > 1 && lineage.length < 5; pid = parentOf(pid)) lineage.push(pid);
57
+ return lineage;
58
+ }
59
+ function claudeConversation() {
60
+ if (!envSession("CLAUDE_CODE_SESSION_ID") || process.env.CLAUDE_CODE_CHILD_SESSION) return void 0;
61
+ const dir = envSession("CLAUDE_CONFIG_DIR") ?? join(homedir(), ".claude");
62
+ for (const pid of ancestors()) {
63
+ try {
64
+ const { sessionId: sessionId2 } = JSON.parse(readFileSync(join(dir, "sessions", `${pid}.json`), "utf8"));
65
+ return typeof sessionId2 === "string" && sessionId2.trim() ? sessionId2 : void 0;
66
+ } catch {
67
+ }
68
+ }
69
+ return void 0;
70
+ }
40
71
  function sessionId() {
41
- return SESSION_ID;
72
+ return envSession("PAIGY_SESSION_ID") ?? claudeConversation() ?? envSession("CLAUDE_CODE_SESSION_ID") ?? envSession("CODEX_THREAD_ID") ?? envSession("CODEX_SESSION_ID") ?? WORKSPACE_ID;
42
73
  }
43
74
  function isNetworkError(e) {
44
75
  return e instanceof Error && e.message.startsWith(NETWORK_MSG);
45
76
  }
46
77
  async function reach(url, init) {
47
- return send(url, init, SESSION_ID);
78
+ return send(url, init, sessionId());
48
79
  }
49
80
  async function send(url, init, session) {
50
81
  try {
@@ -1270,6 +1301,7 @@ var zodToJsonSchema = (schema, options) => {
1270
1301
  };
1271
1302
  var OPTIONS_MIN = 2;
1272
1303
  var OPTIONS_MAX = 6;
1304
+ var NIGHT = { from: 23, to: 7 };
1273
1305
  function draft2020(node) {
1274
1306
  if (Array.isArray(node)) return node.map(draft2020);
1275
1307
  if (node && typeof node === "object") {
@@ -1301,7 +1333,7 @@ function mcpInputSchema(s) {
1301
1333
  }
1302
1334
  var AskInputSchema = z.object({
1303
1335
  id: z.string().optional().describe("Optional idempotency key or client-side ID for this specific ask."),
1304
- parentId: z.string().uuid().optional().describe("Optional explicit parent Goal ID. If omitted, the platform's semantic router will auto-cluster it."),
1336
+ parentId: z.string().uuid().optional().describe("The Goal this question is about \u2014 usually the one you are working on. The question goes onto that Goal and its answer comes back there. Omit it and Paigy places the question in the tree itself."),
1305
1337
  repo: z.string().optional().describe("Optional repository context."),
1306
1338
  ask: z.string().trim().min(1).max(1e4).describe(
1307
1339
  "The question, and only what is needed to answer it. News, progress and findings are their own contact \u2014 a call contact JOINS a call already happening, so several arrive as one call. Do not bundle: a DecisionNeed is settled only by an answer in the shape this ask declares, so someone who answers the part that interested them settles nothing and is asked again."
@@ -1320,7 +1352,7 @@ var StartContactSchema = z.object({
1320
1352
  }).strict();
1321
1353
  var ContactSchema = z.union([StartContactSchema, z.object({ deliveryId: z.string().uuid() }).strict()]);
1322
1354
  var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
1323
- var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. The platform will semantically cluster your asks under the correct Goals automatically. Notification returns immediately; collect durable answers with check_replies. On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. Never bundle multiple questions into a single 'ask' string; pass them as separate objects in the 'asks' array.";
1355
+ var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. An ask that names a Goal (parentId) goes onto that Goal; one that names none is placed in the person's tree by Paigy. Notification returns immediately; collect durable answers with check_replies. On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. Never bundle multiple questions into a single 'ask' string; pass them as separate objects in the 'asks' array.";
1324
1356
  var CreateGoalSchema = z2.object({
1325
1357
  outcome: z2.string().trim().min(1).max(1e4),
1326
1358
  /** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
@@ -1376,9 +1408,9 @@ var CLAIM_GOAL_DESCRIPTION = "Claim the oldest runnable or review-pending Goal y
1376
1408
  var CHECK_REPLIES_DESCRIPTION = "Your open Deliveries: every Notification or Call currently addressed to you \u2014 a request the user started toward you, an answer relayed to something you asked, a handoff \u2014 one row each: its Goals, how many decisions are still open, and the newest words in brief. A pure read with no arguments: nothing is consumed, acknowledged or claimed by reading it, so call it on startup, after a long wait, or whenever you want to know what is outstanding. To act on one, claim its Goal (claim_goal) or reread it in full with contact({deliveryId}). Once you have acted on what arrived, update_goal with reviewed: true closes the Deliveries addressed to you on that Goal. Your runnable and review-pending Goals come from claim_goal, not from here.";
1377
1409
  var CheckRepliesSchema = z2.object({}).strict();
1378
1410
  var GetThreadSchema = z2.object({
1379
- parentId: z2.string().describe("The Thread to read \u2014 the threadId a Delivery returned, or the parentId of a search hit.")
1411
+ parentId: z2.string().describe("The Thread to read \u2014 the parentId of a search_threads hit.")
1380
1412
  }).strict();
1381
- var GET_THREAD_DESCRIPTION = "Read the authorized durable Entries on one conversation Thread \u2014 what you wrote there and what was delivered to you, oldest first. Use claim_goal to find the work to resume; use this to rehydrate a Thread that a search hit or a Delivery named.";
1413
+ var GET_THREAD_DESCRIPTION = "Read the authorized durable Entries on one conversation Thread \u2014 what you wrote there and what was delivered to you, oldest first. Use claim_goal to find the work to resume and get_goal for the conversation on a Goal; use this only to rehydrate a Thread that a search hit named.";
1382
1414
  var SearchThreadsSchema = z2.object({
1383
1415
  q: z2.string().describe("What to look for \u2014 plain words or a phrase (e.g. 'the livekit timeout', 'deploy to prod').")
1384
1416
  }).strict();
@@ -1856,7 +1888,7 @@ var InboxItemSchema = z3.object({
1856
1888
  error: z3.string().optional(),
1857
1889
  clarifies: z3.string().optional(),
1858
1890
  /** The ring ladder ran out while this was still pending — we tried to reach you and
1859
- * STOPPED trying (`arbitration/arbitrate.ts` `nextRing` → `stop`). Distinct from an
1891
+ * STOPPED trying (`arbitration/arbitrate.ts` `ringAt` → null). Distinct from an
1860
1892
  * agent with nothing to say, which the roster drew identically until now: "nothing to
1861
1893
  * say" and "gave up saying it" are opposite situations wearing the same face
1862
1894
  * (navigation-design.md, gap 1). False for anything that never rang. */
@@ -1925,6 +1957,8 @@ var MissedCallSchema = z3.enum([
1925
1957
  "inbox",
1926
1958
  "dismiss"
1927
1959
  ]);
1960
+ var clock = (h) => h === 0 ? "midnight" : h === 12 ? "noon" : h < 12 ? `${h} am` : `${h - 12} pm`;
1961
+ var QUIET = ` Nothing rings from ${clock(NIGHT.from)} to ${clock(NIGHT.to)} your time; the count waits for morning.`;
1928
1962
  var BrokerTuningSchema = z3.object({
1929
1963
  /** 'none' = skip the spoken ack after a mapped answer (power users find it slow). */
1930
1964
  ackVerbosity: z3.enum(["normal", "none"]).optional(),
@@ -2352,13 +2386,15 @@ function sessionSlot(sessionId2) {
2352
2386
  const id = sessionId2 ?? sessionId();
2353
2387
  return `session:${id.slice(0, 8)}`;
2354
2388
  }
2355
- var AGENT_NAME = process.env.PAIGY_AGENT || sessionSlot();
2356
- var TOKEN_PATH = join(homedir(), ".paigy", "token.json");
2357
- var KEY_PATH = join(homedir(), ".paigy", "key.json");
2389
+ function agentName() {
2390
+ return process.env.PAIGY_AGENT || sessionSlot();
2391
+ }
2392
+ var TOKEN_PATH = join2(homedir2(), ".paigy", "token.json");
2393
+ var KEY_PATH = join2(homedir2(), ".paigy", "key.json");
2358
2394
  function readTokenFile() {
2359
2395
  if (!existsSync(TOKEN_PATH)) return {};
2360
2396
  try {
2361
- const parsed = JSON.parse(readFileSync(TOKEN_PATH, "utf8"));
2397
+ const parsed = JSON.parse(readFileSync2(TOKEN_PATH, "utf8"));
2362
2398
  if (typeof parsed.access_token === "string") return {};
2363
2399
  const slots = parsed;
2364
2400
  delete slots["*"];
@@ -2370,7 +2406,7 @@ function readTokenFile() {
2370
2406
  function withTokenLock(mutate) {
2371
2407
  const lock = TOKEN_PATH + ".lock";
2372
2408
  const spin = new Int32Array(new SharedArrayBuffer(4));
2373
- mkdirSync(join(homedir(), ".paigy"), { recursive: true });
2409
+ mkdirSync(join2(homedir2(), ".paigy"), { recursive: true });
2374
2410
  for (const deadline = Date.now() + 5e3; Date.now() < deadline; ) {
2375
2411
  try {
2376
2412
  closeSync(openSync(lock, "wx"));
@@ -2387,14 +2423,14 @@ function withTokenLock(mutate) {
2387
2423
  rmSync(lock, { force: true });
2388
2424
  }
2389
2425
  }
2390
- function saveToken(token, agent2 = AGENT_NAME) {
2426
+ function saveToken(token, agent2 = agentName()) {
2391
2427
  withTokenLock(() => {
2392
2428
  const slots = { ...readTokenFile(), [agent2]: token };
2393
2429
  writeFileSync(TOKEN_PATH, JSON.stringify(slots, null, 2) + "\n", { mode: 384 });
2394
2430
  });
2395
2431
  }
2396
2432
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
2397
- function readToken(agent2 = AGENT_NAME) {
2433
+ function readToken(agent2 = agentName()) {
2398
2434
  if (process.env.PAIGY_TOKEN) return process.env.PAIGY_TOKEN;
2399
2435
  return readTokenFile()[agent2]?.access_token ?? "";
2400
2436
  }
@@ -2404,7 +2440,7 @@ function listSlots() {
2404
2440
  function slotName(agent2) {
2405
2441
  return readTokenFile()[agent2]?.name ?? null;
2406
2442
  }
2407
- function deleteToken(agent2 = AGENT_NAME) {
2443
+ function deleteToken(agent2 = agentName()) {
2408
2444
  return withTokenLock(() => {
2409
2445
  const slots = readTokenFile();
2410
2446
  if (!(agent2 in slots)) return false;
@@ -2421,7 +2457,7 @@ async function revokeToken(token) {
2421
2457
  });
2422
2458
  return res.ok;
2423
2459
  }
2424
- async function requestCode(suggestedName = AGENT_NAME, device) {
2460
+ async function requestCode(suggestedName = agentName(), device) {
2425
2461
  const res = await reach(`${BACKEND_URL}/api/device/code`, {
2426
2462
  method: "POST",
2427
2463
  headers: { "content-type": "application/json" },
@@ -2613,10 +2649,12 @@ async function contact(input, opts = {}) {
2613
2649
  deliveryId = deliveries[0]?.deliveryId ?? "";
2614
2650
  if (!deliveryId) throw new Error("Goal contact returned no valid Delivery identity");
2615
2651
  }
2652
+ const all = deliveries.filter((d) => !!d.deliveryId && !!d.goalId);
2616
2653
  const read = async (signal2) => {
2617
2654
  const res = ensureAuthed(await send2(`${BACKEND_URL}/api/deliveries/${encodeURIComponent(deliveryId)}`, { headers, signal: signal2 }));
2618
2655
  if (!res.ok) await fail("read_delivery", res);
2619
- return await res.json();
2656
+ const one = await res.json();
2657
+ return all.length > 1 ? { ...one, deliveries: all } : one;
2620
2658
  };
2621
2659
  const settled = (d) => d.kind === "notification" || d.state === "closed" || d.answers.length > 0 || d.entries.some((e) => e.kind === "contribution");
2622
2660
  if (opts.waits === false) return read(opts.signal);
@@ -2712,6 +2750,8 @@ function deliveryView(d) {
2712
2750
  state: d.state,
2713
2751
  goalIds: d.goalIds,
2714
2752
  conversation: conversation(d),
2753
+ // Each ask's own Delivery when a contact carried several — the only handle on the others.
2754
+ deliveries: d.deliveries,
2715
2755
  next: d.kind === "notification" ? "Answers land on the Goal: read them with claim_goal or get_goal. Do not poll this notification." : d.message
2716
2756
  });
2717
2757
  }
@@ -2876,7 +2916,7 @@ async function subscribeWake(onNudge, opts = {}) {
2876
2916
  export {
2877
2917
  isNetworkError,
2878
2918
  sessionSlot,
2879
- AGENT_NAME,
2919
+ agentName,
2880
2920
  TOKEN_PATH,
2881
2921
  saveToken,
2882
2922
  sleep,
@@ -5,6 +5,7 @@ import { zodToJsonSchema } from "zod-to-json-schema";
5
5
  import { z } from "zod";
6
6
  var OPTIONS_MIN = 2;
7
7
  var OPTIONS_MAX = 6;
8
+ var NIGHT = { from: 23, to: 7 };
8
9
  function draft2020(node) {
9
10
  if (Array.isArray(node)) return node.map(draft2020);
10
11
  if (node && typeof node === "object") {
@@ -36,7 +37,7 @@ function mcpInputSchema(s) {
36
37
  }
37
38
  var AskInputSchema = z.object({
38
39
  id: z.string().optional().describe("Optional idempotency key or client-side ID for this specific ask."),
39
- parentId: z.string().uuid().optional().describe("Optional explicit parent Goal ID. If omitted, the platform's semantic router will auto-cluster it."),
40
+ parentId: z.string().uuid().optional().describe("The Goal this question is about \u2014 usually the one you are working on. The question goes onto that Goal and its answer comes back there. Omit it and Paigy places the question in the tree itself."),
40
41
  repo: z.string().optional().describe("Optional repository context."),
41
42
  ask: z.string().trim().min(1).max(1e4).describe(
42
43
  "The question, and only what is needed to answer it. News, progress and findings are their own contact \u2014 a call contact JOINS a call already happening, so several arrive as one call. Do not bundle: a DecisionNeed is settled only by an answer in the shape this ask declares, so someone who answers the part that interested them settles nothing and is asked again."
@@ -55,7 +56,7 @@ var StartContactSchema = z.object({
55
56
  }).strict();
56
57
  var ContactSchema = z.union([StartContactSchema, z.object({ deliveryId: z.string().uuid() }).strict()]);
57
58
  var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
58
- var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. The platform will semantically cluster your asks under the correct Goals automatically. Notification returns immediately; collect durable answers with check_replies. On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. Never bundle multiple questions into a single 'ask' string; pass them as separate objects in the 'asks' array.";
59
+ var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. An ask that names a Goal (parentId) goes onto that Goal; one that names none is placed in the person's tree by Paigy. Notification returns immediately; collect durable answers with check_replies. On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. Never bundle multiple questions into a single 'ask' string; pass them as separate objects in the 'asks' array.";
59
60
  var CreateGoalSchema = z2.object({
60
61
  outcome: z2.string().trim().min(1).max(1e4),
61
62
  /** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
@@ -111,9 +112,9 @@ var CLAIM_GOAL_DESCRIPTION = "Claim the oldest runnable or review-pending Goal y
111
112
  var CHECK_REPLIES_DESCRIPTION = "Your open Deliveries: every Notification or Call currently addressed to you \u2014 a request the user started toward you, an answer relayed to something you asked, a handoff \u2014 one row each: its Goals, how many decisions are still open, and the newest words in brief. A pure read with no arguments: nothing is consumed, acknowledged or claimed by reading it, so call it on startup, after a long wait, or whenever you want to know what is outstanding. To act on one, claim its Goal (claim_goal) or reread it in full with contact({deliveryId}). Once you have acted on what arrived, update_goal with reviewed: true closes the Deliveries addressed to you on that Goal. Your runnable and review-pending Goals come from claim_goal, not from here.";
112
113
  var CheckRepliesSchema = z2.object({}).strict();
113
114
  var GetThreadSchema = z2.object({
114
- parentId: z2.string().describe("The Thread to read \u2014 the threadId a Delivery returned, or the parentId of a search hit.")
115
+ parentId: z2.string().describe("The Thread to read \u2014 the parentId of a search_threads hit.")
115
116
  }).strict();
116
- var GET_THREAD_DESCRIPTION = "Read the authorized durable Entries on one conversation Thread \u2014 what you wrote there and what was delivered to you, oldest first. Use claim_goal to find the work to resume; use this to rehydrate a Thread that a search hit or a Delivery named.";
117
+ var GET_THREAD_DESCRIPTION = "Read the authorized durable Entries on one conversation Thread \u2014 what you wrote there and what was delivered to you, oldest first. Use claim_goal to find the work to resume and get_goal for the conversation on a Goal; use this only to rehydrate a Thread that a search hit named.";
117
118
  var SearchThreadsSchema = z2.object({
118
119
  q: z2.string().describe("What to look for \u2014 plain words or a phrase (e.g. 'the livekit timeout', 'deploy to prod').")
119
120
  }).strict();
@@ -129,14 +130,15 @@ var AGENT_TOOLS = [
129
130
  { name: "update_goal", description: UPDATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(UpdateGoalToolSchema) }
130
131
  ];
131
132
  var AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
132
- function serverInstructions(_opts) {
133
- return `On startup and after a wake, call check_replies for the open Deliveries addressed to you, and claim_goal for your runnable or review-pending Goal and the conversation on it. get_goal rereads it without claiming. Every read ends in \`next\`, the one step to take. Contact only for an existing Goal. Notifications return immediately: keep working and collect answers through claim_goal/get_goal. On stdio, Calls hold one bounded window; on hosted MCP, Calls return after one read; continue with contact({deliveryId}) to reread that exact Delivery. Evidence can repeat: reads do not acknowledge or hide it. Use update_goal to report progress and explicitly acknowledge review. Never infer ringing from an open Call Delivery. Soft waiting, multipart contact, and re-presentation are unsupported. Your user is remote. Always interact with the user through Paigy. For decisions, approvals, or questions, contact them with structured options. Never assume anyone is reading the terminal stdout.
133
+ function serverInstructions(opts) {
134
+ const calls = opts.waits ? "A Call holds one bounded window here; continue with contact({deliveryId}) to hold the next or reread that exact Delivery." : "A Call returns after one read; continue with contact({deliveryId}) to reread that exact Delivery.";
135
+ return `On startup and after a wake, call check_replies for the open Deliveries addressed to you, and claim_goal for your runnable or review-pending Goal and the conversation on it. get_goal rereads it without claiming. Every read ends in \`next\`, the one step to take. Notifications return immediately: keep working and collect answers through claim_goal/get_goal. ${calls} Evidence can repeat: reads do not acknowledge or hide it. Report progress and acknowledge review with update_goal, never as a contact. Never infer ringing from an open Call Delivery. Soft waiting and re-presentation are unsupported. Your user is remote. Always interact with the user through Paigy. For decisions, approvals, or questions, contact them with structured options. Never assume anyone is reading the terminal stdout.
134
136
 
135
- CRITICAL PAIGY WORKFLOW PHILOSOPHY:
136
- 1. Atomicity: Never dump a 5-part question into a single \`ask\`. Emit 5 separate objects in the \`asks\` array during a single \`contact\` call so the user can answer them independently.
137
- 2. Tree Structure: Always pass \`parentId\` to deliberately nest sub-decisions under the overarching Goal, ensuring the user workspace remains a clean tree rather than a flat mess.
138
- 3. Urgency: Use \`waiting: hard\` strictly for immediate, blocking decisions, and \`waiting: none\` for async, long-running progress updates or non-blocking questions.
139
- 4. Asynchrony: Never hold the process open with while-loops. Let the current process finish or explicitly schedule wakeups, and rely on \`check_replies\` or \`claim_goal\` on the next wake to process asynchronous answers.`;
137
+ HOW TO ASK:
138
+ 1. One question per ask. Five questions are five objects in \`asks\`, in one contact, so each can be answered on its own; one question with five parts settles nothing until all five are answered.
139
+ 2. Name the Goal you are working on as the ask's \`parentId\`: the question goes onto that Goal and its answer comes back there. Without one, Paigy places it.
140
+ 3. \`waiting: hard\` only for a decision you are blocked on; \`waiting: none\` for a question you can keep working around.
141
+ 4. Never hold the process open with while-loops. Let the current process finish or schedule a wakeup, and collect answers with \`check_replies\` or \`claim_goal\` on the next wake.`;
140
142
  }
141
143
  function entryWords(entry) {
142
144
  const content = entry.content;
@@ -600,7 +602,7 @@ var InboxItemSchema = z3.object({
600
602
  error: z3.string().optional(),
601
603
  clarifies: z3.string().optional(),
602
604
  /** The ring ladder ran out while this was still pending — we tried to reach you and
603
- * STOPPED trying (`arbitration/arbitrate.ts` `nextRing` → `stop`). Distinct from an
605
+ * STOPPED trying (`arbitration/arbitrate.ts` `ringAt` → null). Distinct from an
604
606
  * agent with nothing to say, which the roster drew identically until now: "nothing to
605
607
  * say" and "gave up saying it" are opposite situations wearing the same face
606
608
  * (navigation-design.md, gap 1). False for anything that never rang. */
@@ -669,6 +671,8 @@ var MissedCallSchema = z3.enum([
669
671
  "inbox",
670
672
  "dismiss"
671
673
  ]);
674
+ var clock = (h) => h === 0 ? "midnight" : h === 12 ? "noon" : h < 12 ? `${h} am` : `${h - 12} pm`;
675
+ var QUIET = ` Nothing rings from ${clock(NIGHT.from)} to ${clock(NIGHT.to)} your time; the count waits for morning.`;
672
676
  var BrokerTuningSchema = z3.object({
673
677
  /** 'none' = skip the spoken ack after a mapped answer (power users find it slow). */
674
678
  ackVerbosity: z3.enum(["normal", "none"]).optional(),
@@ -4,7 +4,7 @@ import {
4
4
  requestCode,
5
5
  saveToken,
6
6
  sleep
7
- } from "./chunk-5OY6ED2Q.js";
7
+ } from "./chunk-H5GGWCPS.js";
8
8
 
9
9
  // src/identity.ts
10
10
  var CLIENT_LABELS = {
package/dist/enable.js CHANGED
@@ -2,9 +2,9 @@
2
2
  import {
3
3
  PAIGY_TOOL_IDS,
4
4
  enablePaigyTools
5
- } from "./chunk-UOROFXDI.js";
6
- import "./chunk-UFOR2QEP.js";
7
- import "./chunk-5OY6ED2Q.js";
5
+ } from "./chunk-FQVK7SO4.js";
6
+ import "./chunk-I5QINUN6.js";
7
+ import "./chunk-H5GGWCPS.js";
8
8
 
9
9
  // src/enable.ts
10
10
  function main() {
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  resolvePairing,
6
6
  startPairing,
7
7
  suggestedAgentName
8
- } from "./chunk-LYPZ5QO2.js";
8
+ } from "./chunk-XIHVHYDU.js";
9
9
  import {
10
10
  ENABLE_COMMAND,
11
11
  OnboardSchema,
@@ -14,15 +14,15 @@ import {
14
14
  SERVER_INSTRUCTIONS,
15
15
  TOOLS,
16
16
  paigyToolsAllowlisted
17
- } from "./chunk-UOROFXDI.js";
18
- import "./chunk-UFOR2QEP.js";
17
+ } from "./chunk-FQVK7SO4.js";
18
+ import "./chunk-I5QINUN6.js";
19
19
  import {
20
20
  clearSurface,
21
21
  writeSurface
22
22
  } from "./chunk-3SVNPJPN.js";
23
23
  import {
24
- AGENT_NAME,
25
24
  UnpairedError,
25
+ agentName,
26
26
  deleteToken,
27
27
  hatch,
28
28
  heartbeat,
@@ -35,7 +35,7 @@ import {
35
35
  setIdentity,
36
36
  slotName,
37
37
  whoAmI
38
- } from "./chunk-5OY6ED2Q.js";
38
+ } from "./chunk-H5GGWCPS.js";
39
39
 
40
40
  // src/index.ts
41
41
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -108,7 +108,7 @@ function renderPairOutcome(outcome, device_code) {
108
108
  function unidentifiedResult() {
109
109
  return { isError: true, content: [{ type: "text", text: JSON.stringify({
110
110
  error: "no_identity",
111
- slot: AGENT_NAME,
111
+ slot: agentName(),
112
112
  message: `This session has no Paigy identity yet, so nothing was sent \u2014 it will not speak as another session. This machine holds a device credential but hatching under it just failed (it may have been revoked), so call \`pair\` (no arguments) to set this session up, then retry \u2014 it re-hatches if the credential recovered and runs the code ceremony if it didn't. To reuse an existing identity instead, start the session with PAIGY_AGENT set to its slot (${listSlots().filter((s) => s !== "Desktop").join(", ") || "none yet"}).`
113
113
  }) }] };
114
114
  }
@@ -238,7 +238,7 @@ async function handleTool(request, signal) {
238
238
  case "onboard": {
239
239
  const { name, voice, device_code, scope } = OnboardSchema.parse(request.params.arguments ?? {});
240
240
  if (device_code) return runPair(device_code, name, voice);
241
- const slot = AGENT_NAME;
241
+ const slot = agentName();
242
242
  const secret = readToken();
243
243
  if (secret) {
244
244
  let who = await whoAmI().catch(() => null);
@@ -307,7 +307,7 @@ async function handleTool(request, signal) {
307
307
  default: {
308
308
  const name = request.params.name;
309
309
  const result = await runTool(name, request.params.arguments ?? {}, { waits: true, signal });
310
- const sentAs = name === "contact" ? slotName(AGENT_NAME) : null;
310
+ const sentAs = name === "contact" ? slotName(agentName()) : null;
311
311
  const shaped = sentAs && result && typeof result === "object" ? { ...result, sentAs } : result;
312
312
  return { content: [{ type: "text", text: JSON.stringify(shaped) }] };
313
313
  }
package/dist/listen.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  entryWords
4
- } from "./chunk-UFOR2QEP.js";
4
+ } from "./chunk-I5QINUN6.js";
5
5
  import {
6
6
  checkReplies,
7
7
  contact,
8
8
  getGoal,
9
9
  subscribeWake
10
- } from "./chunk-5OY6ED2Q.js";
10
+ } from "./chunk-H5GGWCPS.js";
11
11
 
12
12
  // src/listen.ts
13
13
  import { spawn } from "child_process";
package/dist/onboard.js CHANGED
@@ -3,16 +3,16 @@ import {
3
3
  resolvePairing,
4
4
  startPairing,
5
5
  suggestedAgentName
6
- } from "./chunk-LYPZ5QO2.js";
6
+ } from "./chunk-XIHVHYDU.js";
7
7
  import {
8
8
  autoConfigureClients,
9
9
  claudeInstallHint,
10
10
  openBrowser
11
- } from "./chunk-UOROFXDI.js";
12
- import "./chunk-UFOR2QEP.js";
11
+ } from "./chunk-FQVK7SO4.js";
12
+ import "./chunk-I5QINUN6.js";
13
13
  import {
14
- AGENT_NAME,
15
14
  TOKEN_PATH,
15
+ agentName,
16
16
  hatch,
17
17
  listSlots,
18
18
  overrideToken,
@@ -20,7 +20,7 @@ import {
20
20
  saveToken,
21
21
  setIdentity,
22
22
  whoAmI
23
- } from "./chunk-5OY6ED2Q.js";
23
+ } from "./chunk-H5GGWCPS.js";
24
24
 
25
25
  // src/onboard.ts
26
26
  function reportRegistered() {
@@ -36,7 +36,7 @@ function reportRegistered() {
36
36
  }
37
37
  async function main() {
38
38
  console.log(`
39
- Paigy MCP onboarding (agent: "${AGENT_NAME}")
39
+ Paigy MCP onboarding (agent: "${agentName()}")
40
40
  `);
41
41
  const chosen = process.env.PAIGY_NAME ?? suggestedAgentName();
42
42
  if (readToken()) {
package/dist/slot.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- AGENT_NAME
4
- } from "./chunk-5OY6ED2Q.js";
3
+ agentName
4
+ } from "./chunk-H5GGWCPS.js";
5
5
 
6
6
  // src/slot.ts
7
- process.stdout.write(AGENT_NAME);
7
+ process.stdout.write(agentName());
@@ -7,7 +7,7 @@ import {
7
7
  readToken,
8
8
  sessionSlot,
9
9
  slotName
10
- } from "./chunk-5OY6ED2Q.js";
10
+ } from "./chunk-H5GGWCPS.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.9",
3
+ "version": "0.40.11",
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",