@paigy/mcp 0.40.24 → 0.40.25

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.
package/README.md CHANGED
@@ -117,7 +117,7 @@ Then pair: `PAIGY_AGENT=gemini npx -p @paigy/mcp@latest paigy-mcp-onboard`.
117
117
  ## Tools
118
118
 
119
119
  One catalog, on every transport (2026-09-11): the agent tools are `AGENT_TOOLS` in
120
- `packages/schema/src/tools.ts` — contact, check_replies, create_goal, claim_goal,
120
+ `packages/schema/src/tools.ts` — who_is_working, contact, manage_goals, claim_goal,
121
121
  get_goal, update_goal — and both this server and the hosted MCP
122
122
  (`apps/api/src/mcp`) publish that list and dispatch it through the SDK's one `runTool`.
123
123
  This server adds only onboard, pair and unpair, the tools that mint and delete the token
@@ -125,10 +125,12 @@ file this machine holds. See the [SDK contract](../../packages/sdk/README.md) fo
125
125
  single-Goal start shape. Notification returns immediately. Call holds one cancellable
126
126
  ~45-second window here (the hosted transport returns after one read); continue with
127
127
  contact({deliveryId}) without sending another ask. Durable Entries and accepted answers can
128
- repeat on reads; check_replies lists every open Delivery addressed to you (a request the
129
- user started toward you, an answer relayed to something you asked, a handoff) without
130
- consuming any of it, and claim_goal is the catch-up for your own Goals. Bookkeeping ids
131
- (`operationId`, `idempotencyKey`) are minted here, never asked of the model. Retired
128
+ repeat on reads; contact with nothing to send receives what is addressed to you (a request the
129
+ user started toward you, an answer relayed to something you asked, a handoff, a question you owe) without
130
+ consuming any of it until you acknowledge it (`ackEventIds`), and claim_goal is the catch-up for
131
+ your own Goals. manage_goals creates, edits, assigns, organizes and closes work, a change at a
132
+ time. Bookkeeping (`operationId`, the manage request id, the revision an edit was read at) is
133
+ minted here, never asked of the model. Retired
132
134
  reply-lease/ACK/work/callback tools and hidden notification aliases are rejected.
133
135
 
134
136
  The standalone listener below is still a **legacy host and target-release blocker**.
@@ -154,7 +156,7 @@ wake channel and reads what is waiting on every nudge. Keep it alive past the te
154
156
  with `paigy-listen --install` (launchd on macOS, systemd user unit on Linux;
155
157
  `--uninstall` removes it).
156
158
 
157
- The read is `check_replies` — the open Deliveries addressed to this agent — and it
159
+ The read is `receive` (behind `contact({})`) — the open Deliveries addressed to this agent — and it
158
160
  **consumes nothing**: no lease, no acknowledgement, and the same read twice returns the
159
161
  same list. So the daemon hands the launcher what it found and stops there; claiming is
160
162
  the agent's own first act (`claim_goal`), because the daemon shares this machine's
@@ -231,7 +233,7 @@ codex exec "Paigy goal $PAIGY_GOAL_ID. claim_goal it first. The user said: $PAIG
231
233
  ## Stalled work (turn-end hook — Claude Code, Codex, Gemini CLI, Antigravity)
232
234
 
233
235
  An agent that goes quiet leaves its Goals behind. Three things hand it that work back, all
234
- reading one rule (no progress for 3 days, `COLD_AFTER_MS`): `check_replies` lists it under
236
+ reading one rule (no progress for 3 days, `COLD_AFTER_MS`): `contact({})` lists it under
235
237
  `stalled`, `paigy-listen` carries it in `PAIGY_WORK` (and launches for it on boot), and this
236
238
  hook holds a session's turn end once a day to list it. One program, four tools:
237
239
 
@@ -6,7 +6,7 @@ import {
6
6
  saveToken,
7
7
  setIdentity,
8
8
  sleep
9
- } from "./chunk-WR2M3RME.js";
9
+ } from "./chunk-EJYXBEWN.js";
10
10
 
11
11
  // src/identity.ts
12
12
  var CLIENT_LABELS = {
@@ -1424,7 +1424,7 @@ var SPOKEN_HOUR = {
1424
1424
  eleven: 11,
1425
1425
  twelve: 12
1426
1426
  };
1427
- function parseWhen(said2, tz, nowMs, read = "earliest") {
1427
+ function parseWhen(said2, tz, nowMs, read22 = "earliest") {
1428
1428
  const text = said2.trim();
1429
1429
  if (!text) return null;
1430
1430
  const rel = RELATIVE.exec(text);
@@ -1463,13 +1463,13 @@ function parseWhen(said2, tz, nowMs, read = "earliest") {
1463
1463
  if (hour === 12) hour = 0;
1464
1464
  } else if (hour < 12 && dayShift === 0 && hour <= today.h) hour += 12;
1465
1465
  } else {
1466
- const parts = PARTS[read];
1466
+ const parts = PARTS[read22];
1467
1467
  const part = Object.keys(parts).find((k) => new RegExp(`\\b${k}\\b`, "i").test(text));
1468
1468
  if (/\btonight\b/i.test(text)) hour = parts["night"];
1469
1469
  else if (part) hour = parts[part];
1470
1470
  else if (dayShift > 0) hour = 9;
1471
1471
  else if (/\bend of (the )?day\b|\beod\b/i.test(text)) hour = END_OF_DAY;
1472
- else if (read === "ring" && VAGUE.test(text)) return new Date(nowMs + 30 * 6e4).toISOString();
1472
+ else if (read22 === "ring" && VAGUE.test(text)) return new Date(nowMs + 30 * 6e4).toISOString();
1473
1473
  else return null;
1474
1474
  }
1475
1475
  const at2 = instantAt(tz, {
@@ -1492,8 +1492,8 @@ function parseWhen(said2, tz, nowMs, read = "earliest") {
1492
1492
  }) : at2;
1493
1493
  return new Date(rolled ?? at2).toISOString();
1494
1494
  }
1495
- function parseDelay(said2, tz, nowMs, read = "earliest") {
1496
- const at2 = parseWhen(said2, tz, nowMs, read);
1495
+ function parseDelay(said2, tz, nowMs, read22 = "earliest") {
1496
+ const at2 = parseWhen(said2, tz, nowMs, read22);
1497
1497
  if (!at2) return null;
1498
1498
  return Math.max(0, Math.round((Date.parse(at2) - nowMs) / 1e3));
1499
1499
  }
@@ -1551,9 +1551,17 @@ var StartContactSchema = z2.object({
1551
1551
  waiting: z2.enum(["none", "hard"]).default("none").describe("hard requests a call even when channel is notification; user permissions and ring cooldowns still apply."),
1552
1552
  channel: z2.enum(["notification", "call"]).default("notification")
1553
1553
  }).strict();
1554
- var ContactSchema = z2.union([StartContactSchema, z2.object({ deliveryId: z2.string().uuid() }).strict()]);
1554
+ var ReceiveContactSchema = z2.object({
1555
+ wait: z2.boolean().optional().describe(
1556
+ "Only controls when this returns. true (the default when you send and acknowledge nothing): wait up to about 45 seconds for incoming communication addressed to you, and return as soon as some is available. false: return what is waiting now. It does not mean work is blocked, set urgency, or ask for a call. `waitOutcome` says which happened: `available`, `expired` (nothing arrived in the window; wait again without sending again), or `not_waited` (a hosted connection never holds a wait)."
1557
+ ),
1558
+ ackEventIds: z2.array(z2.string().uuid()).min(1).max(100).optional().describe(
1559
+ "The exact eventIds of the events you have handled: an acknowledgment (ACK). Reading alone acknowledges nothing, so an event comes back until you acknowledge it; after that the next batch can come. Each recipient acknowledges separately, for itself only. A Question you owe is answered (an ask with `answers`), never acknowledged away: that id is refused until it is answered."
1560
+ )
1561
+ }).strict();
1562
+ var ContactSchema = z2.union([StartContactSchema, z2.object({ deliveryId: z2.string().uuid() }).strict(), ReceiveContactSchema]);
1555
1563
  var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
1556
- 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 starts a new Goal of yours. 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'. Each ask is sent exactly as written, with no reading in between: a bundled ask arrives as one card, so separate questions 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; Where your work stands while under way is progress: report it with update_goal, not contact. 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'). select:'many' makes the card a checklist, for independent options they may want several of; select:'one' (the default) a pick, for alternatives. 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.";
1564
+ 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 starts a new Goal of yours. Notification returns immediately; collect answers by receiving (below). On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. One question per 'ask'; several questions are several objects in 'asks'. Each ask is sent exactly as written, with no reading in between: a bundled ask arrives as one card, so separate questions are separate asks. An ask with no options that is not blocking is a report, never a claim on them, so no answer is owed and none should be awaited. A report reaches the person only when it answers something they asked you, or once the Goal is done; any other report is recorded as the Goal's progress and nobody is notified. So when the work is finished, mark the Goal done first, then send one message saying what is done and anything they need to do or check. On a Goal whose report card is still open, a report that does reach them is added to that card, with no new push. 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'). select:'many' makes the card a checklist, for independent options they may want several of; select:'one' (the default) a pick, for alternatives. The person can always answer in their own words, so never add an 'Other' option. If the person is already on a call, a contact that reaches them 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.\n\nRECEIVING: contact with no asks sends nothing and returns `events`, a limited batch of what is addressed to you (not a history page): `question`, a Question you owe (answer it with an ask whose `answers` is its questionId); `update`, something new on one of your Goals (a reply, an answer: read it with get_goal); `instruction`, a request or note sent to you. `hasMore` says more are waiting. Reading acknowledges nothing: once you have handled events, confirm their eventIds with contact({ackEventIds}), and the next batch can come. contact({}) waits up to about 45 seconds for something to arrive; contact({wait:false}) returns at once. It also lists work given to you that nobody has started (`assigned`; claim_goal starts it) and your work gone quiet (`stalled`).";
1557
1565
  var CreateGoalSchema = z3.object({
1558
1566
  outcome: z3.string().trim().min(1).max(1e4),
1559
1567
  /** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
@@ -1573,10 +1581,6 @@ var CreateGoalSchema = z3.object({
1573
1581
  * repo name. Delegated work inherits this from its parent Goal when omitted. */
1574
1582
  repo: z3.string().trim().min(1).max(200).optional()
1575
1583
  });
1576
- var CreateGoalToolSchema = CreateGoalSchema.extend({
1577
- idempotencyKey: CreateGoalSchema.shape.idempotencyKey.optional().describe("Optional. One is minted per call; pass your own only so a retry lands on the same Goal.")
1578
- }).strict();
1579
- 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.';
1580
1584
  var UpdateGoalSchema = z3.object({
1581
1585
  revision: z3.number().int().positive(),
1582
1586
  changes: z3.object({
@@ -1600,7 +1604,65 @@ var UpdateGoalSchema = z3.object({
1600
1604
  reason: z3.string().trim().min(1).max(2e3),
1601
1605
  operationId: z3.string().uuid().optional()
1602
1606
  }).strict();
1603
- var UpdateGoalToolSchema = UpdateGoalSchema.omit({ operationId: true }).extend({ goalId: z3.string().uuid() }).strict();
1607
+ var UpdateGoalToolSchema = z3.object({
1608
+ goalId: z3.string().uuid(),
1609
+ revision: UpdateGoalSchema.shape.revision,
1610
+ changes: UpdateGoalSchema.shape.changes.innerType().pick({ progress: true, reviewed: true, dueAt: true, withdraw: true }).strict().refine((v) => Object.keys(v).length > 0),
1611
+ reason: UpdateGoalSchema.shape.reason
1612
+ }).strict();
1613
+ var changedGoal = z3.string().uuid().describe("The Goal to change, as a read shows it.");
1614
+ var goalTitle = z3.string().trim().min(1).max(80).describe("The Goal's short display name: one to five words, how a person refers to it out loud.");
1615
+ var goalOutcome = z3.string().trim().min(1).max(1e4).describe("The full desired result. There is no third description field.");
1616
+ var CreateChange = z3.object({
1617
+ kind: z3.literal("create"),
1618
+ title: goalTitle,
1619
+ outcome: goalOutcome,
1620
+ ownerId: z3.string().trim().min(1).optional().describe("Who owns the work: an agent's participant (as who_is_working shows it) or the person's. Omitted: you."),
1621
+ parentGoalId: z3.string().uuid().optional().describe("The Goal it belongs under. Omitted: a root."),
1622
+ sourceEntryIds: z3.array(z3.string().uuid()).max(20).optional().describe("The whole Entries the work came from, ones you can read.")
1623
+ }).strict();
1624
+ var EditChange = z3.object({ kind: z3.literal("edit"), goalId: changedGoal, title: goalTitle.optional(), outcome: goalOutcome.optional() }).strict();
1625
+ var StateChange = z3.object({ kind: z3.literal("state"), goalId: changedGoal, state: z3.enum(["open", "completed", "canceled"]).describe(
1626
+ "completed: the Goal's own work is done now (refused while required children are open); open: reopen it; canceled: it will not be done."
1627
+ ) }).strict();
1628
+ var AssignChange = z3.object({ kind: z3.literal("assign"), goalId: changedGoal, ownerId: z3.string().trim().min(1).describe("The new owner's participant.") }).strict();
1629
+ var DeferChange = z3.object({ kind: z3.literal("defer"), goalId: changedGoal, until: z3.string().datetime({ offset: true }).nullable().describe(
1630
+ "Postpone it until this instant (its owner is woken then), or null to take the postponement back."
1631
+ ) }).strict();
1632
+ var MoveChange = z3.object({ kind: z3.literal("move"), goalId: changedGoal, parentGoalId: z3.string().uuid().nullable().describe(
1633
+ "Its new parent, or null for a root. Only this Goal moves; its new parent's other children stay."
1634
+ ) }).strict();
1635
+ var DependencyChange = z3.object({
1636
+ kind: z3.literal("dependency"),
1637
+ change: z3.enum(["add", "remove"]),
1638
+ goalId: changedGoal,
1639
+ dependsOnGoalId: z3.string().uuid().describe("The Goal it waits on."),
1640
+ action: z3.enum(["start", "complete"]).describe("What waits: starting goalId, or completing it."),
1641
+ reason: z3.string().trim().min(1).max(2e3).optional().describe("Why, as an explanation, never as policy.")
1642
+ }).strict();
1643
+ var read = { revision: z3.number().int().positive().optional() };
1644
+ var GoalChangeSchema = z3.discriminatedUnion("kind", [
1645
+ CreateChange,
1646
+ EditChange.extend(read),
1647
+ StateChange.extend(read),
1648
+ AssignChange.extend(read),
1649
+ DeferChange.extend(read),
1650
+ MoveChange.extend(read),
1651
+ DependencyChange
1652
+ ]);
1653
+ var editNames = (changes, ctx) => changes.forEach((c, i) => {
1654
+ if (c.kind === "edit" && c.title === void 0 && c.outcome === void 0) {
1655
+ ctx.addIssue({ code: z3.ZodIssueCode.custom, path: ["changes", i], message: "an edit supplies title, outcome, or both" });
1656
+ }
1657
+ });
1658
+ var ManageGoalsSchema = z3.object({
1659
+ requestId: z3.string().uuid().optional(),
1660
+ changes: z3.array(GoalChangeSchema).min(1).max(50)
1661
+ }).strict().superRefine((v, ctx) => editNames(v.changes, ctx));
1662
+ var ManageGoalsToolSchema = z3.object({
1663
+ changes: z3.array(z3.discriminatedUnion("kind", [CreateChange, EditChange, StateChange, AssignChange, DeferChange, MoveChange, DependencyChange])).min(1).max(50).describe("The changes, applied in order, each on its own.")
1664
+ }).strict().superRefine((v, ctx) => editNames(v.changes, ctx));
1665
+ var MANAGE_GOALS_DESCRIPTION = "Create, edit, assign, organize or close work. Every change here changes Goals; answering, withdrawing or asking Questions stays in contact. Each change applies independently: a refused one names why (`results[i].error`, e.g. goal_revision_conflict, goal_children_open, goal_not_joined, goal_not_found) and the rest still apply, so read every result: a partial result is never a complete success. Kinds: create (title, outcome, ownerId, parentGoalId, sourceEntryIds) returns the new Goal's id in `results[i].goalId`, in the order requested, to use in later calls; edit (title and/or outcome); state (open, completed, canceled); assign (ownerId); defer (until, or null); move (parentGoalId, or null for a root: only this Goal moves); dependency (add or remove: goalId waits on dependsOnGoalId to start or to complete). A Goal cannot be completed while its required children or dependencies remain open: finish or move them first. Finishing the children does not prove the parent's own work is done. An edit applies at the version you last read: if someone changed the Goal since, it is refused as goal_revision_conflict, so read it again (get_goal) and reconsider. You may change a Goal you own or have written on. Returns `ok` (every change applied), `results` per change (applied or failed), and `goals`, each changed Goal's id, state, title, outcome and revision.";
1604
1666
  var ClaimGoalSchema = z3.object({ goalId: z3.string().uuid().optional() }).strict();
1605
1667
  var GetGoalSchema = z3.object({
1606
1668
  goalId: z3.string().uuid(),
@@ -1613,18 +1675,24 @@ var GetGoalSchema = z3.object({
1613
1675
  diagnose: z3.boolean().optional()
1614
1676
  }).strict();
1615
1677
  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.";
1616
- 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.`;
1617
- 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).";
1618
- 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.";
1619
- var CheckRepliesSchema = z3.object({}).strict();
1678
+ var UPDATE_GOAL_DESCRIPTION = "Report work on a Goal you are on, at an exact revision: progress, review acknowledgement, when to wake for it, and withdrawing a question of yours. What the Goal is (its title, outcome, state, owner, parent, dependencies) changes with manage_goals. 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). 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. progress says where the work stands. reviewed: true acknowledges new evidence and closes the Deliveries addressed to you on that Goal, never over an open decision (contact({ackEventIds}) does the same for an `update` event). 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. Returns the Goal as get_goal reads it, at its new revision.";
1679
+ 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 it; use manage_goals (assign) 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).";
1680
+ var SearchToolSchema = z3.object({
1681
+ query: z3.string().trim().min(1).max(500),
1682
+ types: z3.array(z3.enum(["entry", "goal", "answer"])).min(1).optional(),
1683
+ goalId: z3.string().uuid().optional(),
1684
+ limit: z3.number().int().min(1).max(20).optional()
1685
+ }).strict();
1686
+ var SEARCH_DESCRIPTION = "Search your person's history across all of their agents: Entries (what anyone said or wrote, typed or spoken on a call), Goals (by title and outcome) and Answers (found by their Question or by the words that gave them). query is words to look for; records sharing more of its words rank first, and exact names work. types narrows it to entry, goal and/or answer (default: all three). goalId searches under one Goal: its Entries, its Questions' Answers, and it and its immediate children. limit is matches per type, 1 to 20 (default 8). Read-only. Each match carries its whole saved words, its ID and its links (an Answer carries its Question, the choice made and the Entries that support it); `omitted` counts what matched but was left out, so narrow the words or add a goalId to see it. Nothing found is not proof that nothing exists; a refused search says why. Sealed (encrypted) content is never searched or returned.";
1687
+ var WhoIsWorkingSchema = z3.object({}).strict();
1620
1688
  var AGENT_TOOLS = [
1621
- { 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) },
1689
+ { 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(WhoIsWorkingSchema) },
1622
1690
  { name: "contact", description: CONTACT_DESCRIPTION, inputSchema: CONTACT_SCHEMA },
1623
- { name: "check_replies", description: CHECK_REPLIES_DESCRIPTION, inputSchema: mcpInputSchema(CheckRepliesSchema) },
1624
- { name: "create_goal", description: CREATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(CreateGoalToolSchema) },
1691
+ { name: "manage_goals", description: MANAGE_GOALS_DESCRIPTION, inputSchema: mcpInputSchema(ManageGoalsToolSchema) },
1625
1692
  { name: "claim_goal", description: CLAIM_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(ClaimGoalSchema) },
1626
1693
  { name: "get_goal", description: GET_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(GetGoalSchema) },
1627
- { name: "update_goal", description: UPDATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(UpdateGoalToolSchema) }
1694
+ { name: "update_goal", description: UPDATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(UpdateGoalToolSchema) },
1695
+ { name: "search", description: SEARCH_DESCRIPTION, inputSchema: mcpInputSchema(SearchToolSchema) }
1628
1696
  ];
1629
1697
  var AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
1630
1698
  var id = z4.string().uuid();
@@ -1655,17 +1723,6 @@ var existingGoalChanges = [
1655
1723
  reason: z4.string().max(500).optional()
1656
1724
  }).strict()
1657
1725
  ];
1658
- var GoalChangeSchema = z4.union([
1659
- z4.object({
1660
- kind: z4.literal("create"),
1661
- title: z4.string().min(1).max(120),
1662
- outcome: z4.string().min(1),
1663
- ownerId: participant,
1664
- parentGoalId: id.optional(),
1665
- sourceEntryIds: z4.array(id)
1666
- }).strict(),
1667
- ...existingGoalChanges
1668
- ]);
1669
1726
  var BrainGoalChangeSchema = z4.union([
1670
1727
  // newSession: Paigy starts a session to own it (owner, 2026-10-06); until it has, the person owns it.
1671
1728
  z4.object({
@@ -2073,7 +2130,7 @@ var AwaitItemSchema = z5.discriminatedUnion("type", [
2073
2130
  /** The awaited ask was REPLACED by a newer notification on its thread (e.g. a
2074
2131
  * post-feedback revision, #633) — the user will never answer this id. Stop
2075
2132
  * awaiting it; the live ask is the thread's newest turn (await that one, or
2076
- * re-orient via check_replies). */
2133
+ * re-orient via contact({})). */
2077
2134
  z5.object({
2078
2135
  type: z5.literal("superseded"),
2079
2136
  parentId: z5.string(),
@@ -2803,7 +2860,7 @@ var HostDecisionSchema = z5.object({
2803
2860
  goalId: z5.string().uuid().nullable().optional(),
2804
2861
  /** When the server last heard from the agent, as the host read it: the presence it stood back for. */
2805
2862
  seenAt: z5.string().datetime().nullable().optional(),
2806
- /** When that work last moved (`claimable.since` on `check_replies`), the fact the bound is judged on. */
2863
+ /** When that work last moved (`claimable.since` on an agent's `contact({})` read), the fact the bound is judged on. */
2807
2864
  since: z5.string().datetime().nullable().optional(),
2808
2865
  /** What the host said, in its log's own words: why it stood back, or what the take-over did. */
2809
2866
  said: z5.string().max(300).optional()
@@ -3170,28 +3227,28 @@ var authToken = (explicit) => explicit ?? tokenOverride ?? readToken();
3170
3227
  async function fail(what, res) {
3171
3228
  throw new ApiError(what, res.status, await res.text());
3172
3229
  }
3173
- async function createGoal(input, opts = {}) {
3230
+ async function manageGoals(input, opts = {}) {
3174
3231
  const token = authToken(opts.token) ?? "";
3175
- const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals`, {
3232
+ const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/manage`, {
3176
3233
  method: "POST",
3177
- headers: { "content-type": "application/json", authorization: `Bearer ${token}`, "x-paigy-model": "goal-entry-v1" },
3234
+ headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
3178
3235
  body: JSON.stringify(input)
3179
3236
  }));
3180
- if (!res.ok) await fail("create_goal", res);
3237
+ if (!res.ok) await fail("manage_goals", res);
3181
3238
  return await res.json();
3182
3239
  }
3183
3240
  async function claimGoal(goalId, opts = {}) {
3184
3241
  const token = authToken(opts.token) ?? "";
3185
- const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/claim`, { method: "POST", headers: { "content-type": "application/json", authorization: `Bearer ${token}`, "x-paigy-model": "goal-entry-v1" }, body: JSON.stringify(goalId ? { goalId } : {}) }));
3242
+ const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/claim`, { method: "POST", headers: { "content-type": "application/json", authorization: `Bearer ${token}` }, body: JSON.stringify(goalId ? { goalId } : {}) }));
3186
3243
  if (!res.ok) await fail("claim_goal", res);
3187
3244
  return await res.json();
3188
3245
  }
3189
- async function getGoal(goalId, opts = {}, read = {}) {
3246
+ async function getGoal(goalId, opts = {}, read3 = {}) {
3190
3247
  const token = authToken(opts.token) ?? "";
3191
- const query = [read.history ? "history=1" : "", read.diagnose ? "diagnose=1" : ""].filter(Boolean).join("&");
3248
+ const query = [read3.history ? "history=1" : "", read3.diagnose ? "diagnose=1" : ""].filter(Boolean).join("&");
3192
3249
  const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/${encodeURIComponent(goalId)}${query ? `?${query}` : ""}`, {
3193
3250
  method: "GET",
3194
- headers: { authorization: `Bearer ${token}`, "x-paigy-model": "goal-entry-v1" }
3251
+ headers: { authorization: `Bearer ${token}` }
3195
3252
  }));
3196
3253
  if (!res.ok) await fail("get_goal", res);
3197
3254
  return await res.json();
@@ -3199,7 +3256,7 @@ async function getGoal(goalId, opts = {}, read = {}) {
3199
3256
  async function updateGoal(goalId, input, opts = {}) {
3200
3257
  const token = authToken(opts.token) ?? "";
3201
3258
  const operationId = input.operationId ?? randomUUID2();
3202
- const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/${encodeURIComponent(goalId)}`, { method: "PATCH", headers: { "content-type": "application/json", authorization: `Bearer ${token}`, "x-paigy-model": "goal-entry-v1" }, body: JSON.stringify({ ...input, operationId }) }));
3259
+ const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/${encodeURIComponent(goalId)}`, { method: "PATCH", headers: { "content-type": "application/json", authorization: `Bearer ${token}` }, body: JSON.stringify({ ...input, operationId }) }));
3203
3260
  if (!res.ok) await fail("update_goal", res);
3204
3261
  return await res.json();
3205
3262
  }
@@ -3315,7 +3372,7 @@ async function listNotes(opts = {}) {
3315
3372
  }
3316
3373
  async function listConnections(opts = {}) {
3317
3374
  const res = await ensureAuthed(await reach(`${BACKEND_URL}/api/tokens`, {
3318
- headers: { authorization: `Bearer ${authToken(opts.token)}`, "x-paigy-model": "goal-entry-v1" }
3375
+ headers: { authorization: `Bearer ${authToken(opts.token)}` }
3319
3376
  }));
3320
3377
  if (!res.ok) throw new Error(`list_connections failed: ${res.status} ${await res.text()}`);
3321
3378
  return await res.json();
@@ -3353,9 +3410,18 @@ async function dismissTriage(proposalId, opts = {}) {
3353
3410
  if (!res.ok) throw new Error(`dismiss_triage failed: ${res.status} ${await res.text()}`);
3354
3411
  return await res.json();
3355
3412
  }
3413
+ async function searchRecords(input, opts = {}) {
3414
+ const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/records/search`, {
3415
+ method: "POST",
3416
+ headers: { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token) ?? ""}` },
3417
+ body: JSON.stringify(input)
3418
+ }));
3419
+ if (!res.ok) await fail("search", res);
3420
+ return await res.json();
3421
+ }
3356
3422
  async function whoIsWorking(opts = {}) {
3357
3423
  const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/agents/working`, {
3358
- headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}`, "x-paigy-model": "goal-entry-v1" }
3424
+ headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}` }
3359
3425
  }));
3360
3426
  if (!res.ok) await fail("who_is_working", res);
3361
3427
  return await res.json();
@@ -3386,13 +3452,14 @@ function currentRepo(cwd = process.cwd()) {
3386
3452
  }
3387
3453
  return asked;
3388
3454
  }
3389
- var NOT_SENT = "Recorded as progress, not sent: nobody was notified. If the person should see it (a result, something to try or check, a finding), contact again and say so; report progress with update_goal.";
3455
+ var NOT_SENT = "Recorded as the Goal's progress, not sent: nobody was notified. The person is told only a question, an answer to one they asked you, and what you say once the Goal is done. To close the work, mark the Goal done first, then send the one message saying what is done and anything they need to do or check.";
3390
3456
  var DEMOTED = "Delivered as a card, not a call: the person's settings do not allow a call right now (silent, quiet hours, or a mode). It has reached them; do not send it again, with or without a Goal.";
3391
3457
  async function contact(input, opts = {}) {
3392
3458
  const parsed = ContactSchema.parse(input);
3459
+ if (!("asks" in parsed) && !("deliveryId" in parsed)) throw new Error("contact with nothing to send receives: call receiveContact");
3393
3460
  opts.signal?.throwIfAborted();
3394
3461
  const send2 = opts.reach ?? reach;
3395
- const headers = { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token) ?? ""}`, "x-paigy-model": "goal-entry-v1" };
3462
+ const headers = { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token) ?? ""}` };
3396
3463
  let deliveryId;
3397
3464
  let deliveries = [];
3398
3465
  let recorded = [];
@@ -3437,7 +3504,7 @@ async function contact(input, opts = {}) {
3437
3504
  const all = deliveries.filter((d) => !!d.deliveryId && !!d.goalId).map(({ id: id2, deliveryId: deliveryId2, goalId }) => ({ ...id2 ? { id: id2 } : {}, deliveryId: deliveryId2, goalId }));
3438
3505
  const joinedCard = deliveries[0]?.joinedCard === true;
3439
3506
  const joinedCall = deliveries[0]?.joinedCall === true;
3440
- const read = async (signal2) => {
3507
+ const read3 = async (signal2) => {
3441
3508
  const res = await ensureAuthed(await send2(`${BACKEND_URL}/api/deliveries/${encodeURIComponent(deliveryId)}`, { headers, signal: signal2 }));
3442
3509
  if (!res.ok) await fail("read_delivery", res);
3443
3510
  const one = await res.json();
@@ -3451,14 +3518,14 @@ async function contact(input, opts = {}) {
3451
3518
  };
3452
3519
  };
3453
3520
  const settled = (d) => d.kind === "notification" || d.state === "closed" || d.answers.length > 0 || d.entries.some((e) => e.kind === "contribution");
3454
- if (opts.waits === false) return read(opts.signal);
3521
+ if (opts.waits === false) return read3(opts.signal);
3455
3522
  const window = AbortSignal.timeout(AWAIT_WINDOW_MS);
3456
3523
  const signal = opts.signal ? AbortSignal.any([opts.signal, window]) : window;
3457
3524
  let latest;
3458
3525
  try {
3459
3526
  while (true) {
3460
3527
  signal.throwIfAborted();
3461
- latest = await read(signal);
3528
+ latest = await read3(signal);
3462
3529
  if (settled(latest)) return latest;
3463
3530
  await sleep2(5e3, void 0, { signal });
3464
3531
  }
@@ -3468,13 +3535,69 @@ async function contact(input, opts = {}) {
3468
3535
  throw error;
3469
3536
  }
3470
3537
  }
3471
- async function checkReplies(opts = {}) {
3538
+ async function receive(opts = {}) {
3472
3539
  const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/deliveries`, {
3473
- headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}`, "x-paigy-model": "goal-entry-v1" }
3540
+ headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}` }
3474
3541
  }));
3475
- if (!res.ok) await fail("check_replies", res);
3542
+ if (!res.ok) await fail("receive", res);
3476
3543
  return await res.json();
3477
3544
  }
3545
+ async function acknowledge(eventIds, opts = {}) {
3546
+ const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/deliveries/ack`, {
3547
+ method: "POST",
3548
+ headers: { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token) ?? ""}` },
3549
+ body: JSON.stringify({ eventIds })
3550
+ }));
3551
+ if (!res.ok) await fail("acknowledge", res);
3552
+ return (await res.json()).results ?? [];
3553
+ }
3554
+ var EVENT_BATCH = 20;
3555
+ function pendingEvents(w) {
3556
+ const fromDelivery = (d) => ({
3557
+ eventId: d.deliveryId,
3558
+ kind: d.questions?.length ? "question" : "instruction",
3559
+ ...d.goalIds.length ? { goalIds: d.goalIds } : {},
3560
+ ...d.about ? { text: d.about } : {},
3561
+ ...d.questions?.length ? { questions: d.questions } : {}
3562
+ });
3563
+ const deliveries = (w.deliveries ?? []).map(fromDelivery);
3564
+ return [
3565
+ ...deliveries.filter((e) => e.kind === "question"),
3566
+ ...(w.review ?? []).map((r) => ({
3567
+ eventId: r.eventId ?? r.goalId,
3568
+ kind: "update",
3569
+ goalIds: [r.goalId],
3570
+ text: r.said ? `${r.title}: ${r.said}` : r.title,
3571
+ ...r.saidAt ? { at: r.saidAt } : {}
3572
+ })),
3573
+ ...deliveries.filter((e) => e.kind !== "question")
3574
+ ];
3575
+ }
3576
+ async function receiveContact(input, opts = {}) {
3577
+ opts.signal?.throwIfAborted();
3578
+ const acknowledged = input.ackEventIds?.length ? await acknowledge(input.ackEventIds, opts) : void 0;
3579
+ const shaped = (waiting, waitOutcome) => {
3580
+ const all = pendingEvents(waiting);
3581
+ return { waiting, events: all.slice(0, EVENT_BATCH), hasMore: all.length > EVENT_BATCH, waitOutcome, ...acknowledged ? { acknowledged } : {} };
3582
+ };
3583
+ const wait = (input.wait ?? !input.ackEventIds?.length) && opts.waits !== false;
3584
+ if (!wait) return shaped(await receive(opts), "not_waited");
3585
+ const window = AbortSignal.timeout(AWAIT_WINDOW_MS);
3586
+ const signal = opts.signal ? AbortSignal.any([opts.signal, window]) : window;
3587
+ let latest;
3588
+ try {
3589
+ while (true) {
3590
+ signal.throwIfAborted();
3591
+ latest = await receive({ ...opts });
3592
+ if (pendingEvents(latest).length) return shaped(latest, "available");
3593
+ await sleep2(5e3, void 0, { signal });
3594
+ }
3595
+ } catch (error) {
3596
+ opts.signal?.throwIfAborted();
3597
+ if (window.aborted && latest) return shaped(latest, "expired");
3598
+ throw error;
3599
+ }
3600
+ }
3478
3601
  function compact(o) {
3479
3602
  return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0 && v !== null && v !== "" && !(Array.isArray(v) && v.length === 0)));
3480
3603
  }
@@ -3531,7 +3654,7 @@ function more(g) {
3531
3654
  earlier,
3532
3655
  g.subtree?.length ? "A Goal below, in full: get_goal({ goalId }) with its goalId above." : null,
3533
3656
  g.parentGoalId ? `The Goal above: get_goal({ goalId: "${g.parentGoalId}" }).` : null,
3534
- "Anything else waiting for you (other requests, answers, reviews): check_replies()."
3657
+ "Anything else addressed to you (requests, answers, questions you owe): contact({ wait: false })."
3535
3658
  ].filter((line) => !!line);
3536
3659
  }
3537
3660
  function goalView(g) {
@@ -3585,93 +3708,106 @@ function deliveryView(d) {
3585
3708
  notSent: d.notSent,
3586
3709
  joinedCard: d.joinedCard,
3587
3710
  joinedCall: d.joinedCall,
3588
- next: d.joinedCall ? `${JOINED_CALL}${d.message ? ` ${d.message}` : ""}` : d.kind === "notification" ? `${d.demoted ? `${d.demoted} ` : ""}${d.joinedCard ? `Added to the open report card on its Goal; no new push was sent. ${answers}` : answers}` : d.state === "open" && d.decisionNeeds.some((n) => n.state === "open") ? `Decision pending. Call contact(${JSON.stringify({ deliveryId: d.deliveryId })}) to continue this same Call \u2014 each call is ONE bounded window, and never resend the ask. When a window comes back with nothing new said, they are not typing: stop rereading, leave the question open, and collect the answer with check_replies or claim_goal on your next wake. ${d.message ?? ""}`.trim() : d.message
3711
+ next: d.joinedCall ? `${JOINED_CALL}${d.message ? ` ${d.message}` : ""}` : d.kind === "notification" ? `${d.demoted ? `${d.demoted} ` : ""}${d.joinedCard ? `Added to the open report card on its Goal; no new push was sent. ${answers}` : answers}` : d.state === "open" && d.decisionNeeds.some((n) => n.state === "open") ? `Decision pending. Call contact(${JSON.stringify({ deliveryId: d.deliveryId })}) to continue this same Call \u2014 each call is ONE bounded window, and never resend the ask. When a window comes back with nothing new said, they are not typing: stop rereading, leave the question open, and collect the answer with contact({ wait: false }) or claim_goal on your next wake. ${d.message ?? ""}`.trim() : d.message
3589
3712
  });
3590
3713
  }
3591
- function repliesView(r) {
3592
- const deliveries = r.deliveries ?? [];
3593
- const assigned = r.assigned ?? [];
3594
- const review = r.review ?? [];
3595
- const stalled = r.stalled ?? [];
3596
- const others = r.stalledOthers ?? [];
3597
- return {
3598
- deliveries: deliveries.map((d) => compact({
3599
- deliveryId: d.deliveryId,
3600
- kind: d.kind,
3601
- goalIds: d.goalIds,
3602
- openDecisions: d.decisionNeeds.filter((n) => n.state === "open").length || void 0,
3603
- latest: "about" in d ? d.about : void 0
3604
- })),
3605
- // WORK ASSIGNED TO YOU (owner, 2026-09-24): your Goals nobody has started, however they
3606
- // became yours — a Goal handed to you is not a Delivery, and must not wait for a claim to be seen.
3607
- ...assigned.length ? { assigned } : {},
3608
- // WHAT claim_goal HANDS YOU NEXT (owner, 2026-09-27: "Wake on any work"): the same predicate that
3609
- // starts a sleeping agent, so "nothing is waiting" is never said while a claim would hand you work
3610
- // — a Goal you started in a process that died is on no other list.
3611
- ...r.claimable ? { claimable: r.claimable } : {},
3612
- // NEW ON YOUR GOALS (owner, 2026-09-23): a reply is an Entry on the Goal, not a Delivery, so the
3613
- // Goals flagged for your review are listed here until you acknowledge them.
3614
- ...review.length ? { review } : {},
3615
- // YOUR STALLED WORK (#2257): Goals you own with no progress for three days. Named here so an
3616
- // agent that reads its replies also sees what it left, and says what to do about each.
3617
- ...stalled.length ? { stalled } : {},
3618
- // OTHER AGENTS' STALLED WORK (owner, 2026-09-23): any agent may take it over and close it.
3619
- ...others.length ? { stalledOthers: others } : {},
3714
+ function receivedView(r) {
3715
+ const w = r.waiting;
3716
+ const assigned = w.assigned ?? [];
3717
+ const stalled = w.stalled ?? [];
3718
+ const others = w.stalledOthers ?? [];
3719
+ const owed = r.events.filter((e) => e.kind === "question");
3720
+ const refused = (r.acknowledged ?? []).filter((a) => a.refused);
3721
+ return compact({
3722
+ events: r.events.map((e) => compact({ ...e })),
3723
+ hasMore: r.hasMore || void 0,
3724
+ waitOutcome: r.waitOutcome,
3725
+ acknowledged: r.acknowledged?.filter((a) => a.acknowledged).map((a) => a.eventId),
3726
+ notAcknowledged: refused.length ? refused.map((a) => ({ eventId: a.eventId, refused: a.refused })) : void 0,
3727
+ // WORK ASSIGNED TO YOU (owner, 2026-09-24): your Goals nobody has started, however they became yours.
3728
+ assigned,
3729
+ // WHAT claim_goal HANDS YOU NEXT (owner, 2026-09-27: "Wake on any work"): the predicate that starts a
3730
+ // sleeping agent, so "nothing is waiting" is never said while a claim would hand you work.
3731
+ claimable: w.claimable,
3732
+ // YOUR STALLED WORK (#2257), and OTHER AGENTS' (owner, 2026-09-23): any agent may take it over.
3733
+ stalled,
3734
+ stalledOthers: others,
3620
3735
  next: [
3621
- deliveries.length || r.claimable || review.length || assigned.length ? "Act on a Goal with claim_goal; reread one Delivery in full with contact({deliveryId})." : "Nothing is waiting.",
3736
+ r.events.length ? `Handle each event, then acknowledge the ones you handled: contact({ ackEventIds: [${r.events.filter((e) => e.kind !== "question").map((e) => `"${e.eventId}"`).join(", ")}] }).` : w.claimable || assigned.length ? "No messages." : "Nothing is waiting.",
3737
+ ...owed.length ? [`You owe ${owed.length === 1 ? "an answer" : `${owed.length} answers`}: contact({ asks: [{ ask: <your answer>, answers: "<questionId>" }] }) for each question; a question is answered, never acknowledged.`] : [],
3738
+ ...r.hasMore ? ["More are waiting: acknowledge these, then receive again."] : [],
3739
+ ...r.waitOutcome === "expired" ? ["Nothing arrived in the window; receive again to keep waiting, without sending again."] : [],
3622
3740
  ...assigned.length ? [`${assigned.length} of your Goals are assigned to you and not started (assigned): claim_goal({goalId}) to start one.`] : [],
3623
- // THE PERSON SPOKE, AND THAT IS NOT "SOMETHING NEW" (2026-09-29). This line said only how
3624
- // many Goals were flagged, so four things the owner said on a call read as four chores. The
3625
- // rows carry their words now (`said`, `saidAt`, newest first, `delivery/read.ts`); the
3626
- // sentence names the person and quotes the freshest one, because a count is not a message.
3627
- ...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.`] : [],
3628
- ...stalled.length ? [`${stalled.length} of your Goals have had no progress for 3 days: update_goal each with progress, finish it, or cancel it.`] : [],
3629
- ...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.`] : [],
3741
+ ...w.claimable && !assigned.length ? [`claim_goal() hands you ${w.claimable.title ?? w.claimable.goalId} next.`] : [],
3742
+ ...stalled.length ? [`${stalled.length} of your Goals have had no progress for 3 days: update_goal each with progress, or finish or cancel it with manage_goals.`] : [],
3743
+ ...others.length ? [`${others.length} of other agents' Goals have gone quiet for 3 days: claim_goal({goalId}) joins one without changing its owner.`] : [],
3630
3744
  // No name of its own (#2525): the API's ask, verbatim.
3631
- ...r.unnamed ? [r.unnamed] : []
3745
+ ...w.unnamed ? [w.unnamed] : []
3632
3746
  ].join(" ")
3633
- };
3747
+ });
3634
3748
  }
3635
- function spoke(review) {
3636
- const withWords = review.filter((r) => r.said?.trim());
3637
- if (!withWords.length) return null;
3638
- const newest = withWords[0];
3639
- const rest = review.length - 1;
3640
- return `The person has spoken on ${withWords.length === 1 ? "one of your Goals" : `${withWords.length} of your Goals`} \u2014 newest, on ${newest.title ?? newest.goalId}: "${newest.said}". Read it with claim_goal or get_goal, answer what it asks, then update_goal with reviewed: true.${rest > 0 ? ` (${rest} more flagged; see review.)` : ""}`;
3749
+ function managedView(m, changes) {
3750
+ const results = m.results ?? [];
3751
+ const failed = results.filter((r) => r.status !== "applied");
3752
+ return compact({
3753
+ ok: m.ok,
3754
+ results: results.map((r) => compact({ index: r.index, kind: changes[r.index]?.kind, status: r.status, goalId: r.goalId, error: r.error })),
3755
+ goals: m.goals,
3756
+ next: failed.length ? `${failed.length} of ${results.length} changes did not apply (see results[].error); the rest did. A goal_revision_conflict means the Goal changed since you read it: read it with get_goal and decide again.` : void 0
3757
+ });
3758
+ }
3759
+ var lastRead = /* @__PURE__ */ new Map();
3760
+ var readKey = (token, goalId) => `${token ?? ""}:${goalId}`;
3761
+ function remember(token, goalId, revision) {
3762
+ if (revision === void 0) return;
3763
+ if (lastRead.size >= 5e3) lastRead.clear();
3764
+ lastRead.set(readKey(token, goalId), revision);
3765
+ }
3766
+ function read2(token, goal) {
3767
+ if (goal.goalId) remember(token, goal.goalId, goal.revision);
3768
+ return goal;
3641
3769
  }
3642
3770
  async function runTool(name, args, opts) {
3643
3771
  const { waits, signal, ...client } = opts;
3644
3772
  const input = args ?? {};
3645
3773
  switch (name) {
3646
3774
  case "contact": {
3647
- const sent = await contact(input, { ...client, signal, waits });
3775
+ const parsed = ContactSchema.parse(input);
3776
+ if (!("asks" in parsed) && !("deliveryId" in parsed)) return receivedView(await receiveContact(parsed, { ...client, signal, waits }));
3777
+ const sent = await contact(parsed, { ...client, signal, waits });
3648
3778
  return "sent" in sent ? sent : deliveryView(sent);
3649
3779
  }
3650
3780
  case "who_is_working":
3651
- CheckRepliesSchema.parse(input);
3781
+ WhoIsWorkingSchema.parse(input);
3652
3782
  return whoIsWorking(client);
3653
- case "check_replies":
3654
- CheckRepliesSchema.parse(input);
3655
- return repliesView(await checkReplies(client));
3656
- case "create_goal": {
3657
- const goal = CreateGoalToolSchema.parse(input);
3658
- return createGoal({ ...goal, idempotencyKey: goal.idempotencyKey ?? randomUUID4() }, client);
3783
+ case "manage_goals": {
3784
+ const { changes } = ManageGoalsToolSchema.parse(input);
3785
+ const versioned = changes.map((c) => {
3786
+ if (c.kind === "create" || c.kind === "dependency") return c;
3787
+ const revision = lastRead.get(readKey(client.token, c.goalId));
3788
+ return revision === void 0 ? c : { ...c, revision };
3789
+ });
3790
+ const managed = await manageGoals({ requestId: randomUUID4(), changes: versioned }, client);
3791
+ for (const g of managed.goals ?? []) remember(client.token, g.goalId, g.revision);
3792
+ return managedView(managed, changes);
3659
3793
  }
3660
3794
  case "claim_goal": {
3661
3795
  const { goalId } = ClaimGoalSchema.parse(input);
3662
- return goalView(await claimGoal(goalId, client));
3796
+ return goalView(read2(client.token, await claimGoal(goalId, client)));
3663
3797
  }
3664
3798
  case "get_goal": {
3665
3799
  const { goalId, history, diagnose } = GetGoalSchema.parse(input);
3666
- const goal = await getGoal(goalId, client, { history, diagnose });
3800
+ const goal = read2(client.token, await getGoal(goalId, client, { history, diagnose }));
3667
3801
  const view = goalView(goal);
3668
3802
  const diagnosis = goal.diagnosis;
3669
3803
  return diagnosis === void 0 ? view : { ...view, diagnosis };
3670
3804
  }
3671
3805
  case "update_goal": {
3672
3806
  const { goalId, ...body } = UpdateGoalToolSchema.parse(input);
3673
- return goalView(await updateGoal(goalId, body, client));
3807
+ return goalView(read2(client.token, await updateGoal(goalId, body, client)));
3674
3808
  }
3809
+ case "search":
3810
+ return searchRecords(SearchToolSchema.parse(input), client);
3675
3811
  default:
3676
3812
  throw new Error(`Unknown tool: ${name}`);
3677
3813
  }
@@ -3910,7 +4046,7 @@ export {
3910
4046
  ApiError,
3911
4047
  overrideToken,
3912
4048
  authToken,
3913
- createGoal,
4049
+ manageGoals,
3914
4050
  claimGoal,
3915
4051
  getGoal,
3916
4052
  updateGoal,
@@ -3931,11 +4067,15 @@ export {
3931
4067
  getTriage,
3932
4068
  acceptTriage,
3933
4069
  dismissTriage,
4070
+ searchRecords,
3934
4071
  whoIsWorking,
3935
4072
  repoFromRemote,
3936
4073
  currentRepo,
3937
4074
  contact,
3938
- checkReplies,
4075
+ receive,
4076
+ acknowledge,
4077
+ pendingEvents,
4078
+ receiveContact,
3939
4079
  runTool,
3940
4080
  subscribeWake,
3941
4081
  parseDue,
@@ -78,9 +78,17 @@ var StartContactSchema = z2.object({
78
78
  waiting: z2.enum(["none", "hard"]).default("none").describe("hard requests a call even when channel is notification; user permissions and ring cooldowns still apply."),
79
79
  channel: z2.enum(["notification", "call"]).default("notification")
80
80
  }).strict();
81
- var ContactSchema = z2.union([StartContactSchema, z2.object({ deliveryId: z2.string().uuid() }).strict()]);
81
+ var ReceiveContactSchema = z2.object({
82
+ wait: z2.boolean().optional().describe(
83
+ "Only controls when this returns. true (the default when you send and acknowledge nothing): wait up to about 45 seconds for incoming communication addressed to you, and return as soon as some is available. false: return what is waiting now. It does not mean work is blocked, set urgency, or ask for a call. `waitOutcome` says which happened: `available`, `expired` (nothing arrived in the window; wait again without sending again), or `not_waited` (a hosted connection never holds a wait)."
84
+ ),
85
+ ackEventIds: z2.array(z2.string().uuid()).min(1).max(100).optional().describe(
86
+ "The exact eventIds of the events you have handled: an acknowledgment (ACK). Reading alone acknowledges nothing, so an event comes back until you acknowledge it; after that the next batch can come. Each recipient acknowledges separately, for itself only. A Question you owe is answered (an ask with `answers`), never acknowledged away: that id is refused until it is answered."
87
+ )
88
+ }).strict();
89
+ var ContactSchema = z2.union([StartContactSchema, z2.object({ deliveryId: z2.string().uuid() }).strict(), ReceiveContactSchema]);
82
90
  var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
83
- 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 starts a new Goal of yours. 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'. Each ask is sent exactly as written, with no reading in between: a bundled ask arrives as one card, so separate questions 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; Where your work stands while under way is progress: report it with update_goal, not contact. 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'). select:'many' makes the card a checklist, for independent options they may want several of; select:'one' (the default) a pick, for alternatives. 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.";
91
+ 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 starts a new Goal of yours. Notification returns immediately; collect answers by receiving (below). On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. One question per 'ask'; several questions are several objects in 'asks'. Each ask is sent exactly as written, with no reading in between: a bundled ask arrives as one card, so separate questions are separate asks. An ask with no options that is not blocking is a report, never a claim on them, so no answer is owed and none should be awaited. A report reaches the person only when it answers something they asked you, or once the Goal is done; any other report is recorded as the Goal's progress and nobody is notified. So when the work is finished, mark the Goal done first, then send one message saying what is done and anything they need to do or check. On a Goal whose report card is still open, a report that does reach them is added to that card, with no new push. 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'). select:'many' makes the card a checklist, for independent options they may want several of; select:'one' (the default) a pick, for alternatives. The person can always answer in their own words, so never add an 'Other' option. If the person is already on a call, a contact that reaches them 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.\n\nRECEIVING: contact with no asks sends nothing and returns `events`, a limited batch of what is addressed to you (not a history page): `question`, a Question you owe (answer it with an ask whose `answers` is its questionId); `update`, something new on one of your Goals (a reply, an answer: read it with get_goal); `instruction`, a request or note sent to you. `hasMore` says more are waiting. Reading acknowledges nothing: once you have handled events, confirm their eventIds with contact({ackEventIds}), and the next batch can come. contact({}) waits up to about 45 seconds for something to arrive; contact({wait:false}) returns at once. It also lists work given to you that nobody has started (`assigned`; claim_goal starts it) and your work gone quiet (`stalled`).";
84
92
  var CreateGoalSchema = z3.object({
85
93
  outcome: z3.string().trim().min(1).max(1e4),
86
94
  /** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
@@ -100,10 +108,6 @@ var CreateGoalSchema = z3.object({
100
108
  * repo name. Delegated work inherits this from its parent Goal when omitted. */
101
109
  repo: z3.string().trim().min(1).max(200).optional()
102
110
  });
103
- var CreateGoalToolSchema = CreateGoalSchema.extend({
104
- idempotencyKey: CreateGoalSchema.shape.idempotencyKey.optional().describe("Optional. One is minted per call; pass your own only so a retry lands on the same Goal.")
105
- }).strict();
106
- 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.';
107
111
  var UpdateGoalSchema = z3.object({
108
112
  revision: z3.number().int().positive(),
109
113
  changes: z3.object({
@@ -127,7 +131,65 @@ var UpdateGoalSchema = z3.object({
127
131
  reason: z3.string().trim().min(1).max(2e3),
128
132
  operationId: z3.string().uuid().optional()
129
133
  }).strict();
130
- var UpdateGoalToolSchema = UpdateGoalSchema.omit({ operationId: true }).extend({ goalId: z3.string().uuid() }).strict();
134
+ var UpdateGoalToolSchema = z3.object({
135
+ goalId: z3.string().uuid(),
136
+ revision: UpdateGoalSchema.shape.revision,
137
+ changes: UpdateGoalSchema.shape.changes.innerType().pick({ progress: true, reviewed: true, dueAt: true, withdraw: true }).strict().refine((v) => Object.keys(v).length > 0),
138
+ reason: UpdateGoalSchema.shape.reason
139
+ }).strict();
140
+ var changedGoal = z3.string().uuid().describe("The Goal to change, as a read shows it.");
141
+ var goalTitle = z3.string().trim().min(1).max(80).describe("The Goal's short display name: one to five words, how a person refers to it out loud.");
142
+ var goalOutcome = z3.string().trim().min(1).max(1e4).describe("The full desired result. There is no third description field.");
143
+ var CreateChange = z3.object({
144
+ kind: z3.literal("create"),
145
+ title: goalTitle,
146
+ outcome: goalOutcome,
147
+ ownerId: z3.string().trim().min(1).optional().describe("Who owns the work: an agent's participant (as who_is_working shows it) or the person's. Omitted: you."),
148
+ parentGoalId: z3.string().uuid().optional().describe("The Goal it belongs under. Omitted: a root."),
149
+ sourceEntryIds: z3.array(z3.string().uuid()).max(20).optional().describe("The whole Entries the work came from, ones you can read.")
150
+ }).strict();
151
+ var EditChange = z3.object({ kind: z3.literal("edit"), goalId: changedGoal, title: goalTitle.optional(), outcome: goalOutcome.optional() }).strict();
152
+ var StateChange = z3.object({ kind: z3.literal("state"), goalId: changedGoal, state: z3.enum(["open", "completed", "canceled"]).describe(
153
+ "completed: the Goal's own work is done now (refused while required children are open); open: reopen it; canceled: it will not be done."
154
+ ) }).strict();
155
+ var AssignChange = z3.object({ kind: z3.literal("assign"), goalId: changedGoal, ownerId: z3.string().trim().min(1).describe("The new owner's participant.") }).strict();
156
+ var DeferChange = z3.object({ kind: z3.literal("defer"), goalId: changedGoal, until: z3.string().datetime({ offset: true }).nullable().describe(
157
+ "Postpone it until this instant (its owner is woken then), or null to take the postponement back."
158
+ ) }).strict();
159
+ var MoveChange = z3.object({ kind: z3.literal("move"), goalId: changedGoal, parentGoalId: z3.string().uuid().nullable().describe(
160
+ "Its new parent, or null for a root. Only this Goal moves; its new parent's other children stay."
161
+ ) }).strict();
162
+ var DependencyChange = z3.object({
163
+ kind: z3.literal("dependency"),
164
+ change: z3.enum(["add", "remove"]),
165
+ goalId: changedGoal,
166
+ dependsOnGoalId: z3.string().uuid().describe("The Goal it waits on."),
167
+ action: z3.enum(["start", "complete"]).describe("What waits: starting goalId, or completing it."),
168
+ reason: z3.string().trim().min(1).max(2e3).optional().describe("Why, as an explanation, never as policy.")
169
+ }).strict();
170
+ var read = { revision: z3.number().int().positive().optional() };
171
+ var GoalChangeSchema = z3.discriminatedUnion("kind", [
172
+ CreateChange,
173
+ EditChange.extend(read),
174
+ StateChange.extend(read),
175
+ AssignChange.extend(read),
176
+ DeferChange.extend(read),
177
+ MoveChange.extend(read),
178
+ DependencyChange
179
+ ]);
180
+ var editNames = (changes, ctx) => changes.forEach((c, i) => {
181
+ if (c.kind === "edit" && c.title === void 0 && c.outcome === void 0) {
182
+ ctx.addIssue({ code: z3.ZodIssueCode.custom, path: ["changes", i], message: "an edit supplies title, outcome, or both" });
183
+ }
184
+ });
185
+ var ManageGoalsSchema = z3.object({
186
+ requestId: z3.string().uuid().optional(),
187
+ changes: z3.array(GoalChangeSchema).min(1).max(50)
188
+ }).strict().superRefine((v, ctx) => editNames(v.changes, ctx));
189
+ var ManageGoalsToolSchema = z3.object({
190
+ changes: z3.array(z3.discriminatedUnion("kind", [CreateChange, EditChange, StateChange, AssignChange, DeferChange, MoveChange, DependencyChange])).min(1).max(50).describe("The changes, applied in order, each on its own.")
191
+ }).strict().superRefine((v, ctx) => editNames(v.changes, ctx));
192
+ var MANAGE_GOALS_DESCRIPTION = "Create, edit, assign, organize or close work. Every change here changes Goals; answering, withdrawing or asking Questions stays in contact. Each change applies independently: a refused one names why (`results[i].error`, e.g. goal_revision_conflict, goal_children_open, goal_not_joined, goal_not_found) and the rest still apply, so read every result: a partial result is never a complete success. Kinds: create (title, outcome, ownerId, parentGoalId, sourceEntryIds) returns the new Goal's id in `results[i].goalId`, in the order requested, to use in later calls; edit (title and/or outcome); state (open, completed, canceled); assign (ownerId); defer (until, or null); move (parentGoalId, or null for a root: only this Goal moves); dependency (add or remove: goalId waits on dependsOnGoalId to start or to complete). A Goal cannot be completed while its required children or dependencies remain open: finish or move them first. Finishing the children does not prove the parent's own work is done. An edit applies at the version you last read: if someone changed the Goal since, it is refused as goal_revision_conflict, so read it again (get_goal) and reconsider. You may change a Goal you own or have written on. Returns `ok` (every change applied), `results` per change (applied or failed), and `goals`, each changed Goal's id, state, title, outcome and revision.";
131
193
  var ClaimGoalSchema = z3.object({ goalId: z3.string().uuid().optional() }).strict();
132
194
  var GetGoalSchema = z3.object({
133
195
  goalId: z3.string().uuid(),
@@ -140,29 +202,35 @@ var GetGoalSchema = z3.object({
140
202
  diagnose: z3.boolean().optional()
141
203
  }).strict();
142
204
  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.";
143
- 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.`;
144
- 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).";
145
- 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.";
146
- var CheckRepliesSchema = z3.object({}).strict();
205
+ var UPDATE_GOAL_DESCRIPTION = "Report work on a Goal you are on, at an exact revision: progress, review acknowledgement, when to wake for it, and withdrawing a question of yours. What the Goal is (its title, outcome, state, owner, parent, dependencies) changes with manage_goals. 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). 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. progress says where the work stands. reviewed: true acknowledges new evidence and closes the Deliveries addressed to you on that Goal, never over an open decision (contact({ackEventIds}) does the same for an `update` event). 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. Returns the Goal as get_goal reads it, at its new revision.";
206
+ 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 it; use manage_goals (assign) 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).";
207
+ var SearchToolSchema = z3.object({
208
+ query: z3.string().trim().min(1).max(500),
209
+ types: z3.array(z3.enum(["entry", "goal", "answer"])).min(1).optional(),
210
+ goalId: z3.string().uuid().optional(),
211
+ limit: z3.number().int().min(1).max(20).optional()
212
+ }).strict();
213
+ var SEARCH_DESCRIPTION = "Search your person's history across all of their agents: Entries (what anyone said or wrote, typed or spoken on a call), Goals (by title and outcome) and Answers (found by their Question or by the words that gave them). query is words to look for; records sharing more of its words rank first, and exact names work. types narrows it to entry, goal and/or answer (default: all three). goalId searches under one Goal: its Entries, its Questions' Answers, and it and its immediate children. limit is matches per type, 1 to 20 (default 8). Read-only. Each match carries its whole saved words, its ID and its links (an Answer carries its Question, the choice made and the Entries that support it); `omitted` counts what matched but was left out, so narrow the words or add a goalId to see it. Nothing found is not proof that nothing exists; a refused search says why. Sealed (encrypted) content is never searched or returned.";
214
+ var WhoIsWorkingSchema = z3.object({}).strict();
147
215
  var AGENT_TOOLS = [
148
- { 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) },
216
+ { 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(WhoIsWorkingSchema) },
149
217
  { name: "contact", description: CONTACT_DESCRIPTION, inputSchema: CONTACT_SCHEMA },
150
- { name: "check_replies", description: CHECK_REPLIES_DESCRIPTION, inputSchema: mcpInputSchema(CheckRepliesSchema) },
151
- { name: "create_goal", description: CREATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(CreateGoalToolSchema) },
218
+ { name: "manage_goals", description: MANAGE_GOALS_DESCRIPTION, inputSchema: mcpInputSchema(ManageGoalsToolSchema) },
152
219
  { name: "claim_goal", description: CLAIM_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(ClaimGoalSchema) },
153
220
  { name: "get_goal", description: GET_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(GetGoalSchema) },
154
- { name: "update_goal", description: UPDATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(UpdateGoalToolSchema) }
221
+ { name: "update_goal", description: UPDATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(UpdateGoalToolSchema) },
222
+ { name: "search", description: SEARCH_DESCRIPTION, inputSchema: mcpInputSchema(SearchToolSchema) }
155
223
  ];
156
224
  var AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
157
225
  function serverInstructions(opts) {
158
226
  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 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.
227
+ return `On startup and after a wake, call contact({wait:false}) for the events addressed to you (acknowledge the ones you handled with contact({ackEventIds})), 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 (manage_goals). Create, edit, assign, organize or close work with manage_goals, never as a contact. Every read ends in \`next\`, the one step to take. Notifications return immediately: keep working and collect answers through claim_goal/get_goal. ${calls} Events repeat until you acknowledge them: reading alone acknowledges nothing. Report progress 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
228
 
161
229
  HOW TO ASK:
162
230
  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.
163
231
  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.
164
232
  3. \`waiting: hard\` only for a decision you are blocked on; \`waiting: none\` for a question you can keep working around.
165
- 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.
233
+ 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 \`contact({wait:false})\` or \`claim_goal\` on that wake.
166
234
  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
235
  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
236
  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\`. \`select: "many"\` makes the card a checklist (options they may want several of); \`"one"\`, the default, a pick (one rules out the others). Your ask is sent exactly as you wrote it. They can always answer in their own words, so no "Other" option.
@@ -196,17 +264,6 @@ var existingGoalChanges = [
196
264
  reason: z4.string().max(500).optional()
197
265
  }).strict()
198
266
  ];
199
- var GoalChangeSchema = z4.union([
200
- z4.object({
201
- kind: z4.literal("create"),
202
- title: z4.string().min(1).max(120),
203
- outcome: z4.string().min(1),
204
- ownerId: participant,
205
- parentGoalId: id.optional(),
206
- sourceEntryIds: z4.array(id)
207
- }).strict(),
208
- ...existingGoalChanges
209
- ]);
210
267
  var BrainGoalChangeSchema = z4.union([
211
268
  // newSession: Paigy starts a session to own it (owner, 2026-10-06); until it has, the person owns it.
212
269
  z4.object({
@@ -607,7 +664,7 @@ var AwaitItemSchema = z5.discriminatedUnion("type", [
607
664
  /** The awaited ask was REPLACED by a newer notification on its thread (e.g. a
608
665
  * post-feedback revision, #633) — the user will never answer this id. Stop
609
666
  * awaiting it; the live ask is the thread's newest turn (await that one, or
610
- * re-orient via check_replies). */
667
+ * re-orient via contact({})). */
611
668
  z5.object({
612
669
  type: z5.literal("superseded"),
613
670
  parentId: z5.string(),
@@ -1335,7 +1392,7 @@ var HostDecisionSchema = z5.object({
1335
1392
  goalId: z5.string().uuid().nullable().optional(),
1336
1393
  /** When the server last heard from the agent, as the host read it: the presence it stood back for. */
1337
1394
  seenAt: z5.string().datetime().nullable().optional(),
1338
- /** When that work last moved (`claimable.since` on `check_replies`), the fact the bound is judged on. */
1395
+ /** When that work last moved (`claimable.since` on an agent's `contact({})` read), the fact the bound is judged on. */
1339
1396
  since: z5.string().datetime().nullable().optional(),
1340
1397
  /** What the host said, in its log's own words: why it stood back, or what the take-over did. */
1341
1398
  said: z5.string().max(300).optional()
@@ -1555,7 +1612,7 @@ function sessionStartHook() {
1555
1612
  [ -f "\${HOME}/.paigy/token.json" ] || exit 0 # not paired on this machine: nothing to listen as
1556
1613
  cat <<'PAIGY'
1557
1614
  Paigy: nothing pushes answers into a session started in a terminal \u2014 an answer waits for your
1558
- next check_replies. Start your own listener now, as a BACKGROUND task whose output you watch
1615
+ next contact({ wait: false }). Start your own listener now, as a BACKGROUND task whose output you watch
1559
1616
  (your harness's monitor/background-task tool), and leave it running for this session:
1560
1617
 
1561
1618
  npx -y -p @paigy/mcp@latest paigy-listen
@@ -4,12 +4,12 @@ import {
4
4
  serverInstructions,
5
5
  sessionStartHook,
6
6
  withSessionStartHook
7
- } from "./chunk-VUKR2E24.js";
7
+ } from "./chunk-GHSB2VP6.js";
8
8
  import {
9
9
  CODEX_ENV,
10
10
  agentName,
11
11
  withCodexEnv
12
- } from "./chunk-WR2M3RME.js";
12
+ } from "./chunk-EJYXBEWN.js";
13
13
 
14
14
  // src/toolset.ts
15
15
  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.";
@@ -51,7 +51,7 @@ var IDENTITY_TOOLS = [
51
51
  },
52
52
  {
53
53
  name: "listen",
54
- description: "Am I listening for answers, and can Paigy resume this session? Takes no arguments. Returns status `listening` for harness delivery or a running listener with a ready wake adapter; `receiving` means a process is running but automatic resume is not confirmed (read its wake error or watch its output); `unpaired` means call onboard; `start` gives the exact command to run NOW as a watched background task for this session. Start it once. Codex queues into CODEX_THREAD_ID automatically when its CLI supports queue; PAIGY_ON_WAKE overrides it. A successful queue is not evidence the agent handled the answer: read check_replies and the relevant Goal, then respond with contact. This tool never subscribes or starts anything itself.",
54
+ description: "Am I listening for answers, and can Paigy resume this session? Takes no arguments. Returns status `listening` for harness delivery or a running listener with a ready wake adapter; `receiving` means a process is running but automatic resume is not confirmed (read its wake error or watch its output); `unpaired` means call onboard; `start` gives the exact command to run NOW as a watched background task for this session. Start it once. Codex queues into CODEX_THREAD_ID automatically when its CLI supports queue; PAIGY_ON_WAKE overrides it. A successful queue is not evidence the agent handled the answer: receive with contact({ wait: false }) and read the relevant Goal, then respond with contact. This tool never subscribes or starts anything itself.",
55
55
  inputSchema: mcpInputSchema(z.object({}))
56
56
  }
57
57
  ];
@@ -14,14 +14,13 @@ import {
14
14
  TOKEN_PATH,
15
15
  UnpairedError,
16
16
  acceptTriage,
17
+ acknowledge,
17
18
  agentName,
18
19
  authToken,
19
- checkReplies,
20
20
  claimGoal,
21
21
  claimSessionEnds,
22
22
  claimSessions,
23
23
  contact,
24
- createGoal,
25
24
  currentRepo,
26
25
  deleteToken,
27
26
  dismissTriage,
@@ -34,13 +33,17 @@ import {
34
33
  listConnections,
35
34
  listNotes,
36
35
  listSlots,
36
+ manageGoals,
37
37
  overrideToken,
38
38
  parseDue,
39
+ pendingEvents,
39
40
  pollToken,
40
41
  reach,
41
42
  reachAs,
42
43
  readSlot,
43
44
  readToken,
45
+ receive,
46
+ receiveContact,
44
47
  recordDecision,
45
48
  registerDelivery,
46
49
  repoFromRemote,
@@ -48,6 +51,7 @@ import {
48
51
  revokeToken,
49
52
  runTool,
50
53
  saveToken,
54
+ searchRecords,
51
55
  sessionId,
52
56
  sessionSlot,
53
57
  setClient,
@@ -62,7 +66,7 @@ import {
62
66
  whoAmI,
63
67
  whoIsWorking,
64
68
  withCodexEnv
65
- } from "./chunk-WR2M3RME.js";
69
+ } from "./chunk-EJYXBEWN.js";
66
70
  export {
67
71
  AGENT_TOOLS,
68
72
  AGENT_TOOL_NAMES,
@@ -79,14 +83,13 @@ export {
79
83
  TOKEN_PATH,
80
84
  UnpairedError,
81
85
  acceptTriage,
86
+ acknowledge,
82
87
  agentName,
83
88
  authToken,
84
- checkReplies,
85
89
  claimGoal,
86
90
  claimSessionEnds,
87
91
  claimSessions,
88
92
  contact,
89
- createGoal,
90
93
  currentRepo,
91
94
  deleteToken,
92
95
  dismissTriage,
@@ -99,13 +102,17 @@ export {
99
102
  listConnections,
100
103
  listNotes,
101
104
  listSlots,
105
+ manageGoals,
102
106
  overrideToken,
103
107
  parseDue,
108
+ pendingEvents,
104
109
  pollToken,
105
110
  reach,
106
111
  reachAs,
107
112
  readSlot,
108
113
  readToken,
114
+ receive,
115
+ receiveContact,
109
116
  recordDecision,
110
117
  registerDelivery,
111
118
  repoFromRemote,
@@ -113,6 +120,7 @@ export {
113
120
  revokeToken,
114
121
  runTool,
115
122
  saveToken,
123
+ searchRecords,
116
124
  sessionId,
117
125
  sessionSlot,
118
126
  setClient,
package/dist/enable.js CHANGED
@@ -3,9 +3,9 @@ import {
3
3
  PAIGY_TOOL_IDS,
4
4
  enablePaigyTools,
5
5
  installSessionListening
6
- } from "./chunk-J7T3EB5Y.js";
7
- import "./chunk-VUKR2E24.js";
8
- import "./chunk-WR2M3RME.js";
6
+ } from "./chunk-UAVXYNLV.js";
7
+ import "./chunk-GHSB2VP6.js";
8
+ import "./chunk-EJYXBEWN.js";
9
9
 
10
10
  // src/enable.ts
11
11
  function main() {
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  resolvePairing,
10
10
  startPairing,
11
11
  suggestedAgentName
12
- } from "./chunk-TP7DM2MP.js";
12
+ } from "./chunk-2ROD77AW.js";
13
13
  import {
14
14
  ENABLE_COMMAND,
15
15
  OnboardSchema,
@@ -18,13 +18,13 @@ import {
18
18
  SERVER_INSTRUCTIONS,
19
19
  TOOLS,
20
20
  paigyToolsAllowlisted
21
- } from "./chunk-J7T3EB5Y.js";
21
+ } from "./chunk-UAVXYNLV.js";
22
22
  import {
23
23
  decideListen,
24
24
  existingListener,
25
25
  listenerKey,
26
26
  wakeStatusPath
27
- } from "./chunk-VUKR2E24.js";
27
+ } from "./chunk-GHSB2VP6.js";
28
28
  import {
29
29
  clearSurface,
30
30
  writeSurface
@@ -45,7 +45,7 @@ import {
45
45
  setClient,
46
46
  slotName,
47
47
  whoAmI
48
- } from "./chunk-WR2M3RME.js";
48
+ } from "./chunk-EJYXBEWN.js";
49
49
 
50
50
  // src/index.ts
51
51
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
package/dist/listen.js CHANGED
@@ -10,16 +10,16 @@ import {
10
10
  removeListenMark,
11
11
  wakeStatusPath,
12
12
  writeListenMark
13
- } from "./chunk-VUKR2E24.js";
13
+ } from "./chunk-GHSB2VP6.js";
14
14
  import {
15
15
  agentName,
16
- checkReplies,
17
16
  contact,
18
17
  getGoal,
19
18
  heartbeat,
19
+ receive,
20
20
  slotName,
21
21
  subscribeWake
22
- } from "./chunk-WR2M3RME.js";
22
+ } from "./chunk-EJYXBEWN.js";
23
23
 
24
24
  // src/listen.ts
25
25
  import { createHash } from "crypto";
@@ -169,7 +169,7 @@ async function sweep(reason, goalId, bridge = new WakeBridge(process.env, wakeSt
169
169
  try {
170
170
  void Promise.resolve().then(() => heartbeat(void 0, { listening: true })).catch(() => {
171
171
  });
172
- const { deliveries: rows, stalled, review, assigned } = await checkReplies();
172
+ const { deliveries: rows, stalled, review, assigned } = await receive();
173
173
  const lead = leadOf(rows, goalId);
174
174
  const full = lead ? await contact({ deliveryId: lead.deliveryId }, { waits: false }).then((d) => "sent" in d ? null : d).catch(() => null) : null;
175
175
  const deliveries = rows.map((d) => d === lead && full ? full : d);
@@ -200,7 +200,7 @@ async function sweep(reason, goalId, bridge = new WakeBridge(process.env, wakeSt
200
200
  ...(assigned ?? []).map((g) => `assigned:${g.goalId}`),
201
201
  ...!rows.length && !reviews.length && !assigned?.length ? (stalled ?? []).map((g) => `stalled:${g.goalId}:${g.since}`) : []
202
202
  ];
203
- const message = `Paigy has waiting work${goalId ? ` on Goal ${goalId}` : ""}. Read check_replies and the relevant Goal conversation, handle new user input, and acknowledge only evidence actually handled. Preserve this session's current task; do not start unrelated old work. This wake is not new authorization.`;
203
+ const message = `Paigy has waiting work${goalId ? ` on Goal ${goalId}` : ""}. Read contact({ wait: false }) and the relevant Goal conversation, handle new user input, and acknowledge (contact({ ackEventIds })) only events actually handled. Preserve this session's current task; do not start unrelated old work. This wake is not new authorization.`;
204
204
  await bridge.deliver(fingerprints, message, launchEnv(waiting, reason, goalId));
205
205
  } else if (bridge.status().target.kind === "codex") {
206
206
  await bridge.deliver([], "", {});
package/dist/onboard.js CHANGED
@@ -3,13 +3,13 @@ import {
3
3
  resolvePairing,
4
4
  startPairing,
5
5
  suggestedAgentName
6
- } from "./chunk-TP7DM2MP.js";
6
+ } from "./chunk-2ROD77AW.js";
7
7
  import {
8
8
  autoConfigureClients,
9
9
  claudeInstallHint,
10
10
  openBrowser
11
- } from "./chunk-J7T3EB5Y.js";
12
- import "./chunk-VUKR2E24.js";
11
+ } from "./chunk-UAVXYNLV.js";
12
+ import "./chunk-GHSB2VP6.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-WR2M3RME.js";
23
+ } from "./chunk-EJYXBEWN.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-WR2M3RME.js";
4
+ } from "./chunk-EJYXBEWN.js";
5
5
 
6
6
  // src/slot.ts
7
7
  process.stdout.write(agentName());
package/dist/stalled.js CHANGED
@@ -15,7 +15,7 @@ function stopReply(stalled, event = "Stop") {
15
15
  if (!stalled.length) return null;
16
16
  const list = stalled.slice(0, 8).map((g) => `"${g.title}" (${g.goalId})`).join(", ");
17
17
  const more = stalled.length > 8 ? ` and ${stalled.length - 8} more` : "";
18
- const reason = `Before you stop: ${stalled.length} of your Paigy Goals have had no progress for 3 days: ${list}${more}. For each, update_goal with progress, finish it, or cancel it.`;
18
+ const reason = `Before you stop: ${stalled.length} of your Paigy Goals have had no progress for 3 days: ${list}${more}. For each, update_goal with progress, or finish or cancel it with manage_goals.`;
19
19
  return event === "AfterAgent" ? { decision: "block", reason, systemMessage: reason } : { decision: "block", reason };
20
20
  }
21
21
  async function main() {
@@ -29,8 +29,8 @@ 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-EKLTLANV.js");
33
- const reply = stopReply((await checkReplies()).stalled ?? [], input.hook_event_name ?? "Stop");
32
+ const { receive } = await import("./dist-NMHU2UNG.js");
33
+ const reply = stopReply((await receive()).stalled ?? [], input.hook_event_name ?? "Stop");
34
34
  if (!reply) return;
35
35
  mkdirSync(dir, { recursive: true });
36
36
  writeFileSync(mark, "");
@@ -7,7 +7,7 @@ import {
7
7
  readToken,
8
8
  sessionSlot,
9
9
  slotName
10
- } from "./chunk-WR2M3RME.js";
10
+ } from "./chunk-EJYXBEWN.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.24",
3
+ "version": "0.40.25",
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",