@paigy/mcp 0.40.38 → 0.40.40

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.
@@ -1,4 +1,4 @@
1
- // ../../../../../../private/tmp/paigy-draft-reaches/packages/schema/dist/index.js
1
+ // ../../packages/schema/dist/index.js
2
2
  import { z as z12 } from "zod";
3
3
  import { z } from "zod";
4
4
  import { z as z3 } from "zod";
@@ -17,6 +17,7 @@ import { zodToJsonSchema as zodToJsonSchema5 } from "zod-to-json-schema";
17
17
  import { z as z10 } from "zod";
18
18
  import { zodToJsonSchema as zodToJsonSchema6 } from "zod-to-json-schema";
19
19
  import { z as z11 } from "zod";
20
+ import { zodToJsonSchema as zodToJsonSchema7 } from "zod-to-json-schema";
20
21
  var OPTIONS_MIN = 2;
21
22
  var OPTIONS_MAX = 6;
22
23
  var OPTION_LABEL_MAX = 50;
@@ -46,7 +47,7 @@ var OptionInputSchema = z.object({
46
47
  imgUrl: OptionSchema.shape.image
47
48
  }).strict();
48
49
  var NIGHT = { from: 23, to: 7 };
49
- var ASK_MAX = 250;
50
+ var ASK_MAX = 80;
50
51
  var UNITS_MAX = 12;
51
52
  var UNIT_TITLE_MAX = 50;
52
53
  var UNIT_BODY_MAX = 200;
@@ -111,15 +112,15 @@ var CompletionSchema = z3.object({
111
112
  }).strict();
112
113
  var CompleteChangeSchema = CompletionSchema.extend({ kind: z3.literal("complete") });
113
114
  var ASKING_RULES = `HOW TO ASK:
114
- 1. Write one question per object. Five questions are five objects in \`questions\`, in one contact, so each can be answered on its own; one question with five parts settles nothing until all five are answered.
115
- 2. Name the Goal you are working on as the question's \`goalId\`: the question goes onto that Goal and its answer comes back there. It must exist: create it with manage_goals first.
116
- 3. \`workItBlocks\` only for work you are truly blocked on (it asks for a call); leave it out for a question you can keep working around.
117
- 4. Never hold the process open with while-loops. A wait that comes back \`expired\` is not a finished conversation: wait again with contact({wait:true}) only while you have nothing else to do. While a Call you placed is open, keep receiving window after window until it ends, even when a window is empty: the person may give follow-ups or instructions. With no open Call, STOP WAITING when a window comes back with nothing new \u2014 the question stays open, and its answer reaches you on the Goal. Otherwise, yield only with a working listener or scheduled wakeup, and collect answers with \`contact({wait:false})\` or \`get_goals\` on that wake.
118
- 5. One question, one row. Never restate a question that is still waiting inside a new contact: keep waiting on the original, or the answer lands on one copy and the other stays open. Take back one that no longer matters with \`withdrawQuestionIds\`.
119
- 6. Read the Goal's conversation before asking. Never ask again what was answered or already shipped \u2014 and tell its states apart: \`answered\` carries the choice in \`answer\`, while \`replied\` means they said something and chose none of your options, so that question is still yours to settle and their words are the line beside it.
120
- 7. A question carries its options when you have them; a free-text question may omit them. Each option\u2019s label must stand on its own and name its choice in full -- never \`All three\` or \`Option 2\`. \`pickMode: "many"\` makes the card a checklist (options they may want several of); \`"one"\`, the default, a pick (one rules out the others); \`"rank"\` an ordering. Each question is sent exactly as written, with no reading in between. They can always answer in their own words, so never add an 'Other' option.
121
- 8. A diagnosis says when, why and how it happens, then proposes one fix. Never options first.
122
- 9. GIVE A REPORT ITS REPLY: an update takes \`reply\` too, one line the person can tap instead of typing, written in THEIR voice for THAT report ("Sounds good, I'll test later") \u2014 the same care you give a question's options. What they tap is what you read back, which tells you whether they verified your work or only received it. One line, because a report has one way to close, and it may not change what happens next: a reply that would is a question. Requested delivery and completed-Goal reports require a reply (reply_required); ordinary progress may omit it.`;
115
+ 1. One question per object in \`questions\`, so each is answered on its own.
116
+ 2. \`goalId\` names the existing Goal it is about (create one with manage_goals first); the answer comes back there.
117
+ 3. \`workItBlocks\` only when you are truly blocked: it asks for a call.
118
+ 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.
119
+ 5. One question, one row: never re-send one still waiting; take back one that no longer matters with \`withdrawQuestionIds\`.
120
+ 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.
121
+ 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.
122
+ 8. A diagnosis says when, why and how it happens, then proposes one fix.
123
+ 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 reaches them or completes a Goal.`;
123
124
  function draft2020(node) {
124
125
  if (Array.isArray(node)) return node.map(draft2020);
125
126
  if (node && typeof node === "object") {
@@ -162,7 +163,7 @@ var userExplicitlyRequested = z4.enum(["any", "call"]).optional().describe(
162
163
  `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.`
163
164
  );
164
165
  var units = z4.array(UnitInputSchema).optional().describe(
165
- `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. Leave it out when the main text says it all.`
166
+ `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. 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.`
166
167
  );
167
168
  var UpdateInputSchema = z4.object({
168
169
  message: z4.string().trim().min(1).describe(`The one point, at most ${ASK_MAX} characters; the context goes in units.`),
@@ -217,7 +218,7 @@ var questionFields = {
217
218
  ),
218
219
  goalId: z4.string().uuid().describe("The Goal the question is about, which must exist (create it with manage_goals first). Its answer comes back there."),
219
220
  units,
220
- options: z4.array(OptionInputSchema).min(1).max(6).optional().describe("The choices, when you have them. Each label stands on its own, at most 50 characters; a hint, at most 200."),
221
+ options: z4.array(OptionInputSchema).min(1).max(6).optional().describe("The choices, when you have them. Each label stands on its own, at most 50 characters; a hint, at most 200. They can always answer in their own words, so no 'Other' or 'None of these'."),
221
222
  // HOW THE OPTIONS ARE ANSWERED, SAID BY THE AGENT (owner, 2026-10-02: "one and many makes sense";
222
223
  // brain_prompts.md §1: structured agent questions need no model). The app draws all three.
223
224
  pickMode: z4.enum(["one", "many", "rank"]).optional().describe(
@@ -259,7 +260,9 @@ var QuestionDependencySchema = z4.object({
259
260
  reason: reason.optional()
260
261
  }).strict().refine(actionFits, ACTION_FITS);
261
262
  var sendFields = {
262
- completions: z4.array(CompletionSchema).min(1).max(20).optional().describe("Close existing Goals and deliver their completion reports atomically. Ordinary updates never close work."),
263
+ completions: z4.array(CompletionSchema).min(1).max(20).optional().describe(
264
+ "COMPLETIONS close and report in one operation, the same contract as manage_goals kind:complete: closure and the report commit together or neither does. Required children and dependencies must already be finished. options suggest next steps: a tap acknowledges the report and requests that step, and never reopens the Goal (follow-up work needs a new one). No reviewer rejudges it: say only what your evidence supports. Ordinary updates never close a Goal."
265
+ ),
263
266
  updates: z4.array(UpdateInputSchema).min(1).max(20).optional().describe(
264
267
  "News: progress, findings, a result. An update asks nothing. It reaches the person only when they asked for it (userExplicitlyRequested), when it answers something they said, or once its Goal is done; otherwise it is recorded as the Goal's progress and nobody is notified."
265
268
  ),
@@ -273,7 +276,7 @@ var sendFields = {
273
276
  };
274
277
  var ContactSchema = z4.object({
275
278
  ...sendFields,
276
- questions: z4.array(QuestionInputSchema).min(1).max(20).optional().describe("Questions for the person, one per object."),
279
+ questions: z4.array(QuestionInputSchema).min(1).max(20).optional().describe("Questions for the person, one per object, each sent exactly as written, with no reading in between."),
277
280
  /** RECEIVING (2I, I2; brain_prompts.md §2.2): the events this agent handled. */
278
281
  ackEventIds: z4.array(z4.string().uuid()).min(1).max(100).optional().describe(
279
282
  "The exact eventIds of the events you have handled: an acknowledgment (ACK). Reading alone acknowledges nothing, so an event comes back until you acknowledge it; after that the next batch can come. Each recipient acknowledges separately, for itself only. A Question you owe is answered (answers), never acknowledged away."
@@ -289,15 +292,13 @@ var ContactWireSchema = z4.object({
289
292
  questions: z4.array(QuestionWireSchema).min(1).max(20).optional()
290
293
  }).strict().refine(sendsAnything, "a contact sends at least one update, question, answer, withdrawal or question dependency");
291
294
  var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
292
- var CONTACT_DESCRIPTION = "Send to and receive from the person: completions, updates, questions, answers to questions addressed to you, withdrawals of your own questions, and what a question holds up. Every Goal you name must already exist: create it with manage_goals first; an unknown Goal is refused and nothing is sent. Every item applies on its own: read `results`, where a failed item names why while the rest still apply.\n\n" + ASKING_RULES + `
293
-
294
- CUT A LONG MESSAGE BEFORE YOU SEND IT: a question, an update's message or an answer is the one point, in at most ${ASK_MAX} characters; everything else it needs goes in 'units', up to ${UNITS_MAX} {title, body} in reading order, one idea each, every title at most ${UNIT_TITLE_MAX} characters and every body at most ${UNIT_BODY_MAX}. The person sees the main text and the titles, and opens a unit to read its body. 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.
295
+ var CONTACT_DESCRIPTION = "Send to and receive from the person: completions, updates, questions, answers to questions addressed to you, withdrawals, and what a question holds up. Every Goal named must exist: an unknown one refuses the whole contact. Each item applies on its own: read `results`, where a failed one names why.\n\n" + ASKING_RULES + `
295
296
 
296
- UPDATES ask nothing, so no answer is owed and none should be awaited. An update reaches the person only when they asked you for it (userExplicitlyRequested), when it answers something they said, or once its Goal is done; any other is recorded as the Goal's progress and nobody is notified. Ordinary updates never close a Goal. On a Goal whose report card is still open, an update that reaches them is added to that card, with no new push. If the person is already on a call, anything that reaches them joins that call, with no ring.
297
+ LENGTH: the question, message or answer is the one point, at most ${ASK_MAX} characters; the rest goes in \`units\`, one idea each. Over a cap is refused by name and nothing is sent. Several results are several updates.
297
298
 
298
- COMPLETIONS close and report in one operation: completions:[{goalId,message,units?,reply,options?}]. The same contract works in manage_goals changes with kind:complete. message is at most 50 characters; use units when useful for initial state, changes, current state (verification and limits), and next steps / leftover work with its owner. reply is one acknowledgement in the person's voice. Optional options:[{label,hint}] suggest next steps: a tap acknowledges the report and requests that step from its author. It never reopens the completed Goal; follow-up work needs a new Goal. Closure and the report commit together or neither does. Required children and dependencies must already be finished. No Reviewer rejudges your completion; say only what your evidence supports.
299
+ 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.
299
300
 
300
- RECEIVING: a contact that sends nothing returns \`events\`, a limited batch of what is addressed to you (not a history page): \`question\`, a Question you owe (answer it in answers, with its questionId); \`update\`, something new on one of your Goals (a reply, an answer: read it with get_goals); \`instruction\`, a request or note sent to you. \`hasMore\` says more are waiting. Reading acknowledges nothing: once you have handled events, confirm their eventIds with contact({ackEventIds}), and the next batch can come. contact({}) waits up to about 45 seconds for something to arrive; contact({wait:false}) returns at once. It also lists work given to you that nobody has started (\`assigned\`; your first write to it starts it) and your work gone quiet (\`stalled\`).`;
301
+ RECEIVING: contact({}) waits ~45 s; contact({wait:false}) returns \`events\` at once. Confirm the ones you handled with contact({ackEventIds}).`;
301
302
  var CreateGoalSchema = z5.object({
302
303
  outcome: z5.string().trim().min(1).max(1e4),
303
304
  /** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
@@ -431,8 +432,8 @@ var AGENT_TOOLS = [
431
432
  ];
432
433
  var AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
433
434
  function serverInstructions(opts) {
434
- const waits = opts.waits ? "contact({..., wait:true}) holds one bounded ~45 s window for a response to what you sent; contact({}) holds one for anything addressed to you." : "A contact here returns after one read (`waitOutcome: not_waited`); answers wake you, and contact({wait:false}) collects them.";
435
- return `On startup and after a wake, call contact({wait:false}) for the events addressed to you (acknowledge the ones you handled with contact({ackEventIds})), which hand you your work: answers, assignments, reviews and due Goals. get_goals reads a Goal and the conversation on it; a read never puts you on it. You are on a Goal from your first write to it (a contact update, question or answer naming it, or a manage_goals change); there is nothing to claim or join. Every Goal of your person is open to you, whichever of their agents owns it: read it with get_goals, write on it (a contact naming its id) and change it (manage_goals). Create, edit, assign and organize work with manage_goals. Complete and report atomically with contact.completions or manage_goals kind:complete; both take the same completion contract. A contact only names Goals that already exist. Every read ends in \`next\`, the one step to take. Sending returns immediately unless you ask it to wait: keep working and collect answers by receiving or through get_goals. ${waits} Events repeat until you acknowledge them: reading alone acknowledges nothing; an \`update\` event acknowledged is a review cleared. Report progress as a contact update: one the person does not need yet is kept as the Goal's progress and reaches no one. To follow up later, defer the Goal with manage_goals: you are woken when its time comes. Never infer ringing from an open Call Delivery. Soft waiting and re-presentation are unsupported. Your user is remote. Always interact with the user through Paigy. For decisions, approvals, or questions, contact them with structured options. Never assume anyone is reading the terminal stdout.
435
+ const waits = opts.waits ? "contact({wait:true}) waits ~45 s for a reply to what you sent; contact({}) for anything addressed to you." : "A contact here returns after one read; answers wake you, and contact({wait:false}) collects them.";
436
+ return `On startup and after a wake, contact({wait:false}) returns your events. They repeat until you acknowledge them with contact({ackEventIds}); reading acknowledges nothing. get_goals reads a Goal and its conversation; your first write to one puts you on it, and every Goal of your person is open to you. manage_goals creates, assigns and organizes work; to follow up later, defer the Goal with manage_goals (you're woken when due). Complete and report in one step: contact.completions or manage_goals kind:complete. Every read ends in \`next\`: your next step. ${waits} Report progress as a contact update; 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; ask decisions with options.
436
437
 
437
438
  ${ASKING_RULES}`;
438
439
  }
@@ -518,11 +519,28 @@ var messageBase = {
518
519
  obligationIds: z6.array(id)
519
520
  };
520
521
  var BrainMessageSchema = z6.union([
521
- z6.object({ ...messageBase, to: z6.object({ kind: z6.literal("user") }).strict() }).strict(),
522
+ // A MESSAGE TO THE PERSON KEEPS ITS SENTENCES (turns.md): the talker writes them one by one, and each is
523
+ // its own unit of speech (`<cycle>:<key>:<n>`). `text` is the same words joined, for every reader of a
524
+ // message's words. Absent on results written before sentences.
525
+ z6.object({
526
+ ...messageBase,
527
+ to: z6.object({ kind: z6.literal("user") }).strict(),
528
+ sentences: z6.array(z6.string().min(1)).min(1).optional(),
529
+ // THE REPORTS IT TELLS, BY THEIR OWN IDENTITY (call 9d1c096c, 2026-10-15): the talker's message names the report
530
+ // Entries it speaks of, so the plan's line names that report rather than re-finding it by its Goal. Absent on
531
+ // results written before it.
532
+ tells: z6.array(z6.object({ id }).strict()).optional()
533
+ }).strict(),
522
534
  z6.object({
523
535
  ...messageBase,
524
536
  to: z6.object({ kind: z6.literal("agent"), agentId: z6.string().regex(/^agent:(?!paigy$)\S+$/) }).strict(),
525
- call: z6.object({ callId: id, listen: z6.literal(true) }).strict().optional()
537
+ call: z6.object({ callId: id, listen: z6.literal(true) }).strict().optional(),
538
+ // WHAT IT ASKS THE AGENT, IN UNDER TEN WORDS (owner, 2026-10-15: "Yes, add the summary"): the talker's own, stored
539
+ // as the sent Entry's summary, which the recap's "Sent to <agent>: …" row shows. Absent on results written before it.
540
+ summary: z6.string().optional(),
541
+ // THE PERSON ASKED TO BE CALLED BACK ONCE THIS AGENT'S WORK IS DONE (turns.md, step 0): the agent owes
542
+ // the call, and its next contact about it rings them (`public.callbacks`).
543
+ callback: z6.literal(true).optional()
526
544
  }).strict()
527
545
  ]);
528
546
  var BrainSearchSchema = z6.object({
@@ -546,92 +564,97 @@ var BrainResultSchema = z6.object({
546
564
  next: BrainNextSchema,
547
565
  search: BrainSearchSchema.optional()
548
566
  }).strict();
549
- var str = z6.string();
567
+ var str2 = z6.string();
550
568
  var strs = z6.array(z6.string());
551
569
  var CompactResultSchema = z6.object({
552
- next: z6.object({ say: strs, then: z6.enum(["listen", "hold", "end", "none"]), waitFor: strs, reason: str, instructions: strs }).strict(),
553
- messages: z6.array(z6.object({ key: str, to: str, text: str, about: strs, entries: strs, owed: strs, inviteToCall: z6.boolean() }).strict()),
554
- entries: z6.array(z6.object({ key: str, from: str, on: strs }).strict()),
555
- answers: z6.array(z6.object({ question: str, entries: strs, summary: str, picked: strs }).strict()),
570
+ next: z6.object({ say: strs, then: z6.enum(["listen", "hold", "end", "none"]), waitFor: strs, reason: str2, instructions: strs }).strict(),
571
+ messages: z6.array(z6.object({ key: str2, to: str2, text: str2, about: strs, entries: strs, owed: strs, inviteToCall: z6.boolean() }).strict()),
572
+ entries: z6.array(z6.object({ key: str2, from: str2, on: strs }).strict()),
573
+ answers: z6.array(z6.object({ question: str2, entries: strs, summary: str2, picked: strs }).strict()),
556
574
  changes: z6.array(z6.object({
557
- key: str,
575
+ key: str2,
558
576
  what: z6.enum(["goal", "question", "lesson"]),
559
577
  op: z6.enum(["create", "edit", "assign", "open", "complete", "cancel", "defer", "move", "block", "unblock", "withdraw", "remember", "revise"]),
560
- id: str,
561
- text: str,
562
- title: str,
563
- who: str,
564
- under: str,
578
+ id: str2,
579
+ text: str2,
580
+ title: str2,
581
+ who: str2,
582
+ under: str2,
565
583
  gate: z6.enum(["start", "complete", "answer", "none"]),
566
- options: z6.array(z6.object({ id: str, label: str }).strict()),
584
+ options: z6.array(z6.object({ id: str2, label: str2 }).strict()),
567
585
  pick: z6.enum(["one", "many", "rank", "words"]),
568
586
  entries: strs
569
587
  }).strict()),
570
588
  feedback: z6.array(strs),
571
- search: z6.object({ query: str, within: strs, goal: str }).strict()
589
+ search: z6.object({ query: str2, within: strs, goal: str2 }).strict()
572
590
  }).strict();
573
591
  var COMPACT_RESULT_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
574
592
  zodToJsonSchema2(CompactResultSchema, { $refStrategy: "none" })
575
593
  );
576
- var str2 = z7.string();
594
+ var str3 = z7.string();
577
595
  var strs2 = z7.array(z7.string());
578
596
  var TalkerReplySchema = z7.object({
579
597
  /** What to say and send, messages to the person first: spoken in the order written. `to` is "user"
580
- * or an agent's handle; `about` names handles; `blocks` is the item a question to an agent must be
581
- * answered before ("" for none). */
582
- messages: z7.array(z7.object({ key: str2, to: str2, text: str2, about: strs2, blocks: str2 }).strict()),
598
+ * or an agent's handle; `sentences` is what a message to the person says, one short sentence each
599
+ * ([] to an agent); `text` is what a message to an agent says ("" to the person); `summary` is what it
600
+ * asks the agent, in under ten words, as the person would put it ("" to the person; owner, 2026-10-15:
601
+ * "Yes, add the summary"), which the recap's "Sent to <agent>: …" row shows; `about` names
602
+ * handles; `blocks` is the item a question to an agent must be answered before ("" for none);
603
+ * `callback` is true on a message to the agent doing work the person asked to be called back about
604
+ * once it is done (false otherwise): that agent then owes the call (turns.md, step 0). */
605
+ messages: z7.array(z7.object({ key: str3, to: str3, sentences: strs2, text: str3, summary: str3, about: strs2, blocks: str3, callback: z7.boolean() }).strict()),
583
606
  /** What the call does next: listen, hold (the person asked for a moment) or end (they asked to). */
584
607
  then: z7.enum(["listen", "hold", "end"]),
585
608
  /** The person's replies to items (replies, owner 2026-10-07): the item's handle, the chosen option
586
609
  * IDs, the line handles, how they replied in under ten words (keeping any condition), and `defer`:
587
610
  * "" when their words settle the item, else what a reply that puts it off waits for -- "call" (the
588
611
  * end of this call), a question's handle (that question's answer) or an ISO time. */
589
- replies: z7.array(z7.object({ item: str2, options: strs2, lines: strs2, summary: str2, defer: str2 }).strict()),
612
+ replies: z7.array(z7.object({ item: str3, options: strs2, lines: strs2, summary: str3, defer: str3 }).strict()),
590
613
  /** The line handles that tell the talker how to run this call; they last until it ends and reach no agent. */
591
614
  instruction: strs2,
592
615
  /** The line handles the filer acts on, and the only ones it reads (3e): new work, a change to existing
593
616
  * work, feedback on Paigy, anything said to an agent, and any rule meant to outlast the call. */
594
617
  file: strs2,
595
618
  /** Evidence to fetch for a second round: a search, an item's full text, or an agent by name. */
596
- need: z7.object({ kind: z7.enum(["search", "item", "agent", ""]), text: str2 }).strict(),
619
+ need: z7.object({ kind: z7.enum(["search", "item", "agent", ""]), text: str3 }).strict(),
597
620
  /** The handle of the item to explain ("" for none). */
598
- explain: str2
621
+ explain: str3
599
622
  }).strict();
600
623
  var TALKER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
601
624
  zodToJsonSchema3(TalkerReplySchema, { $refStrategy: "none" })
602
625
  );
603
- var str3 = z8.string();
626
+ var str4 = z8.string();
604
627
  var strs3 = z8.array(z8.string());
605
628
  var FilerReplySchema = z8.object({
606
629
  /** Which Goals each new line goes on: a line's handle, Goal handles (or keys of new Goals), and what
607
630
  * the line says in at most ten words. */
608
- filed: z8.array(z8.object({ line: str3, goals: strs3, summary: str3 }).strict()),
631
+ filed: z8.array(z8.object({ line: str4, goals: strs3, summary: str4 }).strict()),
609
632
  goals: z8.array(z8.object({
610
633
  op: z8.enum(["create", "edit", "complete", "cancel", "reopen", "assign", "defer", "move", "block", "unblock"]),
611
- key: str3,
612
- goal: str3,
613
- title: str3,
614
- outcome: str3,
615
- owner: str3,
616
- under: str3,
617
- until: str3,
618
- on: str3,
634
+ key: str4,
635
+ goal: str4,
636
+ title: str4,
637
+ outcome: str4,
638
+ owner: str4,
639
+ under: str4,
640
+ until: str4,
641
+ on: str4,
619
642
  gate: z8.enum(["start", "complete", "answer", ""]),
620
- why: str3
643
+ why: str4
621
644
  }).strict()),
622
645
  questions: z8.array(z8.object({
623
646
  op: z8.enum(["create", "edit", "assign", "withdraw"]),
624
- key: str3,
625
- question: str3,
626
- text: str3,
627
- summary: str3,
628
- to: str3,
629
- goal: str3,
630
- options: z8.array(z8.object({ id: str3, label: str3 }).strict()),
647
+ key: str4,
648
+ question: str4,
649
+ text: str4,
650
+ summary: str4,
651
+ to: str4,
652
+ goal: str4,
653
+ options: z8.array(z8.object({ id: str4, label: str4 }).strict()),
631
654
  pick: z8.enum(["one", "many", "rank", "words", ""]),
632
- blocks: str3
655
+ blocks: str4
633
656
  }).strict()),
634
- lessons: z8.array(z8.object({ op: z8.enum(["remember", "revise", "withdraw"]), lesson: str3, text: str3, scope: str3 }).strict()),
657
+ lessons: z8.array(z8.object({ op: z8.enum(["remember", "revise", "withdraw"]), lesson: str4, text: str4, scope: str4 }).strict()),
635
658
  /** Line handles whose words comment on Paigy itself. */
636
659
  feedback: strs3
637
660
  }).strict();
@@ -692,6 +715,27 @@ var TidyReplySchema = z10.object({
692
715
  var TIDY_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
693
716
  zodToJsonSchema6(TidyReplySchema, { $refStrategy: "none" })
694
717
  );
718
+ var HandoutReplySchema = z11.object({
719
+ assign: z11.array(z11.object({
720
+ /** The Goal's handle (G). */
721
+ goal: z11.string(),
722
+ /** The agent's handle (A). */
723
+ to: z11.string(),
724
+ /** At most ten words, naming the Goal of theirs it is most like: the reason the hand-out records. */
725
+ why: z11.string()
726
+ }).strict()),
727
+ /** The Goals no listed agent's work is even loosely like: Paigy starts a session for them (owner, 2026-10-24,
728
+ * Goal 37603006, "One per family of related Goals"). */
729
+ new: z11.array(z11.object({
730
+ /** The Goal's handle (G). */
731
+ goal: z11.string(),
732
+ /** At most ten words: why nothing active fits it. */
733
+ why: z11.string()
734
+ }).strict())
735
+ }).strict();
736
+ var HANDOUT_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
737
+ zodToJsonSchema7(HandoutReplySchema, { $refStrategy: "none" })
738
+ );
695
739
  function entryWords(entry) {
696
740
  const content = entry.content;
697
741
  if (content && "sealed" in content) return "";
@@ -720,42 +764,6 @@ var LIVE_MS = 3 * 6e4;
720
764
  var WORKING_MS = 60 * 6e4;
721
765
  var WORKING_TIER_MS = 2 * 60 * 6e4;
722
766
  var RECENT_TIER_MS = 24 * 60 * 6e4;
723
- var json = z11.lazy(() => z11.union([
724
- z11.string(),
725
- z11.number().finite(),
726
- z11.boolean(),
727
- z11.null(),
728
- z11.array(json),
729
- z11.record(json)
730
- ]));
731
- var clock = z11.number().int().nonnegative().safe();
732
- var HeardEventSchema = z11.object({
733
- eventId: z11.string().uuid(),
734
- source: z11.enum(["phone", "bot"]),
735
- sequence: clock,
736
- kind: z11.string().min(1),
737
- phoneAtMs: clock.optional(),
738
- botAtMs: clock.optional(),
739
- payload: z11.record(json)
740
- }).strict();
741
- var HeardCursorSchema = HeardEventSchema.pick({ source: true, sequence: true, eventId: true });
742
- var HeardPageSchema = z11.object({
743
- events: z11.array(HeardEventSchema).max(200),
744
- next: HeardCursorSchema.nullable()
745
- }).strict();
746
- var MAX_EVENTS = 50;
747
- var MAX_BYTES = 8e4;
748
- var batchBytes = (events) => {
749
- let bytes = 0;
750
- for (const char of JSON.stringify({ events })) {
751
- const point = char.codePointAt(0);
752
- bytes += point < 128 ? 1 : point < 2048 ? 2 : point < 65536 ? 3 : 4;
753
- }
754
- return bytes;
755
- };
756
- var HeardBatchSchema = z11.object({
757
- events: z11.array(HeardEventSchema).min(1).max(MAX_EVENTS)
758
- }).strict().refine((value) => batchBytes(value.events) <= MAX_BYTES, "heard_batch_too_large");
759
767
  var ContextSchema = z12.object({
760
768
  title: z12.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
761
769
  description: z12.array(z12.string().min(1)).describe(
@@ -1080,6 +1088,10 @@ var AgendaTurnSchema = z12.object({
1080
1088
  /** The spoken sentences of the turn, in order. No count: how long a turn is is the brain's call
1081
1089
  * (owner, 2026-09-25), and a count here refused whole plans. */
1082
1090
  info: z12.array(z12.string().min(1)).default([]),
1091
+ /** THE ID OF EACH SENTENCE, when the plan names them (turns.md): a talker message's sentences are its
1092
+ * `info`, one piece each, and `units[i]` is the id the bot's line receipt carries for `info[i]`
1093
+ * (`<cycle>:<key>:<n>`), so what was heard is known per sentence. Absent on every other turn. */
1094
+ units: z12.array(z12.string().min(1)).optional(),
1083
1095
  question: z12.string().min(1).nullable(),
1084
1096
  /** True on the one turn carrying the agent's own declared question. */
1085
1097
  asks: z12.boolean().optional(),
@@ -1104,6 +1116,21 @@ var AgendaTurnSchema = z12.object({
1104
1116
  * the phone (`asking`, so the agenda goes gold and its options show) without treating the line as a
1105
1117
  * question turn — the talker owns re-asking and the waits, so `question` stays null. */
1106
1118
  onTable: z12.boolean().optional(),
1119
+ /** THE CARRIED ITEM THIS LINE TELLS (Goal 2a1e50eb, owner 2026-10-14: "Build the whole design") — a
1120
+ * request the Call carries that asks NOTHING: a report, a progress note, another agent's waiting news.
1121
+ * Its request Entry id, so the phone can make that agenda row the current one while it is read
1122
+ * (`telling` on the `turn` topic, `docs/clients/app/walk/design.md` §11).
1123
+ *
1124
+ * A FIELD OF ITS OWN, NEVER `claimId`: the claim is the RETURN identity — answers route by it and the
1125
+ * record tags the line with it (`workId`) — and an answer must never route to an Entry that asks
1126
+ * nothing. Absent on a turn speaking for a question (`claimId` says that) and on one speaking for no
1127
+ * carried item at all (an opener, a sign-off, Paigy's own line). */
1128
+ telling: z12.string().optional(),
1129
+ /** PAIGY'S OWN LINE (owner, 2026-10-15; calls ed2cbdce, ad50ad25): a turn that speaks for no item -- an
1130
+ * opener, a sign-off, a reply about nothing an agent asked or told -- is recorded as Paigy's, not as the
1131
+ * agent that rang. Absent on every turn that speaks for an item (`agentId` names whose), and on plans
1132
+ * from before it: a bot that does not know it records the ringing agent, as before. */
1133
+ speaksFor: z12.literal("paigy").optional(),
1107
1134
  select: SelectShapeSchema.optional(),
1108
1135
  options: z12.array(OptionSchema.omit({ id: true })).optional(),
1109
1136
  /* `pace` STOOD HERE (#826). A turn could carry seconds and the model chose them. The walk
@@ -1192,7 +1219,7 @@ var InboxItemSchema = z12.object({
1192
1219
  questions: z12.array(CallQuestionSchema).optional(),
1193
1220
  /** The same call carrying order, including request Entries with no DecisionNeed as reports.
1194
1221
  * Additive: questions stays decision-only for every other surface and older bundles. */
1195
- callItems: z12.array(CallQuestionSchema.extend({ state: z12.enum(["open", "answered", "report"]) })).optional(),
1222
+ callItems: z12.array(CallQuestionSchema.extend({ state: z12.enum(["open", "answered", "report"]), acknowledged: z12.boolean().optional() })).optional(),
1196
1223
  visuals: z12.array(VisualSchema).optional(),
1197
1224
  /** The connected agent's name (the single pairing name — user-typed, or the
1198
1225
  * agent's suggestion, or a default silly name). */
@@ -1257,6 +1284,21 @@ var InboxItemSchema = z12.object({
1257
1284
  * urgency (see the main README's "premier use case" + docs/delivery/notify/states.md). Drives the
1258
1285
  * inbox's blocking badge and the extra confirm step before dismissing it. */
1259
1286
  blocking: z12.boolean().default(false),
1287
+ /** WHAT FREED THIS QUESTION (owner, 2026-10-16, Goal 2383ef14: "we don't really show the question
1288
+ * at the top until the question it could reply to with is resolved. And then we show it again
1289
+ * with the context of the other thing that was resolved"; asked how it should carry that, "The
1290
+ * answer above it"). A question they answered with a question of their own waits for theirs
1291
+ * (`private.ask_back`) and drops out of the front while it waits; when it comes back, the card
1292
+ * draws their question and the answer it got ABOVE the ask, as one card. Absent on a question
1293
+ * nothing ever held back. */
1294
+ freedBy: z12.object({
1295
+ questionId: z12.string(),
1296
+ asked: z12.string(),
1297
+ answer: z12.string(),
1298
+ answeredAt: z12.string(),
1299
+ /** The answer in ten words, when the talker wrote one beside it. */
1300
+ summary: z12.string().optional()
1301
+ }).optional(),
1260
1302
  /** The user's locked-in answer; present only for replied items (GET /api/replied/:id). */
1261
1303
  answer: UserAnswerSchema.optional(),
1262
1304
  /** THE TARGET FACTS A CARD RENDERS (#1796 point 5, 2026-09-11): the Delivery it is a view of,
@@ -1316,8 +1358,8 @@ var MissedCallSchema = z12.enum([
1316
1358
  "inbox",
1317
1359
  "dismiss"
1318
1360
  ]);
1319
- var clock2 = (h) => h === 0 ? "midnight" : h === 12 ? "noon" : h < 12 ? `${h} am` : `${h - 12} pm`;
1320
- var QUIET = ` Nothing rings from ${clock2(NIGHT.from)} to ${clock2(NIGHT.to)} your time; the count waits for morning.`;
1361
+ var clock = (h) => h === 0 ? "midnight" : h === 12 ? "noon" : h < 12 ? `${h} am` : `${h - 12} pm`;
1362
+ var QUIET = ` Nothing rings from ${clock(NIGHT.from)} to ${clock(NIGHT.to)} your time; the count waits for morning.`;
1321
1363
  var BrokerTuningSchema = z12.object({
1322
1364
  /** 'none' = skip the spoken ack after a mapped answer (power users find it slow). */
1323
1365
  ackVerbosity: z12.enum(["normal", "none"]).optional(),
@@ -1654,6 +1696,13 @@ var QueueItemSchema = z12.object({
1654
1696
  * can see (`replies`, `questions[].answeredAt`) miss a spontaneous note entirely — a `request`
1655
1697
  * Entry with no `about_id` is in neither. */
1656
1698
  lastPersonAt: z12.string().nullable().optional(),
1699
+ /** EACH SIDE'S NEWEST WORD IN THE LAST DAY (owner, 2026-10-13, the Work tab's order): the person's
1700
+ * (`yours`) and the agents' (`theirs`), each null when that side wrote nothing on it in 24 hours.
1701
+ * `latest` holds one Entry, so whichever side wrote first is hidden there; Work orders a Goal by
1702
+ * these two and draws the side it was placed by under its title. Optional, so hand-built queues
1703
+ * and a door older than 20261013045542 need not spell them. */
1704
+ yours: GoalLatestSchema.nullable().optional(),
1705
+ theirs: GoalLatestSchema.nullable().optional(),
1657
1706
  /** WHAT THIS ROW IS, IN TWELVE CHARACTERS (#2928) — the hash of every other field on it, stamped
1658
1707
  * by the one projection that builds the row (`apps/api/src/goal/queue.ts`). It is how the
1659
1708
  * incremental read knows a row has not moved: the phone echoes back the revs it holds
@@ -1925,10 +1974,20 @@ var CallRecapSchema = z12.object({
1925
1974
  title: z12.string(),
1926
1975
  owner: z12.string(),
1927
1976
  state: z12.string(),
1977
+ /** Presented Entry content and its author, separately from the current Goal owner. */
1978
+ items: z12.array(z12.object({ entryId: z12.string(), author: z12.string(), text: z12.string(), summary: z12.string().nullable(), acknowledged: z12.boolean().optional() })).optional(),
1928
1979
  /** Whether the call put it to the person. An item it carried and never said has no row on the
1929
1980
  * review (owner, 2026-10-08: "why do we get the third one when it didn't happen in the call?"). */
1930
1981
  presented: z12.boolean(),
1931
- questions: z12.array(z12.object({ id: z12.string().uuid(), state: z12.string(), title: z12.string() })),
1982
+ /** Whether THIS call created it — the third way a call touches work, beside carrying a request
1983
+ * there and filing words there (owner, 2026-10-08, Goal `5b4b1a0a`: "Show the new work, keep my
1984
+ * words under it"). Created work is never carried, so it is never `presented`; without this the
1985
+ * row is dropped by the "only what happened on the call" filter. False on a database before
1986
+ * 20261013080133. */
1987
+ created: z12.boolean(),
1988
+ /** `request` is the Entry that asked it — what a line's `about` names when the line answered or put
1989
+ * it off. Optional: an older API sent none. */
1990
+ questions: z12.array(z12.object({ id: z12.string().uuid(), state: z12.string(), title: z12.string(), request: z12.string().optional() })),
1932
1991
  /** `words` is always what they SAID, verbatim — the record, never replaced. `summary` is what
1933
1992
  * the line says in ten words (owner, 2026-10-07): the filer's, or the talker's for the answer it
1934
1993
  * gave, so the row scans like a chosen option and their own words stay under it. Absent when
@@ -1964,7 +2023,44 @@ var CallRecapSchema = z12.object({
1964
2023
  })),
1965
2024
  more: z12.object({ lines: z12.number(), entries: z12.number(), topics: z12.number() }),
1966
2025
  /** Last stored position on this page when another exists; absent before paging shipped. */
1967
- next: z12.number().int().nonnegative().nullable().optional()
2026
+ next: z12.number().int().nonnegative().nullable().optional(),
2027
+ /** THE CALL AS ONE LIST OF ROWS (owner, 2026-10-14: "we only really want a thing like transcript but
2028
+ * built from the summaries", then "the transcript tabs should show the same but with full words").
2029
+ * Both tabs draw these rows, in this order: Summary from `summary`, Transcript from `words`.
2030
+ * - `put`: an item put to the person, at the first agent line that voiced it. Paigy's sentences
2031
+ * that voiced it are folded into `spoken`, never rows of their own.
2032
+ * - `said`: the person's words, one row per reply or per filed line no reply covers.
2033
+ * - `did`: what Paigy did ("Sent to Mouth: …", "Recorded: …", "Filed on …").
2034
+ * `by` is the row's real author, never inferred from `kind`: a relay Paigy sent to an agent is
2035
+ * Paigy's, not the person's. Optional and additive (owner: "make sure whatever you do is backward
2036
+ * compatible"): an API before it sends none, and the screen falls back to `topics` and `said`. */
2037
+ timeline: z12.array(z12.object({
2038
+ id: z12.string(),
2039
+ position: z12.number(),
2040
+ atMs: z12.number(),
2041
+ kind: z12.enum(["put", "said", "did"]),
2042
+ by: z12.object({ participant: z12.string().nullable(), agentId: z12.string().nullable(), name: z12.string().nullable() }),
2043
+ goalId: z12.string().uuid().nullable(),
2044
+ requestId: z12.string().nullable(),
2045
+ summary: z12.string().nullable(),
2046
+ words: z12.string(),
2047
+ /** Paigy's sentences folded into this row: on a `put` row, what it said putting the item to the
2048
+ * person (before their next row); on a `said` row, what it said in reply (until their next row).
2049
+ * `atMs` is when each was said; optional, absent before 2026-10-14's fix. */
2050
+ spoken: z12.array(z12.object({ lineId: z12.string(), words: z12.string(), atMs: z12.number().optional() })),
2051
+ /** On a `said` row: the work these words were filed on, and whether they answered (recorded) a
2052
+ * question. They replace the "Filed on ..." and "Recorded: ..." rows that repeated the person's own
2053
+ * summary (Brain, 2026-10-14). Optional: absent from an API before it. */
2054
+ filedOn: z12.array(z12.object({ goalId: z12.string().uuid(), title: z12.string() })).optional(),
2055
+ recorded: z12.boolean().optional(),
2056
+ settled: z12.object({ requestId: z12.string(), answer: z12.string() }).nullable().optional(),
2057
+ acknowledged: z12.boolean().optional(),
2058
+ /** On a `put` row: still waiting for the person's answer (not settled, not answered, not put off).
2059
+ * The server's judgement; the screen draws "Still waiting for your answer" from it. Optional: absent
2060
+ * from an API before it, where the screen falls back to its own reading. */
2061
+ waiting: z12.boolean().optional(),
2062
+ lineIds: z12.array(z12.string())
2063
+ })).optional()
1968
2064
  });
1969
2065
 
1970
2066
  // src/listening.ts
@@ -1985,14 +2081,21 @@ function sessionStartHook() {
1985
2081
  [ -f "\${HOME}/.paigy/token.json" ] || exit 0 # not paired on this machine: nothing to listen as
1986
2082
  cat <<'PAIGY'
1987
2083
  Paigy: nothing pushes answers into a session started in a terminal \u2014 an answer waits for your
1988
- next contact({ wait: false }). Start your own listener now, as a BACKGROUND task whose output you watch
1989
- (your harness's monitor/background-task tool), and leave it running for this session:
2084
+ next contact({ wait: false }). Start your own listener now with your harness's MONITOR tool, the kind
2085
+ that wakes you on every line it prints (in Claude Code: Monitor, not a background Bash command, which
2086
+ only tells you when it exits). When the monitor expires, start it again:
1990
2087
 
1991
- npx -y -p @paigy/mcp@latest paigy-listen
2088
+ npx -y -p @paigy/mcp@latest paigy-listen --brief
1992
2089
 
1993
2090
  Each line it prints is a Delivery waiting for you: read the Goal, and reply with contact when
1994
- the answer needs a decision. Start it once; if it is already running, do nothing. Then call
1995
- contact({ wait: false }) once: work handed to you before this session started waits there.
2091
+ the answer needs a decision.
2092
+
2093
+ ONE LISTENER PER SESSION, AND ANOTHER SESSION'S IS NOT YOURS. Identities here are per session, so
2094
+ a paigy-listen you find in ps is almost certainly another session listening as itself, and
2095
+ starting yours takes nothing from it. Never decide this from ps: the listen tool reads the mark
2096
+ for YOUR OWN slot and answers in one call. Ask it, or just start the listener above; only a
2097
+ second one for the same slot is the mistake. Then call contact({ wait: false }) once: work handed
2098
+ to you before this session started waits there.
1996
2099
  PAIGY
1997
2100
  exit 0
1998
2101
  `;
@@ -2028,7 +2131,7 @@ function decideListen(f) {
2028
2131
  return {
2029
2132
  status: "start",
2030
2133
  command: `PAIGY_AGENT=${word(f.slot)} PAIGY_SESSION_ID=${word(f.session)} ${f.env.CODEX_THREAD_ID ? `CODEX_THREAD_ID=${word(f.env.CODEX_THREAD_ID)} ` : ""}${f.env.PAIGY_ON_WAKE ? `PAIGY_ON_WAKE=${word(f.env.PAIGY_ON_WAKE)} ` : ""}${word(f.execPath)} ${word(f.listenJs)} --brief`,
2031
- how: "Run this command NOW as a watched background task (your harness's background-task/monitor tool), and leave it running for this session. Each line it prints is a Delivery waiting for you: read the Goal it names with get_goals, and answer with contact. Start it once."
2134
+ how: "Run this command NOW with 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); start it again when the monitor expires. Each line it prints is a Delivery waiting for you: read the Goal it names with get_goals, and answer with contact. Start it once."
2032
2135
  };
2033
2136
  }
2034
2137
  function listenMark(slot, home = homedir()) {
@@ -2044,6 +2147,12 @@ function writeListenMark(slot, pid, home = homedir()) {
2044
2147
  function removeListenMark(slot, home = homedir()) {
2045
2148
  rmSync(listenMark(slot, home), { force: true });
2046
2149
  }
2150
+ function clear(path) {
2151
+ try {
2152
+ rmSync(path, { force: true });
2153
+ } catch {
2154
+ }
2155
+ }
2047
2156
  function listenerAlive(slot, home = homedir()) {
2048
2157
  const path = listenMark(slot, home);
2049
2158
  let pid;
@@ -2053,7 +2162,7 @@ function listenerAlive(slot, home = homedir()) {
2053
2162
  return null;
2054
2163
  }
2055
2164
  if (!Number.isInteger(pid) || pid <= 0) {
2056
- rmSync(path, { force: true });
2165
+ clear(path);
2057
2166
  return null;
2058
2167
  }
2059
2168
  try {
@@ -2061,7 +2170,7 @@ function listenerAlive(slot, home = homedir()) {
2061
2170
  return pid;
2062
2171
  } catch (e) {
2063
2172
  if (e.code === "EPERM") return pid;
2064
- rmSync(path, { force: true });
2173
+ clear(path);
2065
2174
  return null;
2066
2175
  }
2067
2176
  }
@@ -2074,6 +2183,100 @@ function wakeStatusPath(slot, home = homedir()) {
2074
2183
  function existingListener(slot, env, home = homedir()) {
2075
2184
  return listenerAlive(listenerKey(slot, env), home) ?? (env.CODEX_THREAD_ID ? listenerAlive(slot, home) : null);
2076
2185
  }
2186
+ var UNLISTENED_EVERY_MS = 10 * 6e4;
2187
+ var WATCHING_CLIENTS = /* @__PURE__ */ new Set(["claude-code"]);
2188
+ var NON_WATCHING_CLIENTS = /* @__PURE__ */ new Set(["claude-ai", "cursor", "cursor-vscode", "windsurf", "cline", "roo-cline", "continue", "vscode", "visual studio code", "zed", "antigravity"]);
2189
+ function canWatch(f) {
2190
+ if (harnessDriven(f.env) || f.env.PAIGY_ON_WAKE || f.env.CODEX_THREAD_ID) return "yes";
2191
+ const c = f.client?.toLowerCase();
2192
+ if (c && WATCHING_CLIENTS.has(c)) return "yes";
2193
+ if (c && NON_WATCHING_CLIENTS.has(c)) return "no";
2194
+ return "unknown";
2195
+ }
2196
+ function unlistenedNote(f, _now) {
2197
+ if (harnessDriven(f.env) || f.pidAlive) return null;
2198
+ const decision = decideListen(f);
2199
+ if (decision.status !== "start") return null;
2200
+ const head = `NOTHING CAN REACH THIS SESSION WHILE IT RESTS. No listener runs for your slot (${f.slot}) and no harness pump delivers to it`;
2201
+ switch (canWatch(f)) {
2202
+ case "no":
2203
+ return { unlistened: `${head}, and this host cannot run a listener. Work reaches you only as you act \u2014 it rides your tool calls, as it did this one \u2014 so call contact({ wait: false }) before you stop.` };
2204
+ case "yes":
2205
+ return { unlistened: `${head}. Start the listener below as a watched background task; a listener you see in ps belongs to another session's slot, never to yours.`, listen_command: decision.command };
2206
+ default:
2207
+ return { unlistened: `${head}. If this host can run a watched background task, start the listener below; if it cannot, work reaches you only as you act, so call contact({ wait: false }) before you stop.`, listen_command: decision.command };
2208
+ }
2209
+ }
2210
+ var ReachTeller = class {
2211
+ constructor(every = UNLISTENED_EVERY_MS) {
2212
+ this.every = every;
2213
+ }
2214
+ every;
2215
+ checked = /* @__PURE__ */ new Map();
2216
+ async note(slot, tool, d) {
2217
+ const now = d.now();
2218
+ const last = this.checked.get(slot);
2219
+ if (last !== void 0 && now - last < this.every) return null;
2220
+ this.checked.set(slot, now);
2221
+ let note;
2222
+ try {
2223
+ note = unlistenedNote(d.facts(), now);
2224
+ } catch {
2225
+ return null;
2226
+ }
2227
+ if (!note) return null;
2228
+ let waiting = null;
2229
+ if (tool !== "contact") {
2230
+ try {
2231
+ waiting = waitingSummary(await d.poll());
2232
+ } catch {
2233
+ }
2234
+ }
2235
+ return { paigy_reach: { ...note, ...waiting ?? {} } };
2236
+ }
2237
+ };
2238
+ function withReach(shaped, reach) {
2239
+ if (!reach || !shaped || typeof shaped !== "object" || Array.isArray(shaped)) return shaped;
2240
+ return { ...reach, ...shaped };
2241
+ }
2242
+ var HOOKED_HARNESSES = "Claude Code and Codex";
2243
+ function listenBlock(decision, f, stale) {
2244
+ const block = {
2245
+ listen: decision,
2246
+ listen_permanent: `\`${ENABLE_LISTEN_COMMAND}\` installs the SessionStart hook for ${HOOKED_HARNESSES}, so every future session there is told this at its start. Other hosts are not hooked; their sessions learn it as they act.`
2247
+ };
2248
+ if (decision.status === "start") {
2249
+ block.listen_prompt = canWatch(f) === "no" ? "This host cannot run a listener, so nothing reaches this session while it rests. Work arrives with your tool calls; call contact({ wait: false }) before you stop." : "Start your own listener NOW, as described in `listen` \u2014 a watched background task, left running for this session. Without it, every answer waits until you call contact({ wait: false }).";
2250
+ }
2251
+ if (stale) {
2252
+ block.hook_refresh = `The SessionStart hook on this machine is an older text. Ask the user to re-run \`${ENABLE_LISTEN_COMMAND}\` to refresh it \u2014 the old one tells a session to skip its listener when it sees another session's.`;
2253
+ }
2254
+ return block;
2255
+ }
2256
+ var ENABLE_LISTEN_COMMAND = "npx -y -p @paigy/mcp@latest paigy-enable-tools";
2257
+ function hookStale(home = homedir()) {
2258
+ try {
2259
+ return readFileSync(join(home, ".paigy", "hooks", "session-start.sh"), "utf8") !== sessionStartHook();
2260
+ } catch {
2261
+ return false;
2262
+ }
2263
+ }
2264
+ function waitingSummary(received) {
2265
+ const r = received ?? {};
2266
+ const events = Array.isArray(r.events) ? r.events : [];
2267
+ const ids = events.map((e) => String(e?.eventId ?? "")).filter(Boolean);
2268
+ const claimable = r.claimable && typeof r.claimable === "object" ? String(r.claimable.title ?? "") : "";
2269
+ if (!ids.length && !claimable) return null;
2270
+ const kinds = events.map((e) => String(e?.kind ?? "event")).join(", ");
2271
+ const parts = [
2272
+ ids.length ? `${ids.length} event${ids.length === 1 ? "" : "s"} addressed to you (${kinds})` : "",
2273
+ claimable ? `work waiting: ${claimable}` : ""
2274
+ ].filter(Boolean);
2275
+ return {
2276
+ waiting: `${parts.join("; ")}. Nothing acknowledged them \u2014 handle them with contact, and acknowledge with contact({ ackEventIds }) so they stop repeating. Read a Goal with get_goals before acting on a summary of it.`,
2277
+ ...ids.length ? { waiting_events: ids } : {}
2278
+ };
2279
+ }
2077
2280
 
2078
2281
  export {
2079
2282
  mcpInputSchema,
@@ -2089,5 +2292,10 @@ export {
2089
2292
  removeListenMark,
2090
2293
  listenerKey,
2091
2294
  wakeStatusPath,
2092
- existingListener
2295
+ existingListener,
2296
+ ReachTeller,
2297
+ withReach,
2298
+ listenBlock,
2299
+ ENABLE_LISTEN_COMMAND,
2300
+ hookStale
2093
2301
  };