@paigy/mcp 0.36.0 → 0.38.0

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.
@@ -10,7 +10,7 @@ import {
10
10
  saveToken,
11
11
  sleep,
12
12
  startE2ee
13
- } from "./chunk-PRC6GKRN.js";
13
+ } from "./chunk-PPACGOTU.js";
14
14
 
15
15
  // src/identity.ts
16
16
  var CLIENT_LABELS = {
@@ -13,10 +13,10 @@ import {
13
13
  SetWorkStateSchema,
14
14
  mcpInputSchema,
15
15
  serverInstructions
16
- } from "./chunk-WNE6STMA.js";
16
+ } from "./chunk-KE63GZEQ.js";
17
17
  import {
18
18
  AGENT_NAME
19
- } from "./chunk-PRC6GKRN.js";
19
+ } from "./chunk-PPACGOTU.js";
20
20
 
21
21
  // src/toolset.ts
22
22
  var ONBOARD_DESCRIPTION = "Get this agent talking to Paigy \u2014 call it FIRST, before contact/await_reply, 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.";
@@ -24,9 +24,11 @@ var SERVER_INSTRUCTIONS = serverInstructions({ waits: true });
24
24
 
25
25
  // src/tools.ts
26
26
  import { z } from "zod";
27
+ var UpdateGoalToolSchema = z.object({ goalId: z.string().uuid(), revision: z.number().int().positive(), changes: z.record(z.unknown()), reason: z.string().min(1) });
27
28
  var AwaitReplySchema = z.object({
28
29
  notificationId: z.string().describe("The notificationId returned by contact \u2014 waits for the user's reply to THIS notification only.")
29
30
  });
31
+ var ClaimGoalSchema = z.object({ goalId: z.string().uuid() });
30
32
  var OnboardSchema = z.object({
31
33
  name: z.string().max(60).optional(),
32
34
  voice: z.string().max(40).optional(),
@@ -106,6 +108,8 @@ var TOOLS = [
106
108
  description: CREATE_GOAL_DESCRIPTION,
107
109
  inputSchema: mcpInputSchema(CreateGoalSchema)
108
110
  },
111
+ { name: "claim_goal", description: "Claim a ready Goal you own and begin work. Returns its active revision.", inputSchema: mcpInputSchema(ClaimGoalSchema) },
112
+ { name: "update_goal", description: "Update an owned Goal at an exact revision; stale revisions are rejected.", inputSchema: mcpInputSchema(UpdateGoalToolSchema) },
109
113
  {
110
114
  name: "set_work_state",
111
115
  description: SET_WORK_STATE_DESCRIPTION,
@@ -291,7 +295,9 @@ function enablePaigyTools(scope = "user", cwd = process.cwd()) {
291
295
 
292
296
  export {
293
297
  SERVER_INSTRUCTIONS,
298
+ UpdateGoalToolSchema,
294
299
  AwaitReplySchema,
300
+ ClaimGoalSchema,
295
301
  OnboardSchema,
296
302
  PairSchema,
297
303
  GetThreadSchema,
@@ -42,6 +42,16 @@ var CreateGoalSchema = z.object({
42
42
  idempotencyKey: z.string().trim().min(1).max(200)
43
43
  });
44
44
  var CREATE_GOAL_DESCRIPTION = "Create a durable root Goal for an outcome. Admission only: claim it before doing work, then update its state as it advances. Use a stable idempotencyKey so retries do not create duplicates. Returns goalId, state, revision, ownerParticipant, and next claim_goal.";
45
+ var UpdateGoalSchema = z.object({
46
+ revision: z.number().int().positive(),
47
+ changes: z.object({
48
+ outcome: z.string().trim().min(1).max(1e4).optional(),
49
+ state: z.enum(["active", "waiting", "done", "cancelled"]).optional(),
50
+ progress: z.string().trim().min(1).max(1e4).optional(),
51
+ reviewed: z.literal(true).optional()
52
+ }).refine((v) => Object.keys(v).length > 0),
53
+ reason: z.string().trim().min(1).max(2e3)
54
+ });
45
55
  var fmtMin = (m) => m >= 60 ? `${m / 60} hr` : `${m} min`;
46
56
  var STANDARD_MEANS = (() => {
47
57
  const plan = MISSED_CALL_PLAN.backoff_standard;
@@ -455,6 +465,7 @@ var AwaitItemSchema = z2.discriminatedUnion("type", [
455
465
  var CallbackTriggerSchema = z2.enum(["on_done", "on_blocked", "scheduled"]);
456
466
  var ScheduleCallbackSchema = z2.object({
457
467
  parentId: z2.string().describe("The thread to call back on (from a prior contact / reply / request)."),
468
+ goalId: z2.string().uuid().optional().describe("The Goal this callback advances; preferred for Goal-owned work."),
458
469
  trigger: CallbackTriggerSchema,
459
470
  dueInSeconds: z2.number().int().positive().optional().describe("For 'scheduled' only: how many seconds from now to fire."),
460
471
  note: z2.string().optional().describe("What to tell the user when you follow up.")
@@ -899,6 +910,15 @@ var AgentActivitySchema = z2.object({
899
910
  var ConnectionSummarySchema = z2.object({
900
911
  /** The connection = the agent's token id (used to address a request). */
901
912
  id: z2.string(),
913
+ /** The credential kind: "device" = a paired machine (mint-only — it hosts and mints, it
914
+ * never talks); "agent" = an identity that sends. The roster and devices surfaces split
915
+ * on this. Optional/absent reads as "agent" (a row predating the kind column). See
916
+ * apps/api/src/tokens/devices-vs-agents-design.md. */
917
+ kind: z2.enum(["device", "agent"]).optional(),
918
+ /** For an agent, the token id of the DEVICE that minted it — so agents group under their
919
+ * machine, and revoking a device cascades to them. Null on devices, and on unlinked
920
+ * agents (phone-launched, provider-managed, or minted before the link existed). */
921
+ mintedByDevice: z2.string().nullable().optional(),
902
922
  device: z2.string().nullable(),
903
923
  /** The agent's display name (the single pairing name). */
904
924
  name: z2.string(),
@@ -1,12 +1,12 @@
1
1
  // ../../packages/sdk/dist/index.js
2
2
  import { createRequire as __sdkCreateRequire } from "module";
3
- import { randomUUID } from "crypto";
3
+ import { createHash, randomUUID } from "crypto";
4
4
  import { z as z2 } from "zod";
5
5
  import { z } from "zod";
6
6
  import { ZodFirstPartyTypeKind as ZodFirstPartyTypeKind3 } from "zod/v3";
7
7
  import { ZodFirstPartyTypeKind } from "zod/v3";
8
8
  import { ZodFirstPartyTypeKind as ZodFirstPartyTypeKind2 } from "zod/v3";
9
- import { createHash } from "crypto";
9
+ import { createHash as createHash2 } from "crypto";
10
10
  import { closeSync, existsSync, mkdirSync, openSync, readFileSync, rmSync, statSync, writeFileSync } from "fs";
11
11
  import { homedir } from "os";
12
12
  import { join } from "path";
@@ -2274,7 +2274,18 @@ async function proxy() {
2274
2274
  return agent ??= new (await import("undici")).EnvHttpProxyAgent();
2275
2275
  }
2276
2276
  var INSTANCE_ID = randomUUID();
2277
- var SESSION_ID = process.env.PAIGY_SESSION_ID ?? process.env.CLAUDE_CODE_SESSION_ID ?? INSTANCE_ID;
2277
+ var WORKSPACE_ID = (() => {
2278
+ try {
2279
+ return `cwd-${createHash("sha256").update(process.cwd()).digest("hex").slice(0, 16)}`;
2280
+ } catch {
2281
+ return INSTANCE_ID;
2282
+ }
2283
+ })();
2284
+ var envSession = (key) => {
2285
+ const v = process.env[key];
2286
+ return v && v.trim() ? v : void 0;
2287
+ };
2288
+ var SESSION_ID = envSession("PAIGY_SESSION_ID") ?? envSession("CLAUDE_CODE_SESSION_ID") ?? envSession("CODEX_THREAD_ID") ?? envSession("CODEX_SESSION_ID") ?? WORKSPACE_ID;
2278
2289
  function sessionId() {
2279
2290
  return SESSION_ID;
2280
2291
  }
@@ -3542,6 +3553,16 @@ var CreateGoalSchema = z.object({
3542
3553
  ownerParticipant: z.string().trim().min(1).optional(),
3543
3554
  idempotencyKey: z.string().trim().min(1).max(200)
3544
3555
  });
3556
+ var UpdateGoalSchema = z.object({
3557
+ revision: z.number().int().positive(),
3558
+ changes: z.object({
3559
+ outcome: z.string().trim().min(1).max(1e4).optional(),
3560
+ state: z.enum(["active", "waiting", "done", "cancelled"]).optional(),
3561
+ progress: z.string().trim().min(1).max(1e4).optional(),
3562
+ reviewed: z.literal(true).optional()
3563
+ }).refine((v) => Object.keys(v).length > 0),
3564
+ reason: z.string().trim().min(1).max(2e3)
3565
+ });
3545
3566
  var fmtMin = (m) => m >= 60 ? `${m / 60} hr` : `${m} min`;
3546
3567
  var STANDARD_MEANS = (() => {
3547
3568
  const plan = MISSED_CALL_PLAN.backoff_standard;
@@ -3989,6 +4010,7 @@ var AwaitItemSchema = z2.discriminatedUnion("type", [
3989
4010
  var CallbackTriggerSchema = z2.enum(["on_done", "on_blocked", "scheduled"]);
3990
4011
  var ScheduleCallbackSchema = z2.object({
3991
4012
  parentId: z2.string().describe("The thread to call back on (from a prior contact / reply / request)."),
4013
+ goalId: z2.string().uuid().optional().describe("The Goal this callback advances; preferred for Goal-owned work."),
3992
4014
  trigger: CallbackTriggerSchema,
3993
4015
  dueInSeconds: z2.number().int().positive().optional().describe("For 'scheduled' only: how many seconds from now to fire."),
3994
4016
  note: z2.string().optional().describe("What to tell the user when you follow up.")
@@ -4433,6 +4455,15 @@ var AgentActivitySchema = z2.object({
4433
4455
  var ConnectionSummarySchema = z2.object({
4434
4456
  /** The connection = the agent's token id (used to address a request). */
4435
4457
  id: z2.string(),
4458
+ /** The credential kind: "device" = a paired machine (mint-only — it hosts and mints, it
4459
+ * never talks); "agent" = an identity that sends. The roster and devices surfaces split
4460
+ * on this. Optional/absent reads as "agent" (a row predating the kind column). See
4461
+ * apps/api/src/tokens/devices-vs-agents-design.md. */
4462
+ kind: z2.enum(["device", "agent"]).optional(),
4463
+ /** For an agent, the token id of the DEVICE that minted it — so agents group under their
4464
+ * machine, and revoking a device cascades to them. Null on devices, and on unlinked
4465
+ * agents (phone-launched, provider-managed, or minted before the link existed). */
4466
+ mintedByDevice: z2.string().nullable().optional(),
4436
4467
  device: z2.string().nullable(),
4437
4468
  /** The agent's display name (the single pairing name). */
4438
4469
  name: z2.string(),
@@ -5140,7 +5171,7 @@ function subagentThread(env = process.env, sessionId2) {
5140
5171
  return sub ? `${base}:sub:${sub}` : base;
5141
5172
  }
5142
5173
  function uuidFromKey(key) {
5143
- const h = createHash("sha1").update(`paigy-thread:${key}`).digest();
5174
+ const h = createHash2("sha1").update(`paigy-thread:${key}`).digest();
5144
5175
  h[6] = h[6] & 15 | 80;
5145
5176
  h[8] = h[8] & 63 | 128;
5146
5177
  const hex = h.subarray(0, 16).toString("hex");
@@ -5424,6 +5455,18 @@ async function createGoal(input, opts = {}) {
5424
5455
  if (!res.ok) throw new Error(`create_goal failed: ${res.status} ${await res.text()}`);
5425
5456
  return await res.json();
5426
5457
  }
5458
+ async function claimGoal(goalId, opts = {}) {
5459
+ const token = authToken(opts.token) ?? "";
5460
+ const res = ensureAuthed(await reach(`${BACKEND_URL}/api/goals/${encodeURIComponent(goalId)}/claim`, { method: "POST", headers: { authorization: `Bearer ${token}` } }));
5461
+ if (!res.ok) throw new Error(`claim_goal failed: ${res.status} ${await res.text()}`);
5462
+ return await res.json();
5463
+ }
5464
+ async function updateGoal(goalId, input, opts = {}) {
5465
+ const token = authToken(opts.token) ?? "";
5466
+ const res = ensureAuthed(await reach(`${BACKEND_URL}/api/goals/${encodeURIComponent(goalId)}`, { method: "PATCH", headers: { "content-type": "application/json", authorization: `Bearer ${token}` }, body: JSON.stringify(input) }));
5467
+ if (!res.ok) throw new Error(`update_goal failed: ${res.status} ${await res.text()}`);
5468
+ return await res.json();
5469
+ }
5427
5470
  async function submitNotification(req, opts = {}) {
5428
5471
  const token = authToken(opts.token) ?? "";
5429
5472
  const body = await sealForE2ee(req, token);
@@ -5774,6 +5817,7 @@ export {
5774
5817
  NotifyRequestSchema,
5775
5818
  SetWorkStateSchema,
5776
5819
  ScheduleCallbackSchema,
5820
+ sessionSlot,
5777
5821
  contactParentId,
5778
5822
  AGENT_NAME,
5779
5823
  TOKEN_PATH,
@@ -5794,6 +5838,8 @@ export {
5794
5838
  pairStep,
5795
5839
  UnpairedError,
5796
5840
  createGoal,
5841
+ claimGoal,
5842
+ updateGoal,
5797
5843
  submitNotification,
5798
5844
  getThread,
5799
5845
  searchThreads,
package/dist/enable.js CHANGED
@@ -2,9 +2,9 @@
2
2
  import {
3
3
  PAIGY_TOOL_IDS,
4
4
  enablePaigyTools
5
- } from "./chunk-XIPO3U6I.js";
6
- import "./chunk-WNE6STMA.js";
7
- import "./chunk-PRC6GKRN.js";
5
+ } from "./chunk-7IGGSB3H.js";
6
+ import "./chunk-KE63GZEQ.js";
7
+ import "./chunk-PPACGOTU.js";
8
8
 
9
9
  // src/enable.ts
10
10
  function main() {
package/dist/index.js CHANGED
@@ -9,10 +9,11 @@ import {
9
9
  resolvePairing,
10
10
  startPairing,
11
11
  suggestedAgentName
12
- } from "./chunk-VWVZLPZD.js";
12
+ } from "./chunk-4UEL7Y5U.js";
13
13
  import {
14
14
  AnswerCallerQuestionSchema,
15
15
  AwaitReplySchema,
16
+ ClaimGoalSchema,
16
17
  ENABLE_COMMAND,
17
18
  GetThreadSchema,
18
19
  OnboardSchema,
@@ -22,14 +23,15 @@ import {
22
23
  SearchThreadsSchema,
23
24
  SetTaskStateToolSchema,
24
25
  TOOLS,
26
+ UpdateGoalToolSchema,
25
27
  autoConfigureClients,
26
28
  claudeInstallHint,
27
29
  paigyToolsAllowlisted
28
- } from "./chunk-XIPO3U6I.js";
30
+ } from "./chunk-7IGGSB3H.js";
29
31
  import {
30
32
  CreateGoalSchema,
31
33
  HandoffSchema
32
- } from "./chunk-WNE6STMA.js";
34
+ } from "./chunk-KE63GZEQ.js";
33
35
  import {
34
36
  clearSurface,
35
37
  writeSurface
@@ -43,6 +45,7 @@ import {
43
45
  answerCallerQuestion,
44
46
  awaitReply2,
45
47
  checkReplies,
48
+ claimGoal,
46
49
  contactParentId,
47
50
  createGoal,
48
51
  deleteKeyFile,
@@ -63,8 +66,9 @@ import {
63
66
  setWorkState,
64
67
  slotName,
65
68
  submitNotification,
69
+ updateGoal,
66
70
  whoAmI
67
- } from "./chunk-PRC6GKRN.js";
71
+ } from "./chunk-PPACGOTU.js";
68
72
 
69
73
  // src/index.ts
70
74
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -189,6 +193,7 @@ function pairStartResult(start, loggedOut) {
189
193
  qr.make();
190
194
  const link = start.verificationUri;
191
195
  const ascii = qr.createASCII(1, 2);
196
+ const door = "in Paigy \u2192 Devices \u2192 Connect a computer";
192
197
  const userMessage = loggedOut ? `**Your machine was logged out.** Reconnect and every agent here pairs itself again \u2014 no more codes.
193
198
 
194
199
  **[Reconnect this machine](${link})**
@@ -201,14 +206,17 @@ Or scan:
201
206
  ${ascii}
202
207
  \`\`\`
203
208
 
204
- Or enter code **${start.userCode}** in the Paigy app (Inbox \u2192 Add a new agent).` : `**[Approve this agent](${link})**
209
+ Or type **${start.userCode}** ${door}.` : `**Approve this agent from your phone**
205
210
 
206
- Or open: ${link}
207
- Or enter code **${start.userCode}** in the Paigy app (Inbox \u2192 Add a new agent).`;
211
+ **[Open](${link})** ${link}
212
+
213
+ Or scan the QR, or type **${start.userCode}** ${door}.
214
+
215
+ This session isn't on a connected computer, so it asks by code.`;
208
216
  return { content: [
209
217
  { type: "text", text: loggedOut ? `YOUR MACHINE WAS LOGGED OUT \u2014 reconnect: ${link}
210
- (or scan the QR, or enter code ${start.userCode} in the app)` : `APPROVE: ${link}
211
- (or code ${start.userCode} in the app: Inbox \u2192 Add a new agent)` },
218
+ (or scan the QR, or type ${start.userCode} ${door})` : `APPROVE THIS AGENT FROM YOUR PHONE: ${link}
219
+ (or scan the QR, or type ${start.userCode} ${door})` },
212
220
  { type: "text", text: JSON.stringify({
213
221
  status: "awaiting_approval",
214
222
  verification_uri_complete: link,
@@ -443,6 +451,14 @@ async function handleTool(request, signal) {
443
451
  const result = await createGoal(CreateGoalSchema.parse(request.params.arguments));
444
452
  return { content: [{ type: "text", text: JSON.stringify(result) }] };
445
453
  }
454
+ case "claim_goal": {
455
+ const { goalId } = ClaimGoalSchema.parse(request.params.arguments);
456
+ return { content: [{ type: "text", text: JSON.stringify(await claimGoal(goalId)) }] };
457
+ }
458
+ case "update_goal": {
459
+ const { goalId, ...input } = UpdateGoalToolSchema.parse(request.params.arguments);
460
+ return { content: [{ type: "text", text: JSON.stringify(await updateGoal(goalId, input)) }] };
461
+ }
446
462
  case "schedule_callback": {
447
463
  const result = await scheduleCallback(ScheduleCallbackSchema.parse(request.params.arguments));
448
464
  return { content: [{ type: "text", text: JSON.stringify(result) }] };
package/dist/listen.js CHANGED
@@ -5,14 +5,14 @@ import {
5
5
  import {
6
6
  WAKE_EVENT,
7
7
  wakeChannel
8
- } from "./chunk-WNE6STMA.js";
8
+ } from "./chunk-KE63GZEQ.js";
9
9
  import {
10
10
  ackReply,
11
11
  authToken,
12
12
  decodeReply,
13
13
  registerDelivery,
14
14
  sweepLeased
15
- } from "./chunk-PRC6GKRN.js";
15
+ } from "./chunk-PPACGOTU.js";
16
16
 
17
17
  // src/listen.ts
18
18
  import { createClient } from "@supabase/supabase-js";
package/dist/onboard.js CHANGED
@@ -3,13 +3,13 @@ import {
3
3
  resolvePairing,
4
4
  startPairing,
5
5
  suggestedAgentName
6
- } from "./chunk-VWVZLPZD.js";
6
+ } from "./chunk-4UEL7Y5U.js";
7
7
  import {
8
8
  autoConfigureClients,
9
9
  claudeInstallHint,
10
10
  openBrowser
11
- } from "./chunk-XIPO3U6I.js";
12
- import "./chunk-WNE6STMA.js";
11
+ } from "./chunk-7IGGSB3H.js";
12
+ import "./chunk-KE63GZEQ.js";
13
13
  import {
14
14
  AGENT_NAME,
15
15
  TOKEN_PATH,
@@ -20,7 +20,7 @@ import {
20
20
  saveToken,
21
21
  setIdentity,
22
22
  whoAmI
23
- } from "./chunk-PRC6GKRN.js";
23
+ } from "./chunk-PPACGOTU.js";
24
24
 
25
25
  // src/onboard.ts
26
26
  function reportRegistered() {
package/dist/slot.js ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ AGENT_NAME
4
+ } from "./chunk-PPACGOTU.js";
5
+
6
+ // src/slot.ts
7
+ process.stdout.write(AGENT_NAME);
@@ -4,9 +4,12 @@ import {
4
4
  } from "./chunk-3SVNPJPN.js";
5
5
  import {
6
6
  BACKEND_URL,
7
+ listSlots,
7
8
  reach,
8
- readToken
9
- } from "./chunk-PRC6GKRN.js";
9
+ readToken,
10
+ sessionSlot,
11
+ slotName
12
+ } from "./chunk-PPACGOTU.js";
10
13
 
11
14
  // src/statusline.ts
12
15
  import { mkdirSync, readFileSync, realpathSync, writeFileSync } from "fs";
@@ -21,10 +24,43 @@ var MODE_LABEL = {
21
24
  all_calls: "all calls",
22
25
  silent: "silent"
23
26
  };
24
- function render(status, surface = null) {
25
- if (surface) return `paigy \u26A0 ${surface.label}: ${surface.code}`;
26
- if (!status) return "paigy: unpaired";
27
- return `paigy: ${MODE_LABEL[status.sessionMode]} \xB7 ${status.phone ? "phone \u2713" : "no phone"}`;
27
+ function render(v) {
28
+ switch (v.kind) {
29
+ case "surface":
30
+ return `paigy \u26A0 ${v.label}: ${v.code}`;
31
+ case "unpaired":
32
+ return "paigy: unpaired";
33
+ case "device-only":
34
+ return "paigy: paired \xB7 session hatches on first use";
35
+ case "session": {
36
+ const who = v.name ? `${v.name} \xB7 ` : "";
37
+ if (!v.status) return `paigy: ${who}revoked`;
38
+ return `paigy: ${who}${MODE_LABEL[v.status.sessionMode]} \xB7 ${v.status.phone ? "phone \u2713" : "no phone"}`;
39
+ }
40
+ }
41
+ }
42
+ async function stdinSessionId(input = process.stdin) {
43
+ if (input.isTTY) return void 0;
44
+ try {
45
+ const raw = await new Promise((resolve) => {
46
+ let buf = "";
47
+ const done = () => resolve(buf);
48
+ input.setEncoding("utf8");
49
+ input.on("data", (c) => {
50
+ buf += c;
51
+ });
52
+ input.on("end", done);
53
+ input.on("error", done);
54
+ setTimeout(done, 300).unref();
55
+ });
56
+ const sid = JSON.parse(raw).session_id;
57
+ return typeof sid === "string" && sid.trim() ? sid : void 0;
58
+ } catch {
59
+ return void 0;
60
+ }
61
+ }
62
+ function resolveSlot(sid) {
63
+ return process.env.PAIGY_AGENT || sessionSlot(sid);
28
64
  }
29
65
  function readCache() {
30
66
  try {
@@ -49,18 +85,22 @@ async function fetchStatus(token) {
49
85
  }
50
86
  async function main() {
51
87
  const surface = readSurface();
52
- if (surface) return render(null, surface);
53
- const token = readToken();
54
- if (!token) return render(null);
88
+ if (surface) return render({ kind: "surface", ...surface });
89
+ const slot = resolveSlot(await stdinSessionId());
90
+ const token = readToken(slot);
91
+ if (!token) {
92
+ return render(listSlots().includes("Desktop") ? { kind: "device-only" } : { kind: "unpaired" });
93
+ }
94
+ const name = slotName(slot);
55
95
  const cached = readCache();
56
- if (cached && Date.now() - cached.at < TTL_MS) return render(cached.status);
96
+ if (cached && Date.now() - cached.at < TTL_MS) return render({ kind: "session", name, status: cached.status });
57
97
  try {
58
98
  const status = await fetchStatus(token);
59
99
  mkdirSync(DIR, { recursive: true });
60
100
  writeFileSync(CACHE, JSON.stringify({ at: Date.now(), status }));
61
- return render(status);
101
+ return render({ kind: "session", name, status });
62
102
  } catch {
63
- return cached ? render(cached.status) : "paigy: \u2026";
103
+ return cached ? render({ kind: "session", name, status: cached.status }) : "paigy: \u2026";
64
104
  }
65
105
  }
66
106
  function isEntry() {
@@ -75,5 +115,7 @@ function isEntry() {
75
115
  if (isEntry()) void main().then((line) => console.log(line));
76
116
  export {
77
117
  main,
78
- render
118
+ render,
119
+ resolveSlot,
120
+ stdinSessionId
79
121
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paigy/mcp",
3
- "version": "0.36.0",
3
+ "version": "0.38.0",
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",
@@ -10,7 +10,8 @@
10
10
  "paigy-mcp-onboard": "dist/onboard.js",
11
11
  "paigy-listen": "dist/listen.js",
12
12
  "paigy-statusline": "dist/statusline.js",
13
- "paigy-enable-tools": "dist/enable.js"
13
+ "paigy-enable-tools": "dist/enable.js",
14
+ "paigy-slot": "dist/slot.js"
14
15
  },
15
16
  "files": [
16
17
  "dist"