@paigy/mcp 0.40.23 → 0.40.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,13 +7,14 @@ import { homedir } from "os";
7
7
  import { join } from "path";
8
8
  import { randomUUID as randomUUID3 } from "crypto";
9
9
  import { setTimeout as sleep2 } from "timers/promises";
10
- import { z as z4 } from "zod";
10
+ import { z as z5 } from "zod";
11
11
  import { z } from "zod";
12
12
  import { z as z3 } from "zod";
13
13
  import { ZodFirstPartyTypeKind as ZodFirstPartyTypeKind3 } from "zod/v3";
14
14
  import { ZodFirstPartyTypeKind } from "zod/v3";
15
15
  import { ZodFirstPartyTypeKind as ZodFirstPartyTypeKind2 } from "zod/v3";
16
16
  import { z as z2 } from "zod";
17
+ import { z as z4 } from "zod";
17
18
  import { randomUUID as randomUUID2 } from "crypto";
18
19
  import { closeSync, existsSync, mkdirSync, openSync, readFileSync as readFileSync2, rmSync, statSync, writeFileSync } from "fs";
19
20
  import { homedir as homedir2 } from "os";
@@ -22,7 +23,7 @@ import { execFileSync as execFileSync2 } from "child_process";
22
23
  import { randomUUID as randomUUID4 } from "crypto";
23
24
  import { WebSocket } from "undici";
24
25
  var require2 = __sdkCreateRequire(import.meta.url);
25
- var CODEX_ENV = ["CODEX_THREAD_ID", "CODEX_SESSION_ID", "PAIGY_TOKEN", "PAIGY_SESSION_ID", "PAIGY_HARNESS", "PAIGY_INSTANCE_ID"];
26
+ var CODEX_ENV = ["CODEX_THREAD_ID", "CODEX_SESSION_ID", "CODEX_HOME", "PAIGY_TOKEN", "PAIGY_SESSION_ID", "PAIGY_HARNESS", "PAIGY_INSTANCE_ID", "PAIGY_ON_WAKE"];
26
27
  var BACKEND_URL = process.env.PAIGY_BACKEND_URL ?? "https://paigy.ai";
27
28
  var NETWORK_MSG = `Can't reach ${BACKEND_URL} \u2014 the connection was blocked or dropped before an HTTP response. If this agent runs in a sandboxed environment with a network allowlist (e.g. Claude Code on the web, CI), ask the user to add the Paigy domain (paigy.ai) to the environment's allowed domains, then retry.`;
28
29
  var PROXY_ENV = ["HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy"];
@@ -39,8 +40,8 @@ var WORKSPACE_ID = (() => {
39
40
  return INSTANCE_ID;
40
41
  }
41
42
  })();
42
- var envSession = (key) => {
43
- const v = process.env[key];
43
+ var envSession = (key2) => {
44
+ const v = process.env[key2];
44
45
  return v && v.trim() ? v : void 0;
45
46
  };
46
47
  function parentOf(pid) {
@@ -83,12 +84,17 @@ async function reach(url, init) {
83
84
  function reachAs(session, instance) {
84
85
  return (url, init) => send(url, init, session, instance);
85
86
  }
87
+ var CLIENT;
88
+ function setClient(name) {
89
+ CLIENT = name;
90
+ }
86
91
  async function send(url, init, session, instance = envSession("PAIGY_INSTANCE_ID") ?? INSTANCE_ID) {
87
92
  try {
88
93
  const headers = {
89
94
  ...init?.headers,
90
95
  "x-paigy-instance": instance,
91
- "x-paigy-session": session
96
+ "x-paigy-session": session,
97
+ ...CLIENT ? { "x-paigy-client": CLIENT } : {}
92
98
  };
93
99
  return await fetch(url, { ...init, headers, dispatcher: await proxy() });
94
100
  } catch (e) {
@@ -146,19 +152,19 @@ var getRefs = (options) => {
146
152
  ]))
147
153
  };
148
154
  };
149
- function addErrorMessage(res, key, errorMessage, refs) {
155
+ function addErrorMessage(res, key2, errorMessage, refs) {
150
156
  if (!refs?.errorMessages)
151
157
  return;
152
158
  if (errorMessage) {
153
159
  res.errorMessage = {
154
160
  ...res.errorMessage,
155
- [key]: errorMessage
161
+ [key2]: errorMessage
156
162
  };
157
163
  }
158
164
  }
159
- function setResponseValueAndErrors(res, key, value, errorMessage, refs) {
160
- res[key] = value;
161
- addErrorMessage(res, key, errorMessage, refs);
165
+ function setResponseValueAndErrors(res, key2, value, errorMessage, refs) {
166
+ res[key2] = value;
167
+ addErrorMessage(res, key2, errorMessage, refs);
162
168
  }
163
169
  var getRelativePath = (pathA, pathB) => {
164
170
  let i = 0;
@@ -720,11 +726,11 @@ function parseRecordDef(def, refs) {
720
726
  return {
721
727
  type: "object",
722
728
  required: def.keyType._def.values,
723
- properties: def.keyType._def.values.reduce((acc, key) => ({
729
+ properties: def.keyType._def.values.reduce((acc, key2) => ({
724
730
  ...acc,
725
- [key]: parseDef(def.valueType._def, {
731
+ [key2]: parseDef(def.valueType._def, {
726
732
  ...refs,
727
- currentPath: [...refs.currentPath, "properties", key]
733
+ currentPath: [...refs.currentPath, "properties", key2]
728
734
  }) ?? parseAnyDef(refs)
729
735
  }), {}),
730
736
  additionalProperties: refs.rejectedAdditionalProperties
@@ -787,10 +793,10 @@ function parseMapDef(def, refs) {
787
793
  }
788
794
  function parseNativeEnumDef(def) {
789
795
  const object = def.values;
790
- const actualKeys = Object.keys(def.values).filter((key) => {
791
- return typeof object[object[key]] !== "number";
796
+ const actualKeys = Object.keys(def.values).filter((key2) => {
797
+ return typeof object[object[key2]] !== "number";
792
798
  });
793
- const actualValues = actualKeys.map((key) => object[key]);
799
+ const actualValues = actualKeys.map((key2) => object[key2]);
794
800
  const parsedTypes = Array.from(new Set(actualValues.map((values) => typeof values)));
795
801
  return {
796
802
  type: parsedTypes.length === 1 ? parsedTypes[0] === "string" ? "string" : "number" : ["string", "number"],
@@ -1418,7 +1424,7 @@ var SPOKEN_HOUR = {
1418
1424
  eleven: 11,
1419
1425
  twelve: 12
1420
1426
  };
1421
- function parseWhen(said2, tz, nowMs, read = "earliest") {
1427
+ function parseWhen(said2, tz, nowMs, read22 = "earliest") {
1422
1428
  const text = said2.trim();
1423
1429
  if (!text) return null;
1424
1430
  const rel = RELATIVE.exec(text);
@@ -1457,13 +1463,13 @@ function parseWhen(said2, tz, nowMs, read = "earliest") {
1457
1463
  if (hour === 12) hour = 0;
1458
1464
  } else if (hour < 12 && dayShift === 0 && hour <= today.h) hour += 12;
1459
1465
  } else {
1460
- const parts = PARTS[read];
1466
+ const parts = PARTS[read22];
1461
1467
  const part = Object.keys(parts).find((k) => new RegExp(`\\b${k}\\b`, "i").test(text));
1462
1468
  if (/\btonight\b/i.test(text)) hour = parts["night"];
1463
1469
  else if (part) hour = parts[part];
1464
1470
  else if (dayShift > 0) hour = 9;
1465
1471
  else if (/\bend of (the )?day\b|\beod\b/i.test(text)) hour = END_OF_DAY;
1466
- else if (read === "ring" && VAGUE.test(text)) return new Date(nowMs + 30 * 6e4).toISOString();
1472
+ else if (read22 === "ring" && VAGUE.test(text)) return new Date(nowMs + 30 * 6e4).toISOString();
1467
1473
  else return null;
1468
1474
  }
1469
1475
  const at2 = instantAt(tz, {
@@ -1486,8 +1492,8 @@ function parseWhen(said2, tz, nowMs, read = "earliest") {
1486
1492
  }) : at2;
1487
1493
  return new Date(rolled ?? at2).toISOString();
1488
1494
  }
1489
- function parseDelay(said2, tz, nowMs, read = "earliest") {
1490
- const at2 = parseWhen(said2, tz, nowMs, read);
1495
+ function parseDelay(said2, tz, nowMs, read22 = "earliest") {
1496
+ const at2 = parseWhen(said2, tz, nowMs, read22);
1491
1497
  if (!at2) return null;
1492
1498
  return Math.max(0, Math.round((Date.parse(at2) - nowMs) / 1e3));
1493
1499
  }
@@ -1523,32 +1529,39 @@ function mcpInputSchema(s) {
1523
1529
  }
1524
1530
  var AskInputSchema = z2.object({
1525
1531
  id: z2.string().optional().describe("Optional idempotency key or client-side ID for this specific ask."),
1526
- parentId: z2.string().uuid().optional().describe("The Goal this question is about \u2014 usually the one you are working on. The question goes onto that Goal and its answer comes back there. Omit it and Paigy places the question in the tree itself."),
1532
+ parentId: z2.string().uuid().optional().describe("The Goal this question is about \u2014 usually the one you are working on. The question goes onto that Goal and its answer comes back there. Omit it and the question starts a new Goal of yours."),
1527
1533
  repo: z2.string().optional().describe("Optional repository context."),
1528
1534
  ask: z2.string().trim().min(1).max(1e4).describe(
1529
- "ONE question, and only what is needed to answer it. Several questions are several asks in the array, one each: an answer settles the one ask it was given in the shape that ask declares, so a person who answers the part of a bundled ask that interested them settles nothing and is asked again. Paigy reads every ask once and takes a bundled one apart into its own cards anyway \u2014 each question a Question on the one Goal the ask lands on, never a Goal of its own \u2014 but the words it splits are its reading, not yours. News, progress and findings are their own contact; ANY contact for a person already on a call JOINS that call, whatever channel you asked for, so several arrive as one call."
1535
+ "ONE question, and only what is needed to answer it. Several questions are several asks in the array, one each: an answer settles the one ask it was given in the shape that ask declares, so a person who answers the part of a bundled ask that interested them settles nothing and is asked again. Paigy sends each ask exactly as you wrote it: a bundled ask arrives as one card. News, progress and findings are their own contact; ANY contact for a person already on a call JOINS that call, whatever channel you asked for, so several arrive as one call."
1530
1536
  ),
1531
1537
  options: z2.array(OptionInputSchema).min(1).max(6).optional(),
1532
- // YOUR VIEW OF ONE OR MANY, A HINT TO PAIGY'S READ (owner, 2026-10-02: "one and many makes sense...
1533
- // make sure the brain that formats is aware of how it should work"). Three independent fixes went
1534
- // out as "All three / 1 and 3 only / Just log it" because every card was pick-one. #2943 made this
1535
- // field override the read; no agent sent it, so nothing changed. The read now decides each card's
1536
- // shape (`goal/intake.ts` `answerOf`), and this reaches it as the agent's view, weighed, not obeyed.
1538
+ // HOW THE OPTIONS ARE ANSWERED, SAID BY THE AGENT (owner, 2026-10-02: "one and many makes sense";
1539
+ // brain_prompts.md §1: structured agent questions need no model). Every card was pick-one while no
1540
+ // agent sent this; no read reshapes cards any more, so this is the card's shape, and the
1541
+ // description says so plainly.
1537
1542
  select: z2.enum(["one", "many"]).optional().describe(
1538
- `Your view of how the options are answered: "one" to pick one, "many" to tick any number of them. A hint: Paigy's read decides each card's shape, a checklist when the options can all be wanted at once, a pick when choosing one rules out the others. The person can always answer in their own words as well. Ignored without options.`
1543
+ 'How the options are answered: "many" makes a checklist (they may want several, e.g. independent fixes), "one" (the default) a pick (choosing one rules out the others). Send "many" whenever more than one option could be wanted at once. The person can always answer in their own words as well. Ignored without options.'
1539
1544
  ),
1540
1545
  answers: z2.string().trim().regex(/^[0-9a-fA-F-]{8,36}$/).optional().describe(
1541
- "Your reply answers a question the person asked you: the id the conversation shows for it (8 characters or whole). Their question closes with this reply as its answer, and any decision of yours it was holding back goes back to them. Needs parentId: the Goal the question is on. The reply is sent to them as it is; to also ask something new, send that as its own ask."
1546
+ "Your reply answers a question the person asked you: the id the conversation shows for it (8 characters or whole). Their question closes with this reply as its answer, and any decision of yours it was holding back goes back to them. parentId is optional here: the question's own Goal is used. The reply is sent to them as it is; to also ask something new, send that as its own ask."
1542
1547
  )
1543
1548
  }).strict();
1544
1549
  var StartContactSchema = z2.object({
1545
- asks: z2.array(AskInputSchema).min(1).describe("The questions to pose, one per object. An ask with a parentId is filed on that Goal as it is; only an ask naming no Goal is placed in the tree by Paigy."),
1550
+ asks: z2.array(AskInputSchema).min(1).describe("The questions to pose, one per object. An ask with a parentId is filed on that Goal as it is; an ask naming no Goal starts a new Goal of yours."),
1546
1551
  waiting: z2.enum(["none", "hard"]).default("none").describe("hard requests a call even when channel is notification; user permissions and ring cooldowns still apply."),
1547
1552
  channel: z2.enum(["notification", "call"]).default("notification")
1548
1553
  }).strict();
1549
- var ContactSchema = z2.union([StartContactSchema, z2.object({ deliveryId: z2.string().uuid() }).strict()]);
1554
+ var ReceiveContactSchema = z2.object({
1555
+ wait: z2.boolean().optional().describe(
1556
+ "Only controls when this returns. true (the default when you send and acknowledge nothing): wait up to about 45 seconds for incoming communication addressed to you, and return as soon as some is available. false: return what is waiting now. It does not mean work is blocked, set urgency, or ask for a call. `waitOutcome` says which happened: `available`, `expired` (nothing arrived in the window; wait again without sending again), or `not_waited` (a hosted connection never holds a wait)."
1557
+ ),
1558
+ ackEventIds: z2.array(z2.string().uuid()).min(1).max(100).optional().describe(
1559
+ "The exact eventIds of the events you have handled: an acknowledgment (ACK). Reading alone acknowledges nothing, so an event comes back until you acknowledge it; after that the next batch can come. Each recipient acknowledges separately, for itself only. A Question you owe is answered (an ask with `answers`), never acknowledged away: that id is refused until it is answered."
1560
+ )
1561
+ }).strict();
1562
+ var ContactSchema = z2.union([StartContactSchema, z2.object({ deliveryId: z2.string().uuid() }).strict(), ReceiveContactSchema]);
1550
1563
  var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
1551
- var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'select', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. An ask that names a Goal (parentId) goes onto that Goal; one that names none is placed in the person's tree by Paigy. Notification returns immediately; collect durable answers with check_replies. On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. One question per 'ask'; several questions are several objects in 'asks'. A bundled ask is taken apart into its own cards by Paigy's one intake read, every one on the Goal the ask lands on; separate pieces of work are separate asks. An ask with no options that is not blocking is a report, and a report is an UPDATE, never a claim on them: it reaches them and is listed apart from what waits on them, so no answer is owed and none should be awaited. On a Goal whose report card is still open it is added to that card, with no new push; one that only says where your work stands while under way is recorded as the Goal's progress, not sent (the result says so). Send options (or waiting:'hard') when you actually need an answer. Each option's label must stand on its own \u2014 the person may see only the labels \u2014 so never a label that points into your text ('All three', 'Option 2', '1 and 3 only'). Whether a card is a checklist or a pick is decided by Paigy's read: independent options they may want several of become a checklist, alternatives a pick. select:'many' or select:'one' tells it your view, a hint it weighs. The person can always answer in their own words, so never add an 'Other' option. If the person is already on a call, your contact joins that call automatically \u2014 whatever channel you asked for, with no ring \u2014 and the Delivery it returns IS that call: reread it with contact({deliveryId}) to see everything answered on it so far, and contact again while it is live to add information or a further question to the same call.";
1564
+ var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'select', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. An ask that names a Goal (parentId) goes onto that Goal; one that names none starts a new Goal of yours. Notification returns immediately; collect answers by receiving (below). On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. One question per 'ask'; several questions are several objects in 'asks'. Each ask is sent exactly as written, with no reading in between: a bundled ask arrives as one card, so separate questions are separate asks. An ask with no options that is not blocking is a report, never a claim on them, so no answer is owed and none should be awaited. A report reaches the person only when it answers something they asked you, or once the Goal is done; any other report is recorded as the Goal's progress and nobody is notified. So when the work is finished, mark the Goal done first, then send one message saying what is done and anything they need to do or check. On a Goal whose report card is still open, a report that does reach them is added to that card, with no new push. Send options (or waiting:'hard') when you actually need an answer. Each option's label must stand on its own \u2014 the person may see only the labels \u2014 so never a label that points into your text ('All three', 'Option 2', '1 and 3 only'). select:'many' makes the card a checklist, for independent options they may want several of; select:'one' (the default) a pick, for alternatives. The person can always answer in their own words, so never add an 'Other' option. If the person is already on a call, a contact that reaches them joins that call automatically \u2014 whatever channel you asked for, with no ring \u2014 and the Delivery it returns IS that call: reread it with contact({deliveryId}) to see everything answered on it so far, and contact again while it is live to add information or a further question to the same call.\n\nRECEIVING: contact with no asks sends nothing and returns `events`, a limited batch of what is addressed to you (not a history page): `question`, a Question you owe (answer it with an ask whose `answers` is its questionId); `update`, something new on one of your Goals (a reply, an answer: read it with get_goal); `instruction`, a request or note sent to you. `hasMore` says more are waiting. Reading acknowledges nothing: once you have handled events, confirm their eventIds with contact({ackEventIds}), and the next batch can come. contact({}) waits up to about 45 seconds for something to arrive; contact({wait:false}) returns at once. It also lists work given to you that nobody has started (`assigned`; claim_goal starts it) and your work gone quiet (`stalled`).";
1552
1565
  var CreateGoalSchema = z3.object({
1553
1566
  outcome: z3.string().trim().min(1).max(1e4),
1554
1567
  /** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
@@ -1568,10 +1581,6 @@ var CreateGoalSchema = z3.object({
1568
1581
  * repo name. Delegated work inherits this from its parent Goal when omitted. */
1569
1582
  repo: z3.string().trim().min(1).max(200).optional()
1570
1583
  });
1571
- var CreateGoalToolSchema = CreateGoalSchema.extend({
1572
- idempotencyKey: CreateGoalSchema.shape.idempotencyKey.optional().describe("Optional. One is minted per call; pass your own only so a retry lands on the same Goal.")
1573
- }).strict();
1574
- var CREATE_GOAL_DESCRIPTION = 'Create a durable Goal for an outcome. Without parentGoalId it is placed against your open Goals: if one already IS this work, that Goal comes back (existing: true) and nothing new is created \u2014 continue it; if the work belongs under one, it is created there (parentGoalId in the receipt); otherwise it is a root. Pass parentGoalId yourself to put it under a specific Goal. Pass repo to anchor the work to a specific repository ("owner/repo" or repo name); delegated children inherit it. Pass title to name it in one to five words, as a person would refer to it out loud ("the night rings") \u2014 it heads every list and is spoken on a call; without one the brain writes it. Admission only: claim it before doing work, then update it as it advances. Returns an admission receipt with goalId, current state, revision, ownerParticipant, and the next step; no Goal content or execution lease.';
1575
1584
  var UpdateGoalSchema = z3.object({
1576
1585
  revision: z3.number().int().positive(),
1577
1586
  changes: z3.object({
@@ -1595,7 +1604,65 @@ var UpdateGoalSchema = z3.object({
1595
1604
  reason: z3.string().trim().min(1).max(2e3),
1596
1605
  operationId: z3.string().uuid().optional()
1597
1606
  }).strict();
1598
- var UpdateGoalToolSchema = UpdateGoalSchema.omit({ operationId: true }).extend({ goalId: z3.string().uuid() }).strict();
1607
+ var UpdateGoalToolSchema = z3.object({
1608
+ goalId: z3.string().uuid(),
1609
+ revision: UpdateGoalSchema.shape.revision,
1610
+ changes: UpdateGoalSchema.shape.changes.innerType().pick({ progress: true, reviewed: true, dueAt: true, withdraw: true }).strict().refine((v) => Object.keys(v).length > 0),
1611
+ reason: UpdateGoalSchema.shape.reason
1612
+ }).strict();
1613
+ var changedGoal = z3.string().uuid().describe("The Goal to change, as a read shows it.");
1614
+ var goalTitle = z3.string().trim().min(1).max(80).describe("The Goal's short display name: one to five words, how a person refers to it out loud.");
1615
+ var goalOutcome = z3.string().trim().min(1).max(1e4).describe("The full desired result. There is no third description field.");
1616
+ var CreateChange = z3.object({
1617
+ kind: z3.literal("create"),
1618
+ title: goalTitle,
1619
+ outcome: goalOutcome,
1620
+ ownerId: z3.string().trim().min(1).optional().describe("Who owns the work: an agent's participant (as who_is_working shows it) or the person's. Omitted: you."),
1621
+ parentGoalId: z3.string().uuid().optional().describe("The Goal it belongs under. Omitted: a root."),
1622
+ sourceEntryIds: z3.array(z3.string().uuid()).max(20).optional().describe("The whole Entries the work came from, ones you can read.")
1623
+ }).strict();
1624
+ var EditChange = z3.object({ kind: z3.literal("edit"), goalId: changedGoal, title: goalTitle.optional(), outcome: goalOutcome.optional() }).strict();
1625
+ var StateChange = z3.object({ kind: z3.literal("state"), goalId: changedGoal, state: z3.enum(["open", "completed", "canceled"]).describe(
1626
+ "completed: the Goal's own work is done now (refused while required children are open); open: reopen it; canceled: it will not be done."
1627
+ ) }).strict();
1628
+ var AssignChange = z3.object({ kind: z3.literal("assign"), goalId: changedGoal, ownerId: z3.string().trim().min(1).describe("The new owner's participant.") }).strict();
1629
+ var DeferChange = z3.object({ kind: z3.literal("defer"), goalId: changedGoal, until: z3.string().datetime({ offset: true }).nullable().describe(
1630
+ "Postpone it until this instant (its owner is woken then), or null to take the postponement back."
1631
+ ) }).strict();
1632
+ var MoveChange = z3.object({ kind: z3.literal("move"), goalId: changedGoal, parentGoalId: z3.string().uuid().nullable().describe(
1633
+ "Its new parent, or null for a root. Only this Goal moves; its new parent's other children stay."
1634
+ ) }).strict();
1635
+ var DependencyChange = z3.object({
1636
+ kind: z3.literal("dependency"),
1637
+ change: z3.enum(["add", "remove"]),
1638
+ goalId: changedGoal,
1639
+ dependsOnGoalId: z3.string().uuid().describe("The Goal it waits on."),
1640
+ action: z3.enum(["start", "complete"]).describe("What waits: starting goalId, or completing it."),
1641
+ reason: z3.string().trim().min(1).max(2e3).optional().describe("Why, as an explanation, never as policy.")
1642
+ }).strict();
1643
+ var read = { revision: z3.number().int().positive().optional() };
1644
+ var GoalChangeSchema = z3.discriminatedUnion("kind", [
1645
+ CreateChange,
1646
+ EditChange.extend(read),
1647
+ StateChange.extend(read),
1648
+ AssignChange.extend(read),
1649
+ DeferChange.extend(read),
1650
+ MoveChange.extend(read),
1651
+ DependencyChange
1652
+ ]);
1653
+ var editNames = (changes, ctx) => changes.forEach((c, i) => {
1654
+ if (c.kind === "edit" && c.title === void 0 && c.outcome === void 0) {
1655
+ ctx.addIssue({ code: z3.ZodIssueCode.custom, path: ["changes", i], message: "an edit supplies title, outcome, or both" });
1656
+ }
1657
+ });
1658
+ var ManageGoalsSchema = z3.object({
1659
+ requestId: z3.string().uuid().optional(),
1660
+ changes: z3.array(GoalChangeSchema).min(1).max(50)
1661
+ }).strict().superRefine((v, ctx) => editNames(v.changes, ctx));
1662
+ var ManageGoalsToolSchema = z3.object({
1663
+ changes: z3.array(z3.discriminatedUnion("kind", [CreateChange, EditChange, StateChange, AssignChange, DeferChange, MoveChange, DependencyChange])).min(1).max(50).describe("The changes, applied in order, each on its own.")
1664
+ }).strict().superRefine((v, ctx) => editNames(v.changes, ctx));
1665
+ var MANAGE_GOALS_DESCRIPTION = "Create, edit, assign, organize or close work. Every change here changes Goals; answering, withdrawing or asking Questions stays in contact. Each change applies independently: a refused one names why (`results[i].error`, e.g. goal_revision_conflict, goal_children_open, goal_not_joined, goal_not_found) and the rest still apply, so read every result: a partial result is never a complete success. Kinds: create (title, outcome, ownerId, parentGoalId, sourceEntryIds) returns the new Goal's id in `results[i].goalId`, in the order requested, to use in later calls; edit (title and/or outcome); state (open, completed, canceled); assign (ownerId); defer (until, or null); move (parentGoalId, or null for a root: only this Goal moves); dependency (add or remove: goalId waits on dependsOnGoalId to start or to complete). A Goal cannot be completed while its required children or dependencies remain open: finish or move them first. Finishing the children does not prove the parent's own work is done. An edit applies at the version you last read: if someone changed the Goal since, it is refused as goal_revision_conflict, so read it again (get_goal) and reconsider. You may change a Goal you own or have written on. Returns `ok` (every change applied), `results` per change (applied or failed), and `goals`, each changed Goal's id, state, title, outcome and revision.";
1599
1666
  var ClaimGoalSchema = z3.object({ goalId: z3.string().uuid().optional() }).strict();
1600
1667
  var GetGoalSchema = z3.object({
1601
1668
  goalId: z3.string().uuid(),
@@ -1608,20 +1675,162 @@ var GetGoalSchema = z3.object({
1608
1675
  diagnose: z3.boolean().optional()
1609
1676
  }).strict();
1610
1677
  var GET_GOAL_DESCRIPTION = "Read one Goal without claiming it, including others (the ten most recent other contributors, with names, latest entry headlines and times): its outcome, state, revision, progress, blockers, the conversation on it (each question with its options and what was decided), `next`, the one step to take, and `more`, where to read further. A question in that conversation reads `open` (nothing yet), `answered` (a choice was made, and `answer` carries it), `replied` (they said something and the read settled the question on their words \u2014 NO option of yours was chosen, and the words are the reply line beside it, so read that before you act), or `closed`. The conversation carries everything the person said, every open question and your newest entry; your older entries are left out and counted \u2014 history: true reads every entry in full. Every Goal of your person is readable, whichever of their agents owns it; another account's Goals are not disclosed.\n\ndiagnose: true is for when a read has CONFUSED you \u2014 not for the working loop. It adds `diagnosis`, which asks every reader that already answers a question about your work and NAMES the reader behind every value, so you never guess which of two places to look: what state your work is really in (the stored `goals.state` beside the derived `goal_execution_state`, which can differ); whether anything is armed to ring and when (`ladder_candidates`, with the ladder's own last decision); whether a wake fired for you and what it did (durable `wake.*` events, beside when your process was last heard from); and what has reached you (`list_waiting` \u2014 review flags, unstarted work, open questions, the person's newest words). Read `diagnosis.disagreements` FIRST: each one is two readers giving different values for the same fact, with both values and both sources, and it never chooses between them \u2014 that is yours to do, from the Goal's own history. `diagnosis.looked` names every reader asked, including any that could not answer, so an empty answer is never confused with a broken one.";
1611
- var UPDATE_GOAL_DESCRIPTION = `Update a Goal you are on at an exact revision. You are on a Goal you own, or one you have written on (an update with progress, or a contact on it); any of your person's agents may write on any of their Goals, and one that never touched a Goal is refused (409 goal_not_joined). State, ownership, dependencies, children, progress, title, and review acknowledgement are explicit; stale revisions are rejected. A CONTACT ON THIS GOAL MOVES ITS REVISION: a question filed on a Goal is a change to it, so an update prepared before a contact and sent after it is refused as stale (409 goal_revision_conflict) \u2014 re-read the Goal, then write. title is the work's name in one to five words, as a person would refer to it out loud (it is spoken on a call and heads every list); null clears it. reviewed: true acknowledges new evidence and closes the Deliveries addressed to you on that Goal, never over an open decision. dueAt (an ISO instant, or null) makes the Goal wait until then; when it passes you are woken for it \u2014 use it for a promise to follow up later. withdraw: [questionId] takes back a question YOU asked on this Goal that is still open \u2014 because you acted on it yourself, it no longer matters, or you asked it wrongly (e.g. waiting: hard when nothing was blocked): it is cancelled, not answered, its card closes and it stops ringing; the id is the one the conversation shows. state: done means the work was accomplished, and it can be reopened: when the person says it is not done ("it's not working"), set state: active on that same Goal instead of starting a new one; its parent reopens with it. state: done while children are still open records your part as done: the Goal waits and closes by itself when its last open child closes. cancelled is final. Returns the Goal as get_goal reads it, at its new revision.`;
1612
- var CLAIM_GOAL_DESCRIPTION = "Claim a pending answer to your question or the oldest runnable or review-pending Goal you own. Pass goalId to join any Goal of your person; its assignment stays unchanged. Read others before overlapping another agent\u2019s work. Joining lets you contribute and change its state; use update_goal with ownerParticipant when the assignment itself should change. Returns the Goal as get_goal reads it, and marks you as on it, which never shuts another agent out: other agents of your person may write on it and change it too, and two changes at once are told apart by revision (409 goal_revision_conflict).";
1613
- var CHECK_REPLIES_DESCRIPTION = "What is waiting for you: your open Deliveries (a request the user started toward you, a handoff), one row each with its Goals, how many decisions are still open, and the newest words in brief; `assigned`, your Goals nobody has started yet, however they became yours (claim_goal({goalId}) starts one); and `review`, your Goals with something new on them \u2014 the user's answers and notes are Entries on the Goal, not Deliveries. A pure read with no arguments: nothing is consumed, acknowledged or claimed by reading it, so call it on startup, after a long wait, or whenever you want to know what is outstanding. To act on one, claim its Goal (claim_goal) or reread a Delivery in full with contact({deliveryId}). Once you have acted on what arrived, update_goal with reviewed: true clears the Goal from `review` and closes the Deliveries addressed to you on it.";
1614
- var CheckRepliesSchema = z3.object({}).strict();
1678
+ var UPDATE_GOAL_DESCRIPTION = "Report work on a Goal you are on, at an exact revision: progress, review acknowledgement, when to wake for it, and withdrawing a question of yours. What the Goal is (its title, outcome, state, owner, parent, dependencies) changes with manage_goals. You are on a Goal you own, or one you have written on (an update with progress, or a contact on it); any of your person's agents may write on any of their Goals, and one that never touched a Goal is refused (409 goal_not_joined). Stale revisions are rejected. A CONTACT ON THIS GOAL MOVES ITS REVISION: a question filed on a Goal is a change to it, so an update prepared before a contact and sent after it is refused as stale (409 goal_revision_conflict) \u2014 re-read the Goal, then write. progress says where the work stands. reviewed: true acknowledges new evidence and closes the Deliveries addressed to you on that Goal, never over an open decision (contact({ackEventIds}) does the same for an `update` event). dueAt (an ISO instant, or null) makes the Goal wait until then; when it passes you are woken for it \u2014 use it for a promise to follow up later. withdraw: [questionId] takes back a question YOU asked on this Goal that is still open \u2014 because you acted on it yourself, it no longer matters, or you asked it wrongly (e.g. waiting: hard when nothing was blocked): it is cancelled, not answered, its card closes and it stops ringing; the id is the one the conversation shows. Returns the Goal as get_goal reads it, at its new revision.";
1679
+ var CLAIM_GOAL_DESCRIPTION = "Claim a pending answer to your question or the oldest runnable or review-pending Goal you own. Pass goalId to join any Goal of your person; its assignment stays unchanged. Read others before overlapping another agent\u2019s work. Joining lets you contribute and change it; use manage_goals (assign) when the assignment itself should change. Returns the Goal as get_goal reads it, and marks you as on it, which never shuts another agent out: other agents of your person may write on it and change it too, and two changes at once are told apart by revision (409 goal_revision_conflict).";
1680
+ var SearchToolSchema = z3.object({
1681
+ query: z3.string().trim().min(1).max(500),
1682
+ types: z3.array(z3.enum(["entry", "goal", "answer"])).min(1).optional(),
1683
+ goalId: z3.string().uuid().optional(),
1684
+ limit: z3.number().int().min(1).max(20).optional()
1685
+ }).strict();
1686
+ var SEARCH_DESCRIPTION = "Search your person's history across all of their agents: Entries (what anyone said or wrote, typed or spoken on a call), Goals (by title and outcome) and Answers (found by their Question or by the words that gave them). query is words to look for; records sharing more of its words rank first, and exact names work. types narrows it to entry, goal and/or answer (default: all three). goalId searches under one Goal: its Entries, its Questions' Answers, and it and its immediate children. limit is matches per type, 1 to 20 (default 8). Read-only. Each match carries its whole saved words, its ID and its links (an Answer carries its Question, the choice made and the Entries that support it); `omitted` counts what matched but was left out, so narrow the words or add a goalId to see it. Nothing found is not proof that nothing exists; a refused search says why. Sealed (encrypted) content is never searched or returned.";
1687
+ var WhoIsWorkingSchema = z3.object({}).strict();
1615
1688
  var AGENT_TOOLS = [
1616
- { name: "who_is_working", description: "Read your person's agents that contributed in the last 24 hours, newest first: at most 30 agents and their five most recently touched Goals, with their latest entry headline and time. Use get_goal to read a Goal and its other contributors before starting overlapping work. Read-only; no arguments.", inputSchema: mcpInputSchema(CheckRepliesSchema) },
1689
+ { name: "who_is_working", description: "Read your person's agents that contributed in the last 24 hours, newest first: at most 30 agents and their five most recently touched Goals, with their latest entry headline and time. Use get_goal to read a Goal and its other contributors before starting overlapping work. Read-only; no arguments.", inputSchema: mcpInputSchema(WhoIsWorkingSchema) },
1617
1690
  { name: "contact", description: CONTACT_DESCRIPTION, inputSchema: CONTACT_SCHEMA },
1618
- { name: "check_replies", description: CHECK_REPLIES_DESCRIPTION, inputSchema: mcpInputSchema(CheckRepliesSchema) },
1619
- { name: "create_goal", description: CREATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(CreateGoalToolSchema) },
1691
+ { name: "manage_goals", description: MANAGE_GOALS_DESCRIPTION, inputSchema: mcpInputSchema(ManageGoalsToolSchema) },
1620
1692
  { name: "claim_goal", description: CLAIM_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(ClaimGoalSchema) },
1621
1693
  { name: "get_goal", description: GET_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(GetGoalSchema) },
1622
- { name: "update_goal", description: UPDATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(UpdateGoalToolSchema) }
1694
+ { name: "update_goal", description: UPDATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(UpdateGoalToolSchema) },
1695
+ { name: "search", description: SEARCH_DESCRIPTION, inputSchema: mcpInputSchema(SearchToolSchema) }
1623
1696
  ];
1624
1697
  var AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
1698
+ var id = z4.string().uuid();
1699
+ var key = z4.string().min(1).max(40);
1700
+ var participant = z4.string().regex(/^(human|agent):.+$/, "a participant ID such as agent:<uuid>");
1701
+ var SourceSchema = z4.object({ entryId: id }).strict();
1702
+ var ResultRefSchema = z4.union([z4.object({ id }).strict(), z4.object({ local: key }).strict()]);
1703
+ var NodeRefSchema = z4.object({ type: z4.enum(["goal", "question"]), id }).strict();
1704
+ var DependencySchema = z4.object({
1705
+ blocker: NodeRefSchema,
1706
+ blocked: NodeRefSchema,
1707
+ action: z4.enum(["start", "complete", "answer"]),
1708
+ reason: z4.string().max(500).optional()
1709
+ }).strict();
1710
+ var goalState = z4.enum(["open", "completed", "canceled"]);
1711
+ var existingGoalChanges = [
1712
+ z4.object({ kind: z4.literal("edit"), goalId: id, title: z4.string().min(1).max(120).optional(), outcome: z4.string().min(1).optional() }).strict().refine((c) => c.title !== void 0 || c.outcome !== void 0, "an edit changes the title or the outcome"),
1713
+ z4.object({ kind: z4.literal("state"), goalId: id, state: goalState }).strict(),
1714
+ z4.object({ kind: z4.literal("assign"), goalId: id, ownerId: participant }).strict(),
1715
+ z4.object({ kind: z4.literal("defer"), goalId: id, until: z4.string().datetime().nullable() }).strict(),
1716
+ z4.object({ kind: z4.literal("move"), goalId: id, parentGoalId: id.nullable() }).strict(),
1717
+ z4.object({
1718
+ kind: z4.literal("dependency"),
1719
+ change: z4.enum(["add", "remove"]),
1720
+ goalId: id,
1721
+ dependsOnGoalId: id,
1722
+ action: z4.enum(["start", "complete"]),
1723
+ reason: z4.string().max(500).optional()
1724
+ }).strict()
1725
+ ];
1726
+ var BrainGoalChangeSchema = z4.union([
1727
+ // newSession: Paigy starts a session to own it (owner, 2026-10-06); until it has, the person owns it.
1728
+ z4.object({
1729
+ kind: z4.literal("create"),
1730
+ outcome: z4.string().min(1),
1731
+ title: z4.string().min(1).max(120),
1732
+ ownerId: participant,
1733
+ parentGoal: ResultRefSchema.nullable(),
1734
+ newSession: z4.literal(true).optional()
1735
+ }).strict(),
1736
+ ...existingGoalChanges
1737
+ ]);
1738
+ var option = z4.object({ id: z4.string().min(1).max(40), label: z4.string().min(1), hint: z4.string().optional() }).strict();
1739
+ var QuestionChangeSchema = z4.union([
1740
+ z4.object({
1741
+ kind: z4.literal("create"),
1742
+ text: z4.string().min(1),
1743
+ answererId: participant,
1744
+ goal: ResultRefSchema.optional(),
1745
+ blocks: z4.array(z4.object({ question: ResultRefSchema }).strict()),
1746
+ options: z4.array(option).min(2).optional(),
1747
+ pickMode: z4.enum(["one", "many", "rank"]).optional()
1748
+ }).strict().refine((c) => c.options === void 0 === (c.pickMode === void 0), "options and pickMode come together"),
1749
+ z4.object({ kind: z4.literal("edit"), questionId: id, text: z4.string().min(1) }).strict(),
1750
+ z4.object({ kind: z4.literal("assign"), questionId: id, answererId: participant }).strict(),
1751
+ z4.object({ kind: z4.literal("withdraw"), questionId: id }).strict(),
1752
+ z4.object({ kind: z4.literal("dependency"), change: z4.enum(["add", "remove"]), edge: DependencySchema }).strict()
1753
+ ]);
1754
+ var lessonScope = z4.union([
1755
+ z4.object({ kind: z4.literal("user") }).strict(),
1756
+ z4.object({ kind: z4.literal("goal"), goal: ResultRefSchema }).strict()
1757
+ ]);
1758
+ var LessonChangeSchema = z4.union([
1759
+ z4.object({ kind: z4.literal("remember"), text: z4.string().min(1), scope: lessonScope }).strict(),
1760
+ z4.object({ kind: z4.literal("revise"), lessonId: id, text: z4.string().min(1), scope: lessonScope }).strict(),
1761
+ z4.object({ kind: z4.literal("withdraw"), lessonId: id }).strict()
1762
+ ]);
1763
+ var BrainNextSchema = z4.object({
1764
+ instructions: z4.array(z4.object({ entryKeys: z4.array(key).min(1) }).strict()),
1765
+ say: z4.array(key),
1766
+ then: z4.enum(["listen", "hold", "end", "none"]),
1767
+ waitFor: z4.array(ResultRefSchema),
1768
+ reason: z4.string()
1769
+ }).strict();
1770
+ var messageBase = {
1771
+ key,
1772
+ text: z4.string().min(1),
1773
+ goals: z4.array(ResultRefSchema),
1774
+ questions: z4.array(ResultRefSchema),
1775
+ entryKeys: z4.array(key),
1776
+ obligationIds: z4.array(id)
1777
+ };
1778
+ var BrainMessageSchema = z4.union([
1779
+ z4.object({ ...messageBase, to: z4.object({ kind: z4.literal("user") }).strict() }).strict(),
1780
+ z4.object({
1781
+ ...messageBase,
1782
+ to: z4.object({ kind: z4.literal("agent"), agentId: z4.string().regex(/^agent:(?!paigy$)\S+$/) }).strict(),
1783
+ call: z4.object({ callId: id, listen: z4.literal(true) }).strict().optional()
1784
+ }).strict()
1785
+ ]);
1786
+ var BrainSearchSchema = z4.object({
1787
+ query: z4.string().min(1).max(500),
1788
+ within: z4.array(z4.enum(["entries", "goals", "answers"])).min(1),
1789
+ goalId: id.optional()
1790
+ }).strict();
1791
+ var BrainResultSchema = z4.object({
1792
+ messages: z4.array(BrainMessageSchema),
1793
+ entries: z4.array(z4.object({ key, sources: z4.array(SourceSchema).min(1), goals: z4.array(ResultRefSchema) }).strict()),
1794
+ questions: z4.array(z4.object({ key, entryKeys: z4.array(key), change: QuestionChangeSchema }).strict()),
1795
+ answers: z4.array(z4.object({
1796
+ question: ResultRefSchema,
1797
+ entryKeys: z4.array(key).min(1),
1798
+ summary: z4.string().min(1),
1799
+ selectedOptionIds: z4.array(z4.string().min(1)).optional()
1800
+ }).strict()),
1801
+ goals: z4.array(z4.object({ key, entryKeys: z4.array(key), change: BrainGoalChangeSchema }).strict()),
1802
+ feedback: z4.array(z4.object({ entryKeys: z4.array(key).min(1) }).strict()),
1803
+ lessons: z4.array(z4.object({ entryKeys: z4.array(key).min(1), change: LessonChangeSchema }).strict()),
1804
+ next: BrainNextSchema,
1805
+ search: BrainSearchSchema.optional()
1806
+ }).strict();
1807
+ var str = z4.string();
1808
+ var strs = z4.array(z4.string());
1809
+ var CompactResultSchema = z4.object({
1810
+ next: z4.object({ say: strs, then: z4.enum(["listen", "hold", "end", "none"]), waitFor: strs, reason: str, instructions: strs }).strict(),
1811
+ messages: z4.array(z4.object({ key: str, to: str, text: str, about: strs, entries: strs, owed: strs, inviteToCall: z4.boolean() }).strict()),
1812
+ entries: z4.array(z4.object({ key: str, from: str, on: strs }).strict()),
1813
+ answers: z4.array(z4.object({ question: str, entries: strs, summary: str, picked: strs }).strict()),
1814
+ changes: z4.array(z4.object({
1815
+ key: str,
1816
+ what: z4.enum(["goal", "question", "lesson"]),
1817
+ op: z4.enum(["create", "edit", "assign", "open", "complete", "cancel", "defer", "move", "block", "unblock", "withdraw", "remember", "revise"]),
1818
+ id: str,
1819
+ text: str,
1820
+ title: str,
1821
+ who: str,
1822
+ under: str,
1823
+ gate: z4.enum(["start", "complete", "answer", "none"]),
1824
+ options: z4.array(z4.object({ id: str, label: str }).strict()),
1825
+ pick: z4.enum(["one", "many", "rank", "words"]),
1826
+ entries: strs
1827
+ }).strict()),
1828
+ feedback: z4.array(strs),
1829
+ search: z4.object({ query: str, within: strs, goal: str }).strict()
1830
+ }).strict();
1831
+ var COMPACT_RESULT_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
1832
+ zodToJsonSchema(CompactResultSchema, { $refStrategy: "none" })
1833
+ );
1625
1834
  function entryWords(entry) {
1626
1835
  const content = entry.content;
1627
1836
  if (content && "sealed" in content) return "";
@@ -1634,22 +1843,24 @@ function entryWords(entry) {
1634
1843
  const description = plain.description;
1635
1844
  const parts = [title, ...Array.isArray(description) ? description : []].filter((v) => typeof v === "string");
1636
1845
  if (parts.length) return parts.join("\n\n");
1846
+ const line = plain.line;
1847
+ if (typeof line === "string" && line.trim()) return line;
1637
1848
  }
1638
1849
  return entry.sources.map((source) => source.text).join("\n");
1639
1850
  }
1640
1851
  var LIVE_MS = 3 * 6e4;
1641
1852
  var WORKING_MS = 60 * 6e4;
1642
- var ContextSchema = z4.object({
1643
- title: z4.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
1644
- description: z4.array(z4.string().min(1)).describe(
1853
+ var ContextSchema = z5.object({
1854
+ title: z5.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
1855
+ description: z5.array(z5.string().min(1)).describe(
1645
1856
  "Semantic chunks of detail (each a standalone, non-empty piece). The user can select chunks to ask you to expand. MAY BE EMPTY: a claim whose whole content is its heading \u2014 a single sentence \u2014 has no body, and saying so beats repeating the heading underneath itself. That repeat is what `min(1)` used to force, at 2x the storage, with every reader subtracting it back out at render time."
1646
1857
  )
1647
1858
  });
1648
- var ParticipantSchema = z4.object({
1649
- kind: z4.enum(["human", "agent"]),
1650
- id: z4.string()
1859
+ var ParticipantSchema = z5.object({
1860
+ kind: z5.enum(["human", "agent"]),
1861
+ id: z5.string()
1651
1862
  });
1652
- var TransformSchema = z4.enum([
1863
+ var TransformSchema = z5.enum([
1653
1864
  "structure",
1654
1865
  // shape intent into an answer contract; pick channel/urgency — broker `ask`, `select` shapes, `points`
1655
1866
  "request_more",
@@ -1672,13 +1883,13 @@ var PAIGY_SELF = { kind: "agent", id: "paigy" };
1672
1883
  function isPaigy(ref) {
1673
1884
  return ref === participantRef(PAIGY_SELF);
1674
1885
  }
1675
- var VisualSchema = z4.object({
1676
- url: z4.string().url(),
1677
- label: z4.string().optional()
1886
+ var VisualSchema = z5.object({
1887
+ url: z5.string().url(),
1888
+ label: z5.string().optional()
1678
1889
  });
1679
- var NotifyLevelSchema = z4.enum(["inbox", "push", "banner", "call"]);
1680
- var SelectShapeSchema = z4.enum(["one", "many", "rank", "confirm", "text"]);
1681
- var ReceiptEventSchema = z4.enum([
1890
+ var NotifyLevelSchema = z5.enum(["inbox", "push", "banner", "call"]);
1891
+ var SelectShapeSchema = z5.enum(["one", "many", "rank", "confirm", "text"]);
1892
+ var ReceiptEventSchema = z5.enum([
1682
1893
  "delivered",
1683
1894
  // the bundle reached the recipient at some level
1684
1895
  "seen",
@@ -1708,47 +1919,47 @@ var ReceiptEventSchema = z4.enum([
1708
1919
  // be rewound by a writer that forgot to advance it.
1709
1920
  "restarted"
1710
1921
  ]);
1711
- var AttentionSchema = z4.object({
1922
+ var AttentionSchema = z5.object({
1712
1923
  urgency: NotifyLevelSchema,
1713
1924
  /** The required answer shape, or null for a plain notify that asks nothing back. */
1714
1925
  select: SelectShapeSchema.nullable(),
1715
1926
  /** Coverage contract (#396) — points the answer must address; null = none declared. */
1716
- points: z4.array(z4.string()).nullable(),
1927
+ points: z5.array(z5.string()).nullable(),
1717
1928
  /** Whether the ask blocks the sender — what lets arbitration escalate it on silence. */
1718
- blocking: z4.boolean(),
1929
+ blocking: z5.boolean(),
1719
1930
  /** Reserved (docs/model/model.md lists it): a response deadline. No row column yet — a later Phase 2
1720
1931
  * slice wires it; optional so today's rows/callers project cleanly. */
1721
- deadline: z4.string().datetime().nullable().optional()
1932
+ deadline: z5.string().datetime().nullable().optional()
1722
1933
  });
1723
- var NotifyRequestFields = z4.object({
1934
+ var NotifyRequestFields = z5.object({
1724
1935
  /** Plaintext message content. Present on the plaintext path (today's shape);
1725
1936
  * ABSENT on the E2EE path, where the sealed `envelope` below carries it. The
1726
1937
  * superRefine at the bottom enforces exactly one of the two. */
1727
1938
  context: ContextSchema.optional(),
1728
- options: z4.array(OptionInputSchema).min(OPTIONS_MIN).max(OPTIONS_MAX).optional().describe(
1939
+ options: z5.array(OptionInputSchema).min(OPTIONS_MIN).max(OPTIONS_MAX).optional().describe(
1729
1940
  "The choices, in order \u2014 required when select is 'one'/'many'/'rank', omitted otherwise. Ids are assigned automatically by position ('1', '2', \u2026); the user's answer references them as optionId(s)."
1730
1941
  ),
1731
- points: z4.array(z4.string().min(1)).optional().describe(
1942
+ points: z5.array(z5.string().min(1)).optional().describe(
1732
1943
  "The distinct things you need answered, each a short phrase \u2014 on a call the broker keeps the conversation going until each is addressed, and the reply reports which were covered, so a half-answer is never silently returned as final. Omit for single-part asks."
1733
1944
  ),
1734
- visuals: z4.array(VisualSchema).optional().describe(
1945
+ visuals: z5.array(VisualSchema).optional().describe(
1735
1946
  "Images attached to the message itself \u2014 context for the whole question (a screenshot, a chart). For a preview on one selectable choice, use that option's `html`/`image` instead."
1736
1947
  ),
1737
1948
  /** Git repo the agent is working in ("owner/name"). Local MCP fills this from the checkout — omit unless overriding. */
1738
- repo: z4.string().optional(),
1949
+ repo: z5.string().optional(),
1739
1950
  /** Git branch the agent is on. Local MCP fills this from the checkout — omit unless overriding. */
1740
- branch: z4.string().optional(),
1951
+ branch: z5.string().optional(),
1741
1952
  /** Continue an existing conversation — the id of any notification in it (its root
1742
1953
  * is the conversation's identity). Omitted = start a new conversation. Renamed
1743
1954
  * from `parentId` (2026-08-03): one linkage system, the parent; the API edge
1744
1955
  * still accepts the old name from older clients. */
1745
- parentId: z4.string().uuid().optional(),
1956
+ parentId: z5.string().uuid().optional(),
1746
1957
  /** The durable outcome this contact advances. Optional during the notification-to-Work
1747
1958
  * migration; when present, a blocking ask creates a DecisionNeed for this Work. */
1748
- workId: z4.string().uuid().optional(),
1959
+ workId: z5.string().uuid().optional(),
1749
1960
  /** Target Goal scope. During staged migration this is accepted by the shared contract but
1750
1961
  * target delivery activation remains model-gated; workId and goalId are mutually exclusive. */
1751
- goalId: z4.string().uuid().optional(),
1962
+ goalId: z5.string().uuid().optional(),
1752
1963
  urgency: NotifyLevelSchema.default("inbox").describe(
1753
1964
  "The level you're requesting \u2014 the user's account permissions + session mode can lower it. 'inbox' (default) = sits silently in the inbox for the user to get to. 'push' = a quiet passive push (lands in Notification Center, no sound) \u2014 a gentle heads-up. 'banner' = a time-sensitive banner/lock-screen push with sound (a 'paige') they tap to open \u2014 use when you need them soon-ish but it's not worth ringing them. 'call' = rings the user's phone now (a CallKit voice call) \u2014 use only when you genuinely need them in the moment (blocked and waiting, time-sensitive). context.title is what they see on the banner/ring, so make it specific."
1754
1965
  ),
@@ -1756,7 +1967,7 @@ var NotifyRequestFields = z4.object({
1756
1967
  * visible and marks it needs_input. Renamed from the old `parentId` (2026-08-03)
1757
1968
  * when `parentId` became the conversation handle: `parentId` says WHERE, this
1758
1969
  * says HOW. */
1759
- clarifies: z4.string().optional(),
1970
+ clarifies: z5.string().optional(),
1760
1971
  select: SelectShapeSchema.optional().describe(
1761
1972
  "How the user answers \u2014 required on the fully-shaped form, pick the shape that fits the question: 'one' = pick one option, 'many' = pick several, 'rank' = pick & order (each needs `options`); 'confirm' = yes/no or approve/deny; 'text' = free-form reply only (status updates, open questions). 'confirm' and 'text' take no options. Omit only when sending the simplified `ask` form \u2014 the broker picks the shape."
1762
1973
  ),
@@ -1770,20 +1981,20 @@ var NotifyRequestFields = z4.object({
1770
1981
  // (docs/brain/broker/agenda-design.md) — not by a wire cap the agent has to pre-summarize under.
1771
1982
  // Owner, 2026-07-28: "our actual limitation on how long something is to the user should
1772
1983
  // come from the broker splitting and summarizing." The cap that remains is a size guard.
1773
- ask: z4.string().min(1).max(1e4).optional().describe(
1984
+ ask: z5.string().min(1).max(1e4).optional().describe(
1774
1985
  'SIMPLIFIED FORM \u2014 state in plain prose what you need to learn from the user and why it matters now (e.g. "I need to know whether to deploy the auth fix \u2014 tests are green, staging verified"). Write as much as the situation needs (up to 10k characters) \u2014 Paigy breaks it into topics and reads it back a few sentences at a time; do NOT pre-summarize it into one line. Paigy derives the title, answer shape, options, and delivery channel for you. Mutually exclusive with context/select/options \u2014 send one form or the other.'
1775
1986
  ),
1776
- needs: z4.array(z4.string().min(1)).optional().describe(
1987
+ needs: z5.array(z5.string().min(1)).optional().describe(
1777
1988
  "With `ask` only: the distinct things you need answered when the ask is multi-part \u2014 becomes the coverage contract (`points`), so a half-answer is never silently final."
1778
1989
  ),
1779
- urgencyHint: z4.enum(["whenever", "soon", "now"]).optional().describe(
1990
+ urgencyHint: z5.enum(["whenever", "soon", "now"]).optional().describe(
1780
1991
  "With `ask` only: how urgently you need the answer \u2014 'whenever' (inbox), 'soon' (worth a heads-up), 'now' (you're blocked this minute). A hint, not a command: the user's settings still have the final word."
1781
1992
  ),
1782
1993
  /** #575: the ONE self-report that replaces urgencyHint + blocking — what happens
1783
1994
  * to the agent's work while it waits. Normalized server-side into those two
1784
1995
  * fields (normalizeWaiting) so everything downstream is untouched; explicit
1785
1996
  * urgencyHint/blocking win when both are sent. */
1786
- waiting: z4.enum(["none", "soft", "hard"]).optional().describe(
1997
+ waiting: z5.enum(["none", "soft", "hard"]).optional().describe(
1787
1998
  "With `ask`: what happens to your work while you wait. 'none' = you're just informing the user. 'soft' = you'd like an answer but can keep working. 'hard' = you are stopped until they answer (reaches them urgently and escalates to a real phone call if unanswered). Replaces urgencyHint + blocking \u2014 send this one field."
1788
1999
  ),
1789
2000
  /** Δ9b (#895): HOLD this claim so the sender can correct the plan before anyone is
@@ -1791,98 +2002,98 @@ var NotifyRequestFields = z4.object({
1791
2002
  * holding by default would charge every quiet claim that minute before any agent could
1792
2003
  * correct anything. Ignored for `waiting: 'hard'`: a blocking ask rings on what we have,
1793
2004
  * and the enrichment can still land mid-call (#781 re-plans the unspoken tail). */
1794
- confirm: z4.boolean().optional().describe(
2005
+ confirm: z5.boolean().optional().describe(
1795
2006
  "Hold this one so you can correct the plan before the user is interrupted. The response comes back with `held: true` and the plan; POST the confirm route to release it (with options/visuals/urgency corrections, or nothing at all). If you never do, it is announced anyway a couple of minutes later. Ignored when waiting is 'hard'."
1796
2007
  ),
1797
2008
  /** #575: a RELAY of the user's explicitly stated preference, never the agent's
1798
2009
  * choice. Outranks waiting in both directions: 'call' rings even for a
1799
2010
  * waiting:'none' "call me when it's done"; 'message' never rings even for
1800
2011
  * waiting:'hard'. */
1801
- channel: z4.enum(["call", "message"]).optional().describe(
2012
+ channel: z5.enum(["call", "message"]).optional().describe(
1802
2013
  "Only if the user explicitly said how to reach them \u2014 'call me' \u2192 'call', 'just message/text me' \u2192 'message'. Omit otherwise; Paigy picks."
1803
2014
  ),
1804
- confirmStyle: z4.enum(["yesno", "approve"]).default("yesno").describe(
2015
+ confirmStyle: z5.enum(["yesno", "approve"]).default("yesno").describe(
1805
2016
  "Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
1806
2017
  ),
1807
- blocking: z4.boolean().default(false).describe(
2018
+ blocking: z5.boolean().default(false).describe(
1808
2019
  "Set true when real downstream work is stuck behind this specific decision \u2014 you can't make meaningful progress until it's answered. This is the real signal for how urgently the user should be reached; it's what the premier use case (an agent that stays unblocked instead of going idle) depends on. Independent of `urgency`: a `banner`-level question can still be `blocking` (something IS stuck, just not time-critical enough to ring for immediately) \u2014 if it goes unanswered a while, Paigy escalates it to a real call using this flag rather than guessing from how many other things happen to be pending. Leave false for anything you could work around, defer, or where other useful work exists meanwhile."
1809
2020
  )
1810
2021
  });
1811
2022
  var NotifyRequestSchema = NotifyRequestFields.superRefine((r, ctx) => {
1812
- if (r.workId && r.goalId) ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["goalId"], message: "pass goalId or workId, not both" });
2023
+ if (r.workId && r.goalId) ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["goalId"], message: "pass goalId or workId, not both" });
1813
2024
  if (r.ask !== void 0) {
1814
2025
  for (const f of ["context", "select", "points"]) {
1815
2026
  if (r[f] !== void 0)
1816
- ctx.addIssue({ code: z4.ZodIssueCode.custom, path: [f], message: `the simplified \`ask\` form takes no ${f} \u2014 the broker derives the answer shape from your prose. Drop ${f} and say it in \`ask\` instead ("should I\u2026" for approve/deny, "which of these\u2026" for a pick), passing \`options\` when you're offering concrete alternatives.` });
2027
+ ctx.addIssue({ code: z5.ZodIssueCode.custom, path: [f], message: `the simplified \`ask\` form takes no ${f} \u2014 the broker derives the answer shape from your prose. Drop ${f} and say it in \`ask\` instead ("should I\u2026" for approve/deny, "which of these\u2026" for a pick), passing \`options\` when you're offering concrete alternatives.` });
1817
2028
  }
1818
2029
  return;
1819
2030
  }
1820
2031
  if (r.needs !== void 0 || r.urgencyHint !== void 0)
1821
- ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["needs"], message: "needs/urgencyHint belong to the simplified `ask` form \u2014 with a shaped request use points/urgency" });
2032
+ ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["needs"], message: "needs/urgencyHint belong to the simplified `ask` form \u2014 with a shaped request use points/urgency" });
1822
2033
  if (!r.context)
1823
- ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["context"], message: "context is required (plaintext path)" });
2034
+ ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["context"], message: "context is required (plaintext path)" });
1824
2035
  if (!r.select)
1825
- ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["select"], message: "select is required on the shaped form" });
2036
+ ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["select"], message: "select is required on the shaped form" });
1826
2037
  const needsOptions = r.select === "one" || r.select === "many" || r.select === "rank";
1827
2038
  if (needsOptions && !r.options?.length)
1828
- ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' requires options` });
2039
+ ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' requires options` });
1829
2040
  if (!needsOptions && r.options?.length)
1830
- ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' takes no options` });
2041
+ ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' takes no options` });
1831
2042
  });
1832
- var NotifyStatusSchema = z4.enum(["pending", "answered", "ignored"]);
1833
- var AgentStateSchema = z4.enum(["idle", "in_progress", "completed", "needs_input"]);
1834
- var TurnSchema = z4.object({
1835
- prompt: z4.string(),
1836
- reply: z4.string()
2043
+ var NotifyStatusSchema = z5.enum(["pending", "answered", "ignored"]);
2044
+ var AgentStateSchema = z5.enum(["idle", "in_progress", "completed", "needs_input"]);
2045
+ var TurnSchema = z5.object({
2046
+ prompt: z5.string(),
2047
+ reply: z5.string()
1837
2048
  });
1838
- var UserAnswerSchema = z4.discriminatedUnion("kind", [
1839
- z4.object({ kind: z4.literal("option"), optionId: z4.string(), label: z4.string().optional() }),
1840
- z4.object({ kind: z4.literal("text"), text: z4.string() }),
1841
- z4.object({ kind: z4.literal("ignored") }),
1842
- z4.object({ kind: z4.literal("multi"), optionIds: z4.array(z4.string()), labels: z4.array(z4.string()).optional() }),
1843
- z4.object({ kind: z4.literal("ranked"), optionIds: z4.array(z4.string()), labels: z4.array(z4.string()).optional() }),
1844
- z4.object({ kind: z4.literal("clarify"), chunks: z4.array(z4.string()).min(1) }),
1845
- z4.object({ kind: z4.literal("confirm"), approved: z4.boolean() }),
1846
- z4.object({ kind: z4.literal("turns"), turns: z4.array(TurnSchema).min(1) }),
2049
+ var UserAnswerSchema = z5.discriminatedUnion("kind", [
2050
+ z5.object({ kind: z5.literal("option"), optionId: z5.string(), label: z5.string().optional() }),
2051
+ z5.object({ kind: z5.literal("text"), text: z5.string() }),
2052
+ z5.object({ kind: z5.literal("ignored") }),
2053
+ z5.object({ kind: z5.literal("multi"), optionIds: z5.array(z5.string()), labels: z5.array(z5.string()).optional() }),
2054
+ z5.object({ kind: z5.literal("ranked"), optionIds: z5.array(z5.string()), labels: z5.array(z5.string()).optional() }),
2055
+ z5.object({ kind: z5.literal("clarify"), chunks: z5.array(z5.string()).min(1) }),
2056
+ z5.object({ kind: z5.literal("confirm"), approved: z5.boolean() }),
2057
+ z5.object({ kind: z5.literal("turns"), turns: z5.array(TurnSchema).min(1) }),
1847
2058
  /** An auto-answer derived from the user's PAST decisions (docs/brain/broker/precedent-design.md §2):
1848
2059
  * delivered through the same settle/await path as a human answer, carrying the judge's
1849
2060
  * derivation and the precedent ids it grew from. Always paired with a visible trail
1850
2061
  * card the user can reply to — the broker never overrides the user. */
1851
- z4.object({ kind: z4.literal("precedent"), answer: z4.string(), derivation: z4.string(), sources: z4.array(z4.string()).min(1) })
2062
+ z5.object({ kind: z5.literal("precedent"), answer: z5.string(), derivation: z5.string(), sources: z5.array(z5.string()).min(1) })
1852
2063
  ]);
1853
- var IntentSchema = z4.object({
2064
+ var IntentSchema = z5.object({
1854
2065
  // The full vocabulary the bot's mapper emits (mapper.INTENT_KINDS) — the schema lagged
1855
2066
  // it by two ("detail", "feedback"), and because the settle handler parsed the array
1856
2067
  // all-or-nothing, ONE feedback act silently dropped EVERY intent on the call,
1857
2068
  // questions included. Found auditing five calls' stored feedback, 2026-08-01.
1858
- kind: z4.enum(["defer", "delegate", "channel", "question", "detail", "feedback", "command", "control"]),
1859
- detail: z4.string(),
2069
+ kind: z5.enum(["defer", "delegate", "channel", "question", "detail", "feedback", "command", "control"]),
2070
+ detail: z5.string(),
1860
2071
  /** Defer only: seconds until the callback the caller asked for, when something upstream
1861
2072
  * already read the time. Nothing sets it today (#397 documented an MCP parser that was
1862
2073
  * never written) — the API reads the defer's `detail` itself with `notes/when.ts`
1863
2074
  * (`parseDelay`, #1292), and a value here simply wins over that reading. */
1864
- dueInSeconds: z4.number().int().positive().optional(),
2075
+ dueInSeconds: z5.number().int().positive().optional(),
1865
2076
  /** Feedback only (#812): WHICH failure the complaint names — typed by the mapper that
1866
- * already read the utterance, so `feedback_from_call.kind` stops defaulting to
2077
+ * already read the utterance, so `signals.kind` stops defaulting to
1867
2078
  * 'other' on every row. A table that records that something was wrong and nothing
1868
2079
  * about what cannot answer "is the bot looping less this week?". */
1869
- fault: z4.enum(["loop", "unanswered", "overridden", "misheard", "slow", "other"]).optional()
2080
+ fault: z5.enum(["loop", "unanswered", "overridden", "misheard", "slow", "other"]).optional()
1870
2081
  });
1871
- var RideAlongSchema = z4.object({
2082
+ var RideAlongSchema = z5.object({
1872
2083
  /** The note this came from — assign/clarify/close it through /api/notes/:id. */
1873
- noteId: z4.string(),
2084
+ noteId: z5.string(),
1874
2085
  /** What to do, in the owner's own words (the note's headline). Never model-rewritten. */
1875
- text: z4.string(),
2086
+ text: z5.string(),
1876
2087
  /** The thread to report back on, when the note was dispatched over the request rail. */
1877
- parentId: z4.string().nullable()
2088
+ parentId: z5.string().nullable()
1878
2089
  });
1879
- var AwaitItemSchema = z4.discriminatedUnion("type", [
1880
- z4.object({
1881
- type: z4.literal("reply"),
1882
- parentId: z4.string(),
1883
- notificationId: z4.string(),
1884
- workId: z4.string().uuid().optional(),
1885
- decisionId: z4.string().uuid().optional(),
2090
+ var AwaitItemSchema = z5.discriminatedUnion("type", [
2091
+ z5.object({
2092
+ type: z5.literal("reply"),
2093
+ parentId: z5.string(),
2094
+ notificationId: z5.string(),
2095
+ workId: z5.string().uuid().optional(),
2096
+ decisionId: z5.string().uuid().optional(),
1886
2097
  answer: UserAnswerSchema,
1887
2098
  /** WHAT THE AGENT CANNOT KNOW FROM THE FIELDS BESIDE IT (owner, 2026-09-04, issue
1888
2099
  * #1537). One line, built from the record: the ask and the caller's reply VERBATIM,
@@ -1892,103 +2103,103 @@ var AwaitItemSchema = z4.discriminatedUnion("type", [
1892
2103
  * "call me back after you merge" in their own words decides for itself what to do,
1893
2104
  * and now knows exactly which call to make. Absent when either half is missing —
1894
2105
  * a sentence with a hole in it is worse than no sentence. */
1895
- note: z4.string().optional(),
2106
+ note: z5.string().optional(),
1896
2107
  /** The call record rendered for THIS agent (`docs/brain/voice/record-design.md`): the words the
1897
2108
  * shaped answer was mapped from, filtered to its own claims. There is no second list
1898
2109
  * of labels beside it — the acts went 2026-09-04 and `intents` went with them (owner,
1899
2110
  * 2026-09-04): the agent reads the sentence and decides. */
1900
- transcript: z4.string().optional(),
2111
+ transcript: z5.string().optional(),
1901
2112
  /** Coverage report (#396), when the ask declared `points`: which of them this
1902
2113
  * answer addressed. Missing points = re-ask or proceed knowingly partial. */
1903
- covered: z4.array(z4.string()).optional(),
2114
+ covered: z5.array(z5.string()).optional(),
1904
2115
  /** Ride-alongs (RideAlongSchema) — pending work for you, attached to the moment you
1905
2116
  * became free. Only `reply` and `idle` carry it: those are the two outcomes that
1906
2117
  * END a wait. `remind`, `superseded` and `turn` are mid-flight, and handing an
1907
2118
  * agent a side-quest while it is still holding the line is how the main thing gets
1908
2119
  * dropped. Absent/empty = nothing owed. */
1909
- also: z4.array(RideAlongSchema).optional()
2120
+ also: z5.array(RideAlongSchema).optional()
1910
2121
  }),
1911
- z4.object({
1912
- type: z4.literal("remind"),
1913
- parentId: z4.string(),
1914
- notificationId: z4.string(),
1915
- remindAt: z4.string().datetime({ offset: true }),
2122
+ z5.object({
2123
+ type: z5.literal("remind"),
2124
+ parentId: z5.string(),
2125
+ notificationId: z5.string(),
2126
+ remindAt: z5.string().datetime({ offset: true }),
1916
2127
  /** Seconds until remindAt, server-computed — pass straight to ScheduleWakeup. */
1917
- remindInSeconds: z4.number()
2128
+ remindInSeconds: z5.number()
1918
2129
  }),
1919
2130
  /** The awaited ask was REPLACED by a newer notification on its thread (e.g. a
1920
2131
  * post-feedback revision, #633) — the user will never answer this id. Stop
1921
2132
  * awaiting it; the live ask is the thread's newest turn (await that one, or
1922
- * re-orient via check_replies). */
1923
- z4.object({
1924
- type: z4.literal("superseded"),
1925
- parentId: z4.string(),
1926
- notificationId: z4.string()
2133
+ * re-orient via contact({})). */
2134
+ z5.object({
2135
+ type: z5.literal("superseded"),
2136
+ parentId: z5.string(),
2137
+ notificationId: z5.string()
1927
2138
  }),
1928
2139
  /** A LIVE call's turn, streamed as it lands (#783). PROVISIONAL: the user can still
1929
2140
  * revise any of these until the final reply arrives — partial = intelligence,
1930
2141
  * settled = authorization. Use it to PREPARE (fetch, draft, warm), never to act
1931
2142
  * irreversibly. If `acts` carries a question aimed at you and you know the answer,
1932
2143
  * contact on the same thread right away — the caller hears it on the same call. */
1933
- z4.object({
1934
- type: z4.literal("partial"),
1935
- notificationId: z4.string(),
1936
- inFlight: z4.literal(true),
1937
- turn: z4.object({
1938
- idx: z4.number(),
1939
- prompt: z4.string(),
1940
- reply: z4.string(),
1941
- acts: z4.array(IntentSchema).nullable().optional()
2144
+ z5.object({
2145
+ type: z5.literal("partial"),
2146
+ notificationId: z5.string(),
2147
+ inFlight: z5.literal(true),
2148
+ turn: z5.object({
2149
+ idx: z5.number(),
2150
+ prompt: z5.string(),
2151
+ reply: z5.string(),
2152
+ acts: z5.array(IntentSchema).nullable().optional()
1942
2153
  })
1943
2154
  }),
1944
- z4.object({
1945
- type: z4.literal("idle"),
1946
- also: z4.array(RideAlongSchema).optional(),
2155
+ z5.object({
2156
+ type: z5.literal("idle"),
2157
+ also: z5.array(RideAlongSchema).optional(),
1947
2158
  /** Is a call live for this agent's user right now? The SDK polls the partial stream
1948
2159
  * (#783) between idle ticks ONLY while this is not `false` — a partial can only exist
1949
2160
  * during a live call, and polling for one on a banner/message was a wasted HTTP call +
1950
2161
  * 3 queries on every idle tick of every waiting agent (~80% of all traffic at scale).
1951
2162
  * Absent = an older API → the SDK keeps polling, exactly as before. */
1952
- inFlight: z4.boolean().optional()
2163
+ inFlight: z5.boolean().optional()
1953
2164
  })
1954
2165
  ]);
1955
- var VoiceKeySchema = z4.enum(["rachel", "george", "jessica", "brian", "lily"]);
1956
- var AgendaTurnSchema = z4.object({
2166
+ var VoiceKeySchema = z5.enum(["rachel", "george", "jessica", "brian", "lily"]);
2167
+ var AgendaTurnSchema = z5.object({
1957
2168
  /** THE TURN'S IDENTITY (the first-sentence stream, 2026-09-09): the brain call that wrote
1958
2169
  * it and its place in that reply — `<brainCallId>:<index>`, with `:p` on the first
1959
2170
  * sentence a re-plan publishes ahead of the rest. A turn is spoken once, by this id: the
1960
2171
  * completion of a streamed re-plan carries the published sentence again, and the walk
1961
2172
  * drops what it already said by identity, never by the API's guess of what was polled.
1962
2173
  * Absent on plans nothing streams (a ring plan, a floor). */
1963
- id: z4.string().optional(),
2174
+ id: z5.string().optional(),
1964
2175
  /** Twin coverage (#1089): sibling claim ids this asking turn's answer ALSO settles —
1965
2176
  * the planner declares duplicates instead of asking them twice. */
1966
- coveredIds: z4.array(z4.string()).optional(),
2177
+ coveredIds: z5.array(z5.string()).optional(),
1967
2178
  /** The spoken sentences of the turn, in order. No count: how long a turn is is the brain's call
1968
2179
  * (owner, 2026-09-25), and a count here refused whole plans. */
1969
- info: z4.array(z4.string().min(1)).default([]),
1970
- question: z4.string().min(1).nullable(),
2180
+ info: z5.array(z5.string().min(1)).default([]),
2181
+ question: z5.string().min(1).nullable(),
1971
2182
  /** True on the one turn carrying the agent's own declared question. */
1972
- asks: z4.boolean().optional(),
2183
+ asks: z5.boolean().optional(),
1973
2184
  /** The claim this turn belongs to (#781) — the RETURN identity: answers route by it.
1974
2185
  * Absent on a single-claim plan (the session's own claim) and on shared context turns,
1975
2186
  * which route nothing. */
1976
- claimId: z4.string().optional(),
2187
+ claimId: z5.string().optional(),
1977
2188
  /** The claim's voice key (#462) — the OUTBOUND identity, audible who-is-asking. */
1978
- voice: z4.string().optional(),
2189
+ voice: z5.string().optional(),
1979
2190
  /** The claim's AGENT NAME (#838) — the spoken identity. A voice alone doesn't say
1980
2191
  * whose request this is: an item that folded in from another agent arrived as a bare
1981
2192
  * non-sequitur ("First real production sign-in is yours to make whenever you want.")
1982
2193
  * and the owner answered "What?". The bot names the agent before its first turn. */
1983
- agent: z4.string().optional(),
2194
+ agent: z5.string().optional(),
1984
2195
  /** The claim's agent by ID — the pairing's connection id (`notifications.token_id`), the
1985
2196
  * same id a face is minted from. A name is not an identity: two pairings may be called
1986
2197
  * "Claude", and a name cannot be joined on. The record's entries carry it (`agent_id`)
1987
2198
  * so "who said that" survives the call, and it rides PER TURN because a coalesced call
1988
2199
  * speaks for several agents — the turn is the only place that knows which. */
1989
- agentId: z4.string().optional(),
2200
+ agentId: z5.string().optional(),
1990
2201
  select: SelectShapeSchema.optional(),
1991
- options: z4.array(OptionSchema.omit({ id: true })).optional(),
2202
+ options: z5.array(OptionSchema.omit({ id: true })).optional(),
1992
2203
  /* `pace` STOOD HERE (#826). A turn could carry seconds and the model chose them. The walk
1993
2204
  paces itself now — a short beat between the sentences of a turn, the longer one at its end
1994
2205
  (owner, 2026-09-30: "remove the bot deciding pace") — and it does that where the words are
@@ -1997,33 +2208,33 @@ var AgendaTurnSchema = z4.object({
1997
2208
  /** Whether the walk WAITS for an answer before moving on. Absent = derived as today
1998
2209
  * (a question blocks, context flows). blocking:false on a question = ask and move
1999
2210
  * on, the claim stays pending; blocking:true on context = hold for a reply. */
2000
- blocking: z4.boolean().optional(),
2211
+ blocking: z5.boolean().optional(),
2001
2212
  /** SPOKEN ONLY IF THEY SAY NOTHING (owner, 2026-10-01, call 812de935: "you're gonna re-ask, but it
2002
2213
  * shouldn't be the same words … more like, hey, are you still there, or are you able to answer, or
2003
2214
  * would you need more information"). The walk holds this turn out of its queue; at the queue's end it
2004
2215
  * listens for the last word, and only if that listen is silent is this turn said and asked. If they
2005
2216
  * speak, it is dropped and their words are taken like any reply. */
2006
- ifSilent: z4.boolean().optional()
2217
+ ifSilent: z5.boolean().optional()
2007
2218
  });
2008
2219
  var CLAIM_STALE_MS = 30 * 6e4;
2009
- var InboxItemSchema = z4.object({
2010
- id: z4.string(),
2011
- tokenId: z4.string().optional(),
2220
+ var InboxItemSchema = z5.object({
2221
+ id: z5.string(),
2222
+ tokenId: z5.string().optional(),
2012
2223
  status: NotifyStatusSchema,
2013
2224
  context: ContextSchema,
2014
- options: z4.array(OptionSchema).optional(),
2225
+ options: z5.array(OptionSchema).optional(),
2015
2226
  /** The ask's declared coverage points (#396), when the agent sent them. */
2016
- points: z4.array(z4.string()).optional(),
2227
+ points: z5.array(z5.string()).optional(),
2017
2228
  /** Does this claim want an ANSWER, or is it telling you something? Written per row from
2018
2229
  * `requestAsks` — the agent's own declaration, not a guess. `false` is what earns a card
2019
2230
  * its acknowledge affordance: without it a status update offers a text box and a dismiss,
2020
2231
  * and neither of those is "got it" (owner, 2026-08-10). */
2021
- asks: z4.boolean().optional(),
2232
+ asks: z5.boolean().optional(),
2022
2233
  /** When a live process last pulsed for this row's agent — the liveness input for
2023
2234
  * "working requires a pulse" (#928): the list said "Working…" from agent_state alone
2024
2235
  * while the party called the same dead claim stalled. Absent = no token/no data,
2025
2236
  * which must never CLAIM stalled. */
2026
- lastSeenAt: z4.string().optional(),
2237
+ lastSeenAt: z5.string().optional(),
2027
2238
  /** WHEN THE AGENT LAST SAID ANYTHING ABOUT THIS CLAIM — the newest `agent_state` row in
2028
2239
  * the `notification_events` ledger (trigger-written since 20260621010000, so every row a
2029
2240
  * user can see has one). The age input for `CLAIM_STALE_MS`, and it has to be this rather
@@ -2033,7 +2244,7 @@ var InboxItemSchema = z4.object({
2033
2244
  * work. Reading the row's birth as the claim's age brands that "No update in 8h" the
2034
2245
  * instant the agent picks it up (#997). Absent = pre-trigger row; fall back to
2035
2246
  * `createdAt`. */
2036
- agentStateAt: z4.string().datetime().optional(),
2247
+ agentStateAt: z5.string().datetime().optional(),
2037
2248
  /** THE QUESTIONS A CALL CARRIES — the call screen's agenda spine (docs/clients/app/walk/design.md §11, owner
2038
2249
  * 2026-09-22). One per DecisionNeed on the Call, in the Call's order, answered or open (a
2039
2250
  * superseded or cancelled need is no longer a question anyone is asked). Present only on a
@@ -2047,54 +2258,54 @@ var InboxItemSchema = z4.object({
2047
2258
  * `turn` topic (`asking`, `settled`), because the bot never sees a DecisionNeed id. `title` is
2048
2259
  * the card's own concise heading; `answer` the accepted answer in words, null while open. It
2049
2260
  * REPLACED `agenda` (turns), which nothing ever filled. */
2050
- questions: z4.array(z4.object({
2051
- id: z4.string(),
2052
- entryId: z4.string(),
2053
- title: z4.string(),
2054
- state: z4.enum(["open", "answered"]),
2055
- answer: z4.string().nullable(),
2261
+ questions: z5.array(z5.object({
2262
+ id: z5.string(),
2263
+ entryId: z5.string(),
2264
+ title: z5.string(),
2265
+ state: z5.enum(["open", "answered"]),
2266
+ answer: z5.string().nullable(),
2056
2267
  /** WHO ASKED IT (owner, 2026-09-23, Goal a345e906: each agenda row wears its agent's face) — the
2057
2268
  * request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
2058
2269
  * carry for the call's one agent, so the phone draws it with the same seed. Absent when the
2059
2270
  * author is not an agent this account holds (unpaired since, or a person). */
2060
- agent: z4.object({ tokenId: z4.string(), name: z4.string(), voice: VoiceKeySchema.optional() }).optional(),
2271
+ agent: z5.object({ tokenId: z5.string(), name: z5.string(), voice: VoiceKeySchema.optional() }).optional(),
2061
2272
  /** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
2062
2273
  * its need offers, exactly as its own card carries them, present only when one of them has a
2063
2274
  * preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
2064
2275
  * never re-sent as a second card to be seen mid-call. Words-only options are absent — the bot
2065
2276
  * says those, and the list stays small (an `html` is up to 16 KB). */
2066
- options: z4.array(OptionSchema).optional()
2277
+ options: z5.array(OptionSchema).optional()
2067
2278
  })).optional(),
2068
- visuals: z4.array(VisualSchema).optional(),
2279
+ visuals: z5.array(VisualSchema).optional(),
2069
2280
  /** The connected agent's name (the single pairing name — user-typed, or the
2070
2281
  * agent's suggestion, or a default silly name). */
2071
- name: z4.string(),
2282
+ name: z5.string(),
2072
2283
  /** The pairing's assigned voice (#462); absent = the default voice. */
2073
2284
  voice: VoiceKeySchema.optional(),
2074
- repo: z4.string().optional(),
2075
- branch: z4.string().optional(),
2076
- createdAt: z4.string().datetime(),
2077
- snoozedUntil: z4.string().datetime().optional(),
2285
+ repo: z5.string().optional(),
2286
+ branch: z5.string().optional(),
2287
+ createdAt: z5.string().datetime(),
2288
+ snoozedUntil: z5.string().datetime().optional(),
2078
2289
  agentState: AgentStateSchema.default("idle"),
2079
2290
  /** Whose action the item is waiting on: "you" = an agent asked you (the default,
2080
2291
  * every agent→user notification); "agent" = you sent a request and it's awaiting the
2081
2292
  * agent (held in the inbox until the agent replies on the thread). */
2082
- turn: z4.enum(["you", "agent"]).default("you"),
2293
+ turn: z5.enum(["you", "agent"]).default("you"),
2083
2294
  /** Hard error reason on an awaiting request (turn="agent") — the wake failed to reach
2084
2295
  * the agent (provider-agnostic; set server-side). Absent = no hard error. Drives the inbox
2085
2296
  * error badge + Retry. */
2086
- error: z4.string().optional(),
2297
+ error: z5.string().optional(),
2087
2298
  /** WHEN THIS AGENT WORK WENT QUIET (turn="agent"), by the one rule (`coldSince`: three days
2088
2299
  * with nothing said), or absent while it is not stalled. The inbox's stalled badge reads
2089
2300
  * this and nothing else (2026-09-23: a 3-minute age rule badged every live Goal stalled,
2090
2301
  * and "dismiss the stalled ones" cancelled 37 pieces of live work). */
2091
- cold: z4.string().datetime().optional(),
2092
- clarifies: z4.string().optional(),
2302
+ cold: z5.string().datetime().optional(),
2303
+ clarifies: z5.string().optional(),
2093
2304
  /** THIS CARD'S QUESTION IS ON A LIVE CALL (owner, 2026-09-24: "Mark it while the call is
2094
2305
  * live"). Present only while an open Call Delivery carries the card's request Entry — read
2095
2306
  * off the same open list the card came from, so it clears when the Call does. A card is the
2096
2307
  * backup for a call not taken; while the call has it, the call is where it is answered. */
2097
- onCall: z4.literal(true).optional(),
2308
+ onCall: z5.literal(true).optional(),
2098
2309
  /** THE RING, ON THE ITEM (docs/clients/app/walk/design.md §12 §17, #2251): the last ring on this card was
2099
2310
  * declined, and what the ladder will do next — read off the cron's own row, never computed
2100
2311
  * on the phone. Present only while a `declined` receipt stands on the card's last Call.
@@ -2104,31 +2315,31 @@ var InboxItemSchema = z4.object({
2104
2315
  * It replaced `gaveUp` (deleted 2026-09-22): "the ladder spent" was a boolean the projection
2105
2316
  * never set, and it is `nextRingAt === null` here — the party's *Missed you* (`party/dress.ts`)
2106
2317
  * and the roster's `unreached` read `declinedAt`, and stand while it does. */
2107
- ring: z4.object({
2108
- declinedAt: z4.string().datetime(),
2109
- anchorAt: z4.string().datetime(),
2110
- nextRingAt: z4.string().datetime().nullable(),
2111
- step: z4.number().int()
2318
+ ring: z5.object({
2319
+ declinedAt: z5.string().datetime(),
2320
+ anchorAt: z5.string().datetime(),
2321
+ nextRingAt: z5.string().datetime().nullable(),
2322
+ step: z5.number().int()
2112
2323
  }).optional(),
2113
2324
  /** Why this arrived the way it did, read back off the delivery receipt (`notify/why.ts`).
2114
2325
  * Absent for anything never delivered through a push, and for older rows written before
2115
2326
  * the reason was recorded. Deliberately a debug affordance, shown small (owner,
2116
2327
  * 2026-08-07) — its real job is to give "this didn't need a call" something to be
2117
2328
  * feedback ABOUT. */
2118
- why: z4.object({
2329
+ why: z5.object({
2119
2330
  asked: NotifyLevelSchema,
2120
2331
  got: NotifyLevelSchema,
2121
- because: z4.enum(["unresponsive", "dismissed", "not_permitted", "silent", "coalesced", "agent_capped", "unplanned", "learned_raise"]).optional(),
2122
- line: z4.string()
2332
+ because: z5.enum(["unresponsive", "dismissed", "not_permitted", "silent", "coalesced", "agent_capped", "unplanned", "learned_raise"]).optional(),
2333
+ line: z5.string()
2123
2334
  }).optional(),
2124
- select: z4.enum(["one", "many", "rank", "confirm", "text"]).default("one"),
2125
- confirmStyle: z4.enum(["yesno", "approve"]).default("yesno").describe(
2335
+ select: z5.enum(["one", "many", "rank", "confirm", "text"]).default("one"),
2336
+ confirmStyle: z5.enum(["yesno", "approve"]).default("yesno").describe(
2126
2337
  "Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
2127
2338
  ),
2128
2339
  /** Real downstream work is stuck behind this one — set by the agent, independent of
2129
2340
  * urgency (see the main README's "premier use case" + docs/delivery/notify/states.md). Drives the
2130
2341
  * inbox's blocking badge and the extra confirm step before dismissing it. */
2131
- blocking: z4.boolean().default(false),
2342
+ blocking: z5.boolean().default(false),
2132
2343
  /** The user's locked-in answer; present only for replied items (GET /api/replied/:id). */
2133
2344
  answer: UserAnswerSchema.optional(),
2134
2345
  /** THE TARGET FACTS A CARD RENDERS (#1796 point 5, 2026-09-11): the Delivery it is a view of,
@@ -2136,38 +2347,49 @@ var InboxItemSchema = z4.object({
2136
2347
  * for a request that asks nothing), whether its content is sealed, and that Goal's state. The
2137
2348
  * answer writer (`POST /api/entries`) and the disposition (`close_delivery`) take their ids from
2138
2349
  * here. The server projects it (`apps/api/src/inbox/project.ts`); a client never builds it. */
2139
- communication: z4.object({
2140
- deliveryId: z4.string(),
2141
- kind: z4.enum(["notification", "call"]),
2142
- entryId: z4.string(),
2143
- goalIds: z4.array(z4.string()),
2144
- decisionNeedId: z4.string().optional(),
2145
- sealed: z4.boolean(),
2146
- goalState: z4.string().optional(),
2350
+ communication: z5.object({
2351
+ deliveryId: z5.string(),
2352
+ kind: z5.enum(["notification", "call"]),
2353
+ entryId: z5.string(),
2354
+ goalIds: z5.array(z5.string()),
2355
+ decisionNeedId: z5.string().optional(),
2356
+ sealed: z5.boolean(),
2357
+ goalState: z5.string().optional(),
2147
2358
  /** THAT GOAL'S NAME (#2416) — what Activity's row is headed by, since a row there is one Goal
2148
2359
  * and the cards it holds sit behind it. Stamped by the same read as `goalState`. */
2149
- goalTitle: z4.string().optional()
2150
- }).optional()
2360
+ goalTitle: z5.string().optional()
2361
+ }).optional(),
2362
+ /** WHAT THIS CARD IS, IN TWELVE CHARACTERS (#3019) — the hash of every other field on it, stamped
2363
+ * by the one read that serves the open list (`apps/api/src/inbox/project.ts` `inboxFor`). It is
2364
+ * how the incremental read knows a card has not moved: the phone echoes back the revs it holds
2365
+ * (`POST /api/inbox/changes`) and is sent only the cards whose rev differs.
2366
+ *
2367
+ * THE PAYLOAD'S OWN HASH, NEVER A STAMP ON THE WORK — the same mechanism as `QueueItem.rev`
2368
+ * (#2928) and an entry's (#3018), for the same reason: a card shows facts no `updated_at` of its
2369
+ * own moves (its Goal's state and name, how cold the work behind it has gone, whether a ring is
2370
+ * live). Optional, so a fixture, the demo and the archive lens need not spell one, and a card
2371
+ * with no rev is simply always re-sent. */
2372
+ rev: z5.string().optional()
2151
2373
  });
2152
2374
  var APNS_TOKEN_RE = /^[0-9a-fA-F]{64}$/;
2153
- var PushTokenSchema = z4.object({
2154
- voipToken: z4.string().min(1).optional(),
2155
- alertToken: z4.string().min(1).optional(),
2156
- fcmToken: z4.string().min(1).optional(),
2157
- platform: z4.enum(["ios", "android"])
2375
+ var PushTokenSchema = z5.object({
2376
+ voipToken: z5.string().min(1).optional(),
2377
+ alertToken: z5.string().min(1).optional(),
2378
+ fcmToken: z5.string().min(1).optional(),
2379
+ platform: z5.enum(["ios", "android"])
2158
2380
  }).superRefine((v, ctx) => {
2159
2381
  if (v.platform !== "ios") return;
2160
2382
  for (const field of ["voipToken", "alertToken"]) {
2161
2383
  const token = v[field];
2162
2384
  if (token === void 0 || APNS_TOKEN_RE.test(token)) continue;
2163
2385
  ctx.addIssue({
2164
- code: z4.ZodIssueCode.custom,
2386
+ code: z5.ZodIssueCode.custom,
2165
2387
  path: [field],
2166
2388
  message: `not an APNs device token (want 64 hex chars, got ${token.length})`
2167
2389
  });
2168
2390
  }
2169
2391
  });
2170
- var MissedCallSchema = z4.enum([
2392
+ var MissedCallSchema = z5.enum([
2171
2393
  "retry_10m",
2172
2394
  "retry_30m",
2173
2395
  "retry_60m",
@@ -2179,31 +2401,31 @@ var MissedCallSchema = z4.enum([
2179
2401
  ]);
2180
2402
  var clock = (h) => h === 0 ? "midnight" : h === 12 ? "noon" : h < 12 ? `${h} am` : `${h - 12} pm`;
2181
2403
  var QUIET = ` Nothing rings from ${clock(NIGHT.from)} to ${clock(NIGHT.to)} your time; the count waits for morning.`;
2182
- var BrokerTuningSchema = z4.object({
2404
+ var BrokerTuningSchema = z5.object({
2183
2405
  /** 'none' = skip the spoken ack after a mapped answer (power users find it slow). */
2184
- ackVerbosity: z4.enum(["normal", "none"]).optional(),
2406
+ ackVerbosity: z5.enum(["normal", "none"]).optional(),
2185
2407
  /** How readily the mapper asks its one clarification: 'low' = only when truly
2186
2408
  * uninterpretable, 'high' = whenever not fully certain. */
2187
- clarifyEagerness: z4.enum(["low", "normal", "high"]).optional(),
2409
+ clarifyEagerness: z5.enum(["low", "normal", "high"]).optional(),
2188
2410
  /** The user's own shorthand: when they say `say`, they mean `mean`. */
2189
- phrasebook: z4.array(z4.object({ say: z4.string().min(1).max(60), mean: z4.string().min(1).max(120) })).max(24).optional(),
2411
+ phrasebook: z5.array(z5.object({ say: z5.string().min(1).max(60), mean: z5.string().min(1).max(120) })).max(24).optional(),
2190
2412
  /** The language calls are PLANNED in, when the account has chosen one (#1272). Absent —
2191
2413
  * which is every account today — means the agent's own words decide, per ask: a call
2192
2414
  * about an English ask opens in English. This is the only thing that overrides that,
2193
2415
  * and a live caller who switches language mid-call still outranks it (broker/lang.ts).
2194
2416
  * Set per user (no UI yet), like `voiceTuning`. */
2195
- language: z4.enum(["en", "es"]).optional()
2417
+ language: z5.enum(["en", "es"]).optional()
2196
2418
  });
2197
- var UserSettingsSchema = z4.object({
2198
- permissions: z4.object({
2199
- call: z4.boolean(),
2200
- banner: z4.boolean(),
2201
- push: z4.boolean()
2419
+ var UserSettingsSchema = z5.object({
2420
+ permissions: z5.object({
2421
+ call: z5.boolean(),
2422
+ banner: z5.boolean(),
2423
+ push: z5.boolean()
2202
2424
  }),
2203
2425
  /** LockedIn / Default / DateNight on screen; the stored words are unchanged on purpose —
2204
2426
  * they are an enum on a live column across every account, and the rename is a rename of
2205
2427
  * what people read (owner, 2026-09-30). */
2206
- sessionMode: z4.enum(["default", "all_calls", "silent"]),
2428
+ sessionMode: z5.enum(["default", "all_calls", "silent"]),
2207
2429
  /** `silentPush` lived here until #2813 and is now GONE, field and column both. It was kept as an
2208
2430
  * optional long after DateNight stopped reading it, on the theory that a phone on an older
2209
2431
  * bundle PATCHing the whole settings object would be REFUSED for sending a key we had stopped
@@ -2212,7 +2434,7 @@ var UserSettingsSchema = z4.object({
2212
2434
  * an old bundle's `silentPush` is accepted and ignored. Worth remembering before keeping the
2213
2435
  * next dead field for the same reason. */
2214
2436
  /** Opt-in (default false) to using your content to improve Paigy and train models. */
2215
- improveConsent: z4.boolean(),
2437
+ improveConsent: z5.boolean(),
2216
2438
  missedCall: MissedCallSchema.default("backoff_standard"),
2217
2439
  /** Where voice audio is processed. 'hosted' (default) = Paigy's voice services
2218
2440
  * (ElevenLabs TTS, faster-whisper STT, the call bot); 'on_device' = the phone
@@ -2220,17 +2442,17 @@ var UserSettingsSchema = z4.object({
2220
2442
  * Optional, NOT defaulted: a stale client PATCHing the full settings object
2221
2443
  * must not silently reset this privacy choice. Absent = leave unchanged on
2222
2444
  * write, 'hosted' on read (see store.ts). */
2223
- voiceMode: z4.enum(["hosted", "on_device"]).optional(),
2445
+ voiceMode: z5.enum(["hosted", "on_device"]).optional(),
2224
2446
  /** Talk — after you answer, the next step is read aloud (docs/clients/app/walk/design.md §6). ALWAYS ON until
2225
2447
  * turned off (owner, 2026-09-18, #2249): a setting, not a per-walk toggle. Optional, NOT
2226
2448
  * defaulted, for the same reason `voiceMode` is: a stale client PATCHing the full settings
2227
2449
  * object must not silently turn it back on. Absent = leave unchanged on write, true on
2228
2450
  * read (see store.ts). */
2229
- talk: z4.boolean().optional(),
2451
+ talk: z5.boolean().optional(),
2230
2452
  /** CALL DIAGNOSTICS (owner, 2026-10-01): the call report carries each listen and the bot's own
2231
2453
  * load timings. SERVER-SET, no UI — on for every account that existed on 2026-10-01, off for
2232
2454
  * newer ones (migration 20261001132859). Read-only here: the settings PATCH never writes it. */
2233
- callDiagnostics: z4.boolean().optional(),
2455
+ callDiagnostics: z5.boolean().optional(),
2234
2456
  /** Per-user ring budget (#603): calls per rolling day before further calls
2235
2457
  * degrade to banner. Absent = the global default (25). A number, never a
2236
2458
  * bypass — every account keeps a ceiling. No UI; set per user for testing. */
@@ -2238,7 +2460,7 @@ var UserSettingsSchema = z4.object({
2238
2460
  * payload['tuning'] (e.g. { silence_s: 3.5 } — a longer pause window for a
2239
2461
  * slower speaker). No API-side semantics; the bot resolves each key with its
2240
2462
  * own defaults. Set per user (no UI yet); absent = bot defaults. */
2241
- voiceTuning: z4.record(z4.string(), z4.union([z4.number(), z4.string()])).optional(),
2463
+ voiceTuning: z5.record(z5.string(), z5.union([z5.number(), z5.string()])).optional(),
2242
2464
  /** The user's IANA timezone (e.g. "America/Bogota"), recorded by the app — it is the
2243
2465
  * only party that knows it. REMINDERS are why it exists: "remind me at ten" becomes
2244
2466
  * an absolute `due_at` only if we know whose ten. Optional and never defaulted, for
@@ -2247,53 +2469,53 @@ var UserSettingsSchema = z4.object({
2247
2469
  * that failure reads as the reminder rail being unreliable rather than as a missing
2248
2470
  * setting. Absent = a spoken time can't be landed, so the reminder rides the next
2249
2471
  * call — honest about what we know. */
2250
- timezone: z4.string().min(1).max(64).optional(),
2472
+ timezone: z5.string().min(1).max(64).optional(),
2251
2473
  /** Rung-2 broker tuning (#381). Optional and NOT defaulted, same stale-client
2252
2474
  * clobber guard as voiceMode: absent = leave unchanged on write. */
2253
2475
  broker: BrokerTuningSchema.optional()
2254
2476
  });
2255
- var HistoryWorkSchema = z4.object({
2256
- id: z4.string(),
2257
- title: z4.string(),
2258
- state: z4.enum(["done", "cancelled"]),
2477
+ var HistoryWorkSchema = z5.object({
2478
+ id: z5.string(),
2479
+ title: z5.string(),
2480
+ state: z5.enum(["done", "cancelled"]),
2259
2481
  /** Who held it (`agent:<tokenId>` or `human:<userId>`). */
2260
- assignee: z4.string()
2482
+ assignee: z5.string()
2261
2483
  });
2262
- var HistoryEntrySchema = z4.union([
2263
- z4.object({ at: z4.string(), card: InboxItemSchema }),
2264
- z4.object({ at: z4.string(), work: HistoryWorkSchema })
2484
+ var HistoryEntrySchema = z5.union([
2485
+ z5.object({ at: z5.string(), card: InboxItemSchema }),
2486
+ z5.object({ at: z5.string(), work: HistoryWorkSchema })
2265
2487
  ]);
2266
- var HistoryPageSchema = z4.object({
2267
- entries: z4.array(HistoryEntrySchema),
2268
- next: z4.string().nullable()
2488
+ var HistoryPageSchema = z5.object({
2489
+ entries: z5.array(HistoryEntrySchema),
2490
+ next: z5.string().nullable()
2269
2491
  });
2270
2492
  var ACTIVITY_LINES = 2;
2271
2493
  var ACTIVITY_LINE_MAX = 80;
2272
- var AgentActivitySchema = z4.object({
2494
+ var AgentActivitySchema = z5.object({
2273
2495
  /** Oldest first, so the newest line is last — the one that replaces in place. */
2274
- lines: z4.array(z4.string().max(ACTIVITY_LINE_MAX)).max(ACTIVITY_LINES),
2496
+ lines: z5.array(z5.string().max(ACTIVITY_LINE_MAX)).max(ACTIVITY_LINES),
2275
2497
  /** When the harness observed this tail. Its own timestamp, not the heartbeat's: a beat
2276
2498
  * that carries an UNCHANGED tail must not make a stalled agent look like it just moved. */
2277
- at: z4.string().datetime()
2499
+ at: z5.string().datetime()
2278
2500
  });
2279
- var ConnectionSummarySchema = z4.object({
2501
+ var ConnectionSummarySchema = z5.object({
2280
2502
  /** The connection = the agent's token id (used to address a request). */
2281
- id: z4.string(),
2503
+ id: z5.string(),
2282
2504
  /** The credential kind: "device" = a paired machine (mint-only — it hosts and mints, it
2283
2505
  * never talks); "agent" = an identity that sends. The roster and devices surfaces split
2284
2506
  * on this. Optional/absent reads as "agent" (a row predating the kind column). See
2285
2507
  * docs/server/tokens/devices-vs-agents-design.md. */
2286
- kind: z4.enum(["device", "agent"]).optional(),
2508
+ kind: z5.enum(["device", "agent"]).optional(),
2287
2509
  /** For an agent, the token id of the DEVICE that minted it — so agents group under their
2288
2510
  * machine, and revoking a device cascades to them. Null on devices, and on unlinked
2289
2511
  * agents (phone-launched, provider-managed, or minted before the link existed). */
2290
- mintedByDevice: z4.string().nullable().optional(),
2291
- device: z4.string().nullable(),
2512
+ mintedByDevice: z5.string().nullable().optional(),
2513
+ device: z5.string().nullable(),
2292
2514
  /** The agent's display name (the single pairing name). */
2293
- name: z4.string(),
2515
+ name: z5.string(),
2294
2516
  /** For a managed connection, the provider key (e.g. "cma") that agentOrigin maps to a
2295
2517
  * label; null for a local connection. Sourced from the token's provider, not the name. */
2296
- provider: z4.string().nullable(),
2518
+ provider: z5.string().nullable(),
2297
2519
  /** The pairing's assigned voice (#462); null = the default voice. */
2298
2520
  voice: VoiceKeySchema.nullable(),
2299
2521
  /** The LOUDEST this agent may ever reach you — a ceiling on `NOTIFY_LADDER`, set by the
@@ -2304,34 +2526,34 @@ var ConnectionSummarySchema = z4.object({
2304
2526
  * every surface at once and outranks even `sessionMode: all_calls` — a mode the user
2305
2527
  * set once must not overrule a rule they set about one agent. */
2306
2528
  reach: NotifyLevelSchema.nullable().optional(),
2307
- createdAt: z4.string().datetime(),
2529
+ createdAt: z5.string().datetime(),
2308
2530
  /** Most recent notification on this connection, either direction. Null = no contact yet.
2309
2531
  * Drives the agents-page recency grouping (Today / This week / …). */
2310
- lastContactAt: z4.string().datetime().nullable(),
2532
+ lastContactAt: z5.string().datetime().nullable(),
2311
2533
  /** Last presence heartbeat from a running agent process (POST /api/presence) — the
2312
2534
  * desktop app while open. Null = never seen; stale = offline. */
2313
- lastSeenAt: z4.string().datetime().nullable().optional(),
2535
+ lastSeenAt: z5.string().datetime().nullable().optional(),
2314
2536
  /** WORKING, NOT JUST CONNECTED (owner, 2026-09-30): the last time the agent itself acted on one of
2315
2537
  * its Goals — took its lease or recorded an operation (`tokens.last_worked_at`). Within
2316
2538
  * `WORKING_MS` it is working; otherwise it is connected but idle. Null = not seen working yet. */
2317
- lastWorkedAt: z4.string().datetime().nullable().optional(),
2539
+ lastWorkedAt: z5.string().datetime().nullable().optional(),
2318
2540
  /** The oldest of its Goals that is `ready` for it — work handed to it that nobody has started.
2319
2541
  * With no work of its own for `WORKING_MS`, an agent sitting on this is not taking its work. */
2320
- oldestReadyAt: z4.string().datetime().nullable().optional(),
2542
+ oldestReadyAt: z5.string().datetime().nullable().optional(),
2321
2543
  /** What a live desktop can run (docs/clients/desktop/companion.md §2.2), advertised on its heartbeat:
2322
2544
  * harness availabilities + granted workspaces — the option set the phone's
2323
2545
  * "new session" sheet offers. Absent for ordinary MCP agents. */
2324
- runtime: z4.object({
2546
+ runtime: z5.object({
2325
2547
  /** The @paigy/harness this host is running — a machine the self-update has not reached
2326
2548
  * shows its age here (`apps/desktop/src/update.ts`). */
2327
- version: z4.string().optional(),
2328
- harnesses: z4.array(z4.object({ name: z4.string(), label: z4.string(), status: z4.string() })).optional(),
2329
- workspaces: z4.array(z4.string()).optional(),
2549
+ version: z5.string().optional(),
2550
+ harnesses: z5.array(z5.object({ name: z5.string(), label: z5.string(), status: z5.string() })).optional(),
2551
+ workspaces: z5.array(z5.string()).optional(),
2330
2552
  /** THE GIT REPOS IN THOSE FOLDERS (2026-10-01, Goal 26982211): each granted folder that is a
2331
2553
  * repo, and each repo directly inside one, with its `origin` remote. A session started for
2332
2554
  * work on `mauurda/paigy` opens in that repo rather than the folder above it, where the repo's
2333
2555
  * own AGENTS.md is never read (`workspaceForRepo`). Absent on hosts that predate it. */
2334
- repos: z4.array(z4.object({ path: z4.string(), remote: z4.string() })).optional()
2556
+ repos: z5.array(z5.object({ path: z5.string(), remote: z5.string() })).optional()
2335
2557
  }).optional(),
2336
2558
  /** The tail of this agent's working log, when a harness is driving it — the agent page's
2337
2559
  * live strip. Absent for anything the desktop harness isn't running (a hatched identity
@@ -2340,173 +2562,156 @@ var ConnectionSummarySchema = z4.object({
2340
2562
  activity: AgentActivitySchema.optional(),
2341
2563
  /** True = a provider-managed agent running in the provider's cloud (e.g. Anthropic CMA);
2342
2564
  * false = a local MCP connection running on the user's computer (Claude Code/Codex/…). */
2343
- managed: z4.boolean()
2565
+ managed: z5.boolean()
2344
2566
  });
2345
- var LedgerItemSchema = z4.object({ id: z4.string(), parentId: z4.string(), title: z4.string(), createdAt: z4.string() });
2346
- var AgentLedgerSchema = z4.object({
2567
+ var LedgerItemSchema = z5.object({ id: z5.string(), parentId: z5.string(), title: z5.string(), createdAt: z5.string() });
2568
+ var AgentLedgerSchema = z5.object({
2347
2569
  /** Null when the agent has not named itself yet — never a placeholder (owner, 2026-10-01). */
2348
- agent: z4.object({ id: z4.string(), name: z4.string().nullable(), revokedAt: z4.string().nullable() }),
2570
+ agent: z5.object({ id: z5.string(), name: z5.string().nullable(), revokedAt: z5.string().nullable() }),
2349
2571
  /** Its own questions you have not answered. */
2350
- asks: z4.array(LedgerItemSchema),
2572
+ asks: z5.array(LedgerItemSchema),
2351
2573
  /** Its questions you answered that nobody acted on — still owed to somebody. */
2352
- answered: z4.array(LedgerItemSchema),
2574
+ answered: z5.array(LedgerItemSchema),
2353
2575
  /** Requests you sent it that it never took. */
2354
- requests: z4.array(LedgerItemSchema),
2355
- goals: z4.array(z4.object({ id: z4.string(), outcome: z4.string(), state: z4.string() })),
2356
- callbacks: z4.array(z4.object({ id: z4.string(), parentId: z4.string(), trigger: z4.string(), note: z4.string(), dueAt: z4.string().nullable() }))
2576
+ requests: z5.array(LedgerItemSchema),
2577
+ goals: z5.array(z5.object({ id: z5.string(), outcome: z5.string(), state: z5.string() })),
2578
+ callbacks: z5.array(z5.object({ id: z5.string(), parentId: z5.string(), trigger: z5.string(), note: z5.string(), dueAt: z5.string().nullable() }))
2357
2579
  });
2358
- var ReassignResultSchema = z4.object({
2359
- moved: z4.object({ asks: z4.number(), answered: z4.number(), requests: z4.number(), goals: z4.number(), callbacks: z4.number() }),
2360
- parentId: z4.string().nullable()
2580
+ var ReassignResultSchema = z5.object({
2581
+ moved: z5.object({ asks: z5.number(), answered: z5.number(), requests: z5.number(), goals: z5.number(), callbacks: z5.number() }),
2582
+ parentId: z5.string().nullable()
2361
2583
  });
2362
- var MoveRingSchema = z4.enum(["home", "travels", "retired", "quarantined"]);
2363
- var MoveSchema = z4.object({
2364
- id: z4.string(),
2365
- /** The reusable question, as distill normalized it. */
2366
- question: z4.string(),
2367
- /** The operative ruling. Editable by the user (PATCH) — which resets the ledger. */
2368
- answer: z4.string(),
2369
- /** The user's stated reason, when they gave one. Null = inherently narrow: the judge is
2370
- * told so, and the ruling only derives essentially the same question in the same scope. */
2371
- rationale: z4.string().nullable(),
2372
- /** Where the ruling lives: a repo/workspace, or 'global'. */
2373
- scope: z4.string(),
2374
- ring: MoveRingSchema,
2375
- /** True = the user pinned it with `always` (travel granted by hand, not by evidence). */
2376
- pinned: z4.boolean(),
2377
- /** True = a pin the user placed was BROKEN by later counter-evidence. Surfaced so the
2378
- * break is visible instead of a pin silently disappearing. */
2379
- pinBroken: z4.boolean(),
2380
- /** When the ruling was distilled. */
2381
- learnedAt: z4.string(),
2382
- /** Last time it answered an ask. Null = never fired. */
2383
- lastUsedAt: z4.string().nullable(),
2384
- /** How many asks it has answered. Instrumentation — deliberately NOT an input to the
2385
- * evidence curve: firing says the question keeps arising, not that the ruling is right. */
2386
- usedCount: z4.number(),
2387
- /** Ledger: outcomes that said it held up. Saturating — the tenth is worth almost nothing. */
2388
- confirms: z4.number(),
2389
- /** Ledger: contradictions, in signal units (a full override = 1, weaker signals less).
2390
- * Linear and priced above the entire confirmation budget, so any full counter wins. */
2391
- counters: z4.number(),
2392
- /** The agent that asked the question this move came from, when known. Null for a move
2393
- * distilled from a clarify ruling (those carry no agent) or one whose source rows are gone. */
2394
- learnedFrom: z4.object({ id: z4.string(), name: z4.string() }).nullable()
2584
+ var LessonStateSchema = z5.enum(["active", "proposed", "retired"]);
2585
+ var LessonViewSchema = z5.object({
2586
+ id: z5.string(),
2587
+ text: z5.string(),
2588
+ state: LessonStateSchema,
2589
+ /** The Goal it is scoped to; null = the whole account. */
2590
+ scopeGoalId: z5.string().nullable(),
2591
+ goalTitle: z5.string().nullable(),
2592
+ version: z5.number(),
2593
+ pinned: z5.boolean(),
2594
+ /** When the person last wrote its text themselves. */
2595
+ editedAt: z5.string().nullable(),
2596
+ createdAt: z5.string(),
2597
+ updatedAt: z5.string(),
2598
+ /** The Entries it came from, oldest first; `words` is null when an Entry has none to show (sealed). */
2599
+ sources: z5.array(z5.object({ entryId: z5.string(), words: z5.string().nullable(), at: z5.string() }))
2395
2600
  });
2396
- var QueueQuestionSchema = z4.object({
2601
+ var QueueQuestionSchema = z5.object({
2397
2602
  /** The decision need's id — what an answer is accepted against. */
2398
- id: z4.string(),
2603
+ id: z5.string(),
2399
2604
  /** The words that were asked, from the request Entry that asked them. */
2400
- question: z4.string(),
2605
+ question: z5.string(),
2401
2606
  /** Where it was asked — which is where the ruling goes (`POST /api/entries`). Null only
2402
2607
  * for a need whose request Entry is carried by no interactive Delivery, which nothing
2403
2608
  * can answer. */
2404
- deliveryId: z4.string().nullable().default(null),
2609
+ deliveryId: z5.string().nullable().default(null),
2405
2610
  /** The Entry the ruling is about. */
2406
- aboutId: z4.string().nullable().default(null),
2611
+ aboutId: z5.string().nullable().default(null),
2407
2612
  /** Empty for a free-text question. */
2408
- options: z4.array(OptionSchema).default([]),
2409
- select: z4.enum(["one", "many", "rank", "confirm", "text"]).default("text"),
2410
- askedAt: z4.string(),
2613
+ options: z5.array(OptionSchema).default([]),
2614
+ select: z5.enum(["one", "many", "rank", "confirm", "text"]).default("text"),
2615
+ askedAt: z5.string(),
2411
2616
  /** Null while the question is open — which is how the page tells the two apart. */
2412
- answeredAt: z4.string().nullable().default(null),
2617
+ answeredAt: z5.string().nullable().default(null),
2413
2618
  /** The ruling in the person's own words, from the contribution that replied — not the
2414
2619
  * option id, which is not something anyone reads back. Null while it is open, and null
2415
2620
  * for a settled question whose reply carried nothing readable. */
2416
- answer: z4.string().nullable().default(null),
2621
+ answer: z5.string().nullable().default(null),
2417
2622
  /** The Goal this question belongs to — a step knows its Goal on its own, not only through
2418
2623
  * an `InboxItem`'s `communication.goalIds[0]` (docs/clients/app/walk/design.md §12 item 3).
2419
2624
  * READ BY `apps/client/src/walk/order.ts`, which stamps it onto every `WalkStep`: the walk's
2420
2625
  * order, its route, home's trees and the list of steps all take a step's Goal from here, so
2421
2626
  * this is the field they agree through rather than each re-deriving it from the row it
2422
2627
  * arrived under. Required because the API projects it on every need it sends. */
2423
- goalId: z4.string(),
2628
+ goalId: z5.string(),
2424
2629
  /** True only while an unmet START gate holds the Goal — a Goal that merely waits to
2425
2630
  * *finish* does not stop a person from answering (owner, 2026-09-16: "per need gate from
2426
2631
  * the API"; §4's dashed node). Not the same fact as `QueueItem.blocked`, which counts any
2427
2632
  * gate at all. */
2428
- blocked: z4.boolean().default(false)
2633
+ blocked: z5.boolean().default(false)
2429
2634
  });
2430
- var QueueReplySchema = z4.object({
2635
+ var QueueReplySchema = z5.object({
2431
2636
  /** The card this note was (`deliveryId:requestEntryId`, minted by the server like every card
2432
2637
  * id) — so the phone can tell a reply it just sent from one the queue already carries, and the
2433
2638
  * walk can name it in its zoom. */
2434
- id: z4.string(),
2639
+ id: z5.string(),
2435
2640
  /** The Goal the note is on. */
2436
- goalId: z4.string(),
2641
+ goalId: z5.string(),
2437
2642
  /** What the note said. */
2438
- note: z4.string(),
2643
+ note: z5.string(),
2439
2644
  /** Where it was carried — where a second reply goes (`POST /api/entries`, #2252). */
2440
- deliveryId: z4.string(),
2441
- requestEntryId: z4.string(),
2442
- askedAt: z4.string(),
2645
+ deliveryId: z5.string(),
2646
+ requestEntryId: z5.string(),
2647
+ askedAt: z5.string(),
2443
2648
  /** When the person last replied — the window's start. */
2444
- repliedAt: z4.string(),
2649
+ repliedAt: z5.string(),
2445
2650
  /** The person's latest words about it; null when there is nothing readable in them. */
2446
- reply: z4.string().nullable()
2651
+ reply: z5.string().nullable()
2447
2652
  });
2448
- var QueueItemSchema = z4.object({
2449
- id: z4.string(),
2653
+ var QueueItemSchema = z5.object({
2654
+ id: z5.string(),
2450
2655
  /** One-line headline — the first sentence of the outcome. */
2451
- title: z4.string(),
2656
+ title: z5.string(),
2452
2657
  /** The outcome in full, verbatim: the person's own words are what an assignee sees. */
2453
- intent: z4.string(),
2658
+ intent: z5.string(),
2454
2659
  /** `ready` | `active` | `waiting` | `done` | `cancelled`, straight off the Goal. */
2455
- state: z4.string(),
2660
+ state: z5.string(),
2456
2661
  /** Who holds it (a participant ref); null when nobody does yet. */
2457
- assignee: z4.string().nullable().default(null),
2662
+ assignee: z5.string().nullable().default(null),
2458
2663
  /** What the agent last said it was doing; null if it has said nothing. */
2459
- progress: z4.string().nullable().default(null),
2664
+ progress: z5.string().nullable().default(null),
2460
2665
  /** HOME'S LINE FOR THAT NOTE (owner, 2026-09-23): a few plain words one read wrote from `progress`,
2461
2666
  * served only while it was written for the current note. Null means show the Goal's name. */
2462
- progressLine: z4.string().nullable().optional(),
2463
- reviewPending: z4.boolean().default(false),
2464
- dueAt: z4.string().nullable().default(null),
2667
+ progressLine: z5.string().nullable().optional(),
2668
+ reviewPending: z5.boolean().default(false),
2669
+ dueAt: z5.string().nullable().default(null),
2465
2670
  /** WHEN ITS OWNER SAID DONE WHILE CHILDREN WERE OPEN (#2704): its own work is finished and it closes
2466
2671
  * with its last open child. Null otherwise; optional, so hand-built queues need not spell it. */
2467
- finishedAt: z4.string().nullable().optional(),
2672
+ finishedAt: z5.string().nullable().optional(),
2468
2673
  /** The Goal this one was opened under; null at the root. */
2469
- parentGoalId: z4.string().nullable().default(null),
2674
+ parentGoalId: z5.string().nullable().default(null),
2470
2675
  /** Goals opened under this one — only those the same list holds. */
2471
- childGoalIds: z4.array(z4.string()).default([]),
2676
+ childGoalIds: z5.array(z5.string()).default([]),
2472
2677
  /** Goals this one waits on (start or finish gates). */
2473
- dependencyGoalIds: z4.array(z4.string()).default([]),
2678
+ dependencyGoalIds: z5.array(z5.string()).default([]),
2474
2679
  /** True while any gate is on a Goal that is not done — the walk draws it dashed. */
2475
- blocked: z4.boolean().default(false),
2680
+ blocked: z5.boolean().default(false),
2476
2681
  /** Its questions: every OPEN one, and at most ten settled, newest settled first
2477
2682
  * (20260929133308) — the page decides which of them to show. NOT the whole set: `asked` and
2478
2683
  * `answered` are, and a settled one's words are a line (280 characters), its body read when the
2479
2684
  * question is opened. */
2480
- questions: z4.array(QueueQuestionSchema).default([]),
2685
+ questions: z5.array(QueueQuestionSchema).default([]),
2481
2686
  /** HOW MANY QUESTIONS THIS WORK HAS ASKED, and how many are answered — the Goal's own totals,
2482
2687
  * bounded at 100 server-side. A tally counted off `questions` is a wrong number that looks
2483
2688
  * right once the cap bites (`walk/trees.ts` `tallyOf`). Optional, and defaulted from the array
2484
2689
  * by the projection, so hand-built queues (fixtures, the demo) need not spell them. */
2485
- asked: z4.number().optional(),
2486
- answered: z4.number().optional(),
2690
+ asked: z5.number().optional(),
2691
+ answered: z5.number().optional(),
2487
2692
  /** Every note on it the person replied to (`QueueReplySchema`) — the page decides which to show.
2488
2693
  * Optional, not defaulted: absent is none, and every hand-built queue (fixtures, the demo) need
2489
2694
  * not spell an empty list. */
2490
- replies: z4.array(QueueReplySchema).optional(),
2695
+ replies: z5.array(QueueReplySchema).optional(),
2491
2696
  /** The repository or project identifier this Goal belongs to (#2280), null if untracked. */
2492
- repo: z4.string().nullable().optional(),
2493
- createdAt: z4.string(),
2494
- updatedAt: z4.string().nullable().default(null),
2697
+ repo: z5.string().nullable().optional(),
2698
+ createdAt: z5.string(),
2699
+ updatedAt: z5.string().nullable().default(null),
2495
2700
  /** When its owner last SAID something about it (`goals.last_progress_at`, written by every
2496
2701
  * `update_goal` that changes `progress`). `updatedAt` moves for reasons nobody chose — a
2497
2702
  * state recomputed, a review flag — so it cannot tell work in hand from work gone quiet. */
2498
- lastProgressAt: z4.string().nullable().optional(),
2703
+ lastProgressAt: z5.string().nullable().optional(),
2499
2704
  /** THE GOAL'S NEWEST WORD, FROM EITHER SIDE (owner, 2026-09-27): the newest Entry on it, of any
2500
2705
  * kind — what the person added ("Add to this"), their reply, the agent's ask or its progress
2501
2706
  * note. A progress note is an Entry, so this is already the newer of the two: the person's note
2502
2707
  * shows the moment it is written, and the agent's reply or next note replaces it by being newer.
2503
2708
  * `said` is bounded to 280 characters server-side (a line, not the conversation). Null when the
2504
2709
  * Goal carries no readable Entry; optional, so hand-built queues need not spell it. */
2505
- latest: z4.object({
2506
- from: z4.enum(["person", "agent"]),
2507
- said: z4.string(),
2508
- at: z4.string(),
2509
- entryId: z4.string()
2710
+ latest: z5.object({
2711
+ from: z5.enum(["person", "agent"]),
2712
+ said: z5.string(),
2713
+ at: z5.string(),
2714
+ entryId: z5.string()
2510
2715
  }).nullable().optional(),
2511
2716
  /** WHEN THIS PERSON LAST PUT A HAND ON IT THEMSELVES (owner, Paigy Goal 16d18f51, 2026-09-30):
2512
2717
  * the newest Entry on the Goal they wrote, of any kind — a line they added, a reply to a note, an
@@ -2519,36 +2724,51 @@ var QueueItemSchema = z4.object({
2519
2724
  * minute after the person speaks erases their instant from it, and the durable traces the client
2520
2725
  * can see (`replies`, `questions[].answeredAt`) miss a spontaneous note entirely — a `request`
2521
2726
  * Entry with no `about_id` is in neither. */
2522
- lastPersonAt: z4.string().nullable().optional()
2727
+ lastPersonAt: z5.string().nullable().optional(),
2728
+ /** WHAT THIS ROW IS, IN TWELVE CHARACTERS (#2928) — the hash of every other field on it, stamped
2729
+ * by the one projection that builds the row (`apps/api/src/goal/queue.ts`). It is how the
2730
+ * incremental read knows a row has not moved: the phone echoes back the revs it holds
2731
+ * (`POST /api/goals/changes`) and is sent only the rows whose rev differs.
2732
+ *
2733
+ * IT IS THE PAYLOAD'S OWN HASH, NEVER A STAMP ON THE WORK. Nothing here reasons about which
2734
+ * writes change which field — the comparison is over the bytes the phone is holding, so a fact
2735
+ * the row shows that no `updated_at` moves for (a lease lapsing, a dependency's state, a
2736
+ * sibling appearing in `childGoalIds`) cannot go unnoticed. Optional because a hand-built
2737
+ * queue (a fixture, the demo) spells none, and a row with no rev is simply always re-sent. */
2738
+ rev: z5.string().optional()
2739
+ });
2740
+ var QueueDeltaSchema = z5.object({
2741
+ ids: z5.array(z5.string()),
2742
+ items: z5.array(QueueItemSchema)
2523
2743
  });
2524
2744
  var COLD_AFTER_MS = 3 * 24 * 60 * 60 * 1e3;
2525
- var NoteSourceSchema = z4.enum(["app", "call"]);
2526
- var NoteStatusSchema = z4.enum(["open", "assigned", "in_progress", "done"]);
2527
- var NoteRepeatSchema = z4.enum(["once", "until_done"]);
2528
- var DecisionSchema = z4.object({
2529
- id: z4.string(),
2745
+ var NoteSourceSchema = z5.enum(["app", "call"]);
2746
+ var NoteStatusSchema = z5.enum(["open", "assigned", "in_progress", "done"]);
2747
+ var NoteRepeatSchema = z5.enum(["once", "until_done"]);
2748
+ var DecisionSchema = z5.object({
2749
+ id: z5.string(),
2530
2750
  /** The note this decision refines; null = recorded on a bare thread (the
2531
2751
  * extensibility seam — any conversation can accrue decisions). */
2532
- noteId: z4.string().nullable(),
2752
+ noteId: z5.string().nullable(),
2533
2753
  /** What was ambiguous — the broker's (or the user's own) question. */
2534
- question: z4.string(),
2754
+ question: z5.string(),
2535
2755
  /** The user's ruling; null while the question is open. */
2536
- answer: z4.string().nullable(),
2537
- decidedAt: z4.string().nullable(),
2538
- createdAt: z4.string()
2756
+ answer: z5.string().nullable(),
2757
+ decidedAt: z5.string().nullable(),
2758
+ createdAt: z5.string()
2539
2759
  });
2540
- var NoteSchema = z4.object({
2541
- id: z4.string(),
2760
+ var NoteSchema = z5.object({
2761
+ id: z5.string(),
2542
2762
  /** One-line headline (broker-titled; deterministic floor). */
2543
- title: z4.string(),
2763
+ title: z5.string(),
2544
2764
  /** The original intent, verbatim — assignees always see the user's own words. */
2545
- intent: z4.string(),
2765
+ intent: z5.string(),
2546
2766
  source: NoteSourceSchema,
2547
2767
  status: NoteStatusSchema,
2548
2768
  /** Who it was assigned to (a participant ref, 'agent:<tokenId>'); null = unassigned. */
2549
- assignee: z4.string().nullable(),
2769
+ assignee: z5.string().nullable(),
2550
2770
  /** The request thread minted at assignment; null until assigned. */
2551
- parentId: z4.string().nullable(),
2771
+ parentId: z5.string().nullable(),
2552
2772
  /** REMINDERS (docs/model/notes/reminders-design.md): the NOT-BEFORE this becomes eligible to ride a
2553
2773
  * call — never a deadline. It only ever comes from the user's own words, so when it
2554
2774
  * passes Paigy rings ONCE (#1293, owner 2026-08-26: a time said out loud is consent to
@@ -2557,149 +2777,154 @@ var NoteSchema = z4.object({
2557
2777
  // Defaulted, not required: a Note from an API deploy older than the reminders
2558
2778
  // migration has none of these, and the defaults ARE what it means — no not-before,
2559
2779
  // one ride, never ridden. Parsing must not fail across a rolling deploy.
2560
- dueAt: z4.string().nullable().default(null),
2780
+ dueAt: z5.string().nullable().default(null),
2561
2781
  repeat: NoteRepeatSchema.default("once"),
2562
2782
  /** How many calls have already carried it — the fatigue cap counts rides, not days. */
2563
- rides: z4.number().int().default(0),
2564
- lastRideAt: z4.string().nullable().default(null),
2565
- createdAt: z4.string()
2783
+ rides: z5.number().int().default(0),
2784
+ lastRideAt: z5.string().nullable().default(null),
2785
+ createdAt: z5.string()
2566
2786
  });
2567
- var TriageItemSchema = z4.object({
2568
- noteId: z4.string(),
2787
+ var TriageItemSchema = z5.object({
2788
+ noteId: z5.string(),
2569
2789
  /** The note's headline at run time. */
2570
- title: z4.string(),
2790
+ title: z5.string(),
2571
2791
  /** WHY, in one short human line, evidence first — this is read on a phone underneath
2572
2792
  * the note's title: "no movement in 34 days", "worked 3 notes in this repo this week".
2573
2793
  * Never a model's reasoning transcript, never an id. */
2574
- why: z4.string()
2794
+ why: z5.string()
2575
2795
  });
2576
- var TriageAssignmentSchema = z4.object({
2796
+ var TriageAssignmentSchema = z5.object({
2577
2797
  /** The agent's token id — what `dispatchNote` resolves and what a request is addressed to. */
2578
- agent: z4.string(),
2798
+ agent: z5.string(),
2579
2799
  /** Its display name at run time (the name on the hatchling's card). Denormalized for the
2580
2800
  * same reason as `title`: the card must render from the proposal alone. */
2581
- agentName: z4.string(),
2582
- notes: z4.array(TriageItemSchema)
2801
+ agentName: z5.string(),
2802
+ notes: z5.array(TriageItemSchema)
2583
2803
  });
2584
- var TriageStatusSchema = z4.enum(["open", "superseded", "dismissed"]);
2585
- var SubmitTriageSchema = z4.object({
2804
+ var TriageStatusSchema = z5.enum(["open", "superseded", "dismissed"]);
2805
+ var SubmitTriageSchema = z5.object({
2586
2806
  /** Which runtime judged: "ollama" (inference never left the machine) or a harness the
2587
2807
  * user already runs under their own credentials ("claude" / "codex" / "agy"). Recorded
2588
2808
  * so the phone can say where the content went — an unattributed privacy claim is worth
2589
2809
  * nothing, and #1106's promise is precisely "Paigy's servers never see this". */
2590
- provider: z4.string().min(1).max(60),
2810
+ provider: z5.string().min(1).max(60),
2591
2811
  /** The concrete model when the provider names one (an ollama tag); null otherwise. */
2592
- model: z4.string().max(200).nullable().optional(),
2812
+ model: z5.string().max(200).nullable().optional(),
2593
2813
  /** How many open notes the run actually looked at — the denominator on the phone
2594
2814
  * ("6 of 50"), and the honest answer to "did it read the whole queue?". */
2595
- reviewed: z4.number().int().min(0).max(1e4).default(0),
2596
- close: z4.array(TriageItemSchema).max(200).default([]),
2597
- stale: z4.array(TriageItemSchema).max(200).default([]),
2598
- assign: z4.array(TriageAssignmentSchema).max(50).default([])
2815
+ reviewed: z5.number().int().min(0).max(1e4).default(0),
2816
+ close: z5.array(TriageItemSchema).max(200).default([]),
2817
+ stale: z5.array(TriageItemSchema).max(200).default([]),
2818
+ assign: z5.array(TriageAssignmentSchema).max(50).default([])
2599
2819
  });
2600
2820
  var TriageProposalSchema = SubmitTriageSchema.extend({
2601
- id: z4.string(),
2602
- runAt: z4.string(),
2821
+ id: z5.string(),
2822
+ runAt: z5.string(),
2603
2823
  status: TriageStatusSchema,
2604
- model: z4.string().nullable().default(null)
2824
+ model: z5.string().nullable().default(null)
2605
2825
  });
2606
- var AcceptTriageSchema = z4.discriminatedUnion("group", [
2607
- z4.object({ group: z4.literal("close"), noteIds: z4.array(z4.string()).max(200).optional() }),
2608
- z4.object({ group: z4.literal("stale"), noteIds: z4.array(z4.string()).max(200).optional() }),
2609
- z4.object({
2610
- group: z4.literal("assign"),
2611
- agent: z4.string().min(1),
2612
- noteIds: z4.array(z4.string()).max(200).optional()
2826
+ var AcceptTriageSchema = z5.discriminatedUnion("group", [
2827
+ z5.object({ group: z5.literal("close"), noteIds: z5.array(z5.string()).max(200).optional() }),
2828
+ z5.object({ group: z5.literal("stale"), noteIds: z5.array(z5.string()).max(200).optional() }),
2829
+ z5.object({
2830
+ group: z5.literal("assign"),
2831
+ agent: z5.string().min(1),
2832
+ noteIds: z5.array(z5.string()).max(200).optional()
2613
2833
  })
2614
2834
  ]);
2615
- var AcceptTriageResultSchema = z4.object({
2616
- accepted: z4.array(z4.string()),
2617
- failed: z4.array(z4.object({ noteId: z4.string(), reason: z4.string() }))
2835
+ var AcceptTriageResultSchema = z5.object({
2836
+ accepted: z5.array(z5.string()),
2837
+ failed: z5.array(z5.object({ noteId: z5.string(), reason: z5.string() }))
2618
2838
  });
2619
- var DeliveryModeSchema = z4.enum(["poll", "self_hosted"]);
2839
+ var DeliveryModeSchema = z5.enum(["poll", "self_hosted"]);
2620
2840
  var WAKE_EVENT = "wake";
2621
2841
  var wakeChannel = (tokenId) => `wake:${tokenId}`;
2622
- var RegisterDeliverySchema = z4.object({ mode: DeliveryModeSchema });
2623
- var OAuthStartSchema = z4.object({
2624
- provider: z4.enum(["cma"]),
2625
- returnTo: z4.string().min(1)
2842
+ var RegisterDeliverySchema = z5.object({ mode: DeliveryModeSchema });
2843
+ var OAuthStartSchema = z5.object({
2844
+ provider: z5.enum(["cma"]),
2845
+ returnTo: z5.string().min(1)
2626
2846
  });
2627
- var DeliveryConfigSchema = z4.object({
2628
- tokenId: z4.string(),
2847
+ var DeliveryConfigSchema = z5.object({
2848
+ tokenId: z5.string(),
2629
2849
  mode: DeliveryModeSchema,
2630
2850
  /** null when the deployment has no anon key configured. `self_hosted` is then REFUSED
2631
2851
  * (503 `self_hosted_unavailable`) rather than registered, so a self_hosted config always
2632
2852
  * carries credentials; only a `poll` registration can come back with null here. */
2633
- realtime: z4.object({ url: z4.string(), anonKey: z4.string() }).nullable()
2853
+ realtime: z5.object({ url: z5.string(), anonKey: z5.string() }).nullable()
2634
2854
  });
2635
- var HostDecisionSchema = z4.object({
2855
+ var HostDecisionSchema = z5.object({
2636
2856
  /** The agent's token id: the row's `recipient`. */
2637
- agent: z4.string().uuid(),
2638
- decision: z4.enum(["stood_back", "took_over"]),
2857
+ agent: z5.string().uuid(),
2858
+ decision: z5.enum(["stood_back", "took_over"]),
2639
2859
  /** The work it was about: the Goal `claim_goal` would hand that agent next. */
2640
- goalId: z4.string().uuid().nullable().optional(),
2860
+ goalId: z5.string().uuid().nullable().optional(),
2641
2861
  /** When the server last heard from the agent, as the host read it: the presence it stood back for. */
2642
- seenAt: z4.string().datetime().nullable().optional(),
2643
- /** When that work last moved (`claimable.since` on `check_replies`), the fact the bound is judged on. */
2644
- since: z4.string().datetime().nullable().optional(),
2862
+ seenAt: z5.string().datetime().nullable().optional(),
2863
+ /** When that work last moved (`claimable.since` on an agent's `contact({})` read), the fact the bound is judged on. */
2864
+ since: z5.string().datetime().nullable().optional(),
2645
2865
  /** What the host said, in its log's own words: why it stood back, or what the take-over did. */
2646
- said: z4.string().max(300).optional()
2866
+ said: z5.string().max(300).optional()
2647
2867
  });
2648
- var WakeNudgeSchema = z4.object({
2649
- kind: z4.enum(["reply", "request", "callback"]),
2650
- notificationId: z4.string().optional(),
2651
- parentId: z4.string()
2868
+ var WakeNudgeSchema = z5.object({
2869
+ kind: z5.enum(["reply", "request", "callback"]),
2870
+ notificationId: z5.string().optional(),
2871
+ parentId: z5.string()
2652
2872
  });
2653
- var PairingStatusSchema = z4.enum(["pending", "approved", "denied", "expired"]);
2654
- var DeviceCodeSchema = z4.object({
2655
- device_code: z4.string(),
2656
- user_code: z4.string(),
2657
- verification_uri: z4.string().url(),
2658
- verification_uri_complete: z4.string().url(),
2659
- interval: z4.number(),
2660
- expires_in: z4.number()
2873
+ var PairingStatusSchema = z5.enum(["pending", "approved", "denied", "expired"]);
2874
+ var DeviceCodeSchema = z5.object({
2875
+ device_code: z5.string(),
2876
+ user_code: z5.string(),
2877
+ verification_uri: z5.string().url(),
2878
+ verification_uri_complete: z5.string().url(),
2879
+ interval: z5.number(),
2880
+ expires_in: z5.number()
2661
2881
  });
2662
- var DeviceInfoSchema = z4.object({
2663
- code: z4.string(),
2882
+ var DeviceInfoSchema = z5.object({
2883
+ code: z5.string(),
2664
2884
  /** The agent's suggested name (from /device/code) — shown on the approval screen,
2665
2885
  * pre-filling the name field the human can edit. */
2666
- name: z4.string(),
2886
+ name: z5.string(),
2667
2887
  /** @deprecated Legacy alias of `name` for the pre-#531 embedded bundle in App Store
2668
2888
  * build 35, whose DeviceFlow renders `info.agent.slice(0, 2)` — without this a FRESH
2669
2889
  * install crashes on the pairing screen on first launch, before the OTA lands
2670
2890
  * (seen live: PAIGY-5T, 2026-07-21). Remove once a newer binary is the floor. */
2671
- agent: z4.string().optional(),
2672
- device: z4.string().nullable(),
2891
+ agent: z5.string().optional(),
2892
+ device: z5.string().nullable(),
2673
2893
  status: PairingStatusSchema
2674
2894
  });
2675
- var DeviceTokenSchema = z4.object({
2676
- access_token: z4.string(),
2895
+ var DeviceTokenSchema = z5.object({
2896
+ access_token: z5.string(),
2677
2897
  /** The pairing's single name (user-typed at approval, the agent's suggestion, or
2678
2898
  * a default silly name). */
2679
- name: z4.string(),
2680
- device: z4.string().nullable(),
2899
+ name: z5.string(),
2900
+ device: z5.string().nullable(),
2681
2901
  /** The pairing's assigned voice, cached so the desktop can seed the SAME face the phone
2682
2902
  * draws — voice is the third ingredient of a hatchling's build (party/traits.ts). */
2683
- voice: z4.string().nullable().optional(),
2903
+ voice: z5.string().nullable().optional(),
2684
2904
  /** The token's server-side id — the face's COLOUR anchor, and the only seed ingredient
2685
2905
  * that survives a rename. Cached by the host's identity beat. */
2686
- token_id: z4.string().nullable().optional(),
2906
+ token_id: z5.string().nullable().optional(),
2687
2907
  /** WHERE this identity works — the folder a wake should land it in. Written by the host
2688
2908
  * at spawn and by `paigy-harness handoff` from a live terminal. Without it every wake
2689
2909
  * landed in the FIRST granted workspace and the agent rediscovered its own repo from
2690
2910
  * the thread each time (host.ts, live catch 2026-08-06 — prompt-papered until now). */
2691
- workspace: z4.string().nullable().optional(),
2911
+ workspace: z5.string().nullable().optional(),
2692
2912
  /** Local host recovery must preserve the launch's runtime and Paigy identity. */
2693
- harness: z4.enum(["claude", "codex", "agy"]).optional(),
2694
- session_id: z4.string().uuid().optional(),
2695
- uik_pub: z4.string().nullable().optional()
2913
+ harness: z5.enum(["claude", "codex", "agy"]).optional(),
2914
+ session_id: z5.string().uuid().optional(),
2915
+ /** A conversation the host must not resume: its context is full, so every turn fails
2916
+ * ("Prompt is too long"). Written when a run hits it (`run.ts` `onFull`); the host skips a slot
2917
+ * whose resumable session is this one, so its Goals reach the dead-agent handoff instead of a
2918
+ * copy that types the person's words into a turn that cannot run (Calls, 2026-10-06). */
2919
+ full_session: z5.string().optional(),
2920
+ uik_pub: z5.string().nullable().optional()
2696
2921
  });
2697
- var SupportRequestSchema = z4.object({
2698
- email: z4.string().email().max(320),
2699
- message: z4.string().trim().min(1).max(5e3),
2700
- name: z4.string().trim().max(120).optional()
2922
+ var SupportRequestSchema = z5.object({
2923
+ email: z5.string().email().max(320),
2924
+ message: z5.string().trim().min(1).max(5e3),
2925
+ name: z5.string().trim().max(120).optional()
2701
2926
  });
2702
- var NotificationFeedbackKindSchema = z4.enum([
2927
+ var NotificationFeedbackKindSchema = z5.enum([
2703
2928
  "break_down",
2704
2929
  // "This should be more than one ask — break it down."
2705
2930
  "regenerate_options",
@@ -2714,116 +2939,116 @@ var NotificationFeedbackKindSchema = z4.enum([
2714
2939
  // anything else — the note carries it.
2715
2940
  ]);
2716
2941
  var SlimOptionSchema = OptionSchema.omit({ html: true });
2717
- var QuestionRowSchema = z4.object({
2942
+ var QuestionRowSchema = z5.object({
2718
2943
  /** The card's id (`deliveryId:needId`, or `deliveryId:entryId` for an update), as the inbox mints it. */
2719
- id: z4.string(),
2720
- deliveryId: z4.string(),
2721
- entryId: z4.string(),
2944
+ id: z5.string(),
2945
+ deliveryId: z5.string(),
2946
+ entryId: z5.string(),
2722
2947
  /** The decision it waits on; null for an update, which asks nothing. */
2723
- needId: z4.string().nullable(),
2724
- goalIds: z4.array(z4.string()),
2948
+ needId: z5.string().nullable(),
2949
+ goalIds: z5.array(z5.string()),
2725
2950
  /** The name of the work it is about, when the read could word it. */
2726
- goalTitle: z4.string().optional(),
2727
- tokenId: z4.string().optional(),
2728
- name: z4.string(),
2729
- title: z4.string(),
2730
- body: z4.string(),
2731
- select: z4.enum(["one", "many", "rank", "confirm", "text"]),
2732
- options: z4.array(SlimOptionSchema),
2733
- hasPreview: z4.boolean(),
2734
- blocking: z4.boolean(),
2735
- askedAt: z4.string().datetime(),
2951
+ goalTitle: z5.string().optional(),
2952
+ tokenId: z5.string().optional(),
2953
+ name: z5.string(),
2954
+ title: z5.string(),
2955
+ body: z5.string(),
2956
+ select: z5.enum(["one", "many", "rank", "confirm", "text"]),
2957
+ options: z5.array(SlimOptionSchema),
2958
+ hasPreview: z5.boolean(),
2959
+ blocking: z5.boolean(),
2960
+ askedAt: z5.string().datetime(),
2736
2961
  ring: InboxItemSchema.shape.ring,
2737
- onCall: z4.literal(true).optional(),
2738
- sealed: z4.boolean()
2962
+ onCall: z5.literal(true).optional(),
2963
+ sealed: z5.boolean()
2739
2964
  });
2740
- var WorkStateSchema = z4.enum(["ready", "active", "waiting", "done", "cancelled"]);
2741
- var WorkRowSchema = z4.object({
2742
- id: z4.string(),
2743
- parentId: z4.string().nullable(),
2744
- title: z4.string(),
2965
+ var WorkStateSchema = z5.enum(["ready", "active", "waiting", "done", "cancelled"]);
2966
+ var WorkRowSchema = z5.object({
2967
+ id: z5.string(),
2968
+ parentId: z5.string().nullable(),
2969
+ title: z5.string(),
2745
2970
  /** Straight off the Goal. */
2746
2971
  state: WorkStateSchema,
2747
- owner: z4.string().nullable(),
2748
- revision: z4.number().int(),
2972
+ owner: z5.string().nullable(),
2973
+ revision: z5.number().int(),
2749
2974
  /** Open questions on it, counted to 100. */
2750
- waiting: z4.number().int(),
2975
+ waiting: z5.number().int(),
2751
2976
  /** Held by a gate on work that is not done. */
2752
- blocked: z4.boolean(),
2753
- lastProgressAt: z4.string().datetime().nullable(),
2977
+ blocked: z5.boolean(),
2978
+ lastProgressAt: z5.string().datetime().nullable(),
2754
2979
  /** The line written for its newest progress note, else that note's first words. */
2755
- line: z4.string().nullable(),
2980
+ line: z5.string().nullable(),
2756
2981
  /** Work directly under it, counted to 100; the list carries up to 12 of them. */
2757
- children: z4.number().int(),
2758
- createdAt: z4.string().datetime(),
2759
- updatedAt: z4.string().datetime(),
2982
+ children: z5.number().int(),
2983
+ createdAt: z5.string().datetime(),
2984
+ updatedAt: z5.string().datetime(),
2760
2985
  /** When anything at or under it last moved — the order the list is in. */
2761
- activeAt: z4.string().datetime(),
2986
+ activeAt: z5.string().datetime(),
2762
2987
  /** A sealed outcome has no title here; the work's page opens it. */
2763
- sealed: z4.boolean()
2988
+ sealed: z5.boolean()
2764
2989
  });
2765
2990
  var ComputerRowSchema = ConnectionSummarySchema.omit({ activity: true });
2766
2991
  var AgentRowSchema = ComputerRowSchema.extend({
2767
2992
  /** Open questions it is asking the person, over every open card; null when that read failed. */
2768
- asking: z4.number().int().nullable(),
2769
- oldestAskAt: z4.string().datetime().nullable(),
2993
+ asking: z5.number().int().nullable(),
2994
+ oldestAskAt: z5.string().datetime().nullable(),
2770
2995
  /** Up to three of the live Goals it holds, oldest first (the order it picks them up), and how
2771
2996
  * many in all among the account's 200 most recently active agent-held live Goals
2772
2997
  * (`agent_holds`); null when that read failed. */
2773
- holds: z4.array(z4.object({ id: z4.string(), title: z4.string() })).nullable(),
2774
- held: z4.number().int().nullable(),
2998
+ holds: z5.array(z5.object({ id: z5.string(), title: z5.string() })).nullable(),
2999
+ held: z5.number().int().nullable(),
2775
3000
  /** The earliest instant any Goal it holds went quiet, by the one rule (`coldSince`); null
2776
3001
  * while none has, or when that read failed. */
2777
- cold: z4.string().datetime().nullable(),
3002
+ cold: z5.string().datetime().nullable(),
2778
3003
  /** The newest line of its working log, and when the harness saw it. */
2779
- line: z4.string().nullable(),
2780
- lineAt: z4.string().datetime().nullable()
3004
+ line: z5.string().nullable(),
3005
+ lineAt: z5.string().datetime().nullable()
2781
3006
  });
2782
- var SnapshotSchema = z4.object({
3007
+ var SnapshotSchema = z5.object({
2783
3008
  /** The API's clock, taken before the first read: what a later delta will start from. */
2784
- at: z4.string().datetime(),
2785
- questions: z4.object({
3009
+ at: z5.string().datetime(),
3010
+ questions: z5.object({
2786
3011
  /** The newest 30 open cards, questions before updates. */
2787
- items: z4.array(QuestionRowSchema),
3012
+ items: z5.array(QuestionRowSchema),
2788
3013
  /** Every open question, and apart from them every update, and what was put off. */
2789
- total: z4.number().int(),
2790
- updates: z4.number().int(),
2791
- putOff: z4.number().int()
3014
+ total: z5.number().int(),
3015
+ updates: z5.number().int(),
3016
+ putOff: z5.number().int()
2792
3017
  }).nullable(),
2793
- agents: z4.object({
3018
+ agents: z5.object({
2794
3019
  /** Up to 60, most recently seen first. */
2795
- items: z4.array(AgentRowSchema),
2796
- more: z4.boolean()
3020
+ items: z5.array(AgentRowSchema),
3021
+ more: z5.boolean()
2797
3022
  }).nullable(),
2798
- work: z4.object({
3023
+ work: z5.object({
2799
3024
  /** The 60 most recently active roots, each followed by up to 12 children; 240 rows at most. */
2800
- items: z4.array(WorkRowSchema),
3025
+ items: z5.array(WorkRowSchema),
2801
3026
  /** How much work is behind each of the Work tab's four filters, each counted to 100, read with
2802
3027
  * the rows. `work_list` (20260928023533) owns the predicates: Live is `ready`, `active` or
2803
3028
  * `waiting`; Waiting on you is live work with an open question or an unmet gate; Not started
2804
3029
  * is `ready`; Done is `done` or `cancelled`. */
2805
- counts: z4.object({ live: z4.number().int(), waiting: z4.number().int(), notStarted: z4.number().int(), done: z4.number().int() })
3030
+ counts: z5.object({ live: z5.number().int(), waiting: z5.number().int(), notStarted: z5.number().int(), done: z5.number().int() })
2806
3031
  }).nullable(),
2807
- you: z4.object({
3032
+ you: z5.object({
2808
3033
  settings: UserSettingsSchema,
2809
- callable: z4.boolean(),
3034
+ callable: z5.boolean(),
2810
3035
  /** Up to 20 paired computers; null when the roster read failed. */
2811
- computers: z4.array(ComputerRowSchema).nullable()
3036
+ computers: z5.array(ComputerRowSchema).nullable()
2812
3037
  }).nullable()
2813
3038
  });
2814
- var CallRecapSchema = z4.object({
2815
- call: z4.object({
2816
- status: z4.string(),
2817
- startedAt: z4.string(),
2818
- durationMs: z4.number().nullable(),
2819
- agents: z4.array(z4.object({ id: z4.string(), name: z4.string().nullable() }))
3039
+ var CallRecapSchema = z5.object({
3040
+ call: z5.object({
3041
+ status: z5.string(),
3042
+ startedAt: z5.string(),
3043
+ durationMs: z5.number().nullable(),
3044
+ agents: z5.array(z5.object({ id: z5.string(), name: z5.string().nullable() }))
2820
3045
  }),
2821
- topics: z4.array(z4.object({
2822
- goalId: z4.string().uuid(),
2823
- title: z4.string(),
2824
- owner: z4.string(),
2825
- state: z4.string(),
2826
- questions: z4.array(z4.object({ id: z4.string().uuid(), state: z4.string(), title: z4.string() })),
3046
+ topics: z5.array(z5.object({
3047
+ goalId: z5.string().uuid(),
3048
+ title: z5.string(),
3049
+ owner: z5.string(),
3050
+ state: z5.string(),
3051
+ questions: z5.array(z5.object({ id: z5.string().uuid(), state: z5.string(), title: z5.string() })),
2827
3052
  /** `words` is always what they SAID, verbatim — the record, never replaced. `headline` is
2828
3053
  * their answer on one line when the call's read wrote one (owner, 2026-10-01: "render them
2829
3054
  * summarized like a pre-made option is"), so the row scans like a chosen option and their
@@ -2831,14 +3056,14 @@ var CallRecapSchema = z4.object({
2831
3056
  * anything that is not an answer. */
2832
3057
  /** `about` is the request the line answered (its question), null for words that answered none —
2833
3058
  * the key the screen groups on, so one question is one row however many times it was answered. */
2834
- lines: z4.array(z4.object({ entryId: z4.string().uuid(), words: z4.string(), headline: z4.string().optional(), about: z4.string().nullable().optional() }))
3059
+ lines: z5.array(z5.object({ entryId: z5.string().uuid(), words: z5.string(), headline: z5.string().optional(), about: z5.string().nullable().optional() }))
2835
3060
  })),
2836
- unfiled: z4.array(z4.object({ lineId: z4.string().uuid(), words: z4.string(), atMs: z4.number() })),
2837
- more: z4.object({ lines: z4.number(), entries: z4.number(), topics: z4.number() })
3061
+ unfiled: z5.array(z5.object({ lineId: z5.string().uuid(), words: z5.string(), atMs: z5.number() })),
3062
+ more: z5.object({ lines: z5.number(), entries: z5.number(), topics: z5.number() })
2838
3063
  });
2839
3064
  function sessionSlot(sessionId2) {
2840
- const id = sessionId2 ?? sessionId();
2841
- return `session:${id.slice(0, 8)}`;
3065
+ const id2 = sessionId2 ?? sessionId();
3066
+ return `session:${id2.slice(0, 8)}`;
2842
3067
  }
2843
3068
  function agentName() {
2844
3069
  return process.env.PAIGY_AGENT || sessionSlot();
@@ -2896,7 +3121,7 @@ function updateSlot(agent2, patch) {
2896
3121
  }
2897
3122
  function slotIdentity(agent2) {
2898
3123
  const t = readTokenFile()[agent2];
2899
- return { name: t?.name ?? null, voice: t?.voice ?? null, tokenId: t?.token_id ?? null, workspace: t?.workspace ?? null, harness: t?.harness ?? null, sessionId: t?.session_id ?? null };
3124
+ return { name: t?.name ?? null, voice: t?.voice ?? null, tokenId: t?.token_id ?? null, workspace: t?.workspace ?? null, harness: t?.harness ?? null, sessionId: t?.session_id ?? null, fullSession: t?.full_session ?? null };
2900
3125
  }
2901
3126
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
2902
3127
  function readToken(agent2 = agentName()) {
@@ -3002,28 +3227,28 @@ var authToken = (explicit) => explicit ?? tokenOverride ?? readToken();
3002
3227
  async function fail(what, res) {
3003
3228
  throw new ApiError(what, res.status, await res.text());
3004
3229
  }
3005
- async function createGoal(input, opts = {}) {
3230
+ async function manageGoals(input, opts = {}) {
3006
3231
  const token = authToken(opts.token) ?? "";
3007
- const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals`, {
3232
+ const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/manage`, {
3008
3233
  method: "POST",
3009
- headers: { "content-type": "application/json", authorization: `Bearer ${token}`, "x-paigy-model": "goal-entry-v1" },
3234
+ headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
3010
3235
  body: JSON.stringify(input)
3011
3236
  }));
3012
- if (!res.ok) await fail("create_goal", res);
3237
+ if (!res.ok) await fail("manage_goals", res);
3013
3238
  return await res.json();
3014
3239
  }
3015
3240
  async function claimGoal(goalId, opts = {}) {
3016
3241
  const token = authToken(opts.token) ?? "";
3017
- const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/claim`, { method: "POST", headers: { "content-type": "application/json", authorization: `Bearer ${token}`, "x-paigy-model": "goal-entry-v1" }, body: JSON.stringify(goalId ? { goalId } : {}) }));
3242
+ const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/claim`, { method: "POST", headers: { "content-type": "application/json", authorization: `Bearer ${token}` }, body: JSON.stringify(goalId ? { goalId } : {}) }));
3018
3243
  if (!res.ok) await fail("claim_goal", res);
3019
3244
  return await res.json();
3020
3245
  }
3021
- async function getGoal(goalId, opts = {}, read = {}) {
3246
+ async function getGoal(goalId, opts = {}, read3 = {}) {
3022
3247
  const token = authToken(opts.token) ?? "";
3023
- const query = [read.history ? "history=1" : "", read.diagnose ? "diagnose=1" : ""].filter(Boolean).join("&");
3248
+ const query = [read3.history ? "history=1" : "", read3.diagnose ? "diagnose=1" : ""].filter(Boolean).join("&");
3024
3249
  const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/${encodeURIComponent(goalId)}${query ? `?${query}` : ""}`, {
3025
3250
  method: "GET",
3026
- headers: { authorization: `Bearer ${token}`, "x-paigy-model": "goal-entry-v1" }
3251
+ headers: { authorization: `Bearer ${token}` }
3027
3252
  }));
3028
3253
  if (!res.ok) await fail("get_goal", res);
3029
3254
  return await res.json();
@@ -3031,7 +3256,7 @@ async function getGoal(goalId, opts = {}, read = {}) {
3031
3256
  async function updateGoal(goalId, input, opts = {}) {
3032
3257
  const token = authToken(opts.token) ?? "";
3033
3258
  const operationId = input.operationId ?? randomUUID2();
3034
- const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/${encodeURIComponent(goalId)}`, { method: "PATCH", headers: { "content-type": "application/json", authorization: `Bearer ${token}`, "x-paigy-model": "goal-entry-v1" }, body: JSON.stringify({ ...input, operationId }) }));
3259
+ const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/${encodeURIComponent(goalId)}`, { method: "PATCH", headers: { "content-type": "application/json", authorization: `Bearer ${token}` }, body: JSON.stringify({ ...input, operationId }) }));
3035
3260
  if (!res.ok) await fail("update_goal", res);
3036
3261
  return await res.json();
3037
3262
  }
@@ -3118,7 +3343,9 @@ async function recordDecision(decision, opts = {}) {
3118
3343
  async function heartbeat(runtime, opts = {}) {
3119
3344
  const body = {
3120
3345
  ...runtime !== void 0 ? { runtime } : {},
3121
- ...opts.activity !== void 0 ? { activity: opts.activity } : {}
3346
+ ...opts.activity !== void 0 ? { activity: opts.activity } : {},
3347
+ // A listener's beat (`paigy-listen`): stamps when it last swept, and is not presence.
3348
+ ...opts.listening ? { listening: true } : {}
3122
3349
  };
3123
3350
  const res = await ensureAuthed(await reach(`${BACKEND_URL}/api/presence`, {
3124
3351
  method: "POST",
@@ -3145,7 +3372,7 @@ async function listNotes(opts = {}) {
3145
3372
  }
3146
3373
  async function listConnections(opts = {}) {
3147
3374
  const res = await ensureAuthed(await reach(`${BACKEND_URL}/api/tokens`, {
3148
- headers: { authorization: `Bearer ${authToken(opts.token)}`, "x-paigy-model": "goal-entry-v1" }
3375
+ headers: { authorization: `Bearer ${authToken(opts.token)}` }
3149
3376
  }));
3150
3377
  if (!res.ok) throw new Error(`list_connections failed: ${res.status} ${await res.text()}`);
3151
3378
  return await res.json();
@@ -3183,9 +3410,18 @@ async function dismissTriage(proposalId, opts = {}) {
3183
3410
  if (!res.ok) throw new Error(`dismiss_triage failed: ${res.status} ${await res.text()}`);
3184
3411
  return await res.json();
3185
3412
  }
3413
+ async function searchRecords(input, opts = {}) {
3414
+ const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/records/search`, {
3415
+ method: "POST",
3416
+ headers: { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token) ?? ""}` },
3417
+ body: JSON.stringify(input)
3418
+ }));
3419
+ if (!res.ok) await fail("search", res);
3420
+ return await res.json();
3421
+ }
3186
3422
  async function whoIsWorking(opts = {}) {
3187
3423
  const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/agents/working`, {
3188
- headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}`, "x-paigy-model": "goal-entry-v1" }
3424
+ headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}` }
3189
3425
  }));
3190
3426
  if (!res.ok) await fail("who_is_working", res);
3191
3427
  return await res.json();
@@ -3216,15 +3452,18 @@ function currentRepo(cwd = process.cwd()) {
3216
3452
  }
3217
3453
  return asked;
3218
3454
  }
3219
- var NOT_SENT = "Recorded as progress, not sent: nobody was notified. If the person should see it (a result, something to try or check, a finding), contact again and say so; report progress with update_goal.";
3455
+ var NOT_SENT = "Recorded as the Goal's progress, not sent: nobody was notified. The person is told only a question, an answer to one they asked you, and what you say once the Goal is done. To close the work, mark the Goal done first, then send the one message saying what is done and anything they need to do or check.";
3456
+ var DEMOTED = "Delivered as a card, not a call: the person's settings do not allow a call right now (silent, quiet hours, or a mode). It has reached them; do not send it again, with or without a Goal.";
3220
3457
  async function contact(input, opts = {}) {
3221
3458
  const parsed = ContactSchema.parse(input);
3459
+ if (!("asks" in parsed) && !("deliveryId" in parsed)) throw new Error("contact with nothing to send receives: call receiveContact");
3222
3460
  opts.signal?.throwIfAborted();
3223
3461
  const send2 = opts.reach ?? reach;
3224
- const headers = { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token) ?? ""}`, "x-paigy-model": "goal-entry-v1" };
3462
+ const headers = { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token) ?? ""}` };
3225
3463
  let deliveryId;
3226
3464
  let deliveries = [];
3227
3465
  let recorded = [];
3466
+ let demoted;
3228
3467
  if ("deliveryId" in parsed) {
3229
3468
  deliveryId = parsed.deliveryId;
3230
3469
  } else {
@@ -3255,16 +3494,17 @@ async function contact(input, opts = {}) {
3255
3494
  if (!res.ok) await fail("batch contact", res);
3256
3495
  const receipt = await res.json();
3257
3496
  deliveries = receipt.deliveries ?? [];
3497
+ if (parsed.channel === "call" && receipt.channel && receipt.channel !== "call") demoted = DEMOTED;
3258
3498
  recorded = receipt.recorded ?? [];
3259
3499
  if (deliveries.length === 0 && recorded.length) return { sent: false, recorded, message: NOT_SENT };
3260
3500
  if (deliveries.length === 0) throw new Error("Goal contact returned no Delivery identities");
3261
3501
  deliveryId = deliveries[0]?.deliveryId ?? "";
3262
3502
  if (!deliveryId) throw new Error("Goal contact returned no valid Delivery identity");
3263
3503
  }
3264
- const all = deliveries.filter((d) => !!d.deliveryId && !!d.goalId).map(({ id, deliveryId: deliveryId2, goalId }) => ({ ...id ? { id } : {}, deliveryId: deliveryId2, goalId }));
3504
+ const all = deliveries.filter((d) => !!d.deliveryId && !!d.goalId).map(({ id: id2, deliveryId: deliveryId2, goalId }) => ({ ...id2 ? { id: id2 } : {}, deliveryId: deliveryId2, goalId }));
3265
3505
  const joinedCard = deliveries[0]?.joinedCard === true;
3266
3506
  const joinedCall = deliveries[0]?.joinedCall === true;
3267
- const read = async (signal2) => {
3507
+ const read3 = async (signal2) => {
3268
3508
  const res = await ensureAuthed(await send2(`${BACKEND_URL}/api/deliveries/${encodeURIComponent(deliveryId)}`, { headers, signal: signal2 }));
3269
3509
  if (!res.ok) await fail("read_delivery", res);
3270
3510
  const one = await res.json();
@@ -3273,18 +3513,19 @@ async function contact(input, opts = {}) {
3273
3513
  ...all.length > 1 ? { deliveries: all } : {},
3274
3514
  ...recorded.length ? { recorded, notSent: NOT_SENT } : {},
3275
3515
  ...joinedCard ? { joinedCard: true } : {},
3276
- ...joinedCall ? { joinedCall: true } : {}
3516
+ ...joinedCall ? { joinedCall: true } : {},
3517
+ ...demoted ? { demoted } : {}
3277
3518
  };
3278
3519
  };
3279
3520
  const settled = (d) => d.kind === "notification" || d.state === "closed" || d.answers.length > 0 || d.entries.some((e) => e.kind === "contribution");
3280
- if (opts.waits === false) return read(opts.signal);
3521
+ if (opts.waits === false) return read3(opts.signal);
3281
3522
  const window = AbortSignal.timeout(AWAIT_WINDOW_MS);
3282
3523
  const signal = opts.signal ? AbortSignal.any([opts.signal, window]) : window;
3283
3524
  let latest;
3284
3525
  try {
3285
3526
  while (true) {
3286
3527
  signal.throwIfAborted();
3287
- latest = await read(signal);
3528
+ latest = await read3(signal);
3288
3529
  if (settled(latest)) return latest;
3289
3530
  await sleep2(5e3, void 0, { signal });
3290
3531
  }
@@ -3294,17 +3535,73 @@ async function contact(input, opts = {}) {
3294
3535
  throw error;
3295
3536
  }
3296
3537
  }
3297
- async function checkReplies(opts = {}) {
3538
+ async function receive(opts = {}) {
3298
3539
  const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/deliveries`, {
3299
- headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}`, "x-paigy-model": "goal-entry-v1" }
3540
+ headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}` }
3300
3541
  }));
3301
- if (!res.ok) await fail("check_replies", res);
3542
+ if (!res.ok) await fail("receive", res);
3302
3543
  return await res.json();
3303
3544
  }
3545
+ async function acknowledge(eventIds, opts = {}) {
3546
+ const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/deliveries/ack`, {
3547
+ method: "POST",
3548
+ headers: { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token) ?? ""}` },
3549
+ body: JSON.stringify({ eventIds })
3550
+ }));
3551
+ if (!res.ok) await fail("acknowledge", res);
3552
+ return (await res.json()).results ?? [];
3553
+ }
3554
+ var EVENT_BATCH = 20;
3555
+ function pendingEvents(w) {
3556
+ const fromDelivery = (d) => ({
3557
+ eventId: d.deliveryId,
3558
+ kind: d.questions?.length ? "question" : "instruction",
3559
+ ...d.goalIds.length ? { goalIds: d.goalIds } : {},
3560
+ ...d.about ? { text: d.about } : {},
3561
+ ...d.questions?.length ? { questions: d.questions } : {}
3562
+ });
3563
+ const deliveries = (w.deliveries ?? []).map(fromDelivery);
3564
+ return [
3565
+ ...deliveries.filter((e) => e.kind === "question"),
3566
+ ...(w.review ?? []).map((r) => ({
3567
+ eventId: r.eventId ?? r.goalId,
3568
+ kind: "update",
3569
+ goalIds: [r.goalId],
3570
+ text: r.said ? `${r.title}: ${r.said}` : r.title,
3571
+ ...r.saidAt ? { at: r.saidAt } : {}
3572
+ })),
3573
+ ...deliveries.filter((e) => e.kind !== "question")
3574
+ ];
3575
+ }
3576
+ async function receiveContact(input, opts = {}) {
3577
+ opts.signal?.throwIfAborted();
3578
+ const acknowledged = input.ackEventIds?.length ? await acknowledge(input.ackEventIds, opts) : void 0;
3579
+ const shaped = (waiting, waitOutcome) => {
3580
+ const all = pendingEvents(waiting);
3581
+ return { waiting, events: all.slice(0, EVENT_BATCH), hasMore: all.length > EVENT_BATCH, waitOutcome, ...acknowledged ? { acknowledged } : {} };
3582
+ };
3583
+ const wait = (input.wait ?? !input.ackEventIds?.length) && opts.waits !== false;
3584
+ if (!wait) return shaped(await receive(opts), "not_waited");
3585
+ const window = AbortSignal.timeout(AWAIT_WINDOW_MS);
3586
+ const signal = opts.signal ? AbortSignal.any([opts.signal, window]) : window;
3587
+ let latest;
3588
+ try {
3589
+ while (true) {
3590
+ signal.throwIfAborted();
3591
+ latest = await receive({ ...opts });
3592
+ if (pendingEvents(latest).length) return shaped(latest, "available");
3593
+ await sleep2(5e3, void 0, { signal });
3594
+ }
3595
+ } catch (error) {
3596
+ opts.signal?.throwIfAborted();
3597
+ if (window.aborted && latest) return shaped(latest, "expired");
3598
+ throw error;
3599
+ }
3600
+ }
3304
3601
  function compact(o) {
3305
3602
  return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0 && v !== null && v !== "" && !(Array.isArray(v) && v.length === 0)));
3306
3603
  }
3307
- var who = (participant) => participant.startsWith("human:") ? "person" : isPaigy(participant) ? "paigy" : participant.startsWith("agent:") ? "agent" : participant;
3604
+ var who = (participant2) => participant2.startsWith("human:") ? "person" : isPaigy(participant2) ? "paigy" : participant2.startsWith("agent:") ? "agent" : participant2;
3308
3605
  var at = (iso) => {
3309
3606
  const t = Date.parse(iso);
3310
3607
  return Number.isFinite(t) ? new Date(t).toISOString().replace(/\.\d{3}Z$/, "Z") : iso;
@@ -3357,14 +3654,14 @@ function more(g) {
3357
3654
  earlier,
3358
3655
  g.subtree?.length ? "A Goal below, in full: get_goal({ goalId }) with its goalId above." : null,
3359
3656
  g.parentGoalId ? `The Goal above: get_goal({ goalId: "${g.parentGoalId}" }).` : null,
3360
- "Anything else waiting for you (other requests, answers, reviews): check_replies()."
3657
+ "Anything else addressed to you (requests, answers, questions you owe): contact({ wait: false })."
3361
3658
  ].filter((line) => !!line);
3362
3659
  }
3363
3660
  function goalView(g) {
3364
3661
  if (!g.goalId) return compact({ state: g.state, next: g.message });
3365
3662
  const lines2 = conversation(g);
3366
3663
  const owed = lines2.filter((l) => l.from === "person" && l.decision?.state === "open" && l.id);
3367
- const owes = owed.length ? `The person asked you ${owed.length === 1 ? "a question" : `${owed.length} questions`} (${owed.map((l) => `id ${l.id}: "${l.said.slice(0, 80)}"`).join("; ")}) \u2014 answer with contact({ asks: [{ ask: <your answer>, parentId: "${g.goalId}", answers: "<id>" }] }). ` : "";
3664
+ const owes = owed.length ? `The person asked you ${owed.length === 1 ? "a question" : `${owed.length} questions`} (${owed.map((l) => `id ${l.id}: "${l.said.slice(0, 80)}"`).join("; ")}) \u2014 answer with contact({ asks: [{ ask: <your answer>, answers: "<id>" }] }). ` : "";
3368
3665
  const newest = [...lines2].reverse().find((l) => l.from === "agent");
3369
3666
  return compact({
3370
3667
  goalId: g.goalId,
@@ -3411,93 +3708,106 @@ function deliveryView(d) {
3411
3708
  notSent: d.notSent,
3412
3709
  joinedCard: d.joinedCard,
3413
3710
  joinedCall: d.joinedCall,
3414
- next: d.joinedCall ? `${JOINED_CALL}${d.message ? ` ${d.message}` : ""}` : d.kind === "notification" ? d.joinedCard ? `Added to the open report card on its Goal; no new push was sent. ${answers}` : answers : d.state === "open" && d.decisionNeeds.some((n) => n.state === "open") ? `Decision pending. Call contact(${JSON.stringify({ deliveryId: d.deliveryId })}) to continue this same Call \u2014 each call is ONE bounded window, and never resend the ask. When a window comes back with nothing new said, they are not typing: stop rereading, leave the question open, and collect the answer with check_replies or claim_goal on your next wake. ${d.message ?? ""}`.trim() : d.message
3711
+ next: d.joinedCall ? `${JOINED_CALL}${d.message ? ` ${d.message}` : ""}` : d.kind === "notification" ? `${d.demoted ? `${d.demoted} ` : ""}${d.joinedCard ? `Added to the open report card on its Goal; no new push was sent. ${answers}` : answers}` : d.state === "open" && d.decisionNeeds.some((n) => n.state === "open") ? `Decision pending. Call contact(${JSON.stringify({ deliveryId: d.deliveryId })}) to continue this same Call \u2014 each call is ONE bounded window, and never resend the ask. When a window comes back with nothing new said, they are not typing: stop rereading, leave the question open, and collect the answer with contact({ wait: false }) or claim_goal on your next wake. ${d.message ?? ""}`.trim() : d.message
3415
3712
  });
3416
3713
  }
3417
- function repliesView(r) {
3418
- const deliveries = r.deliveries ?? [];
3419
- const assigned = r.assigned ?? [];
3420
- const review = r.review ?? [];
3421
- const stalled = r.stalled ?? [];
3422
- const others = r.stalledOthers ?? [];
3423
- return {
3424
- deliveries: deliveries.map((d) => compact({
3425
- deliveryId: d.deliveryId,
3426
- kind: d.kind,
3427
- goalIds: d.goalIds,
3428
- openDecisions: d.decisionNeeds.filter((n) => n.state === "open").length || void 0,
3429
- latest: "about" in d ? d.about : void 0
3430
- })),
3431
- // WORK ASSIGNED TO YOU (owner, 2026-09-24): your Goals nobody has started, however they
3432
- // became yours — a Goal handed to you is not a Delivery, and must not wait for a claim to be seen.
3433
- ...assigned.length ? { assigned } : {},
3434
- // WHAT claim_goal HANDS YOU NEXT (owner, 2026-09-27: "Wake on any work"): the same predicate that
3435
- // starts a sleeping agent, so "nothing is waiting" is never said while a claim would hand you work
3436
- // — a Goal you started in a process that died is on no other list.
3437
- ...r.claimable ? { claimable: r.claimable } : {},
3438
- // NEW ON YOUR GOALS (owner, 2026-09-23): a reply is an Entry on the Goal, not a Delivery, so the
3439
- // Goals flagged for your review are listed here until you acknowledge them.
3440
- ...review.length ? { review } : {},
3441
- // YOUR STALLED WORK (#2257): Goals you own with no progress for three days. Named here so an
3442
- // agent that reads its replies also sees what it left, and says what to do about each.
3443
- ...stalled.length ? { stalled } : {},
3444
- // OTHER AGENTS' STALLED WORK (owner, 2026-09-23): any agent may take it over and close it.
3445
- ...others.length ? { stalledOthers: others } : {},
3714
+ function receivedView(r) {
3715
+ const w = r.waiting;
3716
+ const assigned = w.assigned ?? [];
3717
+ const stalled = w.stalled ?? [];
3718
+ const others = w.stalledOthers ?? [];
3719
+ const owed = r.events.filter((e) => e.kind === "question");
3720
+ const refused = (r.acknowledged ?? []).filter((a) => a.refused);
3721
+ return compact({
3722
+ events: r.events.map((e) => compact({ ...e })),
3723
+ hasMore: r.hasMore || void 0,
3724
+ waitOutcome: r.waitOutcome,
3725
+ acknowledged: r.acknowledged?.filter((a) => a.acknowledged).map((a) => a.eventId),
3726
+ notAcknowledged: refused.length ? refused.map((a) => ({ eventId: a.eventId, refused: a.refused })) : void 0,
3727
+ // WORK ASSIGNED TO YOU (owner, 2026-09-24): your Goals nobody has started, however they became yours.
3728
+ assigned,
3729
+ // WHAT claim_goal HANDS YOU NEXT (owner, 2026-09-27: "Wake on any work"): the predicate that starts a
3730
+ // sleeping agent, so "nothing is waiting" is never said while a claim would hand you work.
3731
+ claimable: w.claimable,
3732
+ // YOUR STALLED WORK (#2257), and OTHER AGENTS' (owner, 2026-09-23): any agent may take it over.
3733
+ stalled,
3734
+ stalledOthers: others,
3446
3735
  next: [
3447
- deliveries.length || r.claimable || review.length || assigned.length ? "Act on a Goal with claim_goal; reread one Delivery in full with contact({deliveryId})." : "Nothing is waiting.",
3736
+ r.events.length ? `Handle each event, then acknowledge the ones you handled: contact({ ackEventIds: [${r.events.filter((e) => e.kind !== "question").map((e) => `"${e.eventId}"`).join(", ")}] }).` : w.claimable || assigned.length ? "No messages." : "Nothing is waiting.",
3737
+ ...owed.length ? [`You owe ${owed.length === 1 ? "an answer" : `${owed.length} answers`}: contact({ asks: [{ ask: <your answer>, answers: "<questionId>" }] }) for each question; a question is answered, never acknowledged.`] : [],
3738
+ ...r.hasMore ? ["More are waiting: acknowledge these, then receive again."] : [],
3739
+ ...r.waitOutcome === "expired" ? ["Nothing arrived in the window; receive again to keep waiting, without sending again."] : [],
3448
3740
  ...assigned.length ? [`${assigned.length} of your Goals are assigned to you and not started (assigned): claim_goal({goalId}) to start one.`] : [],
3449
- // THE PERSON SPOKE, AND THAT IS NOT "SOMETHING NEW" (2026-09-29). This line said only how
3450
- // many Goals were flagged, so four things the owner said on a call read as four chores. The
3451
- // rows carry their words now (`said`, `saidAt`, newest first, `delivery/read.ts`); the
3452
- // sentence names the person and quotes the freshest one, because a count is not a message.
3453
- ...review.length ? [spoke(review) ?? `${review.length} of your Goals have something new (review): read each with claim_goal or get_goal, then update_goal with reviewed: true.`] : [],
3454
- ...stalled.length ? [`${stalled.length} of your Goals have had no progress for 3 days: update_goal each with progress, finish it, or cancel it.`] : [],
3455
- ...others.length ? [`${others.length} of other agents' Goals have gone quiet for 3 days: claim_goal({goalId}) joins one without changing its owner, then finish or cancel it with update_goal.`] : [],
3741
+ ...w.claimable && !assigned.length ? [`claim_goal() hands you ${w.claimable.title ?? w.claimable.goalId} next.`] : [],
3742
+ ...stalled.length ? [`${stalled.length} of your Goals have had no progress for 3 days: update_goal each with progress, or finish or cancel it with manage_goals.`] : [],
3743
+ ...others.length ? [`${others.length} of other agents' Goals have gone quiet for 3 days: claim_goal({goalId}) joins one without changing its owner.`] : [],
3456
3744
  // No name of its own (#2525): the API's ask, verbatim.
3457
- ...r.unnamed ? [r.unnamed] : []
3745
+ ...w.unnamed ? [w.unnamed] : []
3458
3746
  ].join(" ")
3459
- };
3747
+ });
3748
+ }
3749
+ function managedView(m, changes) {
3750
+ const results = m.results ?? [];
3751
+ const failed = results.filter((r) => r.status !== "applied");
3752
+ return compact({
3753
+ ok: m.ok,
3754
+ results: results.map((r) => compact({ index: r.index, kind: changes[r.index]?.kind, status: r.status, goalId: r.goalId, error: r.error })),
3755
+ goals: m.goals,
3756
+ next: failed.length ? `${failed.length} of ${results.length} changes did not apply (see results[].error); the rest did. A goal_revision_conflict means the Goal changed since you read it: read it with get_goal and decide again.` : void 0
3757
+ });
3758
+ }
3759
+ var lastRead = /* @__PURE__ */ new Map();
3760
+ var readKey = (token, goalId) => `${token ?? ""}:${goalId}`;
3761
+ function remember(token, goalId, revision) {
3762
+ if (revision === void 0) return;
3763
+ if (lastRead.size >= 5e3) lastRead.clear();
3764
+ lastRead.set(readKey(token, goalId), revision);
3460
3765
  }
3461
- function spoke(review) {
3462
- const withWords = review.filter((r) => r.said?.trim());
3463
- if (!withWords.length) return null;
3464
- const newest = withWords[0];
3465
- const rest = review.length - 1;
3466
- return `The person has spoken on ${withWords.length === 1 ? "one of your Goals" : `${withWords.length} of your Goals`} \u2014 newest, on ${newest.title ?? newest.goalId}: "${newest.said}". Read it with claim_goal or get_goal, answer what it asks, then update_goal with reviewed: true.${rest > 0 ? ` (${rest} more flagged; see review.)` : ""}`;
3766
+ function read2(token, goal) {
3767
+ if (goal.goalId) remember(token, goal.goalId, goal.revision);
3768
+ return goal;
3467
3769
  }
3468
3770
  async function runTool(name, args, opts) {
3469
3771
  const { waits, signal, ...client } = opts;
3470
3772
  const input = args ?? {};
3471
3773
  switch (name) {
3472
3774
  case "contact": {
3473
- const sent = await contact(input, { ...client, signal, waits });
3775
+ const parsed = ContactSchema.parse(input);
3776
+ if (!("asks" in parsed) && !("deliveryId" in parsed)) return receivedView(await receiveContact(parsed, { ...client, signal, waits }));
3777
+ const sent = await contact(parsed, { ...client, signal, waits });
3474
3778
  return "sent" in sent ? sent : deliveryView(sent);
3475
3779
  }
3476
3780
  case "who_is_working":
3477
- CheckRepliesSchema.parse(input);
3781
+ WhoIsWorkingSchema.parse(input);
3478
3782
  return whoIsWorking(client);
3479
- case "check_replies":
3480
- CheckRepliesSchema.parse(input);
3481
- return repliesView(await checkReplies(client));
3482
- case "create_goal": {
3483
- const goal = CreateGoalToolSchema.parse(input);
3484
- return createGoal({ ...goal, idempotencyKey: goal.idempotencyKey ?? randomUUID4() }, client);
3783
+ case "manage_goals": {
3784
+ const { changes } = ManageGoalsToolSchema.parse(input);
3785
+ const versioned = changes.map((c) => {
3786
+ if (c.kind === "create" || c.kind === "dependency") return c;
3787
+ const revision = lastRead.get(readKey(client.token, c.goalId));
3788
+ return revision === void 0 ? c : { ...c, revision };
3789
+ });
3790
+ const managed = await manageGoals({ requestId: randomUUID4(), changes: versioned }, client);
3791
+ for (const g of managed.goals ?? []) remember(client.token, g.goalId, g.revision);
3792
+ return managedView(managed, changes);
3485
3793
  }
3486
3794
  case "claim_goal": {
3487
3795
  const { goalId } = ClaimGoalSchema.parse(input);
3488
- return goalView(await claimGoal(goalId, client));
3796
+ return goalView(read2(client.token, await claimGoal(goalId, client)));
3489
3797
  }
3490
3798
  case "get_goal": {
3491
3799
  const { goalId, history, diagnose } = GetGoalSchema.parse(input);
3492
- const goal = await getGoal(goalId, client, { history, diagnose });
3800
+ const goal = read2(client.token, await getGoal(goalId, client, { history, diagnose }));
3493
3801
  const view = goalView(goal);
3494
3802
  const diagnosis = goal.diagnosis;
3495
3803
  return diagnosis === void 0 ? view : { ...view, diagnosis };
3496
3804
  }
3497
3805
  case "update_goal": {
3498
3806
  const { goalId, ...body } = UpdateGoalToolSchema.parse(input);
3499
- return goalView(await updateGoal(goalId, body, client));
3807
+ return goalView(read2(client.token, await updateGoal(goalId, body, client)));
3500
3808
  }
3809
+ case "search":
3810
+ return searchRecords(SearchToolSchema.parse(input), client);
3501
3811
  default:
3502
3812
  throw new Error(`Unknown tool: ${name}`);
3503
3813
  }
@@ -3611,9 +3921,9 @@ function connect(line) {
3611
3921
  ws.addEventListener("error", again);
3612
3922
  }
3613
3923
  function subscribeRealtime(args) {
3614
- const key = `${args.url} ${args.anonKey}`;
3615
- let line = lines.get(key);
3616
- if (!line) lines.set(key, line = { url: args.url, anonKey: args.anonKey, open: false, attempt: 0, ref: 0, topics: /* @__PURE__ */ new Map() });
3924
+ const key2 = `${args.url} ${args.anonKey}`;
3925
+ let line = lines.get(key2);
3926
+ if (!line) lines.set(key2, line = { url: args.url, anonKey: args.anonKey, open: false, attempt: 0, ref: 0, topics: /* @__PURE__ */ new Map() });
3617
3927
  let t = line.topics.get(args.topic);
3618
3928
  if (!t) {
3619
3929
  line.topics.set(args.topic, t = { listeners: /* @__PURE__ */ new Set(), joined: false, attempt: 0 });
@@ -3635,7 +3945,7 @@ function subscribeRealtime(args) {
3635
3945
  if (line.open) frame(line, `realtime:${args.topic}`, "phx_leave", {});
3636
3946
  return;
3637
3947
  }
3638
- lines.delete(key);
3948
+ lines.delete(key2);
3639
3949
  stop(line);
3640
3950
  try {
3641
3951
  line.ws?.close();
@@ -3679,6 +3989,28 @@ function machineZone() {
3679
3989
  return null;
3680
3990
  }
3681
3991
  }
3992
+ function withCodexEnv(text) {
3993
+ const head = /^\[mcp_servers\.paigy\][ \t]*(?:#[^\n]*)?\r?\n/m.exec(text);
3994
+ if (!head) return null;
3995
+ const start = head.index + head[0].length;
3996
+ const next = /^\[/m.exec(text.slice(start));
3997
+ const end = next ? start + next.index : text.length;
3998
+ const block = text.slice(start, end);
3999
+ const assignment = /^[ \t]*env_vars[ \t]*=/m.exec(block);
4000
+ let updated;
4001
+ if (!assignment) updated = `env_vars = [${CODEX_ENV.map((v) => JSON.stringify(v)).join(", ")}]
4002
+ ${block}`;
4003
+ else {
4004
+ const array = /^[ \t]*env_vars[ \t]*=[ \t]*\[((?:#[^\n]*|[^\]"'#]|"[^"\n]*"|'[^'\n]*')*)\]/m.exec(block);
4005
+ if (!array) return null;
4006
+ const existing = [...array[1].matchAll(/"([^"\n]*)"|'([^'\n]*)'|#[^\n]*/g)].flatMap((m) => m[0].startsWith("#") ? [] : [m[1] ?? m[2]]);
4007
+ const missing = CODEX_ENV.filter((v) => !existing.includes(v));
4008
+ if (!missing.length) return text;
4009
+ const opening = array.index + array[0].indexOf("[") + 1;
4010
+ updated = block.slice(0, opening) + missing.map((v) => JSON.stringify(v)).join(", ") + (existing.length ? ", " : "") + block.slice(opening);
4011
+ }
4012
+ return text.slice(0, start) + updated + text.slice(end);
4013
+ }
3682
4014
 
3683
4015
  export {
3684
4016
  CODEX_ENV,
@@ -3688,6 +4020,7 @@ export {
3688
4020
  isNetworkError,
3689
4021
  reach,
3690
4022
  reachAs,
4023
+ setClient,
3691
4024
  AGENT_TOOLS,
3692
4025
  AGENT_TOOL_NAMES,
3693
4026
  NotifyRequestSchema,
@@ -3713,7 +4046,7 @@ export {
3713
4046
  ApiError,
3714
4047
  overrideToken,
3715
4048
  authToken,
3716
- createGoal,
4049
+ manageGoals,
3717
4050
  claimGoal,
3718
4051
  getGoal,
3719
4052
  updateGoal,
@@ -3734,12 +4067,17 @@ export {
3734
4067
  getTriage,
3735
4068
  acceptTriage,
3736
4069
  dismissTriage,
4070
+ searchRecords,
3737
4071
  whoIsWorking,
3738
4072
  repoFromRemote,
3739
4073
  currentRepo,
3740
4074
  contact,
3741
- checkReplies,
4075
+ receive,
4076
+ acknowledge,
4077
+ pendingEvents,
4078
+ receiveContact,
3742
4079
  runTool,
3743
4080
  subscribeWake,
3744
- parseDue
4081
+ parseDue,
4082
+ withCodexEnv
3745
4083
  };