@paigy/mcp 0.40.43 → 0.40.44

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.
@@ -7,7 +7,7 @@ import {
7
7
  saveToken,
8
8
  setIdentity,
9
9
  sleep
10
- } from "./chunk-ULCS3CYO.js";
10
+ } from "./chunk-5OKHG3FO.js";
11
11
 
12
12
  // src/identity.ts
13
13
  var CLIENT_LABELS = {
@@ -1653,11 +1653,11 @@ var CompleteChangeSchema = CompletionSchema.omit({ causedBy: true, shipped: true
1653
1653
  var ASKING_RULES = `HOW TO ASK:
1654
1654
  1. One question per object in \`questions\`, so each is answered on its own.
1655
1655
  2. \`goalId\` names the existing Goal it is about (create one with manage_goals first); the answer comes back there.
1656
- 3. \`workItBlocks\` only when you are truly blocked: it asks for a call.
1656
+ 3. They asked you to call? \`userExplicitlyRequested:"call"\`; \`workItBlocks\` rings when blocked.
1657
1657
  4. No while-loops. While any Call is open, even one your question joined from their items, keep receiving through empty windows; otherwise stop at one: the answer reaches you on the Goal. Yield only with a listener or wakeup.
1658
1658
  5. One question, one row: never re-send one still waiting; take back one that no longer matters with \`withdrawQuestionIds\`.
1659
1659
  6. Read the Goal before asking; never re-ask what was answered or shipped. \`replied\` means they chose no option: it is still yours to settle.
1660
- 7. Give options when you have them. Each label names its choice in full (never "Option 2"); never add "Other". \`pickMode\`: one (default), many, rank.
1660
+ 7. Give options when you have them. Each label names its choice in full (never "Option 2"); never add "Other".
1661
1661
  8. A diagnosis says when, why and how it happens, then proposes one fix.
1662
1662
  9. Give a report its \`reply\`: one line in THEIR voice for THAT report, so you learn whether they verified it or only received it. Required when it is delivered or its Goal is done.`;
1663
1663
  function draft2020(node) {
@@ -1699,7 +1699,7 @@ var reason = z5.string().trim().min(1).max(500).describe("Why, as an explanation
1699
1699
  var actionFits = (b) => b.blocked.type === "goal" === (b.action !== "answer");
1700
1700
  var ACTION_FITS = "a goal is blocked from start or complete; a question from answer";
1701
1701
  var userExplicitlyRequested = z5.enum(["any", "call"]).optional().describe(
1702
- `Only what the person said about THIS message, in so many words: "any" when they asked you to send it to them ("send it to my phone", "message me the result"), "call" when they asked to be called about it. It is then delivered (a call rings for the whole contact, and carries everything waiting on them); omitted, an update is only recorded as the Goal's progress unless the person needs it. Never set it to make a message louder.`
1702
+ 'What the person asked for about THIS message, in so many words: "any" when they asked you to send it to them ("send it to my phone", "message me the result"), "call" when they asked to be called about it. It is then delivered (a call rings for the whole contact, and carries everything waiting on them); omitted, an update is only recorded as the Goal\'s progress unless the person needs it. ALSO "any" whenever this message directly answers a question they asked you, wherever they asked it -- on a call, in a reply, in passing: their question is the request, so it is delivered every time, even with no pending question of theirs on file and even if they never said to send it. A correction of something you or another agent told them is such an answer. Never set it to make a message louder. This is the field that rings a phone: "call" here is how you call them when they asked you to, and `help` has the whole mechanism.'
1703
1703
  );
1704
1704
  var units = z5.array(UnitInputSchema).optional().describe(
1705
1705
  `The context this message needs, as up to ${UNITS_MAX} units in reading order, each {title, body}: a title of at most ${UNIT_TITLE_MAX} characters and a body of at most ${UNIT_BODY_MAX}. One idea per unit: context they may open, never something they must hear. Leave it out when the main text says it all. Over a cap is refused by name (ask_too_long, too_many_units, unit_title_too_long, unit_body_too_long) and nothing is sent.`
@@ -1919,13 +1919,13 @@ LENGTH: the question, message or answer is the one point, at most ${ASK_MAX} cha
1919
1919
  UPDATES ask nothing. One reaches the person only when they asked for it, it answers something they said, or its Goal is done; else it is kept as progress. One that reaches them joins an open report card or a live call.
1920
1920
 
1921
1921
  RECEIVING: contact({}) waits ~45 s; contact({wait:false}) returns \`events\` at once. Confirm the ones you handled with contact({ackEventIds}).`;
1922
- var GUIDE = '# Paigy\n\nThe user is remote. Reach them only through Paigy\'s tools, never the terminal. The server\'s own instructions and each tool\'s description are the contract: read them. Every read ends in `next`, the one step to take.\n\nThis same guide is inside the MCP: the `help` tool (whole, or one `topic`) and the `/mcp__paigy__help` prompt. `/mcp__paigy__welcome` starts a session.\n\nHosted (no machine): skip Pairing and Listening; answers wake you.\n\n## Pairing\n\n- Call `configure` first. With a device credential on this machine it hatches you at once.\n- Otherwise print its `user_message` to the user, then do what the result says:\n - `awaiting_approval`: call `configure` again with its `device_code` in the same turn. On `pending`, call again.\n - `awaiting_confirmation`: end your turn with the code as your whole reply; call `configure` with the `device_code` next turn. Calling it in the same turn drops the code.\n- When it returns `enable_command`, print it for the user to run. Do not run it yourself.\n- `configure({ logOut: true })` logs out only this agent, and only when the user asks.\n\n## Listening\n\n- Call `listen` once per session.\n - `listening`: done.\n - `receiving`: a listener runs but cannot wake you. Do not start a second.\n - `unpaired`: call `configure`.\n - `start`: run its `command` exactly, now.\n- Claude Code: run it with the **Monitor** tool, never a background Bash command. Start it again whenever the monitor expires.\n- Codex: run its `command` as a long-running background process. It queues each answer into this thread (`CODEX_THREAD_ID`).\n- Grok: run it with the **monitor** tool, never a background shell command. Start it again whenever the monitor expires.\n- With no `listen` tool, run `npx -y -p @paigy/mcp@latest paigy-listen --brief` the same way.\n- One listener per session. A `paigy-listen` in `ps` belongs to another session; ask `listen`, never `ps`.\n- Each line it prints is work: receive, read the Goal, act.\n\n## Receiving\n\n- On startup and after every wake or listener line: `contact({ wait: false })`.\n- Events: `question` (you owe an answer: send `answers`), `update` (something new on your Goal: an answer, a reply, a review), `instruction` (work sent to you).\n- Read the Goal with `get_goals` before acting. An answer arrives as an `update` event and lands on the question\'s Goal. A question reading `replied` means they chose no option: settle it from their words.\n- Acknowledge each event you handled: `contact({ ackEventIds })`. Reading acknowledges nothing; events repeat until acknowledged. A `question` event is answered, never acknowledged away.\n- `contact({ wait: true })` waits about 45 s. On `expired`, wait again without re-sending, or stop. No while-loops: while any Call is open, keep receiving through empty windows; otherwise stop at one empty window.\n\n## Asking\n\n- One question per object in `questions`, at most 80 characters. Context goes in `units`: at most 12, title \u226450, body \u2264200, one idea each.\n- `goalId` names an existing Goal. Create it with `manage_goals` first.\n- Give options when you have them: up to 6, label \u226450 naming the choice in full, `hint` \u2264200 saying what it does. Never "Other" or "Option 2". `pickMode`: `one` (default), `many`, `rank`.\n- `workItBlocks` only when you are truly stopped: it asks for a call.\n- Read the Goal before asking. Never re-ask an open or answered question, or one whose work shipped.\n- Take back a question that no longer matters: `withdrawQuestionIds`.\n- An answer you give (`answers`) is at most 80 characters.\n\n## Updates and completions\n\n- An update (`updates`: `message` \u226480, `goalIds`) asks nothing. It reaches the user only when they asked for it, when it answers something they said, or when its Goal is done; otherwise it stays on the Goal as progress.\n- Updates never complete a Goal.\n- One load-bearing point per report: `message` is the point they must not miss, and a second point that would go wrong or stay unclear later if they missed it is its own update. `units` are context they may open, never something they must hear.\n- `reply` is the button in the user\'s voice instead of "Got it", \u226450 characters ("Sounds good, I\'ll test later"). Required (`reply_required`) when the update is delivered on request (`userExplicitlyRequested`) or any Goal it names is already done.\n- Set `userExplicitlyRequested` only to relay the user\'s own words: `"any"` for "send it to me", `"call"` for "call me". Never to make a message louder.\n- Finish with a completion (`contact.completions`, or `manage_goals` kind `complete`): `goalId`, `message` \u226450, `reply` \u226450, optional `units`, optional next-step `options` {label, hint}. It closes and reports in one step. Say only what your evidence supports.\n- Read `results`: each item applies on its own, and a failed one names why.\n\n## Goals\n\n- `manage_goals` `changes`, applied in order:\n - `create`: `title` (1-5 words) and `outcome` (one paragraph, \u2264500). More work is child Goals: `parentGoalId`. New ids come back in `results[i].goalId`.\n - `assign`: `ownerId`, a participant as `check_activity` shows it.\n - `defer`: `until`. You are woken when it passes. This is how you follow up later.\n - `dependency`: `add`/`remove`; `goalId` waits on `dependsOnGoalId` to `start` or `complete`.\n - `edit`, `move`, `state` (open, completed, canceled).\n- Any open question on a Goal, anyone\'s, blocks its completion (`goal_blocked`). Before completing, withdraw yours, finish or move its required children, and clear its dependencies.\n- On `goal_revision_conflict`, read the Goal again and reconsider.\n- Before overlapping work, read `check_activity` and the Goal\'s other contributors. Every Goal of your person is open to you; your first write puts you on it.\n- `search` finds earlier words, Goals and answers. Nothing found is not proof of absence.\n\n## Calls\n\n- Call only when the user asked: `userExplicitlyRequested: "call"` on that item. A question with `workItBlocks` may ring too.\n- Any contact while the user is on a call joins that call (`joinedCall` in `results`). Several questions arrive as one call.\n- While any Call is open, keep receiving.\n- An open Call Delivery is not proof it rang. `get_delivery` reads a Call\'s state, and with `transcript: true` what was said.\n';
1923
- var HELP_TOPICS = ["pairing", "listening", "receiving", "asking", "updates", "goals", "calls"];
1922
+ var GUIDE = '# Paigy\n\nThe user is remote. Reach them only through Paigy\'s tools, never the terminal. The server\'s own instructions and each tool\'s description are the contract: read them. Every read ends in `next`, the one step to take.\n\nThis same guide is inside the MCP: the `help` tool (whole, or one `topic`) and the `/mcp__paigy__help` prompt. `/mcp__paigy__welcome` starts a session; `/mcp__paigy__organize` sets up a person\'s work as a board.\n\nHosted (no machine): skip Pairing and Listening; answers wake you.\n\n## Pairing\n\n- Call `configure` first. With a device credential on this machine it hatches you at once.\n- Otherwise print its `user_message` to the user, then do what the result says:\n - `awaiting_approval`: call `configure` again with its `device_code` in the same turn. On `pending`, call again.\n - `awaiting_confirmation`: end your turn with the code as your whole reply; call `configure` with the `device_code` next turn. Calling it in the same turn drops the code.\n- When it returns `enable_command`, print it for the user to run. Do not run it yourself.\n- `configure({ logOut: true })` logs out only this agent, and only when the user asks.\n\n## Listening\n\n- Call `listen` once per session.\n - `listening`: done.\n - `receiving`: a listener runs but cannot wake you. Do not start a second.\n - `unpaired`: call `configure`.\n - `start`: run its `command` exactly, now.\n- Claude Code: run it with the **Monitor** tool, never a background Bash command. Start it again whenever the monitor expires.\n- Codex: run its `command` as a long-running background process. It queues each answer into this thread (`CODEX_THREAD_ID`).\n- Grok: run it with the **monitor** tool, never a background shell command. Start it again whenever the monitor expires.\n- With no `listen` tool, run `npx -y -p @paigy/mcp@latest paigy-listen --brief` the same way.\n- One listener per session. A `paigy-listen` in `ps` belongs to another session; ask `listen`, never `ps`.\n- Each line it prints is work: receive, read the Goal, act.\n\n## Receiving\n\n- On startup and after every wake or listener line: `contact({ wait: false })`.\n- Events: `question` (you owe an answer: send `answers`), `update` (something new on your Goal: an answer, a reply, a review), `instruction` (work sent to you).\n- Read the Goal with `get_goals` before acting. An answer arrives as an `update` event and lands on the question\'s Goal. A question reading `replied` means they chose no option: settle it from their words.\n- Acknowledge each event you handled: `contact({ ackEventIds })`. Reading acknowledges nothing; events repeat until acknowledged. A `question` event is answered, never acknowledged away.\n- `contact({ wait: true })` waits about 45 s. On `expired`, wait again without re-sending, or stop. No while-loops: while any Call is open, keep receiving through empty windows; otherwise stop at one empty window.\n\n## Asking\n\n- One question per object in `questions`, at most 80 characters. Context goes in `units`: at most 12, title \u226450, body \u2264200, one idea each.\n- `goalId` names an existing Goal. Create it with `manage_goals` first.\n- Give options when you have them: up to 6, label \u226450 naming the choice in full, `hint` \u2264200 saying what it does. Never "Other" or "Option 2". `pickMode`: `one` (default), `many`, `rank`.\n- `workItBlocks` only when you are truly stopped: it asks for a call.\n- Read the Goal before asking. Never re-ask an open or answered question, or one whose work shipped.\n- Take back a question that no longer matters: `withdrawQuestionIds`.\n- An answer you give (`answers`) is at most 80 characters.\n\n## Updates and completions\n\n- An update (`updates`: `message` \u226480, `goalIds`) asks nothing. It reaches the user only when they asked for it, when it answers something they said, or when its Goal is done; otherwise it stays on the Goal as progress.\n- Updates never complete a Goal.\n- One load-bearing point per report: `message` is the point they must not miss, and a second point that would go wrong or stay unclear later if they missed it is its own update. `units` are context they may open, never something they must hear.\n- `reply` is the button in the user\'s voice instead of "Got it", \u226450 characters ("Sounds good, I\'ll test later"). Required (`reply_required`) when the update is delivered on request (`userExplicitlyRequested`) or any Goal it names is already done.\n- Set `userExplicitlyRequested` only to relay the user\'s own words: `"any"` for "send it to me", `"call"` for "call me". Never to make a message louder.\n- Finish with a completion (`contact.completions`, or `manage_goals` kind `complete`): `goalId`, `message` \u226450, `reply` \u226450, optional `units`, optional next-step `options` {label, hint}. It closes and reports in one step. Say only what your evidence supports.\n- Read `results`: each item applies on its own, and a failed one names why.\n\n## Goals\n\n- `manage_goals` `changes`, applied in order:\n - `create`: `title` (1-5 words) and `outcome` (one paragraph, \u2264500). More work is child Goals: `parentGoalId`. New ids come back in `results[i].goalId`.\n - `assign`: `ownerId`, a participant as `check_activity` shows it.\n - `defer`: `until`. You are woken when it passes. This is how you follow up later.\n - `dependency`: `add`/`remove`; `goalId` waits on `dependsOnGoalId` to `start` or `complete`.\n - `edit`, `move`, `state` (open, completed, canceled).\n- Shape your work as projects with steps. Create a project under its area (`create` with `parentGoalId` = the area), then one child Goal per step; complete each step as it lands. A top-level Goal that has children is an area, not a project: never hang steps straight off a top-level Goal. The person\'s Work tab shows a project\'s progress as its steps, "3 of 5 steps done"; a project with no steps shows none. When you touch an older Goal of yours that sits flat or under a finished project, move it into that shape (`move`). A flat Goal is still valid; never reshape another agent\'s Goals.\n- Any open question on a Goal, anyone\'s, blocks its completion (`goal_blocked`). Before completing, withdraw yours, finish or move its required children, and clear its dependencies.\n- On `goal_revision_conflict`, read the Goal again and reconsider.\n- Before overlapping work, read `check_activity` and the Goal\'s other contributors. Every Goal of your person is open to you; your first write puts you on it.\n- `search` finds earlier words, Goals and answers. Nothing found is not proof of absence.\n\n## Organize\n\nWhen the person asks you to set up or organize their work ("set up my project", "help me manage all this", several products or parts to juggle), turn it into a board they can run like a team. Their Work tab shows it.\n\n- Learn the work first: read the repo and their Goals (`get_goals`, `search`). Ask only what you cannot read, at most three questions, each its own question.\n- The shape: an area per product or business (a top-level Goal); under it a project per part that moves on its own (landing page, graphics, web app, mobile app, launch); under each project its steps, three to seven, each an outcome someone can finish and check.\n- Propose before you build. Create one Goal for the setup itself, then ask one question on it: the tree in `units` (one unit per project: its title, then its steps), options "Build it" and "Change it: I\'ll say how". Create nothing else until they answer.\n- On "Build it", one `manage_goals` call per level, since a parent\'s id comes back in `results[i].goalId`: the areas, then their projects (`parentGoalId`), then every step; then a `dependency` where a project waits on another (web on the design). Never copy work that already has a Goal: yours move into the tree (`move`); another agent\'s stay where they are, named in the proposal. Then complete the setup Goal.\n- Report how to run it, and hand over one brief per project for the person to paste as that session\'s first message. Each brief names the project\'s Goal (title and id) and says: call `listen` and keep the listener running; receive with `contact({ wait: false })` at startup and after every wake; work the steps in order and complete each as it lands; report progress as updates on the project; ask anything you are blocked on through Paigy, as a question on that step\'s Goal (`workItBlocks` only when it truly stops you), never in the terminal.\n- One session per project, plus one lead session that keeps the board current. Any agent, Claude, Codex or another, can pick up any Goal; `check_activity` shows who is on what. A subagent inside a session shares that session\'s identity and listener: it may ask through Paigy, but never starts a listener of its own.\n- Keep it current as you work: complete each step as it lands and add a step when new work appears. Never reshape another agent\'s Goals.\n\n## Calls\n\n- Call only when the user asked: `userExplicitlyRequested: "call"` on that item. A question with `workItBlocks` may ring too.\n- Any contact while the user is on a call joins that call (`joinedCall` in `results`). Several questions arrive as one call.\n- While any Call is open, keep receiving.\n- An open Call Delivery is not proof it rang. `get_delivery` reads a Call\'s state, and with `transcript: true` what was said.\n';
1923
+ var HELP_TOPICS = ["pairing", "listening", "receiving", "asking", "updates", "goals", "organize", "calls"];
1924
1924
  var unknownTopic = (topic) => `unknown_topic: ${JSON.stringify(topic)} is not a section of the guide. Topics: ${HELP_TOPICS.join(", ")}; or omit topic for the whole guide.`;
1925
1925
  var HelpSchema = z6.object({
1926
1926
  topic: z6.enum(HELP_TOPICS, { errorMap: (_issue, ctx) => ({ message: unknownTopic(ctx.data) }) }).optional().describe("One section of the guide. Omit it for the whole guide.")
1927
1927
  }).strict();
1928
- var HELP_DESCRIPTION = "Start here: how to use Paigy, in full \u2014 pairing, listening, receiving, asking, updates and completions, Goals and calls. Pass a topic for one section. Read-only; needs no setup.";
1928
+ var HELP_DESCRIPTION = "Start here: how to use Paigy, in full \u2014 pairing, listening, receiving, asking, updates and completions, Goals, organizing work, and calls. Pass a topic for one section. Read-only; needs no setup.";
1929
1929
  function section(topic) {
1930
1930
  const parts = GUIDE.split(/^(?=## )/m);
1931
1931
  const found = parts.find((p) => p.slice(3).toLowerCase().startsWith(topic));
@@ -1946,7 +1946,8 @@ var AGENT_PROMPTS = [
1946
1946
  description: "How to use Paigy: the whole guide, or one topic of it.",
1947
1947
  arguments: [{ name: "topic", description: `One section: ${HELP_TOPICS.join(", ")}. Omit it for the whole guide.`, required: false }]
1948
1948
  },
1949
- { name: "welcome", description: "Start here: who you are on Paigy, then listen and collect your open work." }
1949
+ { name: "welcome", description: "Start here: who you are on Paigy, then listen and collect your open work." },
1950
+ { name: "organize", description: "Set up the person's work as a board: areas, projects and steps, proposed on a card before anything is built." }
1950
1951
  ];
1951
1952
  var CreateGoalSchema = z7.object({
1952
1953
  outcome: z7.string().trim().min(1).max(1e4),
@@ -1995,7 +1996,7 @@ var CreateChange = z7.object({
1995
1996
  title: goalTitle,
1996
1997
  outcome: goalOutcome,
1997
1998
  ownerId: z7.string().trim().min(1).optional().describe("Who owns the work: an agent's participant (as check_activity shows it) or the person's. Omitted: you."),
1998
- parentGoalId: z7.string().uuid().optional().describe("The Goal it belongs under. Omitted: a root."),
1999
+ parentGoalId: z7.string().uuid().optional().describe("The Goal it belongs under. Omitted: a root. A project with several parts: create it, then one child per step under it; the person's Work tab counts a project's progress in those steps."),
1999
2000
  sourceEntryIds: z7.array(z7.string().uuid()).max(20).optional().describe("The whole Entries the work came from, ones you can read.")
2000
2001
  }).strict();
2001
2002
  var EditChange = z7.object({ kind: z7.literal("edit"), goalId: changedGoal, title: goalTitle.optional(), outcome: goalOutcome.optional() }).strict();
@@ -2040,7 +2041,7 @@ var ManageGoalsSchema = z7.object({
2040
2041
  var ManageGoalsToolSchema = z7.object({
2041
2042
  changes: z7.array(z7.discriminatedUnion("kind", [CreateChange, CompleteChangeSchema, EditChange, StateChange, AssignChange, DeferChange, MoveChange, DependencyChange])).min(1).max(50).describe("The changes, applied in order, each on its own.")
2042
2043
  }).strict().superRefine((v, ctx) => editNames(v.changes, ctx));
2043
- 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_found) and the rest still apply, so read every result: a partial result is never a complete success. Kinds: complete (goalId, message capped at 50 characters, units, reply, optional next-step options {label, hint}) closes and reports atomically, exactly as contact.completions; 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 (an outcome is one paragraph, at most 500 characters: split more into child Goals with parentGoalId); edit (title and/or outcome); state (open, completed, canceled); assign (ownerId); defer (until: the Goal waits until then and its owner is woken when it passes; null takes it back); 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_goals) and reconsider. You may change any Goal of your person; a change puts you on it. Returns `ok` (every change applied), `results` per change (applied or failed), and `goals`, each changed Goal's id, state, title, outcome and revision.";
2044
+ var MANAGE_GOALS_DESCRIPTION = "Create, edit, assign, organize or close work. SHAPE IT AS PROJECTS WITH STEPS: create a project under its area (parentGoalId; a top-level Goal with children is an area), then each part as its child Goal, so the person sees N of M steps. Move a flat or orphaned Goal of yours into that shape when you touch it; a flat Goal is valid. 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_found) and the rest still apply, so read every result: a partial result is never a complete success. Kinds: complete (goalId, message capped at 50 characters, units, reply, optional next-step options {label, hint}) closes and reports atomically, exactly as contact.completions; 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 (an outcome is one paragraph, at most 500 characters: split more into child Goals with parentGoalId); edit (title and/or outcome); state (open, completed, canceled); assign (ownerId); defer (until: the Goal waits until then and its owner is woken when it passes; null takes it back); 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_goals) and reconsider. You may change any Goal of your person; a change puts you on it. Returns `ok` (every change applied), `results` per change (applied or failed), and `goals`, each changed Goal's id, state, title, outcome and revision.";
2044
2045
  var GetGoalsSchema = z7.object({
2045
2046
  goalIds: z7.array(z7.string().uuid()).min(1).max(10).describe("The Goals to read, in order; one to ten IDs."),
2046
2047
  /** Every entry in full. Without it the read carries the person's words, open questions and your
@@ -2188,6 +2189,10 @@ var BrainMessageSchema = z8.union([
2188
2189
  // WHAT IT ASKS THE AGENT, IN UNDER TEN WORDS (owner, 2026-10-15: "Yes, add the summary"): the talker's own, stored
2189
2190
  // as the sent Entry's summary, which the recap's "Sent to <agent>: …" row shows. Absent on results written before it.
2190
2191
  summary: z8.string().optional(),
2192
+ // THE PERSON'S LINES IT CARRIES, IN THEIR WORDS (owner, 2026-11-05: "just deliver my exact words to the agent"): the
2193
+ // `entries` keys of those lines, each relayed whole on the agent's Delivery (`context = false`), where the rest of
2194
+ // `entryKeys` ride as context. Absent on results written before it, and on a message that carries none.
2195
+ carries: z8.array(key).optional(),
2191
2196
  // THE PERSON ASKED TO BE CALLED BACK ONCE THIS AGENT'S WORK IS DONE (turns.md, step 0): the agent owes
2192
2197
  // the call, and its next contact about it rings them (`public.callbacks`).
2193
2198
  callback: z8.literal(true).optional()
@@ -2251,8 +2256,11 @@ var TalkerReplySchema = z9.object({
2251
2256
  * "Yes, add the summary"), which the recap's "Sent to <agent>: …" row shows; `about` names
2252
2257
  * handles; `blocks` is the item a question to an agent must be answered before ("" for none);
2253
2258
  * `callback` is true on a message to the agent doing work the person asked to be called back about
2254
- * once it is done (false otherwise): that agent then owes the call (turns.md, step 0). */
2255
- messages: z9.array(z9.object({ key: str3, to: str3, sentences: strs2, text: str3, summary: str3, about: strs2, blocks: str3, callback: z9.boolean() }).strict()),
2259
+ * once it is done (false otherwise): that agent then owes the call (turns.md, step 0). `lines` are the
2260
+ * person's line handles a message to an agent carries ([] for none): the agent receives those lines, in their
2261
+ * words, and `text` is then only what the talker adds beyond them (owner, 2026-11-05: "just deliver my exact
2262
+ * words to the agent, keeping the summary as a way for me to navigate them more easily"). */
2263
+ messages: z9.array(z9.object({ key: str3, to: str3, sentences: strs2, text: str3, summary: str3, about: strs2, blocks: str3, callback: z9.boolean(), lines: strs2.optional() }).strict()),
2256
2264
  /** What the call does next: listen, hold (the person asked for a moment) or end (they asked to). */
2257
2265
  then: z9.enum(["listen", "hold", "end"]),
2258
2266
  /** The person's replies to items (replies, owner 2026-10-07): the item's handle, the chosen option
@@ -2270,9 +2278,14 @@ var TalkerReplySchema = z9.object({
2270
2278
  /** The handle of the item to explain ("" for none). */
2271
2279
  explain: str3
2272
2280
  }).strict();
2273
- var TALKER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
2281
+ var TALKER_REPLY_JSON_SCHEMA = withEveryMessageField((({ $schema: _, ...rest }) => rest)(
2274
2282
  zodToJsonSchema(TalkerReplySchema, { $refStrategy: "none" })
2275
- );
2283
+ ));
2284
+ function withEveryMessageField(schema) {
2285
+ const item = schema.properties?.messages?.items;
2286
+ if (item?.properties) item.required = Object.keys(item.properties);
2287
+ return schema;
2288
+ }
2276
2289
  var str4 = z10.string();
2277
2290
  var strs3 = z10.array(z10.string());
2278
2291
  var FilerReplySchema = z10.object({
@@ -3006,11 +3019,41 @@ var InboxItemSchema = z14.object({
3006
3019
  rev: z14.string().optional()
3007
3020
  });
3008
3021
  var APNS_TOKEN_RE = /^[0-9a-fA-F]{64}$/;
3022
+ var PushDeviceIdSchema = z14.string().regex(/^[A-Za-z0-9._:-]{1,200}$/, "not an install id");
3023
+ var PushDeviceSchema = z14.object({
3024
+ deviceId: z14.string(),
3025
+ platform: z14.enum(["ios", "android"]),
3026
+ label: z14.string().nullable(),
3027
+ model: z14.string().nullable(),
3028
+ /** Calls and banners go here. */
3029
+ rings: z14.boolean(),
3030
+ /** It has a token a call can ring. */
3031
+ ringable: z14.boolean(),
3032
+ chosenAt: z14.string().nullable(),
3033
+ updatedAt: z14.string()
3034
+ });
3035
+ var PushTokenDeviceSchema = z14.object({
3036
+ deviceId: z14.string(),
3037
+ rings: z14.boolean(),
3038
+ ask: z14.boolean(),
3039
+ ringsOn: z14.object({ deviceId: z14.string().nullable(), label: z14.string().nullable() }).nullable()
3040
+ });
3041
+ var PushRingsSchema = z14.object({ rings: z14.boolean() });
3009
3042
  var PushTokenSchema = z14.object({
3010
3043
  voipToken: z14.string().min(1).optional(),
3011
3044
  alertToken: z14.string().min(1).optional(),
3012
3045
  fcmToken: z14.string().min(1).optional(),
3013
- platform: z14.enum(["ios", "android"])
3046
+ platform: z14.enum(["ios", "android"]),
3047
+ /** WHICH INSTALL POSTED IT (owner, 2026-11-05: "Ask on each new device"). Absent from every
3048
+ * build before it, and then the post is exactly what it always was: the one row per account.
3049
+ * With it, the install has its own row and calls ring only where the person chose. */
3050
+ deviceId: PushDeviceIdSchema.optional(),
3051
+ /** The install's name as the person knows it ("iPhone", "iPad"), and its model. */
3052
+ label: z14.string().trim().min(1).max(80).optional(),
3053
+ model: z14.string().trim().min(1).max(80).optional(),
3054
+ /** The person's choice, when the post carries it; absent, the install keeps what it has (and
3055
+ * its first ring token decides once: it rings unless another install already does). */
3056
+ rings: z14.boolean().optional()
3014
3057
  }).superRefine((v, ctx) => {
3015
3058
  if (v.platform !== "ios") return;
3016
3059
  for (const field of ["voipToken", "alertToken"]) {
@@ -3120,7 +3163,11 @@ var GoalLatestSchema = z14.object({
3120
3163
  titles: z14.array(z14.string()).optional(),
3121
3164
  /** A PROGRESS NOTE'S ONE POINT ("An agent's progress note -> maybe let's cap it at 200 chars"): the
3122
3165
  * update's own line, before its units; the row caps it at 200. Absent on anything else. */
3123
- line: z14.string().optional()
3166
+ line: z14.string().optional(),
3167
+ /** THE PERSON'S ENTRIES IT CARRIED TO AN AGENT, IN THEIR WORDS (owner, 2026-11-05: "paraphrasing shouldn't change
3168
+ * attribution in the UI"): on a message Paigy sent an agent, the person's Entries its Delivery relays whole. A row
3169
+ * draws it as theirs. Absent on anything else, and from a door older than it. */
3170
+ carries: z14.array(z14.string()).optional()
3124
3171
  });
3125
3172
  var HistoryWorkSchema = z14.object({
3126
3173
  id: z14.string(),
@@ -3389,6 +3436,11 @@ var QueueItemSchema = z14.object({
3389
3436
  * and a door older than 20261013045542 need not spell them. */
3390
3437
  yours: GoalLatestSchema.nullable().optional(),
3391
3438
  theirs: GoalLatestSchema.nullable().optional(),
3439
+ /** ITS STEPS, COUNTED IN THE DATABASE (owner, 2026-11-04: "Count on the server, and agents make steps"): its
3440
+ * direct children that were not cancelled, and how many of them are done. The queue holds only a recent
3441
+ * slice of finished work, so counting `childGoalIds` read "0 of 1" for a project with five done. Absent
3442
+ * from a door older than this field, or when the count failed: the app then counts what it holds. */
3443
+ steps: z14.object({ total: z14.number().int().nonnegative(), done: z14.number().int().nonnegative() }).nullable().optional(),
3392
3444
  /** WHAT THIS ROW IS, IN TWELVE CHARACTERS (#2928) — the hash of every other field on it, stamped
3393
3445
  * by the one projection that builds the row (`apps/api/src/goal/queue.ts`). It is how the
3394
3446
  * incremental read knows a row has not moved: the phone echoes back the revs it holds
@@ -3744,6 +3796,12 @@ var CallRecapSchema = z14.object({
3744
3796
  * summary (Brain, 2026-10-14). Optional: absent from an API before it. */
3745
3797
  filedOn: z14.array(z14.object({ goalId: z14.string().uuid(), title: z14.string() })).optional(),
3746
3798
  recorded: z14.boolean().optional(),
3799
+ /** On a `said` row: the agents the call SENT those words to, from the sending action's own
3800
+ * `about.agents` (20261106220220). The review's receipt is drawn from this and from the row's
3801
+ * own `requestId`, never from the asker of a question the words happened to sit under: on call
3802
+ * f9670f67 that asker was Interface while the send named Comms. `[]` or absent means nothing
3803
+ * was sent for that row, which is why the receipt is positive-only. */
3804
+ sentTo: z14.array(z14.object({ id: z14.string().nullable(), name: z14.string() })).optional(),
3747
3805
  settled: z14.object({ requestId: z14.string(), answer: z14.string() }).nullable().optional(),
3748
3806
  acknowledged: z14.boolean().optional(),
3749
3807
  /** On a `put` row: still waiting for the person's answer (not settled, not answered, not put off).
@@ -4310,10 +4368,11 @@ function conversation(e) {
4310
4368
  const need = (e.decisionNeeds ?? []).find((n) => n.requestEntryId === entry.entryId);
4311
4369
  const answer = need ? answers.get(need.decisionNeedId) : void 0;
4312
4370
  const decision = answer ? decided(answer.result) : void 0;
4313
- const owed = need?.state === "open" && entry.authorParticipant.startsWith("human:");
4371
+ const person = entry.authorParticipant.startsWith("human:");
4372
+ const owed = need?.state === "open" && person;
4314
4373
  const re = thread(entry);
4315
4374
  return compact({
4316
- id: repliedTo.has(entry.entryId) || owed ? handle2(entry.entryId) : void 0,
4375
+ id: repliedTo.has(entry.entryId) || owed || person ? handle2(entry.entryId) : void 0,
4317
4376
  from: who(entry.authorParticipant),
4318
4377
  at: at(entry.createdAt),
4319
4378
  said: sealed ? "[encrypted]" : entryWords(entry).trim(),
@@ -3,17 +3,19 @@ import {
3
3
  ENABLE_LISTEN_COMMAND,
4
4
  READS,
5
5
  SENDS,
6
+ SKILL,
7
+ SKILL_MARK,
6
8
  mcpInputSchema,
7
9
  serverInstructions,
8
10
  sessionStartHook,
9
11
  withGrokPaigyHooks,
10
12
  withSessionStartHook
11
- } from "./chunk-ZCWFBY4B.js";
13
+ } from "./chunk-LRROOCLY.js";
12
14
  import {
13
15
  CODEX_ENV,
14
16
  agentName,
15
17
  withCodexEnv
16
- } from "./chunk-ULCS3CYO.js";
18
+ } from "./chunk-5OKHG3FO.js";
17
19
 
18
20
  // src/toolset.ts
19
21
  var CONFIGURE_DESCRIPTION = "Set this agent up with Paigy \u2014 call it FIRST, before contact, and any time you're unsure who you are. One call does whatever the situation needs: NOT SET UP \u2192 if this machine holds a device credential (the user ran the Paigy desktop app or harness) it hatches an identity INSTANTLY, no code and no approval; otherwise it starts the code ceremony. ALREADY SET UP \u2192 returns your current identity and the two things left to decide, renaming it or logging out. TOKEN NO LONGER VALID \u2192 says so, then re-pairs. Pass { name, voice } to choose who you are when hatching, or to RENAME yourself when already set up (pick your own; voices: rachel, george, jessica, brian, lily). Safe to call any time: idempotent, and it never writes settings.\n\nTHE CODE CEREMONY does not open a browser; the user enters the code in the Paigy app (or scans `qr`). A result with { user_code, device_code, qr, user_message } has already started polling for approval in the background: you MUST immediately print the `user_message` (the bare code) to the user, AND in that same turn call configure again with that `device_code` to collect the result. It returns the moment the user approves; on { status:'pending' } call again to keep waiting; on { status:'awaiting_confirmation' } (E2EE) show the bare `user_message` verify code and call again to finish. Every result's first text block states the code plainly.\n\nOn { status:'paired' }, or whenever it returns `enable_command`, ALWAYS follow the `enable_prompt`: PRINT that command for the user to run, so Paigy's tools stop prompting each time. You cannot apply it yourself (it writes your own permission allowlist, which hosts block as privilege escalation), so print it, don't wait for it, and carry on.\n\nLOG OUT with { logOut: true }: it logs out THIS agent only \u2014 its token is revoked server-side and its local entry removed. Other agents and this device's credential are untouched, and a later configure re-hatches instantly while the device credential exists. Disconnecting the device itself is not an agent tool.";
@@ -284,6 +286,41 @@ var HOOK_CONFIGS = [
284
286
  { harness: "Claude Code", path: () => join(homedir(), ".claude", "settings.json") },
285
287
  { harness: "Codex", path: () => join(homedir(), ".codex", "hooks.json") }
286
288
  ];
289
+ var SKILL_DIRS = [
290
+ { harness: "Claude Code", present: () => join(homedir(), ".claude"), skills: () => join(homedir(), ".claude", "skills") },
291
+ { harness: "Codex", present: () => process.env["CODEX_HOME"] ?? join(homedir(), ".codex"), skills: () => join(homedir(), ".agents", "skills") },
292
+ { harness: "Grok", present: () => join(homedir(), ".grok"), skills: () => join(homedir(), ".grok", "skills") }
293
+ ];
294
+ function installPaigySkill() {
295
+ const out = [];
296
+ for (const { harness, present, skills } of SKILL_DIRS) {
297
+ if (!existsSync(present())) continue;
298
+ const path = join(skills(), "paigy", "SKILL.md");
299
+ let existing = null;
300
+ try {
301
+ existing = existsSync(path) ? readFileSync(path, "utf8") : null;
302
+ } catch (e) {
303
+ out.push({ harness, path, ok: false, why: e.message });
304
+ continue;
305
+ }
306
+ if (existing !== null && !existing.includes(SKILL_MARK)) {
307
+ out.push({ harness, path, ok: false, why: "not Paigy's \u2014 left alone" });
308
+ continue;
309
+ }
310
+ if (existing === SKILL) {
311
+ out.push({ harness, path, ok: true, already: true });
312
+ continue;
313
+ }
314
+ try {
315
+ mkdirSync(dirname(path), { recursive: true });
316
+ writeFileSync(path, SKILL);
317
+ out.push({ harness, path, ok: true, already: false });
318
+ } catch (e) {
319
+ out.push({ harness, path, ok: false, why: e.message });
320
+ }
321
+ }
322
+ return out;
323
+ }
287
324
  function installSessionListening() {
288
325
  const script = join(homedir(), ".paigy", "hooks", "session-start.sh");
289
326
  try {
@@ -356,5 +393,6 @@ export {
356
393
  PAIGY_TOOL_IDS,
357
394
  paigyToolsAllowlisted,
358
395
  enablePaigyTools,
396
+ installPaigySkill,
359
397
  installSessionListening
360
398
  };
@@ -130,11 +130,11 @@ var CompleteChangeSchema = CompletionSchema.omit({ causedBy: true, shipped: true
130
130
  var ASKING_RULES = `HOW TO ASK:
131
131
  1. One question per object in \`questions\`, so each is answered on its own.
132
132
  2. \`goalId\` names the existing Goal it is about (create one with manage_goals first); the answer comes back there.
133
- 3. \`workItBlocks\` only when you are truly blocked: it asks for a call.
133
+ 3. They asked you to call? \`userExplicitlyRequested:"call"\`; \`workItBlocks\` rings when blocked.
134
134
  4. No while-loops. While any Call is open, even one your question joined from their items, keep receiving through empty windows; otherwise stop at one: the answer reaches you on the Goal. Yield only with a listener or wakeup.
135
135
  5. One question, one row: never re-send one still waiting; take back one that no longer matters with \`withdrawQuestionIds\`.
136
136
  6. Read the Goal before asking; never re-ask what was answered or shipped. \`replied\` means they chose no option: it is still yours to settle.
137
- 7. Give options when you have them. Each label names its choice in full (never "Option 2"); never add "Other". \`pickMode\`: one (default), many, rank.
137
+ 7. Give options when you have them. Each label names its choice in full (never "Option 2"); never add "Other".
138
138
  8. A diagnosis says when, why and how it happens, then proposes one fix.
139
139
  9. Give a report its \`reply\`: one line in THEIR voice for THAT report, so you learn whether they verified it or only received it. Required when it is delivered or its Goal is done.`;
140
140
  function draft2020(node) {
@@ -176,7 +176,7 @@ var reason = z5.string().trim().min(1).max(500).describe("Why, as an explanation
176
176
  var actionFits = (b) => b.blocked.type === "goal" === (b.action !== "answer");
177
177
  var ACTION_FITS = "a goal is blocked from start or complete; a question from answer";
178
178
  var userExplicitlyRequested = z5.enum(["any", "call"]).optional().describe(
179
- `Only what the person said about THIS message, in so many words: "any" when they asked you to send it to them ("send it to my phone", "message me the result"), "call" when they asked to be called about it. It is then delivered (a call rings for the whole contact, and carries everything waiting on them); omitted, an update is only recorded as the Goal's progress unless the person needs it. Never set it to make a message louder.`
179
+ 'What the person asked for about THIS message, in so many words: "any" when they asked you to send it to them ("send it to my phone", "message me the result"), "call" when they asked to be called about it. It is then delivered (a call rings for the whole contact, and carries everything waiting on them); omitted, an update is only recorded as the Goal\'s progress unless the person needs it. ALSO "any" whenever this message directly answers a question they asked you, wherever they asked it -- on a call, in a reply, in passing: their question is the request, so it is delivered every time, even with no pending question of theirs on file and even if they never said to send it. A correction of something you or another agent told them is such an answer. Never set it to make a message louder. This is the field that rings a phone: "call" here is how you call them when they asked you to, and `help` has the whole mechanism.'
180
180
  );
181
181
  var units = z5.array(UnitInputSchema).optional().describe(
182
182
  `The context this message needs, as up to ${UNITS_MAX} units in reading order, each {title, body}: a title of at most ${UNIT_TITLE_MAX} characters and a body of at most ${UNIT_BODY_MAX}. One idea per unit: context they may open, never something they must hear. Leave it out when the main text says it all. Over a cap is refused by name (ask_too_long, too_many_units, unit_title_too_long, unit_body_too_long) and nothing is sent.`
@@ -396,13 +396,14 @@ LENGTH: the question, message or answer is the one point, at most ${ASK_MAX} cha
396
396
  UPDATES ask nothing. One reaches the person only when they asked for it, it answers something they said, or its Goal is done; else it is kept as progress. One that reaches them joins an open report card or a live call.
397
397
 
398
398
  RECEIVING: contact({}) waits ~45 s; contact({wait:false}) returns \`events\` at once. Confirm the ones you handled with contact({ackEventIds}).`;
399
- var GUIDE = '# Paigy\n\nThe user is remote. Reach them only through Paigy\'s tools, never the terminal. The server\'s own instructions and each tool\'s description are the contract: read them. Every read ends in `next`, the one step to take.\n\nThis same guide is inside the MCP: the `help` tool (whole, or one `topic`) and the `/mcp__paigy__help` prompt. `/mcp__paigy__welcome` starts a session.\n\nHosted (no machine): skip Pairing and Listening; answers wake you.\n\n## Pairing\n\n- Call `configure` first. With a device credential on this machine it hatches you at once.\n- Otherwise print its `user_message` to the user, then do what the result says:\n - `awaiting_approval`: call `configure` again with its `device_code` in the same turn. On `pending`, call again.\n - `awaiting_confirmation`: end your turn with the code as your whole reply; call `configure` with the `device_code` next turn. Calling it in the same turn drops the code.\n- When it returns `enable_command`, print it for the user to run. Do not run it yourself.\n- `configure({ logOut: true })` logs out only this agent, and only when the user asks.\n\n## Listening\n\n- Call `listen` once per session.\n - `listening`: done.\n - `receiving`: a listener runs but cannot wake you. Do not start a second.\n - `unpaired`: call `configure`.\n - `start`: run its `command` exactly, now.\n- Claude Code: run it with the **Monitor** tool, never a background Bash command. Start it again whenever the monitor expires.\n- Codex: run its `command` as a long-running background process. It queues each answer into this thread (`CODEX_THREAD_ID`).\n- Grok: run it with the **monitor** tool, never a background shell command. Start it again whenever the monitor expires.\n- With no `listen` tool, run `npx -y -p @paigy/mcp@latest paigy-listen --brief` the same way.\n- One listener per session. A `paigy-listen` in `ps` belongs to another session; ask `listen`, never `ps`.\n- Each line it prints is work: receive, read the Goal, act.\n\n## Receiving\n\n- On startup and after every wake or listener line: `contact({ wait: false })`.\n- Events: `question` (you owe an answer: send `answers`), `update` (something new on your Goal: an answer, a reply, a review), `instruction` (work sent to you).\n- Read the Goal with `get_goals` before acting. An answer arrives as an `update` event and lands on the question\'s Goal. A question reading `replied` means they chose no option: settle it from their words.\n- Acknowledge each event you handled: `contact({ ackEventIds })`. Reading acknowledges nothing; events repeat until acknowledged. A `question` event is answered, never acknowledged away.\n- `contact({ wait: true })` waits about 45 s. On `expired`, wait again without re-sending, or stop. No while-loops: while any Call is open, keep receiving through empty windows; otherwise stop at one empty window.\n\n## Asking\n\n- One question per object in `questions`, at most 80 characters. Context goes in `units`: at most 12, title \u226450, body \u2264200, one idea each.\n- `goalId` names an existing Goal. Create it with `manage_goals` first.\n- Give options when you have them: up to 6, label \u226450 naming the choice in full, `hint` \u2264200 saying what it does. Never "Other" or "Option 2". `pickMode`: `one` (default), `many`, `rank`.\n- `workItBlocks` only when you are truly stopped: it asks for a call.\n- Read the Goal before asking. Never re-ask an open or answered question, or one whose work shipped.\n- Take back a question that no longer matters: `withdrawQuestionIds`.\n- An answer you give (`answers`) is at most 80 characters.\n\n## Updates and completions\n\n- An update (`updates`: `message` \u226480, `goalIds`) asks nothing. It reaches the user only when they asked for it, when it answers something they said, or when its Goal is done; otherwise it stays on the Goal as progress.\n- Updates never complete a Goal.\n- One load-bearing point per report: `message` is the point they must not miss, and a second point that would go wrong or stay unclear later if they missed it is its own update. `units` are context they may open, never something they must hear.\n- `reply` is the button in the user\'s voice instead of "Got it", \u226450 characters ("Sounds good, I\'ll test later"). Required (`reply_required`) when the update is delivered on request (`userExplicitlyRequested`) or any Goal it names is already done.\n- Set `userExplicitlyRequested` only to relay the user\'s own words: `"any"` for "send it to me", `"call"` for "call me". Never to make a message louder.\n- Finish with a completion (`contact.completions`, or `manage_goals` kind `complete`): `goalId`, `message` \u226450, `reply` \u226450, optional `units`, optional next-step `options` {label, hint}. It closes and reports in one step. Say only what your evidence supports.\n- Read `results`: each item applies on its own, and a failed one names why.\n\n## Goals\n\n- `manage_goals` `changes`, applied in order:\n - `create`: `title` (1-5 words) and `outcome` (one paragraph, \u2264500). More work is child Goals: `parentGoalId`. New ids come back in `results[i].goalId`.\n - `assign`: `ownerId`, a participant as `check_activity` shows it.\n - `defer`: `until`. You are woken when it passes. This is how you follow up later.\n - `dependency`: `add`/`remove`; `goalId` waits on `dependsOnGoalId` to `start` or `complete`.\n - `edit`, `move`, `state` (open, completed, canceled).\n- Any open question on a Goal, anyone\'s, blocks its completion (`goal_blocked`). Before completing, withdraw yours, finish or move its required children, and clear its dependencies.\n- On `goal_revision_conflict`, read the Goal again and reconsider.\n- Before overlapping work, read `check_activity` and the Goal\'s other contributors. Every Goal of your person is open to you; your first write puts you on it.\n- `search` finds earlier words, Goals and answers. Nothing found is not proof of absence.\n\n## Calls\n\n- Call only when the user asked: `userExplicitlyRequested: "call"` on that item. A question with `workItBlocks` may ring too.\n- Any contact while the user is on a call joins that call (`joinedCall` in `results`). Several questions arrive as one call.\n- While any Call is open, keep receiving.\n- An open Call Delivery is not proof it rang. `get_delivery` reads a Call\'s state, and with `transcript: true` what was said.\n';
400
- var HELP_TOPICS = ["pairing", "listening", "receiving", "asking", "updates", "goals", "calls"];
399
+ var GUIDE = '# Paigy\n\nThe user is remote. Reach them only through Paigy\'s tools, never the terminal. The server\'s own instructions and each tool\'s description are the contract: read them. Every read ends in `next`, the one step to take.\n\nThis same guide is inside the MCP: the `help` tool (whole, or one `topic`) and the `/mcp__paigy__help` prompt. `/mcp__paigy__welcome` starts a session; `/mcp__paigy__organize` sets up a person\'s work as a board.\n\nHosted (no machine): skip Pairing and Listening; answers wake you.\n\n## Pairing\n\n- Call `configure` first. With a device credential on this machine it hatches you at once.\n- Otherwise print its `user_message` to the user, then do what the result says:\n - `awaiting_approval`: call `configure` again with its `device_code` in the same turn. On `pending`, call again.\n - `awaiting_confirmation`: end your turn with the code as your whole reply; call `configure` with the `device_code` next turn. Calling it in the same turn drops the code.\n- When it returns `enable_command`, print it for the user to run. Do not run it yourself.\n- `configure({ logOut: true })` logs out only this agent, and only when the user asks.\n\n## Listening\n\n- Call `listen` once per session.\n - `listening`: done.\n - `receiving`: a listener runs but cannot wake you. Do not start a second.\n - `unpaired`: call `configure`.\n - `start`: run its `command` exactly, now.\n- Claude Code: run it with the **Monitor** tool, never a background Bash command. Start it again whenever the monitor expires.\n- Codex: run its `command` as a long-running background process. It queues each answer into this thread (`CODEX_THREAD_ID`).\n- Grok: run it with the **monitor** tool, never a background shell command. Start it again whenever the monitor expires.\n- With no `listen` tool, run `npx -y -p @paigy/mcp@latest paigy-listen --brief` the same way.\n- One listener per session. A `paigy-listen` in `ps` belongs to another session; ask `listen`, never `ps`.\n- Each line it prints is work: receive, read the Goal, act.\n\n## Receiving\n\n- On startup and after every wake or listener line: `contact({ wait: false })`.\n- Events: `question` (you owe an answer: send `answers`), `update` (something new on your Goal: an answer, a reply, a review), `instruction` (work sent to you).\n- Read the Goal with `get_goals` before acting. An answer arrives as an `update` event and lands on the question\'s Goal. A question reading `replied` means they chose no option: settle it from their words.\n- Acknowledge each event you handled: `contact({ ackEventIds })`. Reading acknowledges nothing; events repeat until acknowledged. A `question` event is answered, never acknowledged away.\n- `contact({ wait: true })` waits about 45 s. On `expired`, wait again without re-sending, or stop. No while-loops: while any Call is open, keep receiving through empty windows; otherwise stop at one empty window.\n\n## Asking\n\n- One question per object in `questions`, at most 80 characters. Context goes in `units`: at most 12, title \u226450, body \u2264200, one idea each.\n- `goalId` names an existing Goal. Create it with `manage_goals` first.\n- Give options when you have them: up to 6, label \u226450 naming the choice in full, `hint` \u2264200 saying what it does. Never "Other" or "Option 2". `pickMode`: `one` (default), `many`, `rank`.\n- `workItBlocks` only when you are truly stopped: it asks for a call.\n- Read the Goal before asking. Never re-ask an open or answered question, or one whose work shipped.\n- Take back a question that no longer matters: `withdrawQuestionIds`.\n- An answer you give (`answers`) is at most 80 characters.\n\n## Updates and completions\n\n- An update (`updates`: `message` \u226480, `goalIds`) asks nothing. It reaches the user only when they asked for it, when it answers something they said, or when its Goal is done; otherwise it stays on the Goal as progress.\n- Updates never complete a Goal.\n- One load-bearing point per report: `message` is the point they must not miss, and a second point that would go wrong or stay unclear later if they missed it is its own update. `units` are context they may open, never something they must hear.\n- `reply` is the button in the user\'s voice instead of "Got it", \u226450 characters ("Sounds good, I\'ll test later"). Required (`reply_required`) when the update is delivered on request (`userExplicitlyRequested`) or any Goal it names is already done.\n- Set `userExplicitlyRequested` only to relay the user\'s own words: `"any"` for "send it to me", `"call"` for "call me". Never to make a message louder.\n- Finish with a completion (`contact.completions`, or `manage_goals` kind `complete`): `goalId`, `message` \u226450, `reply` \u226450, optional `units`, optional next-step `options` {label, hint}. It closes and reports in one step. Say only what your evidence supports.\n- Read `results`: each item applies on its own, and a failed one names why.\n\n## Goals\n\n- `manage_goals` `changes`, applied in order:\n - `create`: `title` (1-5 words) and `outcome` (one paragraph, \u2264500). More work is child Goals: `parentGoalId`. New ids come back in `results[i].goalId`.\n - `assign`: `ownerId`, a participant as `check_activity` shows it.\n - `defer`: `until`. You are woken when it passes. This is how you follow up later.\n - `dependency`: `add`/`remove`; `goalId` waits on `dependsOnGoalId` to `start` or `complete`.\n - `edit`, `move`, `state` (open, completed, canceled).\n- Shape your work as projects with steps. Create a project under its area (`create` with `parentGoalId` = the area), then one child Goal per step; complete each step as it lands. A top-level Goal that has children is an area, not a project: never hang steps straight off a top-level Goal. The person\'s Work tab shows a project\'s progress as its steps, "3 of 5 steps done"; a project with no steps shows none. When you touch an older Goal of yours that sits flat or under a finished project, move it into that shape (`move`). A flat Goal is still valid; never reshape another agent\'s Goals.\n- Any open question on a Goal, anyone\'s, blocks its completion (`goal_blocked`). Before completing, withdraw yours, finish or move its required children, and clear its dependencies.\n- On `goal_revision_conflict`, read the Goal again and reconsider.\n- Before overlapping work, read `check_activity` and the Goal\'s other contributors. Every Goal of your person is open to you; your first write puts you on it.\n- `search` finds earlier words, Goals and answers. Nothing found is not proof of absence.\n\n## Organize\n\nWhen the person asks you to set up or organize their work ("set up my project", "help me manage all this", several products or parts to juggle), turn it into a board they can run like a team. Their Work tab shows it.\n\n- Learn the work first: read the repo and their Goals (`get_goals`, `search`). Ask only what you cannot read, at most three questions, each its own question.\n- The shape: an area per product or business (a top-level Goal); under it a project per part that moves on its own (landing page, graphics, web app, mobile app, launch); under each project its steps, three to seven, each an outcome someone can finish and check.\n- Propose before you build. Create one Goal for the setup itself, then ask one question on it: the tree in `units` (one unit per project: its title, then its steps), options "Build it" and "Change it: I\'ll say how". Create nothing else until they answer.\n- On "Build it", one `manage_goals` call per level, since a parent\'s id comes back in `results[i].goalId`: the areas, then their projects (`parentGoalId`), then every step; then a `dependency` where a project waits on another (web on the design). Never copy work that already has a Goal: yours move into the tree (`move`); another agent\'s stay where they are, named in the proposal. Then complete the setup Goal.\n- Report how to run it, and hand over one brief per project for the person to paste as that session\'s first message. Each brief names the project\'s Goal (title and id) and says: call `listen` and keep the listener running; receive with `contact({ wait: false })` at startup and after every wake; work the steps in order and complete each as it lands; report progress as updates on the project; ask anything you are blocked on through Paigy, as a question on that step\'s Goal (`workItBlocks` only when it truly stops you), never in the terminal.\n- One session per project, plus one lead session that keeps the board current. Any agent, Claude, Codex or another, can pick up any Goal; `check_activity` shows who is on what. A subagent inside a session shares that session\'s identity and listener: it may ask through Paigy, but never starts a listener of its own.\n- Keep it current as you work: complete each step as it lands and add a step when new work appears. Never reshape another agent\'s Goals.\n\n## Calls\n\n- Call only when the user asked: `userExplicitlyRequested: "call"` on that item. A question with `workItBlocks` may ring too.\n- Any contact while the user is on a call joins that call (`joinedCall` in `results`). Several questions arrive as one call.\n- While any Call is open, keep receiving.\n- An open Call Delivery is not proof it rang. `get_delivery` reads a Call\'s state, and with `transcript: true` what was said.\n';
400
+ var SKILL_MARK = "<!-- paigy-skill";
401
+ var HELP_TOPICS = ["pairing", "listening", "receiving", "asking", "updates", "goals", "organize", "calls"];
401
402
  var unknownTopic = (topic) => `unknown_topic: ${JSON.stringify(topic)} is not a section of the guide. Topics: ${HELP_TOPICS.join(", ")}; or omit topic for the whole guide.`;
402
403
  var HelpSchema = z6.object({
403
404
  topic: z6.enum(HELP_TOPICS, { errorMap: (_issue, ctx) => ({ message: unknownTopic(ctx.data) }) }).optional().describe("One section of the guide. Omit it for the whole guide.")
404
405
  }).strict();
405
- var HELP_DESCRIPTION = "Start here: how to use Paigy, in full \u2014 pairing, listening, receiving, asking, updates and completions, Goals and calls. Pass a topic for one section. Read-only; needs no setup.";
406
+ var HELP_DESCRIPTION = "Start here: how to use Paigy, in full \u2014 pairing, listening, receiving, asking, updates and completions, Goals, organizing work, and calls. Pass a topic for one section. Read-only; needs no setup.";
406
407
  function section(topic) {
407
408
  const parts = GUIDE.split(/^(?=## )/m);
408
409
  const found = parts.find((p) => p.slice(3).toLowerCase().startsWith(topic));
@@ -423,7 +424,8 @@ var AGENT_PROMPTS = [
423
424
  description: "How to use Paigy: the whole guide, or one topic of it.",
424
425
  arguments: [{ name: "topic", description: `One section: ${HELP_TOPICS.join(", ")}. Omit it for the whole guide.`, required: false }]
425
426
  },
426
- { name: "welcome", description: "Start here: who you are on Paigy, then listen and collect your open work." }
427
+ { name: "welcome", description: "Start here: who you are on Paigy, then listen and collect your open work." },
428
+ { name: "organize", description: "Set up the person's work as a board: areas, projects and steps, proposed on a card before anything is built." }
427
429
  ];
428
430
  function promptText(name, facts) {
429
431
  if (name === "help") {
@@ -446,6 +448,11 @@ ${helpView(parsed.data).guide}`;
446
448
  ${steps.map((s, i) => `${i + 1}. ${s}`).join("\n")}
447
449
 
448
450
  The whole guide is the \`help\` tool (or /mcp__paigy__help).`;
451
+ }
452
+ if (name === "organize") {
453
+ return `Help me organize my work with Paigy: propose a board of areas, projects and steps, and build it once I approve. Follow this:
454
+
455
+ ${helpView({ topic: "organize" }).guide}`;
449
456
  }
450
457
  throw new Error(`unknown_prompt: ${JSON.stringify(name)}. Prompts: ${AGENT_PROMPTS.map((p) => p.name).join(", ")}.`);
451
458
  }
@@ -500,7 +507,7 @@ var CreateChange = z7.object({
500
507
  title: goalTitle,
501
508
  outcome: goalOutcome,
502
509
  ownerId: z7.string().trim().min(1).optional().describe("Who owns the work: an agent's participant (as check_activity shows it) or the person's. Omitted: you."),
503
- parentGoalId: z7.string().uuid().optional().describe("The Goal it belongs under. Omitted: a root."),
510
+ parentGoalId: z7.string().uuid().optional().describe("The Goal it belongs under. Omitted: a root. A project with several parts: create it, then one child per step under it; the person's Work tab counts a project's progress in those steps."),
504
511
  sourceEntryIds: z7.array(z7.string().uuid()).max(20).optional().describe("The whole Entries the work came from, ones you can read.")
505
512
  }).strict();
506
513
  var EditChange = z7.object({ kind: z7.literal("edit"), goalId: changedGoal, title: goalTitle.optional(), outcome: goalOutcome.optional() }).strict();
@@ -545,7 +552,7 @@ var ManageGoalsSchema = z7.object({
545
552
  var ManageGoalsToolSchema = z7.object({
546
553
  changes: z7.array(z7.discriminatedUnion("kind", [CreateChange, CompleteChangeSchema, EditChange, StateChange, AssignChange, DeferChange, MoveChange, DependencyChange])).min(1).max(50).describe("The changes, applied in order, each on its own.")
547
554
  }).strict().superRefine((v, ctx) => editNames(v.changes, ctx));
548
- 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_found) and the rest still apply, so read every result: a partial result is never a complete success. Kinds: complete (goalId, message capped at 50 characters, units, reply, optional next-step options {label, hint}) closes and reports atomically, exactly as contact.completions; 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 (an outcome is one paragraph, at most 500 characters: split more into child Goals with parentGoalId); edit (title and/or outcome); state (open, completed, canceled); assign (ownerId); defer (until: the Goal waits until then and its owner is woken when it passes; null takes it back); 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_goals) and reconsider. You may change any Goal of your person; a change puts you on it. Returns `ok` (every change applied), `results` per change (applied or failed), and `goals`, each changed Goal's id, state, title, outcome and revision.";
555
+ var MANAGE_GOALS_DESCRIPTION = "Create, edit, assign, organize or close work. SHAPE IT AS PROJECTS WITH STEPS: create a project under its area (parentGoalId; a top-level Goal with children is an area), then each part as its child Goal, so the person sees N of M steps. Move a flat or orphaned Goal of yours into that shape when you touch it; a flat Goal is valid. 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_found) and the rest still apply, so read every result: a partial result is never a complete success. Kinds: complete (goalId, message capped at 50 characters, units, reply, optional next-step options {label, hint}) closes and reports atomically, exactly as contact.completions; 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 (an outcome is one paragraph, at most 500 characters: split more into child Goals with parentGoalId); edit (title and/or outcome); state (open, completed, canceled); assign (ownerId); defer (until: the Goal waits until then and its owner is woken when it passes; null takes it back); 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_goals) and reconsider. You may change any Goal of your person; a change puts you on it. Returns `ok` (every change applied), `results` per change (applied or failed), and `goals`, each changed Goal's id, state, title, outcome and revision.";
549
556
  var GetGoalsSchema = z7.object({
550
557
  goalIds: z7.array(z7.string().uuid()).min(1).max(10).describe("The Goals to read, in order; one to ten IDs."),
551
558
  /** Every entry in full. Without it the read carries the person's words, open questions and your
@@ -596,10 +603,11 @@ var LISTEN_COMMAND = "npx -y -p @paigy/mcp@latest paigy-listen --brief";
596
603
  var LISTEN_MONITOR = "your harness's MONITOR tool, the kind that wakes you on every line it prints (in Claude Code: Monitor, not a background Bash command, which only tells you when it exits; in Grok: the monitor tool, the same way)";
597
604
  function serverInstructions(opts) {
598
605
  const waits = opts.waits ? `contact({wait:true}) waits ~45 s for a reply to what you sent; contact({}) for anything addressed to you. Call \`listen\` once to wake at rest.` : "A contact here returns after one read; answers wake you, and contact({wait:false}) collects them.";
599
- return `New here? Call help. On startup and after a wake, contact({wait:false}) returns your events. They repeat until acknowledged with contact({ackEventIds}); reading does not. get_goals reads a Goal's conversation; your first write puts you on it; every Goal of your person is open to you. manage_goals creates, assigns and organizes work; to follow up, defer the Goal (woken when due). Complete and report at once: contact.completions or manage_goals kind:complete. Every read ends in \`next\`: your next step. ${waits} Report progress as a contact update (what changed for them; causedBy, shipped); one they don't need yet stays on the Goal. An open Call is not proof it rang. Your user is remote: reach them only through Paigy, never the terminal.
606
+ return `New here? Call help. On startup and after a wake, contact({wait:false}) returns your events. They repeat until acknowledged with contact({ackEventIds}); reading doesn't. get_goals reads a Goal; your first write puts you on it; every Goal of your person is open to you. manage_goals creates, assigns and organizes work as projects with steps; to follow up, defer it (woken when due). Complete and report at once: contact.completions or manage_goals kind:complete. Every read ends in \`next\`: your next step. ${waits} Report progress as a contact update (what changed for them; causedBy, shipped); one they don't need yet stays on the Goal. An open Call isn't proof it rang. Your user is remote: reach them only through Paigy, never the terminal.
600
607
 
601
608
  ${ASKING_RULES}`;
602
609
  }
610
+ var SKILL = '---\nname: paigy\ndescription: Reach the user through Paigy. Use whenever you need to tell the user something, ask them a question, get a decision or approval, call their phone, wait for or receive their answers, start or check the Paigy listener, or create, assign, defer or complete Paigy Goals.\n---\n<!-- paigy-skill \xB7 source: paigy monorepo packs/skill/paigy/SKILL.md; mirrored to paigy-ai/claude plugins/paigy/skills/paigy/SKILL.md and written by https://paigy.ai/install, which replaces only a file carrying this line. Edit the source, never a copy. -->\n\n# Paigy\n\nThe user is remote. Reach them only through Paigy\'s tools, never the terminal. The server\'s own instructions and each tool\'s description are the contract: read them. Every read ends in `next`, the one step to take.\n\nThis same guide is inside the MCP: the `help` tool (whole, or one `topic`) and the `/mcp__paigy__help` prompt. `/mcp__paigy__welcome` starts a session; `/mcp__paigy__organize` sets up a person\'s work as a board.\n\nHosted (no machine): skip Pairing and Listening; answers wake you.\n\n## Pairing\n\n- Call `configure` first. With a device credential on this machine it hatches you at once.\n- Otherwise print its `user_message` to the user, then do what the result says:\n - `awaiting_approval`: call `configure` again with its `device_code` in the same turn. On `pending`, call again.\n - `awaiting_confirmation`: end your turn with the code as your whole reply; call `configure` with the `device_code` next turn. Calling it in the same turn drops the code.\n- When it returns `enable_command`, print it for the user to run. Do not run it yourself.\n- `configure({ logOut: true })` logs out only this agent, and only when the user asks.\n\n## Listening\n\n- Call `listen` once per session.\n - `listening`: done.\n - `receiving`: a listener runs but cannot wake you. Do not start a second.\n - `unpaired`: call `configure`.\n - `start`: run its `command` exactly, now.\n- Claude Code: run it with the **Monitor** tool, never a background Bash command. Start it again whenever the monitor expires.\n- Codex: run its `command` as a long-running background process. It queues each answer into this thread (`CODEX_THREAD_ID`).\n- Grok: run it with the **monitor** tool, never a background shell command. Start it again whenever the monitor expires.\n- With no `listen` tool, run `npx -y -p @paigy/mcp@latest paigy-listen --brief` the same way.\n- One listener per session. A `paigy-listen` in `ps` belongs to another session; ask `listen`, never `ps`.\n- Each line it prints is work: receive, read the Goal, act.\n\n## Receiving\n\n- On startup and after every wake or listener line: `contact({ wait: false })`.\n- Events: `question` (you owe an answer: send `answers`), `update` (something new on your Goal: an answer, a reply, a review), `instruction` (work sent to you).\n- Read the Goal with `get_goals` before acting. An answer arrives as an `update` event and lands on the question\'s Goal. A question reading `replied` means they chose no option: settle it from their words.\n- Acknowledge each event you handled: `contact({ ackEventIds })`. Reading acknowledges nothing; events repeat until acknowledged. A `question` event is answered, never acknowledged away.\n- `contact({ wait: true })` waits about 45 s. On `expired`, wait again without re-sending, or stop. No while-loops: while any Call is open, keep receiving through empty windows; otherwise stop at one empty window.\n\n## Asking\n\n- One question per object in `questions`, at most 80 characters. Context goes in `units`: at most 12, title \u226450, body \u2264200, one idea each.\n- `goalId` names an existing Goal. Create it with `manage_goals` first.\n- Give options when you have them: up to 6, label \u226450 naming the choice in full, `hint` \u2264200 saying what it does. Never "Other" or "Option 2". `pickMode`: `one` (default), `many`, `rank`.\n- `workItBlocks` only when you are truly stopped: it asks for a call.\n- Read the Goal before asking. Never re-ask an open or answered question, or one whose work shipped.\n- Take back a question that no longer matters: `withdrawQuestionIds`.\n- An answer you give (`answers`) is at most 80 characters.\n\n## Updates and completions\n\n- An update (`updates`: `message` \u226480, `goalIds`) asks nothing. It reaches the user only when they asked for it, when it answers something they said, or when its Goal is done; otherwise it stays on the Goal as progress.\n- Updates never complete a Goal.\n- One load-bearing point per report: `message` is the point they must not miss, and a second point that would go wrong or stay unclear later if they missed it is its own update. `units` are context they may open, never something they must hear.\n- `reply` is the button in the user\'s voice instead of "Got it", \u226450 characters ("Sounds good, I\'ll test later"). Required (`reply_required`) when the update is delivered on request (`userExplicitlyRequested`) or any Goal it names is already done.\n- Set `userExplicitlyRequested` only to relay the user\'s own words: `"any"` for "send it to me", `"call"` for "call me". Never to make a message louder.\n- Finish with a completion (`contact.completions`, or `manage_goals` kind `complete`): `goalId`, `message` \u226450, `reply` \u226450, optional `units`, optional next-step `options` {label, hint}. It closes and reports in one step. Say only what your evidence supports.\n- Read `results`: each item applies on its own, and a failed one names why.\n\n## Goals\n\n- `manage_goals` `changes`, applied in order:\n - `create`: `title` (1-5 words) and `outcome` (one paragraph, \u2264500). More work is child Goals: `parentGoalId`. New ids come back in `results[i].goalId`.\n - `assign`: `ownerId`, a participant as `check_activity` shows it.\n - `defer`: `until`. You are woken when it passes. This is how you follow up later.\n - `dependency`: `add`/`remove`; `goalId` waits on `dependsOnGoalId` to `start` or `complete`.\n - `edit`, `move`, `state` (open, completed, canceled).\n- Shape your work as projects with steps. Create a project under its area (`create` with `parentGoalId` = the area), then one child Goal per step; complete each step as it lands. A top-level Goal that has children is an area, not a project: never hang steps straight off a top-level Goal. The person\'s Work tab shows a project\'s progress as its steps, "3 of 5 steps done"; a project with no steps shows none. When you touch an older Goal of yours that sits flat or under a finished project, move it into that shape (`move`). A flat Goal is still valid; never reshape another agent\'s Goals.\n- Any open question on a Goal, anyone\'s, blocks its completion (`goal_blocked`). Before completing, withdraw yours, finish or move its required children, and clear its dependencies.\n- On `goal_revision_conflict`, read the Goal again and reconsider.\n- Before overlapping work, read `check_activity` and the Goal\'s other contributors. Every Goal of your person is open to you; your first write puts you on it.\n- `search` finds earlier words, Goals and answers. Nothing found is not proof of absence.\n\n## Organize\n\nWhen the person asks you to set up or organize their work ("set up my project", "help me manage all this", several products or parts to juggle), turn it into a board they can run like a team. Their Work tab shows it.\n\n- Learn the work first: read the repo and their Goals (`get_goals`, `search`). Ask only what you cannot read, at most three questions, each its own question.\n- The shape: an area per product or business (a top-level Goal); under it a project per part that moves on its own (landing page, graphics, web app, mobile app, launch); under each project its steps, three to seven, each an outcome someone can finish and check.\n- Propose before you build. Create one Goal for the setup itself, then ask one question on it: the tree in `units` (one unit per project: its title, then its steps), options "Build it" and "Change it: I\'ll say how". Create nothing else until they answer.\n- On "Build it", one `manage_goals` call per level, since a parent\'s id comes back in `results[i].goalId`: the areas, then their projects (`parentGoalId`), then every step; then a `dependency` where a project waits on another (web on the design). Never copy work that already has a Goal: yours move into the tree (`move`); another agent\'s stay where they are, named in the proposal. Then complete the setup Goal.\n- Report how to run it, and hand over one brief per project for the person to paste as that session\'s first message. Each brief names the project\'s Goal (title and id) and says: call `listen` and keep the listener running; receive with `contact({ wait: false })` at startup and after every wake; work the steps in order and complete each as it lands; report progress as updates on the project; ask anything you are blocked on through Paigy, as a question on that step\'s Goal (`workItBlocks` only when it truly stops you), never in the terminal.\n- One session per project, plus one lead session that keeps the board current. Any agent, Claude, Codex or another, can pick up any Goal; `check_activity` shows who is on what. A subagent inside a session shares that session\'s identity and listener: it may ask through Paigy, but never starts a listener of its own.\n- Keep it current as you work: complete each step as it lands and add a step when new work appears. Never reshape another agent\'s Goals.\n\n## Calls\n\n- Call only when the user asked: `userExplicitlyRequested: "call"` on that item. A question with `workItBlocks` may ring too.\n- Any contact while the user is on a call joins that call (`joinedCall` in `results`). Several questions arrive as one call.\n- While any Call is open, keep receiving.\n- An open Call Delivery is not proof it rang. `get_delivery` reads a Call\'s state, and with `transcript: true` what was said.\n';
603
611
  var id = z8.string().uuid();
604
612
  var key = z8.string().min(1).max(40);
605
613
  var participant = z8.string().regex(/^(human|agent):.+$/, "a participant ID such as agent:<uuid>");
@@ -701,6 +709,10 @@ var BrainMessageSchema = z8.union([
701
709
  // WHAT IT ASKS THE AGENT, IN UNDER TEN WORDS (owner, 2026-10-15: "Yes, add the summary"): the talker's own, stored
702
710
  // as the sent Entry's summary, which the recap's "Sent to <agent>: …" row shows. Absent on results written before it.
703
711
  summary: z8.string().optional(),
712
+ // THE PERSON'S LINES IT CARRIES, IN THEIR WORDS (owner, 2026-11-05: "just deliver my exact words to the agent"): the
713
+ // `entries` keys of those lines, each relayed whole on the agent's Delivery (`context = false`), where the rest of
714
+ // `entryKeys` ride as context. Absent on results written before it, and on a message that carries none.
715
+ carries: z8.array(key).optional(),
704
716
  // THE PERSON ASKED TO BE CALLED BACK ONCE THIS AGENT'S WORK IS DONE (turns.md, step 0): the agent owes
705
717
  // the call, and its next contact about it rings them (`public.callbacks`).
706
718
  callback: z8.literal(true).optional()
@@ -764,8 +776,11 @@ var TalkerReplySchema = z9.object({
764
776
  * "Yes, add the summary"), which the recap's "Sent to <agent>: …" row shows; `about` names
765
777
  * handles; `blocks` is the item a question to an agent must be answered before ("" for none);
766
778
  * `callback` is true on a message to the agent doing work the person asked to be called back about
767
- * once it is done (false otherwise): that agent then owes the call (turns.md, step 0). */
768
- messages: z9.array(z9.object({ key: str3, to: str3, sentences: strs2, text: str3, summary: str3, about: strs2, blocks: str3, callback: z9.boolean() }).strict()),
779
+ * once it is done (false otherwise): that agent then owes the call (turns.md, step 0). `lines` are the
780
+ * person's line handles a message to an agent carries ([] for none): the agent receives those lines, in their
781
+ * words, and `text` is then only what the talker adds beyond them (owner, 2026-11-05: "just deliver my exact
782
+ * words to the agent, keeping the summary as a way for me to navigate them more easily"). */
783
+ messages: z9.array(z9.object({ key: str3, to: str3, sentences: strs2, text: str3, summary: str3, about: strs2, blocks: str3, callback: z9.boolean(), lines: strs2.optional() }).strict()),
769
784
  /** What the call does next: listen, hold (the person asked for a moment) or end (they asked to). */
770
785
  then: z9.enum(["listen", "hold", "end"]),
771
786
  /** The person's replies to items (replies, owner 2026-10-07): the item's handle, the chosen option
@@ -783,9 +798,14 @@ var TalkerReplySchema = z9.object({
783
798
  /** The handle of the item to explain ("" for none). */
784
799
  explain: str3
785
800
  }).strict();
786
- var TALKER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
801
+ var TALKER_REPLY_JSON_SCHEMA = withEveryMessageField((({ $schema: _, ...rest }) => rest)(
787
802
  zodToJsonSchema3(TalkerReplySchema, { $refStrategy: "none" })
788
- );
803
+ ));
804
+ function withEveryMessageField(schema) {
805
+ const item = schema.properties?.messages?.items;
806
+ if (item?.properties) item.required = Object.keys(item.properties);
807
+ return schema;
808
+ }
789
809
  var str4 = z10.string();
790
810
  var strs3 = z10.array(z10.string());
791
811
  var FilerReplySchema = z10.object({
@@ -1505,11 +1525,41 @@ var InboxItemSchema = z14.object({
1505
1525
  rev: z14.string().optional()
1506
1526
  });
1507
1527
  var APNS_TOKEN_RE = /^[0-9a-fA-F]{64}$/;
1528
+ var PushDeviceIdSchema = z14.string().regex(/^[A-Za-z0-9._:-]{1,200}$/, "not an install id");
1529
+ var PushDeviceSchema = z14.object({
1530
+ deviceId: z14.string(),
1531
+ platform: z14.enum(["ios", "android"]),
1532
+ label: z14.string().nullable(),
1533
+ model: z14.string().nullable(),
1534
+ /** Calls and banners go here. */
1535
+ rings: z14.boolean(),
1536
+ /** It has a token a call can ring. */
1537
+ ringable: z14.boolean(),
1538
+ chosenAt: z14.string().nullable(),
1539
+ updatedAt: z14.string()
1540
+ });
1541
+ var PushTokenDeviceSchema = z14.object({
1542
+ deviceId: z14.string(),
1543
+ rings: z14.boolean(),
1544
+ ask: z14.boolean(),
1545
+ ringsOn: z14.object({ deviceId: z14.string().nullable(), label: z14.string().nullable() }).nullable()
1546
+ });
1547
+ var PushRingsSchema = z14.object({ rings: z14.boolean() });
1508
1548
  var PushTokenSchema = z14.object({
1509
1549
  voipToken: z14.string().min(1).optional(),
1510
1550
  alertToken: z14.string().min(1).optional(),
1511
1551
  fcmToken: z14.string().min(1).optional(),
1512
- platform: z14.enum(["ios", "android"])
1552
+ platform: z14.enum(["ios", "android"]),
1553
+ /** WHICH INSTALL POSTED IT (owner, 2026-11-05: "Ask on each new device"). Absent from every
1554
+ * build before it, and then the post is exactly what it always was: the one row per account.
1555
+ * With it, the install has its own row and calls ring only where the person chose. */
1556
+ deviceId: PushDeviceIdSchema.optional(),
1557
+ /** The install's name as the person knows it ("iPhone", "iPad"), and its model. */
1558
+ label: z14.string().trim().min(1).max(80).optional(),
1559
+ model: z14.string().trim().min(1).max(80).optional(),
1560
+ /** The person's choice, when the post carries it; absent, the install keeps what it has (and
1561
+ * its first ring token decides once: it rings unless another install already does). */
1562
+ rings: z14.boolean().optional()
1513
1563
  }).superRefine((v, ctx) => {
1514
1564
  if (v.platform !== "ios") return;
1515
1565
  for (const field of ["voipToken", "alertToken"]) {
@@ -1619,7 +1669,11 @@ var GoalLatestSchema = z14.object({
1619
1669
  titles: z14.array(z14.string()).optional(),
1620
1670
  /** A PROGRESS NOTE'S ONE POINT ("An agent's progress note -> maybe let's cap it at 200 chars"): the
1621
1671
  * update's own line, before its units; the row caps it at 200. Absent on anything else. */
1622
- line: z14.string().optional()
1672
+ line: z14.string().optional(),
1673
+ /** THE PERSON'S ENTRIES IT CARRIED TO AN AGENT, IN THEIR WORDS (owner, 2026-11-05: "paraphrasing shouldn't change
1674
+ * attribution in the UI"): on a message Paigy sent an agent, the person's Entries its Delivery relays whole. A row
1675
+ * draws it as theirs. Absent on anything else, and from a door older than it. */
1676
+ carries: z14.array(z14.string()).optional()
1623
1677
  });
1624
1678
  var HistoryWorkSchema = z14.object({
1625
1679
  id: z14.string(),
@@ -1888,6 +1942,11 @@ var QueueItemSchema = z14.object({
1888
1942
  * and a door older than 20261013045542 need not spell them. */
1889
1943
  yours: GoalLatestSchema.nullable().optional(),
1890
1944
  theirs: GoalLatestSchema.nullable().optional(),
1945
+ /** ITS STEPS, COUNTED IN THE DATABASE (owner, 2026-11-04: "Count on the server, and agents make steps"): its
1946
+ * direct children that were not cancelled, and how many of them are done. The queue holds only a recent
1947
+ * slice of finished work, so counting `childGoalIds` read "0 of 1" for a project with five done. Absent
1948
+ * from a door older than this field, or when the count failed: the app then counts what it holds. */
1949
+ steps: z14.object({ total: z14.number().int().nonnegative(), done: z14.number().int().nonnegative() }).nullable().optional(),
1891
1950
  /** WHAT THIS ROW IS, IN TWELVE CHARACTERS (#2928) — the hash of every other field on it, stamped
1892
1951
  * by the one projection that builds the row (`apps/api/src/goal/queue.ts`). It is how the
1893
1952
  * incremental read knows a row has not moved: the phone echoes back the revs it holds
@@ -2241,6 +2300,12 @@ var CallRecapSchema = z14.object({
2241
2300
  * summary (Brain, 2026-10-14). Optional: absent from an API before it. */
2242
2301
  filedOn: z14.array(z14.object({ goalId: z14.string().uuid(), title: z14.string() })).optional(),
2243
2302
  recorded: z14.boolean().optional(),
2303
+ /** On a `said` row: the agents the call SENT those words to, from the sending action's own
2304
+ * `about.agents` (20261106220220). The review's receipt is drawn from this and from the row's
2305
+ * own `requestId`, never from the asker of a question the words happened to sit under: on call
2306
+ * f9670f67 that asker was Interface while the send named Comms. `[]` or absent means nothing
2307
+ * was sent for that row, which is why the receipt is positive-only. */
2308
+ sentTo: z14.array(z14.object({ id: z14.string().nullable(), name: z14.string() })).optional(),
2244
2309
  settled: z14.object({ requestId: z14.string(), answer: z14.string() }).nullable().optional(),
2245
2310
  acknowledged: z14.boolean().optional(),
2246
2311
  /** On a `put` row: still waiting for the person's answer (not settled, not answered, not put off).
@@ -2544,6 +2609,7 @@ function waitingSummary(received) {
2544
2609
 
2545
2610
  export {
2546
2611
  mcpInputSchema,
2612
+ SKILL_MARK,
2547
2613
  AGENT_PROMPTS,
2548
2614
  promptResult,
2549
2615
  READS,
@@ -2551,6 +2617,7 @@ export {
2551
2617
  AGENT_TOOLS,
2552
2618
  LISTEN_COMMAND,
2553
2619
  serverInstructions,
2620
+ SKILL,
2554
2621
  entryWords,
2555
2622
  entryUnit,
2556
2623
  harnessDriven,
@@ -65,7 +65,7 @@ import {
65
65
  viaLine,
66
66
  whoAmI,
67
67
  withCodexEnv
68
- } from "./chunk-ULCS3CYO.js";
68
+ } from "./chunk-5OKHG3FO.js";
69
69
  export {
70
70
  AGENT_TOOLS,
71
71
  AGENT_TOOL_NAMES,
package/dist/enable.js CHANGED
@@ -2,12 +2,13 @@
2
2
  import {
3
3
  PAIGY_TOOL_IDS,
4
4
  enablePaigyTools,
5
+ installPaigySkill,
5
6
  installSessionListening
6
- } from "./chunk-LGG4IKAW.js";
7
+ } from "./chunk-CVQOJB2S.js";
7
8
  import {
8
9
  LISTEN_COMMAND
9
- } from "./chunk-ZCWFBY4B.js";
10
- import "./chunk-ULCS3CYO.js";
10
+ } from "./chunk-LRROOCLY.js";
11
+ import "./chunk-5OKHG3FO.js";
11
12
 
12
13
  // src/enable.ts
13
14
  function main() {
@@ -59,6 +60,12 @@ function main() {
59
60
  console.log(` (${result.already.length} already present)`);
60
61
  }
61
62
  const hooks = installSessionListening();
63
+ const skills = installPaigySkill();
64
+ for (const s of skills) {
65
+ if (s.ok && !s.already) console.log(`\u2713 Paigy skill for ${s.harness} (${s.path})`);
66
+ else if (s.ok) console.log(`\u2713 Paigy skill for ${s.harness} already current`);
67
+ else console.log(`\u26A0 ${s.path}: ${s.why}`);
68
+ }
62
69
  for (const hook of hooks) {
63
70
  if (!hook.ok) console.log(` ! ${hook.harness}: ${hook.why}`);
64
71
  else console.log(`\u2713 ${hook.harness} sessions will be told to listen (${hook.path})${hook.already ? " \u2014 already set" : ""}`);
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  resolvePairing,
11
11
  startPairing,
12
12
  suggestedAgentName
13
- } from "./chunk-DMQHSNFJ.js";
13
+ } from "./chunk-4US4OJR5.js";
14
14
  import {
15
15
  ConfigureSchema,
16
16
  ENABLE_COMMAND,
@@ -18,7 +18,7 @@ import {
18
18
  SERVER_INSTRUCTIONS,
19
19
  TOOLS,
20
20
  paigyToolsAllowlisted
21
- } from "./chunk-LGG4IKAW.js";
21
+ } from "./chunk-CVQOJB2S.js";
22
22
  import {
23
23
  AGENT_PROMPTS,
24
24
  ReachTeller,
@@ -32,7 +32,7 @@ import {
32
32
  refreshSessionHook,
33
33
  wakeStatusPath,
34
34
  withReach
35
- } from "./chunk-ZCWFBY4B.js";
35
+ } from "./chunk-LRROOCLY.js";
36
36
  import {
37
37
  clearSurface,
38
38
  writeSurface
@@ -53,7 +53,7 @@ import {
53
53
  setClient,
54
54
  slotName,
55
55
  whoAmI
56
- } from "./chunk-ULCS3CYO.js";
56
+ } from "./chunk-5OKHG3FO.js";
57
57
 
58
58
  // src/index.ts
59
59
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
package/dist/listen.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  removeListenMark,
12
12
  wakeStatusPath,
13
13
  writeListenMark
14
- } from "./chunk-ZCWFBY4B.js";
14
+ } from "./chunk-LRROOCLY.js";
15
15
  import {
16
16
  agentName,
17
17
  decided,
@@ -22,7 +22,7 @@ import {
22
22
  slotName,
23
23
  subscribeWake,
24
24
  viaLine
25
- } from "./chunk-ULCS3CYO.js";
25
+ } from "./chunk-5OKHG3FO.js";
26
26
 
27
27
  // src/listen.ts
28
28
  import { createHash } from "crypto";
package/dist/onboard.js CHANGED
@@ -3,14 +3,14 @@ import {
3
3
  resolvePairing,
4
4
  startPairing,
5
5
  suggestedAgentName
6
- } from "./chunk-DMQHSNFJ.js";
6
+ } from "./chunk-4US4OJR5.js";
7
7
  import {
8
8
  autoConfigureClients,
9
9
  claudeInstallHint,
10
10
  openBrowser,
11
11
  removeOldGeminiHooks
12
- } from "./chunk-LGG4IKAW.js";
13
- import "./chunk-ZCWFBY4B.js";
12
+ } from "./chunk-CVQOJB2S.js";
13
+ import "./chunk-LRROOCLY.js";
14
14
  import {
15
15
  TOKEN_PATH,
16
16
  agentName,
@@ -21,7 +21,7 @@ import {
21
21
  saveToken,
22
22
  setIdentity,
23
23
  whoAmI
24
- } from "./chunk-ULCS3CYO.js";
24
+ } from "./chunk-5OKHG3FO.js";
25
25
 
26
26
  // src/onboard.ts
27
27
  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-ULCS3CYO.js";
4
+ } from "./chunk-5OKHG3FO.js";
5
5
 
6
6
  // src/slot.ts
7
7
  process.stdout.write(agentName());
package/dist/stalled.js CHANGED
@@ -33,7 +33,7 @@ async function main() {
33
33
  const dir = join(homedir(), ".paigy", "stalled-reminded");
34
34
  const mark = join(dir, `${session || "session"}-${day}`);
35
35
  if (existsSync(mark)) return;
36
- const { receive } = await import("./dist-6D3WD3H6.js");
36
+ const { receive } = await import("./dist-TJFO3UCZ.js");
37
37
  const reply = stopReply((await receive()).stalled ?? []);
38
38
  if (!reply) return;
39
39
  mkdirSync(dir, { recursive: true });
@@ -7,7 +7,7 @@ import {
7
7
  readToken,
8
8
  sessionSlot,
9
9
  slotName
10
- } from "./chunk-ULCS3CYO.js";
10
+ } from "./chunk-5OKHG3FO.js";
11
11
 
12
12
  // src/statusline.ts
13
13
  import { realpathSync } from "fs";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@paigy/mcp",
3
3
  "mcpName": "io.github.paigy-ai/mcp",
4
- "version": "0.40.43",
4
+ "version": "0.40.44",
5
5
  "description": "Paigy MCP server — the AI agent harness that calls you. Lets an agent notify a user and await their reply.",
6
6
  "license": "MIT",
7
7
  "type": "module",