@paigy/mcp 0.40.22 → 0.40.23

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.
@@ -6,7 +6,7 @@ import {
6
6
  saveToken,
7
7
  setIdentity,
8
8
  sleep
9
- } from "./chunk-KSXJCZ2L.js";
9
+ } from "./chunk-PR7ROBN4.js";
10
10
 
11
11
  // src/identity.ts
12
12
  var CLIENT_LABELS = {
@@ -4,10 +4,11 @@ import {
4
4
  serverInstructions,
5
5
  sessionStartHook,
6
6
  withSessionStartHook
7
- } from "./chunk-WDZ67U4G.js";
7
+ } from "./chunk-JLRBZZNT.js";
8
8
  import {
9
+ CODEX_ENV,
9
10
  agentName
10
- } from "./chunk-KSXJCZ2L.js";
11
+ } from "./chunk-PR7ROBN4.js";
11
12
 
12
13
  // src/toolset.ts
13
14
  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.";
@@ -94,6 +95,7 @@ function withPaigyMcpToml(existing, agent) {
94
95
  "[mcp_servers.paigy]",
95
96
  'command = "npx"',
96
97
  'args = ["-y", "@paigy/mcp@latest"]',
98
+ `env_vars = [${CODEX_ENV.map((name) => JSON.stringify(name)).join(", ")}]`,
97
99
  `env = { PAIGY_AGENT = "${agent}" }`,
98
100
  ""
99
101
  ].join("\n");
@@ -60,15 +60,13 @@ var AskInputSchema = z2.object({
60
60
  "ONE question, and only what is needed to answer it. Several questions are several asks in the array, one each: an answer settles the one ask it was given in the shape that ask declares, so a person who answers the part of a bundled ask that interested them settles nothing and is asked again. Paigy reads every ask once and takes a bundled one apart into its own cards anyway \u2014 each question a Question on the one Goal the ask lands on, never a Goal of its own \u2014 but the words it splits are its reading, not yours. News, progress and findings are their own contact; ANY contact for a person already on a call JOINS that call, whatever channel you asked for, so several arrive as one call."
61
61
  ),
62
62
  options: z2.array(OptionInputSchema).min(1).max(6).optional(),
63
- // A CHECKLIST WHEN THE CHOICES ARE INDEPENDENT (owner, 2026-10-02 01:44 UTC: "bring back the
64
- // checklist mode of answering"). The answer shape was taken off this surface with the wire form
65
- // (#575) on the promise that Paigy would derive it, and the Goal-scoped contact that replaced it
66
- // only ever sends pick-one or words (`goal/intake.ts` `answerOf`): three independent fixes went out
67
- // as "All three / 1 and 3 only / Just log it", which the person could not read without the text and
68
- // could not answer as the ticks he wanted. "many" is the one shape an agent knows and the read does
69
- // not: whether its own options exclude each other.
63
+ // YOUR VIEW OF ONE OR MANY, A HINT TO PAIGY'S READ (owner, 2026-10-02: "one and many makes sense...
64
+ // make sure the brain that formats is aware of how it should work"). Three independent fixes went
65
+ // out as "All three / 1 and 3 only / Just log it" because every card was pick-one. #2943 made this
66
+ // field override the read; no agent sent it, so nothing changed. The read now decides each card's
67
+ // shape (`goal/intake.ts` `answerOf`), and this reaches it as the agent's view, weighed, not obeyed.
70
68
  select: z2.enum(["one", "many"]).optional().describe(
71
- 'How the options are answered: "one" (default) to pick one, "many" to tick any number of them. Use "many" when the options are independent things they may want several of \u2014 never combinations of them like "all three" or "1 and 3". Ignored without options.'
69
+ `Your view of how the options are answered: "one" to pick one, "many" to tick any number of them. A hint: Paigy's read decides each card's shape, a checklist when the options can all be wanted at once, a pick when choosing one rules out the others. The person can always answer in their own words as well. Ignored without options.`
72
70
  ),
73
71
  answers: z2.string().trim().regex(/^[0-9a-fA-F-]{8,36}$/).optional().describe(
74
72
  "Your reply answers a question the person asked you: the id the conversation shows for it (8 characters or whole). Their question closes with this reply as its answer, and any decision of yours it was holding back goes back to them. Needs parentId: the Goal the question is on. The reply is sent to them as it is; to also ask something new, send that as its own ask."
@@ -81,7 +79,7 @@ var StartContactSchema = z2.object({
81
79
  }).strict();
82
80
  var ContactSchema = z2.union([StartContactSchema, z2.object({ deliveryId: z2.string().uuid() }).strict()]);
83
81
  var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
84
- var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'select', '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}. One question per 'ask'; several questions are several objects in 'asks'. A bundled ask is taken apart into its own cards by Paigy's one intake read, every one on the Goal the ask lands on; separate pieces of work are separate asks. An ask with no options that is not blocking is a report, and a report is an UPDATE, never a claim on them: it reaches them and is listed apart from what waits on them, so no answer is owed and none should be awaited. On a Goal whose report card is still open it is added to that card, with no new push; one that only says where your work stands while under way is recorded as the Goal's progress, not sent (the result says so). Send options (or waiting:'hard') when you actually need an answer. Each option's label must stand on its own \u2014 the person may see only the labels \u2014 so never a label that points into your text ('All three', 'Option 2', '1 and 3 only'); when the options are independent and they may want several, pass select:'many' and they get a checklist. If the person is already on a call, your contact joins that call automatically \u2014 whatever channel you asked for, with no ring \u2014 and the Delivery it returns IS that call: reread it with contact({deliveryId}) to see everything answered on it so far, and contact again while it is live to add information or a further question to the same call.";
82
+ var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'select', '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}. One question per 'ask'; several questions are several objects in 'asks'. A bundled ask is taken apart into its own cards by Paigy's one intake read, every one on the Goal the ask lands on; separate pieces of work are separate asks. An ask with no options that is not blocking is a report, and a report is an UPDATE, never a claim on them: it reaches them and is listed apart from what waits on them, so no answer is owed and none should be awaited. On a Goal whose report card is still open it is added to that card, with no new push; one that only says where your work stands while under way is recorded as the Goal's progress, not sent (the result says so). Send options (or waiting:'hard') when you actually need an answer. Each option's label must stand on its own \u2014 the person may see only the labels \u2014 so never a label that points into your text ('All three', 'Option 2', '1 and 3 only'). Whether a card is a checklist or a pick is decided by Paigy's read: independent options they may want several of become a checklist, alternatives a pick. select:'many' or select:'one' tells it your view, a hint it weighs. The person can always answer in their own words, so never add an 'Other' option. If the person is already on a call, your contact joins that call automatically \u2014 whatever channel you asked for, with no ring \u2014 and the Delivery it returns IS that call: reread it with contact({deliveryId}) to see everything answered on it so far, and contact again while it is live to add information or a further question to the same call.";
85
83
  var CreateGoalSchema = z3.object({
86
84
  outcome: z3.string().trim().min(1).max(1e4),
87
85
  /** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
@@ -104,7 +102,7 @@ var CreateGoalSchema = z3.object({
104
102
  var CreateGoalToolSchema = CreateGoalSchema.extend({
105
103
  idempotencyKey: CreateGoalSchema.shape.idempotencyKey.optional().describe("Optional. One is minted per call; pass your own only so a retry lands on the same Goal.")
106
104
  }).strict();
107
- var CREATE_GOAL_DESCRIPTION = 'Create a durable Goal for an outcome. Without parentGoalId it is placed against your open Goals: if one already IS this work, that Goal comes back (existing: true) and nothing new is created \u2014 continue it; if the work belongs under one, it is created there (parentGoalId in the receipt); otherwise it is a root. Pass parentGoalId yourself to put it under a specific Goal. Pass repo to anchor the work to a specific repository ("owner/repo" or repo name); delegated children inherit it. Pass title to name it in one to five words, as a person would refer to it out loud ("the night rings") \u2014 it heads every list and is spoken on a call; without one the brain writes it. Admission only: the owner must claim it before doing work, then update it as it advances. Returns an admission receipt with goalId, current state, revision, ownerParticipant, and the next step; no Goal content or execution lease.';
105
+ var CREATE_GOAL_DESCRIPTION = 'Create a durable Goal for an outcome. Without parentGoalId it is placed against your open Goals: if one already IS this work, that Goal comes back (existing: true) and nothing new is created \u2014 continue it; if the work belongs under one, it is created there (parentGoalId in the receipt); otherwise it is a root. Pass parentGoalId yourself to put it under a specific Goal. Pass repo to anchor the work to a specific repository ("owner/repo" or repo name); delegated children inherit it. Pass title to name it in one to five words, as a person would refer to it out loud ("the night rings") \u2014 it heads every list and is spoken on a call; without one the brain writes it. Admission only: claim it before doing work, then update it as it advances. Returns an admission receipt with goalId, current state, revision, ownerParticipant, and the next step; no Goal content or execution lease.';
108
106
  var UpdateGoalSchema = z3.object({
109
107
  revision: z3.number().int().positive(),
110
108
  changes: z3.object({
@@ -140,12 +138,13 @@ var GetGoalSchema = z3.object({
140
138
  * between two of them named. Off by default; `docs/model/goal/diagnose-design.md`. */
141
139
  diagnose: z3.boolean().optional()
142
140
  }).strict();
143
- var GET_GOAL_DESCRIPTION = "Read one Goal without claiming it: its outcome, state, revision, progress, blockers, the conversation on it (each question with its options and what was decided), `next`, the one step to take, and `more`, where to read further. A question in that conversation reads `open` (nothing yet), `answered` (a choice was made, and `answer` carries it), `replied` (they said something and the read settled the question on their words \u2014 NO option of yours was chosen, and the words are the reply line beside it, so read that before you act), or `closed`. The conversation carries everything the person said, every open question and your newest entry; your older entries are left out and counted \u2014 history: true reads every entry in full. Another account's Goals, and another agent's Goal that is not below one of yours, are not disclosed.\n\ndiagnose: true is for when a read has CONFUSED you \u2014 not for the working loop. It adds `diagnosis`, which asks every reader that already answers a question about your work and NAMES the reader behind every value, so you never guess which of two places to look: what state your work is really in (the stored `goals.state` beside the derived `goal_execution_state`, which can differ); whether anything is armed to ring and when (`ladder_candidates`, with the ladder's own last decision); whether a wake fired for you and what it did (durable `wake.*` events, beside when your process was last heard from); and what has reached you (`list_waiting` \u2014 review flags, unstarted work, open questions, the person's newest words). Read `diagnosis.disagreements` FIRST: each one is two readers giving different values for the same fact, with both values and both sources, and it never chooses between them \u2014 that is yours to do, from the Goal's own history. `diagnosis.looked` names every reader asked, including any that could not answer, so an empty answer is never confused with a broken one.";
144
- var UPDATE_GOAL_DESCRIPTION = `Update an owned Goal at an exact revision. State, ownership, dependencies, children, progress, title, and review acknowledgement are explicit; stale revisions are rejected. A CONTACT ON THIS GOAL MOVES ITS REVISION: a question filed on a Goal is a change to it, so an update prepared before a contact and sent after it is refused as stale (409 goal_revision_conflict) \u2014 re-read the Goal, then write. title is the work's name in one to five words, as a person would refer to it out loud (it is spoken on a call and heads every list); null clears it. reviewed: true acknowledges new evidence and closes the Deliveries addressed to you on that Goal, never over an open decision. dueAt (an ISO instant, or null) makes the Goal wait until then; when it passes you are woken for it \u2014 use it for a promise to follow up later. withdraw: [questionId] takes back a question YOU asked on this Goal that is still open \u2014 because you acted on it yourself, it no longer matters, or you asked it wrongly (e.g. waiting: hard when nothing was blocked): it is cancelled, not answered, its card closes and it stops ringing; the id is the one the conversation shows. state: done means the work was accomplished, and it can be reopened: when the person says it is not done ("it's not working"), set state: active on that same Goal instead of starting a new one; its parent reopens with it. state: done while children are still open records your part as done: the Goal waits and closes by itself when its last open child closes. cancelled is final. Returns the Goal as get_goal reads it, at its new revision.`;
145
- var CLAIM_GOAL_DESCRIPTION = "Claim the oldest runnable or review-pending Goal you own, or pass goalId to claim that Goal. Another agent's Goal that has gone quiet for 3 days (check_replies lists them as stalledOthers) is taken over when you claim it, and becomes yours to finish or cancel. Returns the Goal as get_goal reads it, and creates or renews the execution lease.";
141
+ var GET_GOAL_DESCRIPTION = "Read one Goal without claiming it, including others (the ten most recent other contributors, with names, latest entry headlines and times): its outcome, state, revision, progress, blockers, the conversation on it (each question with its options and what was decided), `next`, the one step to take, and `more`, where to read further. A question in that conversation reads `open` (nothing yet), `answered` (a choice was made, and `answer` carries it), `replied` (they said something and the read settled the question on their words \u2014 NO option of yours was chosen, and the words are the reply line beside it, so read that before you act), or `closed`. The conversation carries everything the person said, every open question and your newest entry; your older entries are left out and counted \u2014 history: true reads every entry in full. Every Goal of your person is readable, whichever of their agents owns it; another account's Goals are not disclosed.\n\ndiagnose: true is for when a read has CONFUSED you \u2014 not for the working loop. It adds `diagnosis`, which asks every reader that already answers a question about your work and NAMES the reader behind every value, so you never guess which of two places to look: what state your work is really in (the stored `goals.state` beside the derived `goal_execution_state`, which can differ); whether anything is armed to ring and when (`ladder_candidates`, with the ladder's own last decision); whether a wake fired for you and what it did (durable `wake.*` events, beside when your process was last heard from); and what has reached you (`list_waiting` \u2014 review flags, unstarted work, open questions, the person's newest words). Read `diagnosis.disagreements` FIRST: each one is two readers giving different values for the same fact, with both values and both sources, and it never chooses between them \u2014 that is yours to do, from the Goal's own history. `diagnosis.looked` names every reader asked, including any that could not answer, so an empty answer is never confused with a broken one.";
142
+ var UPDATE_GOAL_DESCRIPTION = `Update a Goal you are on at an exact revision. You are on a Goal you own, or one you have written on (an update with progress, or a contact on it); any of your person's agents may write on any of their Goals, and one that never touched a Goal is refused (409 goal_not_joined). State, ownership, dependencies, children, progress, title, and review acknowledgement are explicit; stale revisions are rejected. A CONTACT ON THIS GOAL MOVES ITS REVISION: a question filed on a Goal is a change to it, so an update prepared before a contact and sent after it is refused as stale (409 goal_revision_conflict) \u2014 re-read the Goal, then write. title is the work's name in one to five words, as a person would refer to it out loud (it is spoken on a call and heads every list); null clears it. reviewed: true acknowledges new evidence and closes the Deliveries addressed to you on that Goal, never over an open decision. dueAt (an ISO instant, or null) makes the Goal wait until then; when it passes you are woken for it \u2014 use it for a promise to follow up later. withdraw: [questionId] takes back a question YOU asked on this Goal that is still open \u2014 because you acted on it yourself, it no longer matters, or you asked it wrongly (e.g. waiting: hard when nothing was blocked): it is cancelled, not answered, its card closes and it stops ringing; the id is the one the conversation shows. state: done means the work was accomplished, and it can be reopened: when the person says it is not done ("it's not working"), set state: active on that same Goal instead of starting a new one; its parent reopens with it. state: done while children are still open records your part as done: the Goal waits and closes by itself when its last open child closes. cancelled is final. Returns the Goal as get_goal reads it, at its new revision.`;
143
+ var CLAIM_GOAL_DESCRIPTION = "Claim a pending answer to your question or the oldest runnable or review-pending Goal you own. Pass goalId to join any Goal of your person; its assignment stays unchanged. Read others before overlapping another agent\u2019s work. Joining lets you contribute and change its state; use update_goal with ownerParticipant when the assignment itself should change. Returns the Goal as get_goal reads it, and marks you as on it, which never shuts another agent out: other agents of your person may write on it and change it too, and two changes at once are told apart by revision (409 goal_revision_conflict).";
146
144
  var CHECK_REPLIES_DESCRIPTION = "What is waiting for you: your open Deliveries (a request the user started toward you, a handoff), one row each with its Goals, how many decisions are still open, and the newest words in brief; `assigned`, your Goals nobody has started yet, however they became yours (claim_goal({goalId}) starts one); and `review`, your Goals with something new on them \u2014 the user's answers and notes are Entries on the Goal, not Deliveries. 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 a Delivery in full with contact({deliveryId}). Once you have acted on what arrived, update_goal with reviewed: true clears the Goal from `review` and closes the Deliveries addressed to you on it.";
147
145
  var CheckRepliesSchema = z3.object({}).strict();
148
146
  var AGENT_TOOLS = [
147
+ { name: "who_is_working", description: "Read your person's agents that contributed in the last 24 hours, newest first: at most 30 agents and their five most recently touched Goals, with their latest entry headline and time. Use get_goal to read a Goal and its other contributors before starting overlapping work. Read-only; no arguments.", inputSchema: mcpInputSchema(CheckRepliesSchema) },
149
148
  { name: "contact", description: CONTACT_DESCRIPTION, inputSchema: CONTACT_SCHEMA },
150
149
  { name: "check_replies", description: CHECK_REPLIES_DESCRIPTION, inputSchema: mcpInputSchema(CheckRepliesSchema) },
151
150
  { name: "create_goal", description: CREATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(CreateGoalToolSchema) },
@@ -156,7 +155,7 @@ var AGENT_TOOLS = [
156
155
  var AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
157
156
  function serverInstructions(opts) {
158
157
  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.";
159
- 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.
158
+ 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 Goal of your person is open to you, whichever of their agents owns it: read it with get_goal, write on it (contact with its id as parentId, or update_goal progress), and once you have written on it you may change it too. 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.
160
159
 
161
160
  HOW TO ASK:
162
161
  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.
@@ -165,7 +164,7 @@ HOW TO ASK:
165
164
  4. Never hold the process open with while-loops. For an open Call with a pending decision, follow its \`next\` with another bounded \`contact({deliveryId})\` tool call; a returned wait window is not a completed conversation. STOP REREADING when a window comes back with nothing new said \u2014 they are not typing, the question stays open, and its answer reaches you on the Goal. Otherwise, yield only with a working listener or scheduled wakeup, and collect answers with \`check_replies\` or \`claim_goal\` on that wake.
166
165
  5. One ask, one row. Never restate a question that is still waiting inside a new contact: keep waiting on the original, or the answer lands on one copy and the other stays open.
167
166
  6. Read the Goal's conversation before asking. Never ask again what was answered or already shipped \u2014 and tell its states apart: \`answered\` carries the choice in \`answer\`, while \`replied\` means they said something and chose none of your options, so that question is still yours to settle and their words are the line beside it.
168
- 7. A question carries its options. Without them it reaches the person as a bare title nobody can answer. Each option names its choice in full -- never \`All three\` or \`Option 2\` -- and options they may want several of are \`select: "many"\`, a checklist.
167
+ 7. A question carries its options. Without them it reaches the person as a bare title nobody can answer. Each option names its choice in full -- never \`All three\` or \`Option 2\`. Paigy's read decides whether a card is a checklist (options they may want several of) or a pick (one rules out the others); \`select: "many"\` or \`"one"\` is your view, a hint it weighs. They can always answer in their own words, so no "Other" option.
169
168
  8. A diagnosis says when, why and how it happens, then proposes one fix. Never options first.`;
170
169
  }
171
170
  function entryWords(entry) {
@@ -184,7 +183,7 @@ function entryWords(entry) {
184
183
  return entry.sources.map((source) => source.text).join("\n");
185
184
  }
186
185
  var LIVE_MS = 3 * 6e4;
187
- var WORKING_MS = 30 * 6e4;
186
+ var WORKING_MS = 60 * 6e4;
188
187
  var ContextSchema = z4.object({
189
188
  title: z4.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
190
189
  description: z4.array(z4.string().min(1)).describe(
@@ -536,7 +535,13 @@ var AgendaTurnSchema = z4.object({
536
535
  /** Whether the walk WAITS for an answer before moving on. Absent = derived as today
537
536
  * (a question blocks, context flows). blocking:false on a question = ask and move
538
537
  * on, the claim stays pending; blocking:true on context = hold for a reply. */
539
- blocking: z4.boolean().optional()
538
+ blocking: z4.boolean().optional(),
539
+ /** SPOKEN ONLY IF THEY SAY NOTHING (owner, 2026-10-01, call 812de935: "you're gonna re-ask, but it
540
+ * shouldn't be the same words … more like, hey, are you still there, or are you able to answer, or
541
+ * would you need more information"). The walk holds this turn out of its queue; at the queue's end it
542
+ * listens for the last word, and only if that listen is silent is this turn said and asked. If they
543
+ * speak, it is dropped and their words are taken like any reply. */
544
+ ifSilent: z4.boolean().optional()
540
545
  });
541
546
  var CLAIM_STALE_MS = 30 * 6e4;
542
547
  var InboxItemSchema = z4.object({
@@ -22,6 +22,7 @@ import { execFileSync as execFileSync2 } from "child_process";
22
22
  import { randomUUID as randomUUID4 } from "crypto";
23
23
  import { WebSocket } from "undici";
24
24
  var require2 = __sdkCreateRequire(import.meta.url);
25
+ var CODEX_ENV = ["CODEX_THREAD_ID", "CODEX_SESSION_ID", "PAIGY_TOKEN", "PAIGY_SESSION_ID", "PAIGY_HARNESS", "PAIGY_INSTANCE_ID"];
25
26
  var BACKEND_URL = process.env.PAIGY_BACKEND_URL ?? "https://paigy.ai";
26
27
  var NETWORK_MSG = `Can't reach ${BACKEND_URL} \u2014 the connection was blocked or dropped before an HTTP response. If this agent runs in a sandboxed environment with a network allowlist (e.g. Claude Code on the web, CI), ask the user to add the Paigy domain (paigy.ai) to the environment's allowed domains, then retry.`;
27
28
  var PROXY_ENV = ["HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy"];
@@ -79,14 +80,14 @@ function isNetworkError(e) {
79
80
  async function reach(url, init) {
80
81
  return send(url, init, sessionId());
81
82
  }
82
- function reachAs(session) {
83
- return (url, init) => send(url, init, session);
83
+ function reachAs(session, instance) {
84
+ return (url, init) => send(url, init, session, instance);
84
85
  }
85
- async function send(url, init, session) {
86
+ async function send(url, init, session, instance = envSession("PAIGY_INSTANCE_ID") ?? INSTANCE_ID) {
86
87
  try {
87
88
  const headers = {
88
89
  ...init?.headers,
89
- "x-paigy-instance": INSTANCE_ID,
90
+ "x-paigy-instance": instance,
90
91
  "x-paigy-session": session
91
92
  };
92
93
  return await fetch(url, { ...init, headers, dispatcher: await proxy() });
@@ -1528,15 +1529,13 @@ var AskInputSchema = z2.object({
1528
1529
  "ONE question, and only what is needed to answer it. Several questions are several asks in the array, one each: an answer settles the one ask it was given in the shape that ask declares, so a person who answers the part of a bundled ask that interested them settles nothing and is asked again. Paigy reads every ask once and takes a bundled one apart into its own cards anyway \u2014 each question a Question on the one Goal the ask lands on, never a Goal of its own \u2014 but the words it splits are its reading, not yours. News, progress and findings are their own contact; ANY contact for a person already on a call JOINS that call, whatever channel you asked for, so several arrive as one call."
1529
1530
  ),
1530
1531
  options: z2.array(OptionInputSchema).min(1).max(6).optional(),
1531
- // A CHECKLIST WHEN THE CHOICES ARE INDEPENDENT (owner, 2026-10-02 01:44 UTC: "bring back the
1532
- // checklist mode of answering"). The answer shape was taken off this surface with the wire form
1533
- // (#575) on the promise that Paigy would derive it, and the Goal-scoped contact that replaced it
1534
- // only ever sends pick-one or words (`goal/intake.ts` `answerOf`): three independent fixes went out
1535
- // as "All three / 1 and 3 only / Just log it", which the person could not read without the text and
1536
- // could not answer as the ticks he wanted. "many" is the one shape an agent knows and the read does
1537
- // not: whether its own options exclude each other.
1532
+ // YOUR VIEW OF ONE OR MANY, A HINT TO PAIGY'S READ (owner, 2026-10-02: "one and many makes sense...
1533
+ // make sure the brain that formats is aware of how it should work"). Three independent fixes went
1534
+ // out as "All three / 1 and 3 only / Just log it" because every card was pick-one. #2943 made this
1535
+ // field override the read; no agent sent it, so nothing changed. The read now decides each card's
1536
+ // shape (`goal/intake.ts` `answerOf`), and this reaches it as the agent's view, weighed, not obeyed.
1538
1537
  select: z2.enum(["one", "many"]).optional().describe(
1539
- 'How the options are answered: "one" (default) to pick one, "many" to tick any number of them. Use "many" when the options are independent things they may want several of \u2014 never combinations of them like "all three" or "1 and 3". Ignored without options.'
1538
+ `Your view of how the options are answered: "one" to pick one, "many" to tick any number of them. A hint: Paigy's read decides each card's shape, a checklist when the options can all be wanted at once, a pick when choosing one rules out the others. The person can always answer in their own words as well. Ignored without options.`
1540
1539
  ),
1541
1540
  answers: z2.string().trim().regex(/^[0-9a-fA-F-]{8,36}$/).optional().describe(
1542
1541
  "Your reply answers a question the person asked you: the id the conversation shows for it (8 characters or whole). Their question closes with this reply as its answer, and any decision of yours it was holding back goes back to them. Needs parentId: the Goal the question is on. The reply is sent to them as it is; to also ask something new, send that as its own ask."
@@ -1549,7 +1548,7 @@ var StartContactSchema = z2.object({
1549
1548
  }).strict();
1550
1549
  var ContactSchema = z2.union([StartContactSchema, z2.object({ deliveryId: z2.string().uuid() }).strict()]);
1551
1550
  var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
1552
- var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'select', '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}. One question per 'ask'; several questions are several objects in 'asks'. A bundled ask is taken apart into its own cards by Paigy's one intake read, every one on the Goal the ask lands on; separate pieces of work are separate asks. An ask with no options that is not blocking is a report, and a report is an UPDATE, never a claim on them: it reaches them and is listed apart from what waits on them, so no answer is owed and none should be awaited. On a Goal whose report card is still open it is added to that card, with no new push; one that only says where your work stands while under way is recorded as the Goal's progress, not sent (the result says so). Send options (or waiting:'hard') when you actually need an answer. Each option's label must stand on its own \u2014 the person may see only the labels \u2014 so never a label that points into your text ('All three', 'Option 2', '1 and 3 only'); when the options are independent and they may want several, pass select:'many' and they get a checklist. If the person is already on a call, your contact joins that call automatically \u2014 whatever channel you asked for, with no ring \u2014 and the Delivery it returns IS that call: reread it with contact({deliveryId}) to see everything answered on it so far, and contact again while it is live to add information or a further question to the same call.";
1551
+ var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'select', '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}. One question per 'ask'; several questions are several objects in 'asks'. A bundled ask is taken apart into its own cards by Paigy's one intake read, every one on the Goal the ask lands on; separate pieces of work are separate asks. An ask with no options that is not blocking is a report, and a report is an UPDATE, never a claim on them: it reaches them and is listed apart from what waits on them, so no answer is owed and none should be awaited. On a Goal whose report card is still open it is added to that card, with no new push; one that only says where your work stands while under way is recorded as the Goal's progress, not sent (the result says so). Send options (or waiting:'hard') when you actually need an answer. Each option's label must stand on its own \u2014 the person may see only the labels \u2014 so never a label that points into your text ('All three', 'Option 2', '1 and 3 only'). Whether a card is a checklist or a pick is decided by Paigy's read: independent options they may want several of become a checklist, alternatives a pick. select:'many' or select:'one' tells it your view, a hint it weighs. The person can always answer in their own words, so never add an 'Other' option. If the person is already on a call, your contact joins that call automatically \u2014 whatever channel you asked for, with no ring \u2014 and the Delivery it returns IS that call: reread it with contact({deliveryId}) to see everything answered on it so far, and contact again while it is live to add information or a further question to the same call.";
1553
1552
  var CreateGoalSchema = z3.object({
1554
1553
  outcome: z3.string().trim().min(1).max(1e4),
1555
1554
  /** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
@@ -1572,7 +1571,7 @@ var CreateGoalSchema = z3.object({
1572
1571
  var CreateGoalToolSchema = CreateGoalSchema.extend({
1573
1572
  idempotencyKey: CreateGoalSchema.shape.idempotencyKey.optional().describe("Optional. One is minted per call; pass your own only so a retry lands on the same Goal.")
1574
1573
  }).strict();
1575
- var CREATE_GOAL_DESCRIPTION = 'Create a durable Goal for an outcome. Without parentGoalId it is placed against your open Goals: if one already IS this work, that Goal comes back (existing: true) and nothing new is created \u2014 continue it; if the work belongs under one, it is created there (parentGoalId in the receipt); otherwise it is a root. Pass parentGoalId yourself to put it under a specific Goal. Pass repo to anchor the work to a specific repository ("owner/repo" or repo name); delegated children inherit it. Pass title to name it in one to five words, as a person would refer to it out loud ("the night rings") \u2014 it heads every list and is spoken on a call; without one the brain writes it. Admission only: the owner must claim it before doing work, then update it as it advances. Returns an admission receipt with goalId, current state, revision, ownerParticipant, and the next step; no Goal content or execution lease.';
1574
+ var CREATE_GOAL_DESCRIPTION = 'Create a durable Goal for an outcome. Without parentGoalId it is placed against your open Goals: if one already IS this work, that Goal comes back (existing: true) and nothing new is created \u2014 continue it; if the work belongs under one, it is created there (parentGoalId in the receipt); otherwise it is a root. Pass parentGoalId yourself to put it under a specific Goal. Pass repo to anchor the work to a specific repository ("owner/repo" or repo name); delegated children inherit it. Pass title to name it in one to five words, as a person would refer to it out loud ("the night rings") \u2014 it heads every list and is spoken on a call; without one the brain writes it. Admission only: claim it before doing work, then update it as it advances. Returns an admission receipt with goalId, current state, revision, ownerParticipant, and the next step; no Goal content or execution lease.';
1576
1575
  var UpdateGoalSchema = z3.object({
1577
1576
  revision: z3.number().int().positive(),
1578
1577
  changes: z3.object({
@@ -1608,12 +1607,13 @@ var GetGoalSchema = z3.object({
1608
1607
  * between two of them named. Off by default; `docs/model/goal/diagnose-design.md`. */
1609
1608
  diagnose: z3.boolean().optional()
1610
1609
  }).strict();
1611
- var GET_GOAL_DESCRIPTION = "Read one Goal without claiming it: its outcome, state, revision, progress, blockers, the conversation on it (each question with its options and what was decided), `next`, the one step to take, and `more`, where to read further. A question in that conversation reads `open` (nothing yet), `answered` (a choice was made, and `answer` carries it), `replied` (they said something and the read settled the question on their words \u2014 NO option of yours was chosen, and the words are the reply line beside it, so read that before you act), or `closed`. The conversation carries everything the person said, every open question and your newest entry; your older entries are left out and counted \u2014 history: true reads every entry in full. Another account's Goals, and another agent's Goal that is not below one of yours, are not disclosed.\n\ndiagnose: true is for when a read has CONFUSED you \u2014 not for the working loop. It adds `diagnosis`, which asks every reader that already answers a question about your work and NAMES the reader behind every value, so you never guess which of two places to look: what state your work is really in (the stored `goals.state` beside the derived `goal_execution_state`, which can differ); whether anything is armed to ring and when (`ladder_candidates`, with the ladder's own last decision); whether a wake fired for you and what it did (durable `wake.*` events, beside when your process was last heard from); and what has reached you (`list_waiting` \u2014 review flags, unstarted work, open questions, the person's newest words). Read `diagnosis.disagreements` FIRST: each one is two readers giving different values for the same fact, with both values and both sources, and it never chooses between them \u2014 that is yours to do, from the Goal's own history. `diagnosis.looked` names every reader asked, including any that could not answer, so an empty answer is never confused with a broken one.";
1612
- var UPDATE_GOAL_DESCRIPTION = `Update an owned Goal at an exact revision. State, ownership, dependencies, children, progress, title, and review acknowledgement are explicit; stale revisions are rejected. A CONTACT ON THIS GOAL MOVES ITS REVISION: a question filed on a Goal is a change to it, so an update prepared before a contact and sent after it is refused as stale (409 goal_revision_conflict) \u2014 re-read the Goal, then write. title is the work's name in one to five words, as a person would refer to it out loud (it is spoken on a call and heads every list); null clears it. reviewed: true acknowledges new evidence and closes the Deliveries addressed to you on that Goal, never over an open decision. dueAt (an ISO instant, or null) makes the Goal wait until then; when it passes you are woken for it \u2014 use it for a promise to follow up later. withdraw: [questionId] takes back a question YOU asked on this Goal that is still open \u2014 because you acted on it yourself, it no longer matters, or you asked it wrongly (e.g. waiting: hard when nothing was blocked): it is cancelled, not answered, its card closes and it stops ringing; the id is the one the conversation shows. state: done means the work was accomplished, and it can be reopened: when the person says it is not done ("it's not working"), set state: active on that same Goal instead of starting a new one; its parent reopens with it. state: done while children are still open records your part as done: the Goal waits and closes by itself when its last open child closes. cancelled is final. Returns the Goal as get_goal reads it, at its new revision.`;
1613
- var CLAIM_GOAL_DESCRIPTION = "Claim the oldest runnable or review-pending Goal you own, or pass goalId to claim that Goal. Another agent's Goal that has gone quiet for 3 days (check_replies lists them as stalledOthers) is taken over when you claim it, and becomes yours to finish or cancel. Returns the Goal as get_goal reads it, and creates or renews the execution lease.";
1610
+ var GET_GOAL_DESCRIPTION = "Read one Goal without claiming it, including others (the ten most recent other contributors, with names, latest entry headlines and times): its outcome, state, revision, progress, blockers, the conversation on it (each question with its options and what was decided), `next`, the one step to take, and `more`, where to read further. A question in that conversation reads `open` (nothing yet), `answered` (a choice was made, and `answer` carries it), `replied` (they said something and the read settled the question on their words \u2014 NO option of yours was chosen, and the words are the reply line beside it, so read that before you act), or `closed`. The conversation carries everything the person said, every open question and your newest entry; your older entries are left out and counted \u2014 history: true reads every entry in full. Every Goal of your person is readable, whichever of their agents owns it; another account's Goals are not disclosed.\n\ndiagnose: true is for when a read has CONFUSED you \u2014 not for the working loop. It adds `diagnosis`, which asks every reader that already answers a question about your work and NAMES the reader behind every value, so you never guess which of two places to look: what state your work is really in (the stored `goals.state` beside the derived `goal_execution_state`, which can differ); whether anything is armed to ring and when (`ladder_candidates`, with the ladder's own last decision); whether a wake fired for you and what it did (durable `wake.*` events, beside when your process was last heard from); and what has reached you (`list_waiting` \u2014 review flags, unstarted work, open questions, the person's newest words). Read `diagnosis.disagreements` FIRST: each one is two readers giving different values for the same fact, with both values and both sources, and it never chooses between them \u2014 that is yours to do, from the Goal's own history. `diagnosis.looked` names every reader asked, including any that could not answer, so an empty answer is never confused with a broken one.";
1611
+ var UPDATE_GOAL_DESCRIPTION = `Update a Goal you are on at an exact revision. You are on a Goal you own, or one you have written on (an update with progress, or a contact on it); any of your person's agents may write on any of their Goals, and one that never touched a Goal is refused (409 goal_not_joined). State, ownership, dependencies, children, progress, title, and review acknowledgement are explicit; stale revisions are rejected. A CONTACT ON THIS GOAL MOVES ITS REVISION: a question filed on a Goal is a change to it, so an update prepared before a contact and sent after it is refused as stale (409 goal_revision_conflict) \u2014 re-read the Goal, then write. title is the work's name in one to five words, as a person would refer to it out loud (it is spoken on a call and heads every list); null clears it. reviewed: true acknowledges new evidence and closes the Deliveries addressed to you on that Goal, never over an open decision. dueAt (an ISO instant, or null) makes the Goal wait until then; when it passes you are woken for it \u2014 use it for a promise to follow up later. withdraw: [questionId] takes back a question YOU asked on this Goal that is still open \u2014 because you acted on it yourself, it no longer matters, or you asked it wrongly (e.g. waiting: hard when nothing was blocked): it is cancelled, not answered, its card closes and it stops ringing; the id is the one the conversation shows. state: done means the work was accomplished, and it can be reopened: when the person says it is not done ("it's not working"), set state: active on that same Goal instead of starting a new one; its parent reopens with it. state: done while children are still open records your part as done: the Goal waits and closes by itself when its last open child closes. cancelled is final. Returns the Goal as get_goal reads it, at its new revision.`;
1612
+ var CLAIM_GOAL_DESCRIPTION = "Claim a pending answer to your question or the oldest runnable or review-pending Goal you own. Pass goalId to join any Goal of your person; its assignment stays unchanged. Read others before overlapping another agent\u2019s work. Joining lets you contribute and change its state; use update_goal with ownerParticipant when the assignment itself should change. Returns the Goal as get_goal reads it, and marks you as on it, which never shuts another agent out: other agents of your person may write on it and change it too, and two changes at once are told apart by revision (409 goal_revision_conflict).";
1614
1613
  var CHECK_REPLIES_DESCRIPTION = "What is waiting for you: your open Deliveries (a request the user started toward you, a handoff), one row each with its Goals, how many decisions are still open, and the newest words in brief; `assigned`, your Goals nobody has started yet, however they became yours (claim_goal({goalId}) starts one); and `review`, your Goals with something new on them \u2014 the user's answers and notes are Entries on the Goal, not Deliveries. 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 a Delivery in full with contact({deliveryId}). Once you have acted on what arrived, update_goal with reviewed: true clears the Goal from `review` and closes the Deliveries addressed to you on it.";
1615
1614
  var CheckRepliesSchema = z3.object({}).strict();
1616
1615
  var AGENT_TOOLS = [
1616
+ { name: "who_is_working", description: "Read your person's agents that contributed in the last 24 hours, newest first: at most 30 agents and their five most recently touched Goals, with their latest entry headline and time. Use get_goal to read a Goal and its other contributors before starting overlapping work. Read-only; no arguments.", inputSchema: mcpInputSchema(CheckRepliesSchema) },
1617
1617
  { name: "contact", description: CONTACT_DESCRIPTION, inputSchema: CONTACT_SCHEMA },
1618
1618
  { name: "check_replies", description: CHECK_REPLIES_DESCRIPTION, inputSchema: mcpInputSchema(CheckRepliesSchema) },
1619
1619
  { name: "create_goal", description: CREATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(CreateGoalToolSchema) },
@@ -1638,7 +1638,7 @@ function entryWords(entry) {
1638
1638
  return entry.sources.map((source) => source.text).join("\n");
1639
1639
  }
1640
1640
  var LIVE_MS = 3 * 6e4;
1641
- var WORKING_MS = 30 * 6e4;
1641
+ var WORKING_MS = 60 * 6e4;
1642
1642
  var ContextSchema = z4.object({
1643
1643
  title: z4.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
1644
1644
  description: z4.array(z4.string().min(1)).describe(
@@ -1997,7 +1997,13 @@ var AgendaTurnSchema = z4.object({
1997
1997
  /** Whether the walk WAITS for an answer before moving on. Absent = derived as today
1998
1998
  * (a question blocks, context flows). blocking:false on a question = ask and move
1999
1999
  * on, the claim stays pending; blocking:true on context = hold for a reply. */
2000
- blocking: z4.boolean().optional()
2000
+ blocking: z4.boolean().optional(),
2001
+ /** SPOKEN ONLY IF THEY SAY NOTHING (owner, 2026-10-01, call 812de935: "you're gonna re-ask, but it
2002
+ * shouldn't be the same words … more like, hey, are you still there, or are you able to answer, or
2003
+ * would you need more information"). The walk holds this turn out of its queue; at the queue's end it
2004
+ * listens for the last word, and only if that listen is silent is this turn said and asked. If they
2005
+ * speak, it is dropped and their words are taken like any reply. */
2006
+ ifSilent: z4.boolean().optional()
2001
2007
  });
2002
2008
  var CLAIM_STALE_MS = 30 * 6e4;
2003
2009
  var InboxItemSchema = z4.object({
@@ -2897,6 +2903,9 @@ function readToken(agent2 = agentName()) {
2897
2903
  if (process.env.PAIGY_TOKEN) return process.env.PAIGY_TOKEN;
2898
2904
  return readTokenFile()[agent2]?.access_token ?? "";
2899
2905
  }
2906
+ function readSlot(agent2) {
2907
+ return readTokenFile()[agent2]?.access_token ?? "";
2908
+ }
2900
2909
  function listSlots() {
2901
2910
  return Object.keys(readTokenFile());
2902
2911
  }
@@ -3027,11 +3036,16 @@ async function updateGoal(goalId, input, opts = {}) {
3027
3036
  return await res.json();
3028
3037
  }
3029
3038
  var AWAIT_WINDOW_MS = 45e3;
3030
- async function hatch(name, voice = null) {
3031
- const res = await ensureAuthed(await reach(`${BACKEND_URL}/api/hatch`, {
3039
+ async function hatch(name, voice = null, opts = {}) {
3040
+ const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/hatch`, {
3032
3041
  method: "POST",
3033
3042
  headers: { "content-type": "application/json", authorization: `Bearer ${authToken()}` },
3034
- body: JSON.stringify({ name, voice })
3043
+ body: JSON.stringify({
3044
+ name,
3045
+ voice,
3046
+ ...opts.brief ? { brief: opts.brief } : {},
3047
+ ...opts.parentGoalId ? { parentGoalId: opts.parentGoalId } : {}
3048
+ })
3035
3049
  }));
3036
3050
  if (!res.ok) throw new Error(`hatch failed: ${res.status} ${await res.text()}`);
3037
3051
  return await res.json();
@@ -3169,6 +3183,13 @@ async function dismissTriage(proposalId, opts = {}) {
3169
3183
  if (!res.ok) throw new Error(`dismiss_triage failed: ${res.status} ${await res.text()}`);
3170
3184
  return await res.json();
3171
3185
  }
3186
+ async function whoIsWorking(opts = {}) {
3187
+ const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/agents/working`, {
3188
+ headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}`, "x-paigy-model": "goal-entry-v1" }
3189
+ }));
3190
+ if (!res.ok) await fail("who_is_working", res);
3191
+ return await res.json();
3192
+ }
3172
3193
  function repoFromRemote(url) {
3173
3194
  const said = (url ?? "").trim();
3174
3195
  if (!said) return null;
@@ -3352,7 +3373,9 @@ function goalView(g) {
3352
3373
  outcome: g.outcome,
3353
3374
  progress: g.progress && g.progress.trim() !== newest?.said ? g.progress : void 0,
3354
3375
  reviewPending: g.reviewPending || void 0,
3376
+ reviewSince: g.reviewSince,
3355
3377
  owner: g.ownerParticipant,
3378
+ others: g.others,
3356
3379
  parentGoalId: g.parentGoalId,
3357
3380
  // Every child is in the subtree below, with its goalId: listed again, it was said twice.
3358
3381
  childGoalIds: g.subtree?.length ? void 0 : g.childGoalIds,
@@ -3429,7 +3452,7 @@ function repliesView(r) {
3429
3452
  // sentence names the person and quotes the freshest one, because a count is not a message.
3430
3453
  ...review.length ? [spoke(review) ?? `${review.length} of your Goals have something new (review): read each with claim_goal or get_goal, then update_goal with reviewed: true.`] : [],
3431
3454
  ...stalled.length ? [`${stalled.length} of your Goals have had no progress for 3 days: update_goal each with progress, finish it, or cancel it.`] : [],
3432
- ...others.length ? [`${others.length} of other agents' Goals have gone quiet for 3 days: claim_goal({goalId}) takes one over, then finish or cancel it with update_goal.`] : [],
3455
+ ...others.length ? [`${others.length} of other agents' Goals have gone quiet for 3 days: claim_goal({goalId}) joins one without changing its owner, then finish or cancel it with update_goal.`] : [],
3433
3456
  // No name of its own (#2525): the API's ask, verbatim.
3434
3457
  ...r.unnamed ? [r.unnamed] : []
3435
3458
  ].join(" ")
@@ -3450,6 +3473,9 @@ async function runTool(name, args, opts) {
3450
3473
  const sent = await contact(input, { ...client, signal, waits });
3451
3474
  return "sent" in sent ? sent : deliveryView(sent);
3452
3475
  }
3476
+ case "who_is_working":
3477
+ CheckRepliesSchema.parse(input);
3478
+ return whoIsWorking(client);
3453
3479
  case "check_replies":
3454
3480
  CheckRepliesSchema.parse(input);
3455
3481
  return repliesView(await checkReplies(client));
@@ -3655,6 +3681,7 @@ function machineZone() {
3655
3681
  }
3656
3682
 
3657
3683
  export {
3684
+ CODEX_ENV,
3658
3685
  BACKEND_URL,
3659
3686
  NETWORK_MSG,
3660
3687
  sessionId,
@@ -3674,6 +3701,7 @@ export {
3674
3701
  slotIdentity,
3675
3702
  sleep,
3676
3703
  readToken,
3704
+ readSlot,
3677
3705
  listSlots,
3678
3706
  slotName,
3679
3707
  deleteToken,
@@ -3706,6 +3734,7 @@ export {
3706
3734
  getTriage,
3707
3735
  acceptTriage,
3708
3736
  dismissTriage,
3737
+ whoIsWorking,
3709
3738
  repoFromRemote,
3710
3739
  currentRepo,
3711
3740
  contact,
@@ -4,6 +4,7 @@ import {
4
4
  AWAIT_WINDOW_MS,
5
5
  ApiError,
6
6
  BACKEND_URL,
7
+ CODEX_ENV,
7
8
  KEY_PATH,
8
9
  NETWORK_MSG,
9
10
  NameTakenError,
@@ -38,6 +39,7 @@ import {
38
39
  pollToken,
39
40
  reach,
40
41
  reachAs,
42
+ readSlot,
41
43
  readToken,
42
44
  recordDecision,
43
45
  registerDelivery,
@@ -56,14 +58,16 @@ import {
56
58
  subscribeWake,
57
59
  updateGoal,
58
60
  updateSlot,
59
- whoAmI
60
- } from "./chunk-KSXJCZ2L.js";
61
+ whoAmI,
62
+ whoIsWorking
63
+ } from "./chunk-PR7ROBN4.js";
61
64
  export {
62
65
  AGENT_TOOLS,
63
66
  AGENT_TOOL_NAMES,
64
67
  AWAIT_WINDOW_MS,
65
68
  ApiError,
66
69
  BACKEND_URL,
70
+ CODEX_ENV,
67
71
  KEY_PATH,
68
72
  NETWORK_MSG,
69
73
  NameTakenError,
@@ -98,6 +102,7 @@ export {
98
102
  pollToken,
99
103
  reach,
100
104
  reachAs,
105
+ readSlot,
101
106
  readToken,
102
107
  recordDecision,
103
108
  registerDelivery,
@@ -116,5 +121,6 @@ export {
116
121
  subscribeWake,
117
122
  updateGoal,
118
123
  updateSlot,
119
- whoAmI
124
+ whoAmI,
125
+ whoIsWorking
120
126
  };
package/dist/enable.js CHANGED
@@ -3,9 +3,9 @@ import {
3
3
  PAIGY_TOOL_IDS,
4
4
  enablePaigyTools,
5
5
  installSessionListening
6
- } from "./chunk-2ED4GL4N.js";
7
- import "./chunk-WDZ67U4G.js";
8
- import "./chunk-KSXJCZ2L.js";
6
+ } from "./chunk-HKODQKUV.js";
7
+ import "./chunk-JLRBZZNT.js";
8
+ import "./chunk-PR7ROBN4.js";
9
9
 
10
10
  // src/enable.ts
11
11
  function main() {
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  resolvePairing,
7
7
  startPairing,
8
8
  suggestedAgentName
9
- } from "./chunk-BFN2GJ42.js";
9
+ } from "./chunk-BNMMRKEY.js";
10
10
  import {
11
11
  ENABLE_COMMAND,
12
12
  OnboardSchema,
@@ -15,11 +15,11 @@ import {
15
15
  SERVER_INSTRUCTIONS,
16
16
  TOOLS,
17
17
  paigyToolsAllowlisted
18
- } from "./chunk-2ED4GL4N.js";
18
+ } from "./chunk-HKODQKUV.js";
19
19
  import {
20
20
  decideListen,
21
21
  listenerAlive
22
- } from "./chunk-WDZ67U4G.js";
22
+ } from "./chunk-JLRBZZNT.js";
23
23
  import {
24
24
  clearSurface,
25
25
  writeSurface
@@ -39,7 +39,7 @@ import {
39
39
  sessionId,
40
40
  slotName,
41
41
  whoAmI
42
- } from "./chunk-KSXJCZ2L.js";
42
+ } from "./chunk-PR7ROBN4.js";
43
43
 
44
44
  // src/index.ts
45
45
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
package/dist/listen.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  entryWords,
4
4
  removeListenMark,
5
5
  writeListenMark
6
- } from "./chunk-WDZ67U4G.js";
6
+ } from "./chunk-JLRBZZNT.js";
7
7
  import {
8
8
  agentName,
9
9
  checkReplies,
@@ -11,7 +11,7 @@ import {
11
11
  getGoal,
12
12
  slotName,
13
13
  subscribeWake
14
- } from "./chunk-KSXJCZ2L.js";
14
+ } from "./chunk-PR7ROBN4.js";
15
15
 
16
16
  // src/listen.ts
17
17
  import { spawn } from "child_process";
package/dist/onboard.js CHANGED
@@ -3,13 +3,13 @@ import {
3
3
  resolvePairing,
4
4
  startPairing,
5
5
  suggestedAgentName
6
- } from "./chunk-BFN2GJ42.js";
6
+ } from "./chunk-BNMMRKEY.js";
7
7
  import {
8
8
  autoConfigureClients,
9
9
  claudeInstallHint,
10
10
  openBrowser
11
- } from "./chunk-2ED4GL4N.js";
12
- import "./chunk-WDZ67U4G.js";
11
+ } from "./chunk-HKODQKUV.js";
12
+ import "./chunk-JLRBZZNT.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-KSXJCZ2L.js";
23
+ } from "./chunk-PR7ROBN4.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-KSXJCZ2L.js";
4
+ } from "./chunk-PR7ROBN4.js";
5
5
 
6
6
  // src/slot.ts
7
7
  process.stdout.write(agentName());
package/dist/stalled.js CHANGED
@@ -29,7 +29,7 @@ async function main() {
29
29
  const dir = join(homedir(), ".paigy", "stalled-reminded");
30
30
  const mark = join(dir, `${session || "session"}-${day}`);
31
31
  if (existsSync(mark)) return;
32
- const { checkReplies } = await import("./dist-YAKVSYVQ.js");
32
+ const { checkReplies } = await import("./dist-B7AOS5TG.js");
33
33
  const reply = stopReply((await checkReplies()).stalled ?? [], input.hook_event_name ?? "Stop");
34
34
  if (!reply) return;
35
35
  mkdirSync(dir, { recursive: true });
@@ -7,7 +7,7 @@ import {
7
7
  readToken,
8
8
  sessionSlot,
9
9
  slotName
10
- } from "./chunk-KSXJCZ2L.js";
10
+ } from "./chunk-PR7ROBN4.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.22",
3
+ "version": "0.40.23",
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",