@paigy/mcp 0.40.22 → 0.40.24

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,6 +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);
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"];
25
27
  var BACKEND_URL = process.env.PAIGY_BACKEND_URL ?? "https://paigy.ai";
26
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.`;
27
29
  var PROXY_ENV = ["HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy"];
@@ -38,8 +40,8 @@ var WORKSPACE_ID = (() => {
38
40
  return INSTANCE_ID;
39
41
  }
40
42
  })();
41
- var envSession = (key) => {
42
- const v = process.env[key];
43
+ var envSession = (key2) => {
44
+ const v = process.env[key2];
43
45
  return v && v.trim() ? v : void 0;
44
46
  };
45
47
  function parentOf(pid) {
@@ -79,15 +81,20 @@ function isNetworkError(e) {
79
81
  async function reach(url, init) {
80
82
  return send(url, init, sessionId());
81
83
  }
82
- function reachAs(session) {
83
- return (url, init) => send(url, init, session);
84
+ function reachAs(session, instance) {
85
+ return (url, init) => send(url, init, session, instance);
86
+ }
87
+ var CLIENT;
88
+ function setClient(name) {
89
+ CLIENT = name;
84
90
  }
85
- async function send(url, init, session) {
91
+ async function send(url, init, session, instance = envSession("PAIGY_INSTANCE_ID") ?? INSTANCE_ID) {
86
92
  try {
87
93
  const headers = {
88
94
  ...init?.headers,
89
- "x-paigy-instance": INSTANCE_ID,
90
- "x-paigy-session": session
95
+ "x-paigy-instance": instance,
96
+ "x-paigy-session": session,
97
+ ...CLIENT ? { "x-paigy-client": CLIENT } : {}
91
98
  };
92
99
  return await fetch(url, { ...init, headers, dispatcher: await proxy() });
93
100
  } catch (e) {
@@ -145,19 +152,19 @@ var getRefs = (options) => {
145
152
  ]))
146
153
  };
147
154
  };
148
- function addErrorMessage(res, key, errorMessage, refs) {
155
+ function addErrorMessage(res, key2, errorMessage, refs) {
149
156
  if (!refs?.errorMessages)
150
157
  return;
151
158
  if (errorMessage) {
152
159
  res.errorMessage = {
153
160
  ...res.errorMessage,
154
- [key]: errorMessage
161
+ [key2]: errorMessage
155
162
  };
156
163
  }
157
164
  }
158
- function setResponseValueAndErrors(res, key, value, errorMessage, refs) {
159
- res[key] = value;
160
- addErrorMessage(res, key, errorMessage, refs);
165
+ function setResponseValueAndErrors(res, key2, value, errorMessage, refs) {
166
+ res[key2] = value;
167
+ addErrorMessage(res, key2, errorMessage, refs);
161
168
  }
162
169
  var getRelativePath = (pathA, pathB) => {
163
170
  let i = 0;
@@ -719,11 +726,11 @@ function parseRecordDef(def, refs) {
719
726
  return {
720
727
  type: "object",
721
728
  required: def.keyType._def.values,
722
- properties: def.keyType._def.values.reduce((acc, key) => ({
729
+ properties: def.keyType._def.values.reduce((acc, key2) => ({
723
730
  ...acc,
724
- [key]: parseDef(def.valueType._def, {
731
+ [key2]: parseDef(def.valueType._def, {
725
732
  ...refs,
726
- currentPath: [...refs.currentPath, "properties", key]
733
+ currentPath: [...refs.currentPath, "properties", key2]
727
734
  }) ?? parseAnyDef(refs)
728
735
  }), {}),
729
736
  additionalProperties: refs.rejectedAdditionalProperties
@@ -786,10 +793,10 @@ function parseMapDef(def, refs) {
786
793
  }
787
794
  function parseNativeEnumDef(def) {
788
795
  const object = def.values;
789
- const actualKeys = Object.keys(def.values).filter((key) => {
790
- return typeof object[object[key]] !== "number";
796
+ const actualKeys = Object.keys(def.values).filter((key2) => {
797
+ return typeof object[object[key2]] !== "number";
791
798
  });
792
- const actualValues = actualKeys.map((key) => object[key]);
799
+ const actualValues = actualKeys.map((key2) => object[key2]);
793
800
  const parsedTypes = Array.from(new Set(actualValues.map((values) => typeof values)));
794
801
  return {
795
802
  type: parsedTypes.length === 1 ? parsedTypes[0] === "string" ? "string" : "number" : ["string", "number"],
@@ -1522,34 +1529,31 @@ function mcpInputSchema(s) {
1522
1529
  }
1523
1530
  var AskInputSchema = z2.object({
1524
1531
  id: z2.string().optional().describe("Optional idempotency key or client-side ID for this specific ask."),
1525
- 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."),
1526
1533
  repo: z2.string().optional().describe("Optional repository context."),
1527
1534
  ask: z2.string().trim().min(1).max(1e4).describe(
1528
- "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."
1529
1536
  ),
1530
1537
  options: z2.array(OptionInputSchema).min(1).max(6).optional(),
1531
- // A CHECKLIST WHEN THE CHOICES ARE INDEPENDENT (owner, 2026-10-02 01:44 UTC: "bring back the
1532
- // checklist mode of answering"). The answer shape was taken off this surface with the wire form
1533
- // (#575) on the promise that Paigy would derive it, and the Goal-scoped contact that replaced it
1534
- // only ever sends pick-one or words (`goal/intake.ts` `answerOf`): three independent fixes went out
1535
- // as "All three / 1 and 3 only / Just log it", which the person could not read without the text and
1536
- // could not answer as the ticks he wanted. "many" is the one shape an agent knows and the read does
1537
- // not: whether its own options exclude each other.
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.
1538
1542
  select: z2.enum(["one", "many"]).optional().describe(
1539
- 'How the options are answered: "one" (default) to pick one, "many" to tick any number of them. Use "many" when the options are independent things they may want several of \u2014 never combinations of them like "all three" or "1 and 3". Ignored without options.'
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.'
1540
1544
  ),
1541
1545
  answers: z2.string().trim().regex(/^[0-9a-fA-F-]{8,36}$/).optional().describe(
1542
- "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."
1543
1547
  )
1544
1548
  }).strict();
1545
1549
  var StartContactSchema = z2.object({
1546
- 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."),
1547
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."),
1548
1552
  channel: z2.enum(["notification", "call"]).default("notification")
1549
1553
  }).strict();
1550
1554
  var ContactSchema = z2.union([StartContactSchema, z2.object({ deliveryId: z2.string().uuid() }).strict()]);
1551
1555
  var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
1552
- var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'select', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. An ask that names a Goal (parentId) goes onto that Goal; one that names none is placed in the person's tree by Paigy. Notification returns immediately; collect durable answers with check_replies. On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. One question per 'ask'; several questions are several objects in 'asks'. A bundled ask is taken apart into its own cards by Paigy's one intake read, every one on the Goal the ask lands on; separate pieces of work are separate asks. An ask with no options that is not blocking is a report, and a report is an UPDATE, never a claim on them: it reaches them and is listed apart from what waits on them, so no answer is owed and none should be awaited. On a Goal whose report card is still open it is added to that card, with no new push; one that only says where your work stands while under way is recorded as the Goal's progress, not sent (the result says so). Send options (or waiting:'hard') when you actually need an answer. Each option's label must stand on its own \u2014 the person may see only the labels \u2014 so never a label that points into your text ('All three', 'Option 2', '1 and 3 only'); when the options are independent and they may want several, pass select:'many' and they get a checklist. If the person is already on a call, your contact joins that call automatically \u2014 whatever channel you asked for, with no ring \u2014 and the Delivery it returns IS that call: reread it with contact({deliveryId}) to see everything answered on it so far, and contact again while it is live to add information or a further question to the same call.";
1556
+ var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'select', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. An ask that names a Goal (parentId) goes onto that Goal; one that names none starts a new Goal of yours. Notification returns immediately; collect durable answers with check_replies. On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. One question per 'ask'; several questions are several objects in 'asks'. Each ask is sent exactly as written, with no reading in between: a bundled ask arrives as one card, so separate questions are separate asks. An ask with no options that is not blocking is a report, and a report is an UPDATE, never a claim on them: it reaches them and is listed apart from what waits on them, so no answer is owed and none should be awaited. On a Goal whose report card is still open it is added to that card, with no new push; Where your work stands while under way is progress: report it with update_goal, not contact. Send options (or waiting:'hard') when you actually need an answer. Each option's label must stand on its own \u2014 the person may see only the labels \u2014 so never a label that points into your text ('All three', 'Option 2', '1 and 3 only'). select:'many' makes the card a checklist, for independent options they may want several of; select:'one' (the default) a pick, for alternatives. The person can always answer in their own words, so never add an 'Other' option. If the person is already on a call, your contact joins that call automatically \u2014 whatever channel you asked for, with no ring \u2014 and the Delivery it returns IS that call: reread it with contact({deliveryId}) to see everything answered on it so far, and contact again while it is live to add information or a further question to the same call.";
1553
1557
  var CreateGoalSchema = z3.object({
1554
1558
  outcome: z3.string().trim().min(1).max(1e4),
1555
1559
  /** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
@@ -1572,7 +1576,7 @@ var CreateGoalSchema = z3.object({
1572
1576
  var CreateGoalToolSchema = CreateGoalSchema.extend({
1573
1577
  idempotencyKey: CreateGoalSchema.shape.idempotencyKey.optional().describe("Optional. One is minted per call; pass your own only so a retry lands on the same Goal.")
1574
1578
  }).strict();
1575
- var CREATE_GOAL_DESCRIPTION = 'Create a durable Goal for an outcome. Without parentGoalId it is placed against your open Goals: if one already IS this work, that Goal comes back (existing: true) and nothing new is created \u2014 continue it; if the work belongs under one, it is created there (parentGoalId in the receipt); otherwise it is a root. Pass parentGoalId yourself to put it under a specific Goal. Pass repo to anchor the work to a specific repository ("owner/repo" or repo name); delegated children inherit it. Pass title to name it in one to five words, as a person would refer to it out loud ("the night rings") \u2014 it heads every list and is spoken on a call; without one the brain writes it. Admission only: the owner must claim it before doing work, then update it as it advances. Returns an admission receipt with goalId, current state, revision, ownerParticipant, and the next step; no Goal content or execution lease.';
1579
+ var CREATE_GOAL_DESCRIPTION = 'Create a durable Goal for an outcome. Without parentGoalId it is placed against your open Goals: if one already IS this work, that Goal comes back (existing: true) and nothing new is created \u2014 continue it; if the work belongs under one, it is created there (parentGoalId in the receipt); otherwise it is a root. Pass parentGoalId yourself to put it under a specific Goal. Pass repo to anchor the work to a specific repository ("owner/repo" or repo name); delegated children inherit it. Pass title to name it in one to five words, as a person would refer to it out loud ("the night rings") \u2014 it heads every list and is spoken on a call; without one the brain writes it. Admission only: claim it before doing work, then update it as it advances. Returns an admission receipt with goalId, current state, revision, ownerParticipant, and the next step; no Goal content or execution lease.';
1576
1580
  var UpdateGoalSchema = z3.object({
1577
1581
  revision: z3.number().int().positive(),
1578
1582
  changes: z3.object({
@@ -1608,12 +1612,13 @@ var GetGoalSchema = z3.object({
1608
1612
  * between two of them named. Off by default; `docs/model/goal/diagnose-design.md`. */
1609
1613
  diagnose: z3.boolean().optional()
1610
1614
  }).strict();
1611
- var GET_GOAL_DESCRIPTION = "Read one Goal without claiming it: its outcome, state, revision, progress, blockers, the conversation on it (each question with its options and what was decided), `next`, the one step to take, and `more`, where to read further. A question in that conversation reads `open` (nothing yet), `answered` (a choice was made, and `answer` carries it), `replied` (they said something and the read settled the question on their words \u2014 NO option of yours was chosen, and the words are the reply line beside it, so read that before you act), or `closed`. The conversation carries everything the person said, every open question and your newest entry; your older entries are left out and counted \u2014 history: true reads every entry in full. Another account's Goals, and another agent's Goal that is not below one of yours, are not disclosed.\n\ndiagnose: true is for when a read has CONFUSED you \u2014 not for the working loop. It adds `diagnosis`, which asks every reader that already answers a question about your work and NAMES the reader behind every value, so you never guess which of two places to look: what state your work is really in (the stored `goals.state` beside the derived `goal_execution_state`, which can differ); whether anything is armed to ring and when (`ladder_candidates`, with the ladder's own last decision); whether a wake fired for you and what it did (durable `wake.*` events, beside when your process was last heard from); and what has reached you (`list_waiting` \u2014 review flags, unstarted work, open questions, the person's newest words). Read `diagnosis.disagreements` FIRST: each one is two readers giving different values for the same fact, with both values and both sources, and it never chooses between them \u2014 that is yours to do, from the Goal's own history. `diagnosis.looked` names every reader asked, including any that could not answer, so an empty answer is never confused with a broken one.";
1612
- var UPDATE_GOAL_DESCRIPTION = `Update an owned Goal at an exact revision. State, ownership, dependencies, children, progress, title, and review acknowledgement are explicit; stale revisions are rejected. A CONTACT ON THIS GOAL MOVES ITS REVISION: a question filed on a Goal is a change to it, so an update prepared before a contact and sent after it is refused as stale (409 goal_revision_conflict) \u2014 re-read the Goal, then write. title is the work's name in one to five words, as a person would refer to it out loud (it is spoken on a call and heads every list); null clears it. reviewed: true acknowledges new evidence and closes the Deliveries addressed to you on that Goal, never over an open decision. dueAt (an ISO instant, or null) makes the Goal wait until then; when it passes you are woken for it \u2014 use it for a promise to follow up later. withdraw: [questionId] takes back a question YOU asked on this Goal that is still open \u2014 because you acted on it yourself, it no longer matters, or you asked it wrongly (e.g. waiting: hard when nothing was blocked): it is cancelled, not answered, its card closes and it stops ringing; the id is the one the conversation shows. state: done means the work was accomplished, and it can be reopened: when the person says it is not done ("it's not working"), set state: active on that same Goal instead of starting a new one; its parent reopens with it. state: done while children are still open records your part as done: the Goal waits and closes by itself when its last open child closes. cancelled is final. Returns the Goal as get_goal reads it, at its new revision.`;
1613
- var CLAIM_GOAL_DESCRIPTION = "Claim the oldest runnable or review-pending Goal you own, or pass goalId to claim that Goal. Another agent's Goal that has gone quiet for 3 days (check_replies lists them as stalledOthers) is taken over when you claim it, and becomes yours to finish or cancel. Returns the Goal as get_goal reads it, and creates or renews the execution lease.";
1615
+ var GET_GOAL_DESCRIPTION = "Read one Goal without claiming it, including others (the ten most recent other contributors, with names, latest entry headlines and times): its outcome, state, revision, progress, blockers, the conversation on it (each question with its options and what was decided), `next`, the one step to take, and `more`, where to read further. A question in that conversation reads `open` (nothing yet), `answered` (a choice was made, and `answer` carries it), `replied` (they said something and the read settled the question on their words \u2014 NO option of yours was chosen, and the words are the reply line beside it, so read that before you act), or `closed`. The conversation carries everything the person said, every open question and your newest entry; your older entries are left out and counted \u2014 history: true reads every entry in full. Every Goal of your person is readable, whichever of their agents owns it; another account's Goals are not disclosed.\n\ndiagnose: true is for when a read has CONFUSED you \u2014 not for the working loop. It adds `diagnosis`, which asks every reader that already answers a question about your work and NAMES the reader behind every value, so you never guess which of two places to look: what state your work is really in (the stored `goals.state` beside the derived `goal_execution_state`, which can differ); whether anything is armed to ring and when (`ladder_candidates`, with the ladder's own last decision); whether a wake fired for you and what it did (durable `wake.*` events, beside when your process was last heard from); and what has reached you (`list_waiting` \u2014 review flags, unstarted work, open questions, the person's newest words). Read `diagnosis.disagreements` FIRST: each one is two readers giving different values for the same fact, with both values and both sources, and it never chooses between them \u2014 that is yours to do, from the Goal's own history. `diagnosis.looked` names every reader asked, including any that could not answer, so an empty answer is never confused with a broken one.";
1616
+ var UPDATE_GOAL_DESCRIPTION = `Update a Goal you are on at an exact revision. You are on a Goal you own, or one you have written on (an update with progress, or a contact on it); any of your person's agents may write on any of their Goals, and one that never touched a Goal is refused (409 goal_not_joined). State, ownership, dependencies, children, progress, title, and review acknowledgement are explicit; stale revisions are rejected. A CONTACT ON THIS GOAL MOVES ITS REVISION: a question filed on a Goal is a change to it, so an update prepared before a contact and sent after it is refused as stale (409 goal_revision_conflict) \u2014 re-read the Goal, then write. title is the work's name in one to five words, as a person would refer to it out loud (it is spoken on a call and heads every list); null clears it. reviewed: true acknowledges new evidence and closes the Deliveries addressed to you on that Goal, never over an open decision. dueAt (an ISO instant, or null) makes the Goal wait until then; when it passes you are woken for it \u2014 use it for a promise to follow up later. withdraw: [questionId] takes back a question YOU asked on this Goal that is still open \u2014 because you acted on it yourself, it no longer matters, or you asked it wrongly (e.g. waiting: hard when nothing was blocked): it is cancelled, not answered, its card closes and it stops ringing; the id is the one the conversation shows. state: done means the work was accomplished, and it can be reopened: when the person says it is not done ("it's not working"), set state: active on that same Goal instead of starting a new one; its parent reopens with it. state: done while children are still open records your part as done: the Goal waits and closes by itself when its last open child closes. cancelled is final. Returns the Goal as get_goal reads it, at its new revision.`;
1617
+ var CLAIM_GOAL_DESCRIPTION = "Claim a pending answer to your question or the oldest runnable or review-pending Goal you own. Pass goalId to join any Goal of your person; its assignment stays unchanged. Read others before overlapping another agent\u2019s work. Joining lets you contribute and change its state; use update_goal with ownerParticipant when the assignment itself should change. Returns the Goal as get_goal reads it, and marks you as on it, which never shuts another agent out: other agents of your person may write on it and change it too, and two changes at once are told apart by revision (409 goal_revision_conflict).";
1614
1618
  var CHECK_REPLIES_DESCRIPTION = "What is waiting for you: your open Deliveries (a request the user started toward you, a handoff), one row each with its Goals, how many decisions are still open, and the newest words in brief; `assigned`, your Goals nobody has started yet, however they became yours (claim_goal({goalId}) starts one); and `review`, your Goals with something new on them \u2014 the user's answers and notes are Entries on the Goal, not Deliveries. A pure read with no arguments: nothing is consumed, acknowledged or claimed by reading it, so call it on startup, after a long wait, or whenever you want to know what is outstanding. To act on one, claim its Goal (claim_goal) or reread a Delivery in full with contact({deliveryId}). Once you have acted on what arrived, update_goal with reviewed: true clears the Goal from `review` and closes the Deliveries addressed to you on it.";
1615
1619
  var CheckRepliesSchema = z3.object({}).strict();
1616
1620
  var AGENT_TOOLS = [
1621
+ { name: "who_is_working", description: "Read your person's agents that contributed in the last 24 hours, newest first: at most 30 agents and their five most recently touched Goals, with their latest entry headline and time. Use get_goal to read a Goal and its other contributors before starting overlapping work. Read-only; no arguments.", inputSchema: mcpInputSchema(CheckRepliesSchema) },
1617
1622
  { name: "contact", description: CONTACT_DESCRIPTION, inputSchema: CONTACT_SCHEMA },
1618
1623
  { name: "check_replies", description: CHECK_REPLIES_DESCRIPTION, inputSchema: mcpInputSchema(CheckRepliesSchema) },
1619
1624
  { name: "create_goal", description: CREATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(CreateGoalToolSchema) },
@@ -1622,6 +1627,153 @@ var AGENT_TOOLS = [
1622
1627
  { name: "update_goal", description: UPDATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(UpdateGoalToolSchema) }
1623
1628
  ];
1624
1629
  var AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
1630
+ var id = z4.string().uuid();
1631
+ var key = z4.string().min(1).max(40);
1632
+ var participant = z4.string().regex(/^(human|agent):.+$/, "a participant ID such as agent:<uuid>");
1633
+ var SourceSchema = z4.object({ entryId: id }).strict();
1634
+ var ResultRefSchema = z4.union([z4.object({ id }).strict(), z4.object({ local: key }).strict()]);
1635
+ var NodeRefSchema = z4.object({ type: z4.enum(["goal", "question"]), id }).strict();
1636
+ var DependencySchema = z4.object({
1637
+ blocker: NodeRefSchema,
1638
+ blocked: NodeRefSchema,
1639
+ action: z4.enum(["start", "complete", "answer"]),
1640
+ reason: z4.string().max(500).optional()
1641
+ }).strict();
1642
+ var goalState = z4.enum(["open", "completed", "canceled"]);
1643
+ var existingGoalChanges = [
1644
+ 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"),
1645
+ z4.object({ kind: z4.literal("state"), goalId: id, state: goalState }).strict(),
1646
+ z4.object({ kind: z4.literal("assign"), goalId: id, ownerId: participant }).strict(),
1647
+ z4.object({ kind: z4.literal("defer"), goalId: id, until: z4.string().datetime().nullable() }).strict(),
1648
+ z4.object({ kind: z4.literal("move"), goalId: id, parentGoalId: id.nullable() }).strict(),
1649
+ z4.object({
1650
+ kind: z4.literal("dependency"),
1651
+ change: z4.enum(["add", "remove"]),
1652
+ goalId: id,
1653
+ dependsOnGoalId: id,
1654
+ action: z4.enum(["start", "complete"]),
1655
+ reason: z4.string().max(500).optional()
1656
+ }).strict()
1657
+ ];
1658
+ var GoalChangeSchema = z4.union([
1659
+ z4.object({
1660
+ kind: z4.literal("create"),
1661
+ title: z4.string().min(1).max(120),
1662
+ outcome: z4.string().min(1),
1663
+ ownerId: participant,
1664
+ parentGoalId: id.optional(),
1665
+ sourceEntryIds: z4.array(id)
1666
+ }).strict(),
1667
+ ...existingGoalChanges
1668
+ ]);
1669
+ var BrainGoalChangeSchema = z4.union([
1670
+ // newSession: Paigy starts a session to own it (owner, 2026-10-06); until it has, the person owns it.
1671
+ z4.object({
1672
+ kind: z4.literal("create"),
1673
+ outcome: z4.string().min(1),
1674
+ title: z4.string().min(1).max(120),
1675
+ ownerId: participant,
1676
+ parentGoal: ResultRefSchema.nullable(),
1677
+ newSession: z4.literal(true).optional()
1678
+ }).strict(),
1679
+ ...existingGoalChanges
1680
+ ]);
1681
+ var option = z4.object({ id: z4.string().min(1).max(40), label: z4.string().min(1), hint: z4.string().optional() }).strict();
1682
+ var QuestionChangeSchema = z4.union([
1683
+ z4.object({
1684
+ kind: z4.literal("create"),
1685
+ text: z4.string().min(1),
1686
+ answererId: participant,
1687
+ goal: ResultRefSchema.optional(),
1688
+ blocks: z4.array(z4.object({ question: ResultRefSchema }).strict()),
1689
+ options: z4.array(option).min(2).optional(),
1690
+ pickMode: z4.enum(["one", "many", "rank"]).optional()
1691
+ }).strict().refine((c) => c.options === void 0 === (c.pickMode === void 0), "options and pickMode come together"),
1692
+ z4.object({ kind: z4.literal("edit"), questionId: id, text: z4.string().min(1) }).strict(),
1693
+ z4.object({ kind: z4.literal("assign"), questionId: id, answererId: participant }).strict(),
1694
+ z4.object({ kind: z4.literal("withdraw"), questionId: id }).strict(),
1695
+ z4.object({ kind: z4.literal("dependency"), change: z4.enum(["add", "remove"]), edge: DependencySchema }).strict()
1696
+ ]);
1697
+ var lessonScope = z4.union([
1698
+ z4.object({ kind: z4.literal("user") }).strict(),
1699
+ z4.object({ kind: z4.literal("goal"), goal: ResultRefSchema }).strict()
1700
+ ]);
1701
+ var LessonChangeSchema = z4.union([
1702
+ z4.object({ kind: z4.literal("remember"), text: z4.string().min(1), scope: lessonScope }).strict(),
1703
+ z4.object({ kind: z4.literal("revise"), lessonId: id, text: z4.string().min(1), scope: lessonScope }).strict(),
1704
+ z4.object({ kind: z4.literal("withdraw"), lessonId: id }).strict()
1705
+ ]);
1706
+ var BrainNextSchema = z4.object({
1707
+ instructions: z4.array(z4.object({ entryKeys: z4.array(key).min(1) }).strict()),
1708
+ say: z4.array(key),
1709
+ then: z4.enum(["listen", "hold", "end", "none"]),
1710
+ waitFor: z4.array(ResultRefSchema),
1711
+ reason: z4.string()
1712
+ }).strict();
1713
+ var messageBase = {
1714
+ key,
1715
+ text: z4.string().min(1),
1716
+ goals: z4.array(ResultRefSchema),
1717
+ questions: z4.array(ResultRefSchema),
1718
+ entryKeys: z4.array(key),
1719
+ obligationIds: z4.array(id)
1720
+ };
1721
+ var BrainMessageSchema = z4.union([
1722
+ z4.object({ ...messageBase, to: z4.object({ kind: z4.literal("user") }).strict() }).strict(),
1723
+ z4.object({
1724
+ ...messageBase,
1725
+ to: z4.object({ kind: z4.literal("agent"), agentId: z4.string().regex(/^agent:(?!paigy$)\S+$/) }).strict(),
1726
+ call: z4.object({ callId: id, listen: z4.literal(true) }).strict().optional()
1727
+ }).strict()
1728
+ ]);
1729
+ var BrainSearchSchema = z4.object({
1730
+ query: z4.string().min(1).max(500),
1731
+ within: z4.array(z4.enum(["entries", "goals", "answers"])).min(1),
1732
+ goalId: id.optional()
1733
+ }).strict();
1734
+ var BrainResultSchema = z4.object({
1735
+ messages: z4.array(BrainMessageSchema),
1736
+ entries: z4.array(z4.object({ key, sources: z4.array(SourceSchema).min(1), goals: z4.array(ResultRefSchema) }).strict()),
1737
+ questions: z4.array(z4.object({ key, entryKeys: z4.array(key), change: QuestionChangeSchema }).strict()),
1738
+ answers: z4.array(z4.object({
1739
+ question: ResultRefSchema,
1740
+ entryKeys: z4.array(key).min(1),
1741
+ summary: z4.string().min(1),
1742
+ selectedOptionIds: z4.array(z4.string().min(1)).optional()
1743
+ }).strict()),
1744
+ goals: z4.array(z4.object({ key, entryKeys: z4.array(key), change: BrainGoalChangeSchema }).strict()),
1745
+ feedback: z4.array(z4.object({ entryKeys: z4.array(key).min(1) }).strict()),
1746
+ lessons: z4.array(z4.object({ entryKeys: z4.array(key).min(1), change: LessonChangeSchema }).strict()),
1747
+ next: BrainNextSchema,
1748
+ search: BrainSearchSchema.optional()
1749
+ }).strict();
1750
+ var str = z4.string();
1751
+ var strs = z4.array(z4.string());
1752
+ var CompactResultSchema = z4.object({
1753
+ next: z4.object({ say: strs, then: z4.enum(["listen", "hold", "end", "none"]), waitFor: strs, reason: str, instructions: strs }).strict(),
1754
+ messages: z4.array(z4.object({ key: str, to: str, text: str, about: strs, entries: strs, owed: strs, inviteToCall: z4.boolean() }).strict()),
1755
+ entries: z4.array(z4.object({ key: str, from: str, on: strs }).strict()),
1756
+ answers: z4.array(z4.object({ question: str, entries: strs, summary: str, picked: strs }).strict()),
1757
+ changes: z4.array(z4.object({
1758
+ key: str,
1759
+ what: z4.enum(["goal", "question", "lesson"]),
1760
+ op: z4.enum(["create", "edit", "assign", "open", "complete", "cancel", "defer", "move", "block", "unblock", "withdraw", "remember", "revise"]),
1761
+ id: str,
1762
+ text: str,
1763
+ title: str,
1764
+ who: str,
1765
+ under: str,
1766
+ gate: z4.enum(["start", "complete", "answer", "none"]),
1767
+ options: z4.array(z4.object({ id: str, label: str }).strict()),
1768
+ pick: z4.enum(["one", "many", "rank", "words"]),
1769
+ entries: strs
1770
+ }).strict()),
1771
+ feedback: z4.array(strs),
1772
+ search: z4.object({ query: str, within: strs, goal: str }).strict()
1773
+ }).strict();
1774
+ var COMPACT_RESULT_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
1775
+ zodToJsonSchema(CompactResultSchema, { $refStrategy: "none" })
1776
+ );
1625
1777
  function entryWords(entry) {
1626
1778
  const content = entry.content;
1627
1779
  if (content && "sealed" in content) return "";
@@ -1634,22 +1786,24 @@ function entryWords(entry) {
1634
1786
  const description = plain.description;
1635
1787
  const parts = [title, ...Array.isArray(description) ? description : []].filter((v) => typeof v === "string");
1636
1788
  if (parts.length) return parts.join("\n\n");
1789
+ const line = plain.line;
1790
+ if (typeof line === "string" && line.trim()) return line;
1637
1791
  }
1638
1792
  return entry.sources.map((source) => source.text).join("\n");
1639
1793
  }
1640
1794
  var LIVE_MS = 3 * 6e4;
1641
- var WORKING_MS = 30 * 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(
1795
+ var WORKING_MS = 60 * 6e4;
1796
+ var ContextSchema = z5.object({
1797
+ title: z5.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
1798
+ description: z5.array(z5.string().min(1)).describe(
1645
1799
  "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
1800
  )
1647
1801
  });
1648
- var ParticipantSchema = z4.object({
1649
- kind: z4.enum(["human", "agent"]),
1650
- id: z4.string()
1802
+ var ParticipantSchema = z5.object({
1803
+ kind: z5.enum(["human", "agent"]),
1804
+ id: z5.string()
1651
1805
  });
1652
- var TransformSchema = z4.enum([
1806
+ var TransformSchema = z5.enum([
1653
1807
  "structure",
1654
1808
  // shape intent into an answer contract; pick channel/urgency — broker `ask`, `select` shapes, `points`
1655
1809
  "request_more",
@@ -1672,13 +1826,13 @@ var PAIGY_SELF = { kind: "agent", id: "paigy" };
1672
1826
  function isPaigy(ref) {
1673
1827
  return ref === participantRef(PAIGY_SELF);
1674
1828
  }
1675
- var VisualSchema = z4.object({
1676
- url: z4.string().url(),
1677
- label: z4.string().optional()
1829
+ var VisualSchema = z5.object({
1830
+ url: z5.string().url(),
1831
+ label: z5.string().optional()
1678
1832
  });
1679
- var NotifyLevelSchema = z4.enum(["inbox", "push", "banner", "call"]);
1680
- var SelectShapeSchema = z4.enum(["one", "many", "rank", "confirm", "text"]);
1681
- var ReceiptEventSchema = z4.enum([
1833
+ var NotifyLevelSchema = z5.enum(["inbox", "push", "banner", "call"]);
1834
+ var SelectShapeSchema = z5.enum(["one", "many", "rank", "confirm", "text"]);
1835
+ var ReceiptEventSchema = z5.enum([
1682
1836
  "delivered",
1683
1837
  // the bundle reached the recipient at some level
1684
1838
  "seen",
@@ -1708,47 +1862,47 @@ var ReceiptEventSchema = z4.enum([
1708
1862
  // be rewound by a writer that forgot to advance it.
1709
1863
  "restarted"
1710
1864
  ]);
1711
- var AttentionSchema = z4.object({
1865
+ var AttentionSchema = z5.object({
1712
1866
  urgency: NotifyLevelSchema,
1713
1867
  /** The required answer shape, or null for a plain notify that asks nothing back. */
1714
1868
  select: SelectShapeSchema.nullable(),
1715
1869
  /** Coverage contract (#396) — points the answer must address; null = none declared. */
1716
- points: z4.array(z4.string()).nullable(),
1870
+ points: z5.array(z5.string()).nullable(),
1717
1871
  /** Whether the ask blocks the sender — what lets arbitration escalate it on silence. */
1718
- blocking: z4.boolean(),
1872
+ blocking: z5.boolean(),
1719
1873
  /** Reserved (docs/model/model.md lists it): a response deadline. No row column yet — a later Phase 2
1720
1874
  * slice wires it; optional so today's rows/callers project cleanly. */
1721
- deadline: z4.string().datetime().nullable().optional()
1875
+ deadline: z5.string().datetime().nullable().optional()
1722
1876
  });
1723
- var NotifyRequestFields = z4.object({
1877
+ var NotifyRequestFields = z5.object({
1724
1878
  /** Plaintext message content. Present on the plaintext path (today's shape);
1725
1879
  * ABSENT on the E2EE path, where the sealed `envelope` below carries it. The
1726
1880
  * superRefine at the bottom enforces exactly one of the two. */
1727
1881
  context: ContextSchema.optional(),
1728
- options: z4.array(OptionInputSchema).min(OPTIONS_MIN).max(OPTIONS_MAX).optional().describe(
1882
+ options: z5.array(OptionInputSchema).min(OPTIONS_MIN).max(OPTIONS_MAX).optional().describe(
1729
1883
  "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
1884
  ),
1731
- points: z4.array(z4.string().min(1)).optional().describe(
1885
+ points: z5.array(z5.string().min(1)).optional().describe(
1732
1886
  "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
1887
  ),
1734
- visuals: z4.array(VisualSchema).optional().describe(
1888
+ visuals: z5.array(VisualSchema).optional().describe(
1735
1889
  "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
1890
  ),
1737
1891
  /** Git repo the agent is working in ("owner/name"). Local MCP fills this from the checkout — omit unless overriding. */
1738
- repo: z4.string().optional(),
1892
+ repo: z5.string().optional(),
1739
1893
  /** Git branch the agent is on. Local MCP fills this from the checkout — omit unless overriding. */
1740
- branch: z4.string().optional(),
1894
+ branch: z5.string().optional(),
1741
1895
  /** Continue an existing conversation — the id of any notification in it (its root
1742
1896
  * is the conversation's identity). Omitted = start a new conversation. Renamed
1743
1897
  * from `parentId` (2026-08-03): one linkage system, the parent; the API edge
1744
1898
  * still accepts the old name from older clients. */
1745
- parentId: z4.string().uuid().optional(),
1899
+ parentId: z5.string().uuid().optional(),
1746
1900
  /** The durable outcome this contact advances. Optional during the notification-to-Work
1747
1901
  * migration; when present, a blocking ask creates a DecisionNeed for this Work. */
1748
- workId: z4.string().uuid().optional(),
1902
+ workId: z5.string().uuid().optional(),
1749
1903
  /** Target Goal scope. During staged migration this is accepted by the shared contract but
1750
1904
  * target delivery activation remains model-gated; workId and goalId are mutually exclusive. */
1751
- goalId: z4.string().uuid().optional(),
1905
+ goalId: z5.string().uuid().optional(),
1752
1906
  urgency: NotifyLevelSchema.default("inbox").describe(
1753
1907
  "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
1908
  ),
@@ -1756,7 +1910,7 @@ var NotifyRequestFields = z4.object({
1756
1910
  * visible and marks it needs_input. Renamed from the old `parentId` (2026-08-03)
1757
1911
  * when `parentId` became the conversation handle: `parentId` says WHERE, this
1758
1912
  * says HOW. */
1759
- clarifies: z4.string().optional(),
1913
+ clarifies: z5.string().optional(),
1760
1914
  select: SelectShapeSchema.optional().describe(
1761
1915
  "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
1916
  ),
@@ -1770,20 +1924,20 @@ var NotifyRequestFields = z4.object({
1770
1924
  // (docs/brain/broker/agenda-design.md) — not by a wire cap the agent has to pre-summarize under.
1771
1925
  // Owner, 2026-07-28: "our actual limitation on how long something is to the user should
1772
1926
  // 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(
1927
+ ask: z5.string().min(1).max(1e4).optional().describe(
1774
1928
  '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
1929
  ),
1776
- needs: z4.array(z4.string().min(1)).optional().describe(
1930
+ needs: z5.array(z5.string().min(1)).optional().describe(
1777
1931
  "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
1932
  ),
1779
- urgencyHint: z4.enum(["whenever", "soon", "now"]).optional().describe(
1933
+ urgencyHint: z5.enum(["whenever", "soon", "now"]).optional().describe(
1780
1934
  "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
1935
  ),
1782
1936
  /** #575: the ONE self-report that replaces urgencyHint + blocking — what happens
1783
1937
  * to the agent's work while it waits. Normalized server-side into those two
1784
1938
  * fields (normalizeWaiting) so everything downstream is untouched; explicit
1785
1939
  * urgencyHint/blocking win when both are sent. */
1786
- waiting: z4.enum(["none", "soft", "hard"]).optional().describe(
1940
+ waiting: z5.enum(["none", "soft", "hard"]).optional().describe(
1787
1941
  "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
1942
  ),
1789
1943
  /** Δ9b (#895): HOLD this claim so the sender can correct the plan before anyone is
@@ -1791,98 +1945,98 @@ var NotifyRequestFields = z4.object({
1791
1945
  * holding by default would charge every quiet claim that minute before any agent could
1792
1946
  * correct anything. Ignored for `waiting: 'hard'`: a blocking ask rings on what we have,
1793
1947
  * and the enrichment can still land mid-call (#781 re-plans the unspoken tail). */
1794
- confirm: z4.boolean().optional().describe(
1948
+ confirm: z5.boolean().optional().describe(
1795
1949
  "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
1950
  ),
1797
1951
  /** #575: a RELAY of the user's explicitly stated preference, never the agent's
1798
1952
  * choice. Outranks waiting in both directions: 'call' rings even for a
1799
1953
  * waiting:'none' "call me when it's done"; 'message' never rings even for
1800
1954
  * waiting:'hard'. */
1801
- channel: z4.enum(["call", "message"]).optional().describe(
1955
+ channel: z5.enum(["call", "message"]).optional().describe(
1802
1956
  "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
1957
  ),
1804
- confirmStyle: z4.enum(["yesno", "approve"]).default("yesno").describe(
1958
+ confirmStyle: z5.enum(["yesno", "approve"]).default("yesno").describe(
1805
1959
  "Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
1806
1960
  ),
1807
- blocking: z4.boolean().default(false).describe(
1961
+ blocking: z5.boolean().default(false).describe(
1808
1962
  "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
1963
  )
1810
1964
  });
1811
1965
  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" });
1966
+ if (r.workId && r.goalId) ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["goalId"], message: "pass goalId or workId, not both" });
1813
1967
  if (r.ask !== void 0) {
1814
1968
  for (const f of ["context", "select", "points"]) {
1815
1969
  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.` });
1970
+ 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
1971
  }
1818
1972
  return;
1819
1973
  }
1820
1974
  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" });
1975
+ 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
1976
  if (!r.context)
1823
- ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["context"], message: "context is required (plaintext path)" });
1977
+ ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["context"], message: "context is required (plaintext path)" });
1824
1978
  if (!r.select)
1825
- ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["select"], message: "select is required on the shaped form" });
1979
+ ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["select"], message: "select is required on the shaped form" });
1826
1980
  const needsOptions = r.select === "one" || r.select === "many" || r.select === "rank";
1827
1981
  if (needsOptions && !r.options?.length)
1828
- ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' requires options` });
1982
+ ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' requires options` });
1829
1983
  if (!needsOptions && r.options?.length)
1830
- ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' takes no options` });
1984
+ ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' takes no options` });
1831
1985
  });
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()
1986
+ var NotifyStatusSchema = z5.enum(["pending", "answered", "ignored"]);
1987
+ var AgentStateSchema = z5.enum(["idle", "in_progress", "completed", "needs_input"]);
1988
+ var TurnSchema = z5.object({
1989
+ prompt: z5.string(),
1990
+ reply: z5.string()
1837
1991
  });
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) }),
1992
+ var UserAnswerSchema = z5.discriminatedUnion("kind", [
1993
+ z5.object({ kind: z5.literal("option"), optionId: z5.string(), label: z5.string().optional() }),
1994
+ z5.object({ kind: z5.literal("text"), text: z5.string() }),
1995
+ z5.object({ kind: z5.literal("ignored") }),
1996
+ z5.object({ kind: z5.literal("multi"), optionIds: z5.array(z5.string()), labels: z5.array(z5.string()).optional() }),
1997
+ z5.object({ kind: z5.literal("ranked"), optionIds: z5.array(z5.string()), labels: z5.array(z5.string()).optional() }),
1998
+ z5.object({ kind: z5.literal("clarify"), chunks: z5.array(z5.string()).min(1) }),
1999
+ z5.object({ kind: z5.literal("confirm"), approved: z5.boolean() }),
2000
+ z5.object({ kind: z5.literal("turns"), turns: z5.array(TurnSchema).min(1) }),
1847
2001
  /** An auto-answer derived from the user's PAST decisions (docs/brain/broker/precedent-design.md §2):
1848
2002
  * delivered through the same settle/await path as a human answer, carrying the judge's
1849
2003
  * derivation and the precedent ids it grew from. Always paired with a visible trail
1850
2004
  * 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) })
2005
+ z5.object({ kind: z5.literal("precedent"), answer: z5.string(), derivation: z5.string(), sources: z5.array(z5.string()).min(1) })
1852
2006
  ]);
1853
- var IntentSchema = z4.object({
2007
+ var IntentSchema = z5.object({
1854
2008
  // The full vocabulary the bot's mapper emits (mapper.INTENT_KINDS) — the schema lagged
1855
2009
  // it by two ("detail", "feedback"), and because the settle handler parsed the array
1856
2010
  // all-or-nothing, ONE feedback act silently dropped EVERY intent on the call,
1857
2011
  // 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(),
2012
+ kind: z5.enum(["defer", "delegate", "channel", "question", "detail", "feedback", "command", "control"]),
2013
+ detail: z5.string(),
1860
2014
  /** Defer only: seconds until the callback the caller asked for, when something upstream
1861
2015
  * already read the time. Nothing sets it today (#397 documented an MCP parser that was
1862
2016
  * never written) — the API reads the defer's `detail` itself with `notes/when.ts`
1863
2017
  * (`parseDelay`, #1292), and a value here simply wins over that reading. */
1864
- dueInSeconds: z4.number().int().positive().optional(),
2018
+ dueInSeconds: z5.number().int().positive().optional(),
1865
2019
  /** 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
2020
+ * already read the utterance, so `signals.kind` stops defaulting to
1867
2021
  * 'other' on every row. A table that records that something was wrong and nothing
1868
2022
  * about what cannot answer "is the bot looping less this week?". */
1869
- fault: z4.enum(["loop", "unanswered", "overridden", "misheard", "slow", "other"]).optional()
2023
+ fault: z5.enum(["loop", "unanswered", "overridden", "misheard", "slow", "other"]).optional()
1870
2024
  });
1871
- var RideAlongSchema = z4.object({
2025
+ var RideAlongSchema = z5.object({
1872
2026
  /** The note this came from — assign/clarify/close it through /api/notes/:id. */
1873
- noteId: z4.string(),
2027
+ noteId: z5.string(),
1874
2028
  /** What to do, in the owner's own words (the note's headline). Never model-rewritten. */
1875
- text: z4.string(),
2029
+ text: z5.string(),
1876
2030
  /** The thread to report back on, when the note was dispatched over the request rail. */
1877
- parentId: z4.string().nullable()
2031
+ parentId: z5.string().nullable()
1878
2032
  });
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(),
2033
+ var AwaitItemSchema = z5.discriminatedUnion("type", [
2034
+ z5.object({
2035
+ type: z5.literal("reply"),
2036
+ parentId: z5.string(),
2037
+ notificationId: z5.string(),
2038
+ workId: z5.string().uuid().optional(),
2039
+ decisionId: z5.string().uuid().optional(),
1886
2040
  answer: UserAnswerSchema,
1887
2041
  /** WHAT THE AGENT CANNOT KNOW FROM THE FIELDS BESIDE IT (owner, 2026-09-04, issue
1888
2042
  * #1537). One line, built from the record: the ask and the caller's reply VERBATIM,
@@ -1892,103 +2046,103 @@ var AwaitItemSchema = z4.discriminatedUnion("type", [
1892
2046
  * "call me back after you merge" in their own words decides for itself what to do,
1893
2047
  * and now knows exactly which call to make. Absent when either half is missing —
1894
2048
  * a sentence with a hole in it is worse than no sentence. */
1895
- note: z4.string().optional(),
2049
+ note: z5.string().optional(),
1896
2050
  /** The call record rendered for THIS agent (`docs/brain/voice/record-design.md`): the words the
1897
2051
  * shaped answer was mapped from, filtered to its own claims. There is no second list
1898
2052
  * of labels beside it — the acts went 2026-09-04 and `intents` went with them (owner,
1899
2053
  * 2026-09-04): the agent reads the sentence and decides. */
1900
- transcript: z4.string().optional(),
2054
+ transcript: z5.string().optional(),
1901
2055
  /** Coverage report (#396), when the ask declared `points`: which of them this
1902
2056
  * answer addressed. Missing points = re-ask or proceed knowingly partial. */
1903
- covered: z4.array(z4.string()).optional(),
2057
+ covered: z5.array(z5.string()).optional(),
1904
2058
  /** Ride-alongs (RideAlongSchema) — pending work for you, attached to the moment you
1905
2059
  * became free. Only `reply` and `idle` carry it: those are the two outcomes that
1906
2060
  * END a wait. `remind`, `superseded` and `turn` are mid-flight, and handing an
1907
2061
  * agent a side-quest while it is still holding the line is how the main thing gets
1908
2062
  * dropped. Absent/empty = nothing owed. */
1909
- also: z4.array(RideAlongSchema).optional()
2063
+ also: z5.array(RideAlongSchema).optional()
1910
2064
  }),
1911
- z4.object({
1912
- type: z4.literal("remind"),
1913
- parentId: z4.string(),
1914
- notificationId: z4.string(),
1915
- remindAt: z4.string().datetime({ offset: true }),
2065
+ z5.object({
2066
+ type: z5.literal("remind"),
2067
+ parentId: z5.string(),
2068
+ notificationId: z5.string(),
2069
+ remindAt: z5.string().datetime({ offset: true }),
1916
2070
  /** Seconds until remindAt, server-computed — pass straight to ScheduleWakeup. */
1917
- remindInSeconds: z4.number()
2071
+ remindInSeconds: z5.number()
1918
2072
  }),
1919
2073
  /** The awaited ask was REPLACED by a newer notification on its thread (e.g. a
1920
2074
  * post-feedback revision, #633) — the user will never answer this id. Stop
1921
2075
  * awaiting it; the live ask is the thread's newest turn (await that one, or
1922
2076
  * re-orient via check_replies). */
1923
- z4.object({
1924
- type: z4.literal("superseded"),
1925
- parentId: z4.string(),
1926
- notificationId: z4.string()
2077
+ z5.object({
2078
+ type: z5.literal("superseded"),
2079
+ parentId: z5.string(),
2080
+ notificationId: z5.string()
1927
2081
  }),
1928
2082
  /** A LIVE call's turn, streamed as it lands (#783). PROVISIONAL: the user can still
1929
2083
  * revise any of these until the final reply arrives — partial = intelligence,
1930
2084
  * settled = authorization. Use it to PREPARE (fetch, draft, warm), never to act
1931
2085
  * irreversibly. If `acts` carries a question aimed at you and you know the answer,
1932
2086
  * 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()
2087
+ z5.object({
2088
+ type: z5.literal("partial"),
2089
+ notificationId: z5.string(),
2090
+ inFlight: z5.literal(true),
2091
+ turn: z5.object({
2092
+ idx: z5.number(),
2093
+ prompt: z5.string(),
2094
+ reply: z5.string(),
2095
+ acts: z5.array(IntentSchema).nullable().optional()
1942
2096
  })
1943
2097
  }),
1944
- z4.object({
1945
- type: z4.literal("idle"),
1946
- also: z4.array(RideAlongSchema).optional(),
2098
+ z5.object({
2099
+ type: z5.literal("idle"),
2100
+ also: z5.array(RideAlongSchema).optional(),
1947
2101
  /** Is a call live for this agent's user right now? The SDK polls the partial stream
1948
2102
  * (#783) between idle ticks ONLY while this is not `false` — a partial can only exist
1949
2103
  * during a live call, and polling for one on a banner/message was a wasted HTTP call +
1950
2104
  * 3 queries on every idle tick of every waiting agent (~80% of all traffic at scale).
1951
2105
  * Absent = an older API → the SDK keeps polling, exactly as before. */
1952
- inFlight: z4.boolean().optional()
2106
+ inFlight: z5.boolean().optional()
1953
2107
  })
1954
2108
  ]);
1955
- var VoiceKeySchema = z4.enum(["rachel", "george", "jessica", "brian", "lily"]);
1956
- var AgendaTurnSchema = z4.object({
2109
+ var VoiceKeySchema = z5.enum(["rachel", "george", "jessica", "brian", "lily"]);
2110
+ var AgendaTurnSchema = z5.object({
1957
2111
  /** THE TURN'S IDENTITY (the first-sentence stream, 2026-09-09): the brain call that wrote
1958
2112
  * it and its place in that reply — `<brainCallId>:<index>`, with `:p` on the first
1959
2113
  * sentence a re-plan publishes ahead of the rest. A turn is spoken once, by this id: the
1960
2114
  * completion of a streamed re-plan carries the published sentence again, and the walk
1961
2115
  * drops what it already said by identity, never by the API's guess of what was polled.
1962
2116
  * Absent on plans nothing streams (a ring plan, a floor). */
1963
- id: z4.string().optional(),
2117
+ id: z5.string().optional(),
1964
2118
  /** Twin coverage (#1089): sibling claim ids this asking turn's answer ALSO settles —
1965
2119
  * the planner declares duplicates instead of asking them twice. */
1966
- coveredIds: z4.array(z4.string()).optional(),
2120
+ coveredIds: z5.array(z5.string()).optional(),
1967
2121
  /** The spoken sentences of the turn, in order. No count: how long a turn is is the brain's call
1968
2122
  * (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(),
2123
+ info: z5.array(z5.string().min(1)).default([]),
2124
+ question: z5.string().min(1).nullable(),
1971
2125
  /** True on the one turn carrying the agent's own declared question. */
1972
- asks: z4.boolean().optional(),
2126
+ asks: z5.boolean().optional(),
1973
2127
  /** The claim this turn belongs to (#781) — the RETURN identity: answers route by it.
1974
2128
  * Absent on a single-claim plan (the session's own claim) and on shared context turns,
1975
2129
  * which route nothing. */
1976
- claimId: z4.string().optional(),
2130
+ claimId: z5.string().optional(),
1977
2131
  /** The claim's voice key (#462) — the OUTBOUND identity, audible who-is-asking. */
1978
- voice: z4.string().optional(),
2132
+ voice: z5.string().optional(),
1979
2133
  /** The claim's AGENT NAME (#838) — the spoken identity. A voice alone doesn't say
1980
2134
  * whose request this is: an item that folded in from another agent arrived as a bare
1981
2135
  * non-sequitur ("First real production sign-in is yours to make whenever you want.")
1982
2136
  * and the owner answered "What?". The bot names the agent before its first turn. */
1983
- agent: z4.string().optional(),
2137
+ agent: z5.string().optional(),
1984
2138
  /** The claim's agent by ID — the pairing's connection id (`notifications.token_id`), the
1985
2139
  * same id a face is minted from. A name is not an identity: two pairings may be called
1986
2140
  * "Claude", and a name cannot be joined on. The record's entries carry it (`agent_id`)
1987
2141
  * so "who said that" survives the call, and it rides PER TURN because a coalesced call
1988
2142
  * speaks for several agents — the turn is the only place that knows which. */
1989
- agentId: z4.string().optional(),
2143
+ agentId: z5.string().optional(),
1990
2144
  select: SelectShapeSchema.optional(),
1991
- options: z4.array(OptionSchema.omit({ id: true })).optional(),
2145
+ options: z5.array(OptionSchema.omit({ id: true })).optional(),
1992
2146
  /* `pace` STOOD HERE (#826). A turn could carry seconds and the model chose them. The walk
1993
2147
  paces itself now — a short beat between the sentences of a turn, the longer one at its end
1994
2148
  (owner, 2026-09-30: "remove the bot deciding pace") — and it does that where the words are
@@ -1997,27 +2151,33 @@ var AgendaTurnSchema = z4.object({
1997
2151
  /** Whether the walk WAITS for an answer before moving on. Absent = derived as today
1998
2152
  * (a question blocks, context flows). blocking:false on a question = ask and move
1999
2153
  * on, the claim stays pending; blocking:true on context = hold for a reply. */
2000
- blocking: z4.boolean().optional()
2154
+ blocking: z5.boolean().optional(),
2155
+ /** SPOKEN ONLY IF THEY SAY NOTHING (owner, 2026-10-01, call 812de935: "you're gonna re-ask, but it
2156
+ * shouldn't be the same words … more like, hey, are you still there, or are you able to answer, or
2157
+ * would you need more information"). The walk holds this turn out of its queue; at the queue's end it
2158
+ * listens for the last word, and only if that listen is silent is this turn said and asked. If they
2159
+ * speak, it is dropped and their words are taken like any reply. */
2160
+ ifSilent: z5.boolean().optional()
2001
2161
  });
2002
2162
  var CLAIM_STALE_MS = 30 * 6e4;
2003
- var InboxItemSchema = z4.object({
2004
- id: z4.string(),
2005
- tokenId: z4.string().optional(),
2163
+ var InboxItemSchema = z5.object({
2164
+ id: z5.string(),
2165
+ tokenId: z5.string().optional(),
2006
2166
  status: NotifyStatusSchema,
2007
2167
  context: ContextSchema,
2008
- options: z4.array(OptionSchema).optional(),
2168
+ options: z5.array(OptionSchema).optional(),
2009
2169
  /** The ask's declared coverage points (#396), when the agent sent them. */
2010
- points: z4.array(z4.string()).optional(),
2170
+ points: z5.array(z5.string()).optional(),
2011
2171
  /** Does this claim want an ANSWER, or is it telling you something? Written per row from
2012
2172
  * `requestAsks` — the agent's own declaration, not a guess. `false` is what earns a card
2013
2173
  * its acknowledge affordance: without it a status update offers a text box and a dismiss,
2014
2174
  * and neither of those is "got it" (owner, 2026-08-10). */
2015
- asks: z4.boolean().optional(),
2175
+ asks: z5.boolean().optional(),
2016
2176
  /** When a live process last pulsed for this row's agent — the liveness input for
2017
2177
  * "working requires a pulse" (#928): the list said "Working…" from agent_state alone
2018
2178
  * while the party called the same dead claim stalled. Absent = no token/no data,
2019
2179
  * which must never CLAIM stalled. */
2020
- lastSeenAt: z4.string().optional(),
2180
+ lastSeenAt: z5.string().optional(),
2021
2181
  /** WHEN THE AGENT LAST SAID ANYTHING ABOUT THIS CLAIM — the newest `agent_state` row in
2022
2182
  * the `notification_events` ledger (trigger-written since 20260621010000, so every row a
2023
2183
  * user can see has one). The age input for `CLAIM_STALE_MS`, and it has to be this rather
@@ -2027,7 +2187,7 @@ var InboxItemSchema = z4.object({
2027
2187
  * work. Reading the row's birth as the claim's age brands that "No update in 8h" the
2028
2188
  * instant the agent picks it up (#997). Absent = pre-trigger row; fall back to
2029
2189
  * `createdAt`. */
2030
- agentStateAt: z4.string().datetime().optional(),
2190
+ agentStateAt: z5.string().datetime().optional(),
2031
2191
  /** THE QUESTIONS A CALL CARRIES — the call screen's agenda spine (docs/clients/app/walk/design.md §11, owner
2032
2192
  * 2026-09-22). One per DecisionNeed on the Call, in the Call's order, answered or open (a
2033
2193
  * superseded or cancelled need is no longer a question anyone is asked). Present only on a
@@ -2041,54 +2201,54 @@ var InboxItemSchema = z4.object({
2041
2201
  * `turn` topic (`asking`, `settled`), because the bot never sees a DecisionNeed id. `title` is
2042
2202
  * the card's own concise heading; `answer` the accepted answer in words, null while open. It
2043
2203
  * REPLACED `agenda` (turns), which nothing ever filled. */
2044
- questions: z4.array(z4.object({
2045
- id: z4.string(),
2046
- entryId: z4.string(),
2047
- title: z4.string(),
2048
- state: z4.enum(["open", "answered"]),
2049
- answer: z4.string().nullable(),
2204
+ questions: z5.array(z5.object({
2205
+ id: z5.string(),
2206
+ entryId: z5.string(),
2207
+ title: z5.string(),
2208
+ state: z5.enum(["open", "answered"]),
2209
+ answer: z5.string().nullable(),
2050
2210
  /** WHO ASKED IT (owner, 2026-09-23, Goal a345e906: each agenda row wears its agent's face) — the
2051
2211
  * request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
2052
2212
  * carry for the call's one agent, so the phone draws it with the same seed. Absent when the
2053
2213
  * author is not an agent this account holds (unpaired since, or a person). */
2054
- agent: z4.object({ tokenId: z4.string(), name: z4.string(), voice: VoiceKeySchema.optional() }).optional(),
2214
+ agent: z5.object({ tokenId: z5.string(), name: z5.string(), voice: VoiceKeySchema.optional() }).optional(),
2055
2215
  /** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
2056
2216
  * its need offers, exactly as its own card carries them, present only when one of them has a
2057
2217
  * preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
2058
2218
  * never re-sent as a second card to be seen mid-call. Words-only options are absent — the bot
2059
2219
  * says those, and the list stays small (an `html` is up to 16 KB). */
2060
- options: z4.array(OptionSchema).optional()
2220
+ options: z5.array(OptionSchema).optional()
2061
2221
  })).optional(),
2062
- visuals: z4.array(VisualSchema).optional(),
2222
+ visuals: z5.array(VisualSchema).optional(),
2063
2223
  /** The connected agent's name (the single pairing name — user-typed, or the
2064
2224
  * agent's suggestion, or a default silly name). */
2065
- name: z4.string(),
2225
+ name: z5.string(),
2066
2226
  /** The pairing's assigned voice (#462); absent = the default voice. */
2067
2227
  voice: VoiceKeySchema.optional(),
2068
- repo: z4.string().optional(),
2069
- branch: z4.string().optional(),
2070
- createdAt: z4.string().datetime(),
2071
- snoozedUntil: z4.string().datetime().optional(),
2228
+ repo: z5.string().optional(),
2229
+ branch: z5.string().optional(),
2230
+ createdAt: z5.string().datetime(),
2231
+ snoozedUntil: z5.string().datetime().optional(),
2072
2232
  agentState: AgentStateSchema.default("idle"),
2073
2233
  /** Whose action the item is waiting on: "you" = an agent asked you (the default,
2074
2234
  * every agent→user notification); "agent" = you sent a request and it's awaiting the
2075
2235
  * agent (held in the inbox until the agent replies on the thread). */
2076
- turn: z4.enum(["you", "agent"]).default("you"),
2236
+ turn: z5.enum(["you", "agent"]).default("you"),
2077
2237
  /** Hard error reason on an awaiting request (turn="agent") — the wake failed to reach
2078
2238
  * the agent (provider-agnostic; set server-side). Absent = no hard error. Drives the inbox
2079
2239
  * error badge + Retry. */
2080
- error: z4.string().optional(),
2240
+ error: z5.string().optional(),
2081
2241
  /** WHEN THIS AGENT WORK WENT QUIET (turn="agent"), by the one rule (`coldSince`: three days
2082
2242
  * with nothing said), or absent while it is not stalled. The inbox's stalled badge reads
2083
2243
  * this and nothing else (2026-09-23: a 3-minute age rule badged every live Goal stalled,
2084
2244
  * and "dismiss the stalled ones" cancelled 37 pieces of live work). */
2085
- cold: z4.string().datetime().optional(),
2086
- clarifies: z4.string().optional(),
2245
+ cold: z5.string().datetime().optional(),
2246
+ clarifies: z5.string().optional(),
2087
2247
  /** THIS CARD'S QUESTION IS ON A LIVE CALL (owner, 2026-09-24: "Mark it while the call is
2088
2248
  * live"). Present only while an open Call Delivery carries the card's request Entry — read
2089
2249
  * off the same open list the card came from, so it clears when the Call does. A card is the
2090
2250
  * backup for a call not taken; while the call has it, the call is where it is answered. */
2091
- onCall: z4.literal(true).optional(),
2251
+ onCall: z5.literal(true).optional(),
2092
2252
  /** THE RING, ON THE ITEM (docs/clients/app/walk/design.md §12 §17, #2251): the last ring on this card was
2093
2253
  * declined, and what the ladder will do next — read off the cron's own row, never computed
2094
2254
  * on the phone. Present only while a `declined` receipt stands on the card's last Call.
@@ -2098,31 +2258,31 @@ var InboxItemSchema = z4.object({
2098
2258
  * It replaced `gaveUp` (deleted 2026-09-22): "the ladder spent" was a boolean the projection
2099
2259
  * never set, and it is `nextRingAt === null` here — the party's *Missed you* (`party/dress.ts`)
2100
2260
  * and the roster's `unreached` read `declinedAt`, and stand while it does. */
2101
- ring: z4.object({
2102
- declinedAt: z4.string().datetime(),
2103
- anchorAt: z4.string().datetime(),
2104
- nextRingAt: z4.string().datetime().nullable(),
2105
- step: z4.number().int()
2261
+ ring: z5.object({
2262
+ declinedAt: z5.string().datetime(),
2263
+ anchorAt: z5.string().datetime(),
2264
+ nextRingAt: z5.string().datetime().nullable(),
2265
+ step: z5.number().int()
2106
2266
  }).optional(),
2107
2267
  /** Why this arrived the way it did, read back off the delivery receipt (`notify/why.ts`).
2108
2268
  * Absent for anything never delivered through a push, and for older rows written before
2109
2269
  * the reason was recorded. Deliberately a debug affordance, shown small (owner,
2110
2270
  * 2026-08-07) — its real job is to give "this didn't need a call" something to be
2111
2271
  * feedback ABOUT. */
2112
- why: z4.object({
2272
+ why: z5.object({
2113
2273
  asked: NotifyLevelSchema,
2114
2274
  got: NotifyLevelSchema,
2115
- because: z4.enum(["unresponsive", "dismissed", "not_permitted", "silent", "coalesced", "agent_capped", "unplanned", "learned_raise"]).optional(),
2116
- line: z4.string()
2275
+ because: z5.enum(["unresponsive", "dismissed", "not_permitted", "silent", "coalesced", "agent_capped", "unplanned", "learned_raise"]).optional(),
2276
+ line: z5.string()
2117
2277
  }).optional(),
2118
- select: z4.enum(["one", "many", "rank", "confirm", "text"]).default("one"),
2119
- confirmStyle: z4.enum(["yesno", "approve"]).default("yesno").describe(
2278
+ select: z5.enum(["one", "many", "rank", "confirm", "text"]).default("one"),
2279
+ confirmStyle: z5.enum(["yesno", "approve"]).default("yesno").describe(
2120
2280
  "Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
2121
2281
  ),
2122
2282
  /** Real downstream work is stuck behind this one — set by the agent, independent of
2123
2283
  * urgency (see the main README's "premier use case" + docs/delivery/notify/states.md). Drives the
2124
2284
  * inbox's blocking badge and the extra confirm step before dismissing it. */
2125
- blocking: z4.boolean().default(false),
2285
+ blocking: z5.boolean().default(false),
2126
2286
  /** The user's locked-in answer; present only for replied items (GET /api/replied/:id). */
2127
2287
  answer: UserAnswerSchema.optional(),
2128
2288
  /** THE TARGET FACTS A CARD RENDERS (#1796 point 5, 2026-09-11): the Delivery it is a view of,
@@ -2130,38 +2290,49 @@ var InboxItemSchema = z4.object({
2130
2290
  * for a request that asks nothing), whether its content is sealed, and that Goal's state. The
2131
2291
  * answer writer (`POST /api/entries`) and the disposition (`close_delivery`) take their ids from
2132
2292
  * here. The server projects it (`apps/api/src/inbox/project.ts`); a client never builds it. */
2133
- communication: z4.object({
2134
- deliveryId: z4.string(),
2135
- kind: z4.enum(["notification", "call"]),
2136
- entryId: z4.string(),
2137
- goalIds: z4.array(z4.string()),
2138
- decisionNeedId: z4.string().optional(),
2139
- sealed: z4.boolean(),
2140
- goalState: z4.string().optional(),
2293
+ communication: z5.object({
2294
+ deliveryId: z5.string(),
2295
+ kind: z5.enum(["notification", "call"]),
2296
+ entryId: z5.string(),
2297
+ goalIds: z5.array(z5.string()),
2298
+ decisionNeedId: z5.string().optional(),
2299
+ sealed: z5.boolean(),
2300
+ goalState: z5.string().optional(),
2141
2301
  /** THAT GOAL'S NAME (#2416) — what Activity's row is headed by, since a row there is one Goal
2142
2302
  * and the cards it holds sit behind it. Stamped by the same read as `goalState`. */
2143
- goalTitle: z4.string().optional()
2144
- }).optional()
2303
+ goalTitle: z5.string().optional()
2304
+ }).optional(),
2305
+ /** WHAT THIS CARD IS, IN TWELVE CHARACTERS (#3019) — the hash of every other field on it, stamped
2306
+ * by the one read that serves the open list (`apps/api/src/inbox/project.ts` `inboxFor`). It is
2307
+ * how the incremental read knows a card has not moved: the phone echoes back the revs it holds
2308
+ * (`POST /api/inbox/changes`) and is sent only the cards whose rev differs.
2309
+ *
2310
+ * THE PAYLOAD'S OWN HASH, NEVER A STAMP ON THE WORK — the same mechanism as `QueueItem.rev`
2311
+ * (#2928) and an entry's (#3018), for the same reason: a card shows facts no `updated_at` of its
2312
+ * own moves (its Goal's state and name, how cold the work behind it has gone, whether a ring is
2313
+ * live). Optional, so a fixture, the demo and the archive lens need not spell one, and a card
2314
+ * with no rev is simply always re-sent. */
2315
+ rev: z5.string().optional()
2145
2316
  });
2146
2317
  var APNS_TOKEN_RE = /^[0-9a-fA-F]{64}$/;
2147
- var PushTokenSchema = z4.object({
2148
- voipToken: z4.string().min(1).optional(),
2149
- alertToken: z4.string().min(1).optional(),
2150
- fcmToken: z4.string().min(1).optional(),
2151
- platform: z4.enum(["ios", "android"])
2318
+ var PushTokenSchema = z5.object({
2319
+ voipToken: z5.string().min(1).optional(),
2320
+ alertToken: z5.string().min(1).optional(),
2321
+ fcmToken: z5.string().min(1).optional(),
2322
+ platform: z5.enum(["ios", "android"])
2152
2323
  }).superRefine((v, ctx) => {
2153
2324
  if (v.platform !== "ios") return;
2154
2325
  for (const field of ["voipToken", "alertToken"]) {
2155
2326
  const token = v[field];
2156
2327
  if (token === void 0 || APNS_TOKEN_RE.test(token)) continue;
2157
2328
  ctx.addIssue({
2158
- code: z4.ZodIssueCode.custom,
2329
+ code: z5.ZodIssueCode.custom,
2159
2330
  path: [field],
2160
2331
  message: `not an APNs device token (want 64 hex chars, got ${token.length})`
2161
2332
  });
2162
2333
  }
2163
2334
  });
2164
- var MissedCallSchema = z4.enum([
2335
+ var MissedCallSchema = z5.enum([
2165
2336
  "retry_10m",
2166
2337
  "retry_30m",
2167
2338
  "retry_60m",
@@ -2173,31 +2344,31 @@ var MissedCallSchema = z4.enum([
2173
2344
  ]);
2174
2345
  var clock = (h) => h === 0 ? "midnight" : h === 12 ? "noon" : h < 12 ? `${h} am` : `${h - 12} pm`;
2175
2346
  var QUIET = ` Nothing rings from ${clock(NIGHT.from)} to ${clock(NIGHT.to)} your time; the count waits for morning.`;
2176
- var BrokerTuningSchema = z4.object({
2347
+ var BrokerTuningSchema = z5.object({
2177
2348
  /** 'none' = skip the spoken ack after a mapped answer (power users find it slow). */
2178
- ackVerbosity: z4.enum(["normal", "none"]).optional(),
2349
+ ackVerbosity: z5.enum(["normal", "none"]).optional(),
2179
2350
  /** How readily the mapper asks its one clarification: 'low' = only when truly
2180
2351
  * uninterpretable, 'high' = whenever not fully certain. */
2181
- clarifyEagerness: z4.enum(["low", "normal", "high"]).optional(),
2352
+ clarifyEagerness: z5.enum(["low", "normal", "high"]).optional(),
2182
2353
  /** The user's own shorthand: when they say `say`, they mean `mean`. */
2183
- phrasebook: z4.array(z4.object({ say: z4.string().min(1).max(60), mean: z4.string().min(1).max(120) })).max(24).optional(),
2354
+ phrasebook: z5.array(z5.object({ say: z5.string().min(1).max(60), mean: z5.string().min(1).max(120) })).max(24).optional(),
2184
2355
  /** The language calls are PLANNED in, when the account has chosen one (#1272). Absent —
2185
2356
  * which is every account today — means the agent's own words decide, per ask: a call
2186
2357
  * about an English ask opens in English. This is the only thing that overrides that,
2187
2358
  * and a live caller who switches language mid-call still outranks it (broker/lang.ts).
2188
2359
  * Set per user (no UI yet), like `voiceTuning`. */
2189
- language: z4.enum(["en", "es"]).optional()
2360
+ language: z5.enum(["en", "es"]).optional()
2190
2361
  });
2191
- var UserSettingsSchema = z4.object({
2192
- permissions: z4.object({
2193
- call: z4.boolean(),
2194
- banner: z4.boolean(),
2195
- push: z4.boolean()
2362
+ var UserSettingsSchema = z5.object({
2363
+ permissions: z5.object({
2364
+ call: z5.boolean(),
2365
+ banner: z5.boolean(),
2366
+ push: z5.boolean()
2196
2367
  }),
2197
2368
  /** LockedIn / Default / DateNight on screen; the stored words are unchanged on purpose —
2198
2369
  * they are an enum on a live column across every account, and the rename is a rename of
2199
2370
  * what people read (owner, 2026-09-30). */
2200
- sessionMode: z4.enum(["default", "all_calls", "silent"]),
2371
+ sessionMode: z5.enum(["default", "all_calls", "silent"]),
2201
2372
  /** `silentPush` lived here until #2813 and is now GONE, field and column both. It was kept as an
2202
2373
  * optional long after DateNight stopped reading it, on the theory that a phone on an older
2203
2374
  * bundle PATCHing the whole settings object would be REFUSED for sending a key we had stopped
@@ -2206,7 +2377,7 @@ var UserSettingsSchema = z4.object({
2206
2377
  * an old bundle's `silentPush` is accepted and ignored. Worth remembering before keeping the
2207
2378
  * next dead field for the same reason. */
2208
2379
  /** Opt-in (default false) to using your content to improve Paigy and train models. */
2209
- improveConsent: z4.boolean(),
2380
+ improveConsent: z5.boolean(),
2210
2381
  missedCall: MissedCallSchema.default("backoff_standard"),
2211
2382
  /** Where voice audio is processed. 'hosted' (default) = Paigy's voice services
2212
2383
  * (ElevenLabs TTS, faster-whisper STT, the call bot); 'on_device' = the phone
@@ -2214,17 +2385,17 @@ var UserSettingsSchema = z4.object({
2214
2385
  * Optional, NOT defaulted: a stale client PATCHing the full settings object
2215
2386
  * must not silently reset this privacy choice. Absent = leave unchanged on
2216
2387
  * write, 'hosted' on read (see store.ts). */
2217
- voiceMode: z4.enum(["hosted", "on_device"]).optional(),
2388
+ voiceMode: z5.enum(["hosted", "on_device"]).optional(),
2218
2389
  /** Talk — after you answer, the next step is read aloud (docs/clients/app/walk/design.md §6). ALWAYS ON until
2219
2390
  * turned off (owner, 2026-09-18, #2249): a setting, not a per-walk toggle. Optional, NOT
2220
2391
  * defaulted, for the same reason `voiceMode` is: a stale client PATCHing the full settings
2221
2392
  * object must not silently turn it back on. Absent = leave unchanged on write, true on
2222
2393
  * read (see store.ts). */
2223
- talk: z4.boolean().optional(),
2394
+ talk: z5.boolean().optional(),
2224
2395
  /** CALL DIAGNOSTICS (owner, 2026-10-01): the call report carries each listen and the bot's own
2225
2396
  * load timings. SERVER-SET, no UI — on for every account that existed on 2026-10-01, off for
2226
2397
  * newer ones (migration 20261001132859). Read-only here: the settings PATCH never writes it. */
2227
- callDiagnostics: z4.boolean().optional(),
2398
+ callDiagnostics: z5.boolean().optional(),
2228
2399
  /** Per-user ring budget (#603): calls per rolling day before further calls
2229
2400
  * degrade to banner. Absent = the global default (25). A number, never a
2230
2401
  * bypass — every account keeps a ceiling. No UI; set per user for testing. */
@@ -2232,7 +2403,7 @@ var UserSettingsSchema = z4.object({
2232
2403
  * payload['tuning'] (e.g. { silence_s: 3.5 } — a longer pause window for a
2233
2404
  * slower speaker). No API-side semantics; the bot resolves each key with its
2234
2405
  * own defaults. Set per user (no UI yet); absent = bot defaults. */
2235
- voiceTuning: z4.record(z4.string(), z4.union([z4.number(), z4.string()])).optional(),
2406
+ voiceTuning: z5.record(z5.string(), z5.union([z5.number(), z5.string()])).optional(),
2236
2407
  /** The user's IANA timezone (e.g. "America/Bogota"), recorded by the app — it is the
2237
2408
  * only party that knows it. REMINDERS are why it exists: "remind me at ten" becomes
2238
2409
  * an absolute `due_at` only if we know whose ten. Optional and never defaulted, for
@@ -2241,53 +2412,53 @@ var UserSettingsSchema = z4.object({
2241
2412
  * that failure reads as the reminder rail being unreliable rather than as a missing
2242
2413
  * setting. Absent = a spoken time can't be landed, so the reminder rides the next
2243
2414
  * call — honest about what we know. */
2244
- timezone: z4.string().min(1).max(64).optional(),
2415
+ timezone: z5.string().min(1).max(64).optional(),
2245
2416
  /** Rung-2 broker tuning (#381). Optional and NOT defaulted, same stale-client
2246
2417
  * clobber guard as voiceMode: absent = leave unchanged on write. */
2247
2418
  broker: BrokerTuningSchema.optional()
2248
2419
  });
2249
- var HistoryWorkSchema = z4.object({
2250
- id: z4.string(),
2251
- title: z4.string(),
2252
- state: z4.enum(["done", "cancelled"]),
2420
+ var HistoryWorkSchema = z5.object({
2421
+ id: z5.string(),
2422
+ title: z5.string(),
2423
+ state: z5.enum(["done", "cancelled"]),
2253
2424
  /** Who held it (`agent:<tokenId>` or `human:<userId>`). */
2254
- assignee: z4.string()
2425
+ assignee: z5.string()
2255
2426
  });
2256
- var HistoryEntrySchema = z4.union([
2257
- z4.object({ at: z4.string(), card: InboxItemSchema }),
2258
- z4.object({ at: z4.string(), work: HistoryWorkSchema })
2427
+ var HistoryEntrySchema = z5.union([
2428
+ z5.object({ at: z5.string(), card: InboxItemSchema }),
2429
+ z5.object({ at: z5.string(), work: HistoryWorkSchema })
2259
2430
  ]);
2260
- var HistoryPageSchema = z4.object({
2261
- entries: z4.array(HistoryEntrySchema),
2262
- next: z4.string().nullable()
2431
+ var HistoryPageSchema = z5.object({
2432
+ entries: z5.array(HistoryEntrySchema),
2433
+ next: z5.string().nullable()
2263
2434
  });
2264
2435
  var ACTIVITY_LINES = 2;
2265
2436
  var ACTIVITY_LINE_MAX = 80;
2266
- var AgentActivitySchema = z4.object({
2437
+ var AgentActivitySchema = z5.object({
2267
2438
  /** Oldest first, so the newest line is last — the one that replaces in place. */
2268
- lines: z4.array(z4.string().max(ACTIVITY_LINE_MAX)).max(ACTIVITY_LINES),
2439
+ lines: z5.array(z5.string().max(ACTIVITY_LINE_MAX)).max(ACTIVITY_LINES),
2269
2440
  /** When the harness observed this tail. Its own timestamp, not the heartbeat's: a beat
2270
2441
  * that carries an UNCHANGED tail must not make a stalled agent look like it just moved. */
2271
- at: z4.string().datetime()
2442
+ at: z5.string().datetime()
2272
2443
  });
2273
- var ConnectionSummarySchema = z4.object({
2444
+ var ConnectionSummarySchema = z5.object({
2274
2445
  /** The connection = the agent's token id (used to address a request). */
2275
- id: z4.string(),
2446
+ id: z5.string(),
2276
2447
  /** The credential kind: "device" = a paired machine (mint-only — it hosts and mints, it
2277
2448
  * never talks); "agent" = an identity that sends. The roster and devices surfaces split
2278
2449
  * on this. Optional/absent reads as "agent" (a row predating the kind column). See
2279
2450
  * docs/server/tokens/devices-vs-agents-design.md. */
2280
- kind: z4.enum(["device", "agent"]).optional(),
2451
+ kind: z5.enum(["device", "agent"]).optional(),
2281
2452
  /** For an agent, the token id of the DEVICE that minted it — so agents group under their
2282
2453
  * machine, and revoking a device cascades to them. Null on devices, and on unlinked
2283
2454
  * agents (phone-launched, provider-managed, or minted before the link existed). */
2284
- mintedByDevice: z4.string().nullable().optional(),
2285
- device: z4.string().nullable(),
2455
+ mintedByDevice: z5.string().nullable().optional(),
2456
+ device: z5.string().nullable(),
2286
2457
  /** The agent's display name (the single pairing name). */
2287
- name: z4.string(),
2458
+ name: z5.string(),
2288
2459
  /** For a managed connection, the provider key (e.g. "cma") that agentOrigin maps to a
2289
2460
  * label; null for a local connection. Sourced from the token's provider, not the name. */
2290
- provider: z4.string().nullable(),
2461
+ provider: z5.string().nullable(),
2291
2462
  /** The pairing's assigned voice (#462); null = the default voice. */
2292
2463
  voice: VoiceKeySchema.nullable(),
2293
2464
  /** The LOUDEST this agent may ever reach you — a ceiling on `NOTIFY_LADDER`, set by the
@@ -2298,34 +2469,34 @@ var ConnectionSummarySchema = z4.object({
2298
2469
  * every surface at once and outranks even `sessionMode: all_calls` — a mode the user
2299
2470
  * set once must not overrule a rule they set about one agent. */
2300
2471
  reach: NotifyLevelSchema.nullable().optional(),
2301
- createdAt: z4.string().datetime(),
2472
+ createdAt: z5.string().datetime(),
2302
2473
  /** Most recent notification on this connection, either direction. Null = no contact yet.
2303
2474
  * Drives the agents-page recency grouping (Today / This week / …). */
2304
- lastContactAt: z4.string().datetime().nullable(),
2475
+ lastContactAt: z5.string().datetime().nullable(),
2305
2476
  /** Last presence heartbeat from a running agent process (POST /api/presence) — the
2306
2477
  * desktop app while open. Null = never seen; stale = offline. */
2307
- lastSeenAt: z4.string().datetime().nullable().optional(),
2478
+ lastSeenAt: z5.string().datetime().nullable().optional(),
2308
2479
  /** WORKING, NOT JUST CONNECTED (owner, 2026-09-30): the last time the agent itself acted on one of
2309
2480
  * its Goals — took its lease or recorded an operation (`tokens.last_worked_at`). Within
2310
2481
  * `WORKING_MS` it is working; otherwise it is connected but idle. Null = not seen working yet. */
2311
- lastWorkedAt: z4.string().datetime().nullable().optional(),
2482
+ lastWorkedAt: z5.string().datetime().nullable().optional(),
2312
2483
  /** The oldest of its Goals that is `ready` for it — work handed to it that nobody has started.
2313
2484
  * With no work of its own for `WORKING_MS`, an agent sitting on this is not taking its work. */
2314
- oldestReadyAt: z4.string().datetime().nullable().optional(),
2485
+ oldestReadyAt: z5.string().datetime().nullable().optional(),
2315
2486
  /** What a live desktop can run (docs/clients/desktop/companion.md §2.2), advertised on its heartbeat:
2316
2487
  * harness availabilities + granted workspaces — the option set the phone's
2317
2488
  * "new session" sheet offers. Absent for ordinary MCP agents. */
2318
- runtime: z4.object({
2489
+ runtime: z5.object({
2319
2490
  /** The @paigy/harness this host is running — a machine the self-update has not reached
2320
2491
  * shows its age here (`apps/desktop/src/update.ts`). */
2321
- version: z4.string().optional(),
2322
- harnesses: z4.array(z4.object({ name: z4.string(), label: z4.string(), status: z4.string() })).optional(),
2323
- workspaces: z4.array(z4.string()).optional(),
2492
+ version: z5.string().optional(),
2493
+ harnesses: z5.array(z5.object({ name: z5.string(), label: z5.string(), status: z5.string() })).optional(),
2494
+ workspaces: z5.array(z5.string()).optional(),
2324
2495
  /** THE GIT REPOS IN THOSE FOLDERS (2026-10-01, Goal 26982211): each granted folder that is a
2325
2496
  * repo, and each repo directly inside one, with its `origin` remote. A session started for
2326
2497
  * work on `mauurda/paigy` opens in that repo rather than the folder above it, where the repo's
2327
2498
  * own AGENTS.md is never read (`workspaceForRepo`). Absent on hosts that predate it. */
2328
- repos: z4.array(z4.object({ path: z4.string(), remote: z4.string() })).optional()
2499
+ repos: z5.array(z5.object({ path: z5.string(), remote: z5.string() })).optional()
2329
2500
  }).optional(),
2330
2501
  /** The tail of this agent's working log, when a harness is driving it — the agent page's
2331
2502
  * live strip. Absent for anything the desktop harness isn't running (a hatched identity
@@ -2334,173 +2505,156 @@ var ConnectionSummarySchema = z4.object({
2334
2505
  activity: AgentActivitySchema.optional(),
2335
2506
  /** True = a provider-managed agent running in the provider's cloud (e.g. Anthropic CMA);
2336
2507
  * false = a local MCP connection running on the user's computer (Claude Code/Codex/…). */
2337
- managed: z4.boolean()
2508
+ managed: z5.boolean()
2338
2509
  });
2339
- var LedgerItemSchema = z4.object({ id: z4.string(), parentId: z4.string(), title: z4.string(), createdAt: z4.string() });
2340
- var AgentLedgerSchema = z4.object({
2510
+ var LedgerItemSchema = z5.object({ id: z5.string(), parentId: z5.string(), title: z5.string(), createdAt: z5.string() });
2511
+ var AgentLedgerSchema = z5.object({
2341
2512
  /** Null when the agent has not named itself yet — never a placeholder (owner, 2026-10-01). */
2342
- agent: z4.object({ id: z4.string(), name: z4.string().nullable(), revokedAt: z4.string().nullable() }),
2513
+ agent: z5.object({ id: z5.string(), name: z5.string().nullable(), revokedAt: z5.string().nullable() }),
2343
2514
  /** Its own questions you have not answered. */
2344
- asks: z4.array(LedgerItemSchema),
2515
+ asks: z5.array(LedgerItemSchema),
2345
2516
  /** Its questions you answered that nobody acted on — still owed to somebody. */
2346
- answered: z4.array(LedgerItemSchema),
2517
+ answered: z5.array(LedgerItemSchema),
2347
2518
  /** Requests you sent it that it never took. */
2348
- requests: z4.array(LedgerItemSchema),
2349
- goals: z4.array(z4.object({ id: z4.string(), outcome: z4.string(), state: z4.string() })),
2350
- callbacks: z4.array(z4.object({ id: z4.string(), parentId: z4.string(), trigger: z4.string(), note: z4.string(), dueAt: z4.string().nullable() }))
2519
+ requests: z5.array(LedgerItemSchema),
2520
+ goals: z5.array(z5.object({ id: z5.string(), outcome: z5.string(), state: z5.string() })),
2521
+ callbacks: z5.array(z5.object({ id: z5.string(), parentId: z5.string(), trigger: z5.string(), note: z5.string(), dueAt: z5.string().nullable() }))
2351
2522
  });
2352
- var ReassignResultSchema = z4.object({
2353
- moved: z4.object({ asks: z4.number(), answered: z4.number(), requests: z4.number(), goals: z4.number(), callbacks: z4.number() }),
2354
- parentId: z4.string().nullable()
2523
+ var ReassignResultSchema = z5.object({
2524
+ moved: z5.object({ asks: z5.number(), answered: z5.number(), requests: z5.number(), goals: z5.number(), callbacks: z5.number() }),
2525
+ parentId: z5.string().nullable()
2355
2526
  });
2356
- var MoveRingSchema = z4.enum(["home", "travels", "retired", "quarantined"]);
2357
- var MoveSchema = z4.object({
2358
- id: z4.string(),
2359
- /** The reusable question, as distill normalized it. */
2360
- question: z4.string(),
2361
- /** The operative ruling. Editable by the user (PATCH) — which resets the ledger. */
2362
- answer: z4.string(),
2363
- /** The user's stated reason, when they gave one. Null = inherently narrow: the judge is
2364
- * told so, and the ruling only derives essentially the same question in the same scope. */
2365
- rationale: z4.string().nullable(),
2366
- /** Where the ruling lives: a repo/workspace, or 'global'. */
2367
- scope: z4.string(),
2368
- ring: MoveRingSchema,
2369
- /** True = the user pinned it with `always` (travel granted by hand, not by evidence). */
2370
- pinned: z4.boolean(),
2371
- /** True = a pin the user placed was BROKEN by later counter-evidence. Surfaced so the
2372
- * break is visible instead of a pin silently disappearing. */
2373
- pinBroken: z4.boolean(),
2374
- /** When the ruling was distilled. */
2375
- learnedAt: z4.string(),
2376
- /** Last time it answered an ask. Null = never fired. */
2377
- lastUsedAt: z4.string().nullable(),
2378
- /** How many asks it has answered. Instrumentation — deliberately NOT an input to the
2379
- * evidence curve: firing says the question keeps arising, not that the ruling is right. */
2380
- usedCount: z4.number(),
2381
- /** Ledger: outcomes that said it held up. Saturating — the tenth is worth almost nothing. */
2382
- confirms: z4.number(),
2383
- /** Ledger: contradictions, in signal units (a full override = 1, weaker signals less).
2384
- * Linear and priced above the entire confirmation budget, so any full counter wins. */
2385
- counters: z4.number(),
2386
- /** The agent that asked the question this move came from, when known. Null for a move
2387
- * distilled from a clarify ruling (those carry no agent) or one whose source rows are gone. */
2388
- learnedFrom: z4.object({ id: z4.string(), name: z4.string() }).nullable()
2527
+ var LessonStateSchema = z5.enum(["active", "proposed", "retired"]);
2528
+ var LessonViewSchema = z5.object({
2529
+ id: z5.string(),
2530
+ text: z5.string(),
2531
+ state: LessonStateSchema,
2532
+ /** The Goal it is scoped to; null = the whole account. */
2533
+ scopeGoalId: z5.string().nullable(),
2534
+ goalTitle: z5.string().nullable(),
2535
+ version: z5.number(),
2536
+ pinned: z5.boolean(),
2537
+ /** When the person last wrote its text themselves. */
2538
+ editedAt: z5.string().nullable(),
2539
+ createdAt: z5.string(),
2540
+ updatedAt: z5.string(),
2541
+ /** The Entries it came from, oldest first; `words` is null when an Entry has none to show (sealed). */
2542
+ sources: z5.array(z5.object({ entryId: z5.string(), words: z5.string().nullable(), at: z5.string() }))
2389
2543
  });
2390
- var QueueQuestionSchema = z4.object({
2544
+ var QueueQuestionSchema = z5.object({
2391
2545
  /** The decision need's id — what an answer is accepted against. */
2392
- id: z4.string(),
2546
+ id: z5.string(),
2393
2547
  /** The words that were asked, from the request Entry that asked them. */
2394
- question: z4.string(),
2548
+ question: z5.string(),
2395
2549
  /** Where it was asked — which is where the ruling goes (`POST /api/entries`). Null only
2396
2550
  * for a need whose request Entry is carried by no interactive Delivery, which nothing
2397
2551
  * can answer. */
2398
- deliveryId: z4.string().nullable().default(null),
2552
+ deliveryId: z5.string().nullable().default(null),
2399
2553
  /** The Entry the ruling is about. */
2400
- aboutId: z4.string().nullable().default(null),
2554
+ aboutId: z5.string().nullable().default(null),
2401
2555
  /** Empty for a free-text question. */
2402
- options: z4.array(OptionSchema).default([]),
2403
- select: z4.enum(["one", "many", "rank", "confirm", "text"]).default("text"),
2404
- askedAt: z4.string(),
2556
+ options: z5.array(OptionSchema).default([]),
2557
+ select: z5.enum(["one", "many", "rank", "confirm", "text"]).default("text"),
2558
+ askedAt: z5.string(),
2405
2559
  /** Null while the question is open — which is how the page tells the two apart. */
2406
- answeredAt: z4.string().nullable().default(null),
2560
+ answeredAt: z5.string().nullable().default(null),
2407
2561
  /** The ruling in the person's own words, from the contribution that replied — not the
2408
2562
  * option id, which is not something anyone reads back. Null while it is open, and null
2409
2563
  * for a settled question whose reply carried nothing readable. */
2410
- answer: z4.string().nullable().default(null),
2564
+ answer: z5.string().nullable().default(null),
2411
2565
  /** The Goal this question belongs to — a step knows its Goal on its own, not only through
2412
2566
  * an `InboxItem`'s `communication.goalIds[0]` (docs/clients/app/walk/design.md §12 item 3).
2413
2567
  * READ BY `apps/client/src/walk/order.ts`, which stamps it onto every `WalkStep`: the walk's
2414
2568
  * order, its route, home's trees and the list of steps all take a step's Goal from here, so
2415
2569
  * this is the field they agree through rather than each re-deriving it from the row it
2416
2570
  * arrived under. Required because the API projects it on every need it sends. */
2417
- goalId: z4.string(),
2571
+ goalId: z5.string(),
2418
2572
  /** True only while an unmet START gate holds the Goal — a Goal that merely waits to
2419
2573
  * *finish* does not stop a person from answering (owner, 2026-09-16: "per need gate from
2420
2574
  * the API"; §4's dashed node). Not the same fact as `QueueItem.blocked`, which counts any
2421
2575
  * gate at all. */
2422
- blocked: z4.boolean().default(false)
2576
+ blocked: z5.boolean().default(false)
2423
2577
  });
2424
- var QueueReplySchema = z4.object({
2578
+ var QueueReplySchema = z5.object({
2425
2579
  /** The card this note was (`deliveryId:requestEntryId`, minted by the server like every card
2426
2580
  * id) — so the phone can tell a reply it just sent from one the queue already carries, and the
2427
2581
  * walk can name it in its zoom. */
2428
- id: z4.string(),
2582
+ id: z5.string(),
2429
2583
  /** The Goal the note is on. */
2430
- goalId: z4.string(),
2584
+ goalId: z5.string(),
2431
2585
  /** What the note said. */
2432
- note: z4.string(),
2586
+ note: z5.string(),
2433
2587
  /** Where it was carried — where a second reply goes (`POST /api/entries`, #2252). */
2434
- deliveryId: z4.string(),
2435
- requestEntryId: z4.string(),
2436
- askedAt: z4.string(),
2588
+ deliveryId: z5.string(),
2589
+ requestEntryId: z5.string(),
2590
+ askedAt: z5.string(),
2437
2591
  /** When the person last replied — the window's start. */
2438
- repliedAt: z4.string(),
2592
+ repliedAt: z5.string(),
2439
2593
  /** The person's latest words about it; null when there is nothing readable in them. */
2440
- reply: z4.string().nullable()
2594
+ reply: z5.string().nullable()
2441
2595
  });
2442
- var QueueItemSchema = z4.object({
2443
- id: z4.string(),
2596
+ var QueueItemSchema = z5.object({
2597
+ id: z5.string(),
2444
2598
  /** One-line headline — the first sentence of the outcome. */
2445
- title: z4.string(),
2599
+ title: z5.string(),
2446
2600
  /** The outcome in full, verbatim: the person's own words are what an assignee sees. */
2447
- intent: z4.string(),
2601
+ intent: z5.string(),
2448
2602
  /** `ready` | `active` | `waiting` | `done` | `cancelled`, straight off the Goal. */
2449
- state: z4.string(),
2603
+ state: z5.string(),
2450
2604
  /** Who holds it (a participant ref); null when nobody does yet. */
2451
- assignee: z4.string().nullable().default(null),
2605
+ assignee: z5.string().nullable().default(null),
2452
2606
  /** What the agent last said it was doing; null if it has said nothing. */
2453
- progress: z4.string().nullable().default(null),
2607
+ progress: z5.string().nullable().default(null),
2454
2608
  /** HOME'S LINE FOR THAT NOTE (owner, 2026-09-23): a few plain words one read wrote from `progress`,
2455
2609
  * served only while it was written for the current note. Null means show the Goal's name. */
2456
- progressLine: z4.string().nullable().optional(),
2457
- reviewPending: z4.boolean().default(false),
2458
- dueAt: z4.string().nullable().default(null),
2610
+ progressLine: z5.string().nullable().optional(),
2611
+ reviewPending: z5.boolean().default(false),
2612
+ dueAt: z5.string().nullable().default(null),
2459
2613
  /** WHEN ITS OWNER SAID DONE WHILE CHILDREN WERE OPEN (#2704): its own work is finished and it closes
2460
2614
  * with its last open child. Null otherwise; optional, so hand-built queues need not spell it. */
2461
- finishedAt: z4.string().nullable().optional(),
2615
+ finishedAt: z5.string().nullable().optional(),
2462
2616
  /** The Goal this one was opened under; null at the root. */
2463
- parentGoalId: z4.string().nullable().default(null),
2617
+ parentGoalId: z5.string().nullable().default(null),
2464
2618
  /** Goals opened under this one — only those the same list holds. */
2465
- childGoalIds: z4.array(z4.string()).default([]),
2619
+ childGoalIds: z5.array(z5.string()).default([]),
2466
2620
  /** Goals this one waits on (start or finish gates). */
2467
- dependencyGoalIds: z4.array(z4.string()).default([]),
2621
+ dependencyGoalIds: z5.array(z5.string()).default([]),
2468
2622
  /** True while any gate is on a Goal that is not done — the walk draws it dashed. */
2469
- blocked: z4.boolean().default(false),
2623
+ blocked: z5.boolean().default(false),
2470
2624
  /** Its questions: every OPEN one, and at most ten settled, newest settled first
2471
2625
  * (20260929133308) — the page decides which of them to show. NOT the whole set: `asked` and
2472
2626
  * `answered` are, and a settled one's words are a line (280 characters), its body read when the
2473
2627
  * question is opened. */
2474
- questions: z4.array(QueueQuestionSchema).default([]),
2628
+ questions: z5.array(QueueQuestionSchema).default([]),
2475
2629
  /** HOW MANY QUESTIONS THIS WORK HAS ASKED, and how many are answered — the Goal's own totals,
2476
2630
  * bounded at 100 server-side. A tally counted off `questions` is a wrong number that looks
2477
2631
  * right once the cap bites (`walk/trees.ts` `tallyOf`). Optional, and defaulted from the array
2478
2632
  * by the projection, so hand-built queues (fixtures, the demo) need not spell them. */
2479
- asked: z4.number().optional(),
2480
- answered: z4.number().optional(),
2633
+ asked: z5.number().optional(),
2634
+ answered: z5.number().optional(),
2481
2635
  /** Every note on it the person replied to (`QueueReplySchema`) — the page decides which to show.
2482
2636
  * Optional, not defaulted: absent is none, and every hand-built queue (fixtures, the demo) need
2483
2637
  * not spell an empty list. */
2484
- replies: z4.array(QueueReplySchema).optional(),
2638
+ replies: z5.array(QueueReplySchema).optional(),
2485
2639
  /** The repository or project identifier this Goal belongs to (#2280), null if untracked. */
2486
- repo: z4.string().nullable().optional(),
2487
- createdAt: z4.string(),
2488
- updatedAt: z4.string().nullable().default(null),
2640
+ repo: z5.string().nullable().optional(),
2641
+ createdAt: z5.string(),
2642
+ updatedAt: z5.string().nullable().default(null),
2489
2643
  /** When its owner last SAID something about it (`goals.last_progress_at`, written by every
2490
2644
  * `update_goal` that changes `progress`). `updatedAt` moves for reasons nobody chose — a
2491
2645
  * state recomputed, a review flag — so it cannot tell work in hand from work gone quiet. */
2492
- lastProgressAt: z4.string().nullable().optional(),
2646
+ lastProgressAt: z5.string().nullable().optional(),
2493
2647
  /** THE GOAL'S NEWEST WORD, FROM EITHER SIDE (owner, 2026-09-27): the newest Entry on it, of any
2494
2648
  * kind — what the person added ("Add to this"), their reply, the agent's ask or its progress
2495
2649
  * note. A progress note is an Entry, so this is already the newer of the two: the person's note
2496
2650
  * shows the moment it is written, and the agent's reply or next note replaces it by being newer.
2497
2651
  * `said` is bounded to 280 characters server-side (a line, not the conversation). Null when the
2498
2652
  * Goal carries no readable Entry; optional, so hand-built queues need not spell it. */
2499
- latest: z4.object({
2500
- from: z4.enum(["person", "agent"]),
2501
- said: z4.string(),
2502
- at: z4.string(),
2503
- entryId: z4.string()
2653
+ latest: z5.object({
2654
+ from: z5.enum(["person", "agent"]),
2655
+ said: z5.string(),
2656
+ at: z5.string(),
2657
+ entryId: z5.string()
2504
2658
  }).nullable().optional(),
2505
2659
  /** WHEN THIS PERSON LAST PUT A HAND ON IT THEMSELVES (owner, Paigy Goal 16d18f51, 2026-09-30):
2506
2660
  * the newest Entry on the Goal they wrote, of any kind — a line they added, a reply to a note, an
@@ -2513,36 +2667,51 @@ var QueueItemSchema = z4.object({
2513
2667
  * minute after the person speaks erases their instant from it, and the durable traces the client
2514
2668
  * can see (`replies`, `questions[].answeredAt`) miss a spontaneous note entirely — a `request`
2515
2669
  * Entry with no `about_id` is in neither. */
2516
- lastPersonAt: z4.string().nullable().optional()
2670
+ lastPersonAt: z5.string().nullable().optional(),
2671
+ /** WHAT THIS ROW IS, IN TWELVE CHARACTERS (#2928) — the hash of every other field on it, stamped
2672
+ * by the one projection that builds the row (`apps/api/src/goal/queue.ts`). It is how the
2673
+ * incremental read knows a row has not moved: the phone echoes back the revs it holds
2674
+ * (`POST /api/goals/changes`) and is sent only the rows whose rev differs.
2675
+ *
2676
+ * IT IS THE PAYLOAD'S OWN HASH, NEVER A STAMP ON THE WORK. Nothing here reasons about which
2677
+ * writes change which field — the comparison is over the bytes the phone is holding, so a fact
2678
+ * the row shows that no `updated_at` moves for (a lease lapsing, a dependency's state, a
2679
+ * sibling appearing in `childGoalIds`) cannot go unnoticed. Optional because a hand-built
2680
+ * queue (a fixture, the demo) spells none, and a row with no rev is simply always re-sent. */
2681
+ rev: z5.string().optional()
2682
+ });
2683
+ var QueueDeltaSchema = z5.object({
2684
+ ids: z5.array(z5.string()),
2685
+ items: z5.array(QueueItemSchema)
2517
2686
  });
2518
2687
  var COLD_AFTER_MS = 3 * 24 * 60 * 60 * 1e3;
2519
- var NoteSourceSchema = z4.enum(["app", "call"]);
2520
- var NoteStatusSchema = z4.enum(["open", "assigned", "in_progress", "done"]);
2521
- var NoteRepeatSchema = z4.enum(["once", "until_done"]);
2522
- var DecisionSchema = z4.object({
2523
- id: z4.string(),
2688
+ var NoteSourceSchema = z5.enum(["app", "call"]);
2689
+ var NoteStatusSchema = z5.enum(["open", "assigned", "in_progress", "done"]);
2690
+ var NoteRepeatSchema = z5.enum(["once", "until_done"]);
2691
+ var DecisionSchema = z5.object({
2692
+ id: z5.string(),
2524
2693
  /** The note this decision refines; null = recorded on a bare thread (the
2525
2694
  * extensibility seam — any conversation can accrue decisions). */
2526
- noteId: z4.string().nullable(),
2695
+ noteId: z5.string().nullable(),
2527
2696
  /** What was ambiguous — the broker's (or the user's own) question. */
2528
- question: z4.string(),
2697
+ question: z5.string(),
2529
2698
  /** The user's ruling; null while the question is open. */
2530
- answer: z4.string().nullable(),
2531
- decidedAt: z4.string().nullable(),
2532
- createdAt: z4.string()
2699
+ answer: z5.string().nullable(),
2700
+ decidedAt: z5.string().nullable(),
2701
+ createdAt: z5.string()
2533
2702
  });
2534
- var NoteSchema = z4.object({
2535
- id: z4.string(),
2703
+ var NoteSchema = z5.object({
2704
+ id: z5.string(),
2536
2705
  /** One-line headline (broker-titled; deterministic floor). */
2537
- title: z4.string(),
2706
+ title: z5.string(),
2538
2707
  /** The original intent, verbatim — assignees always see the user's own words. */
2539
- intent: z4.string(),
2708
+ intent: z5.string(),
2540
2709
  source: NoteSourceSchema,
2541
2710
  status: NoteStatusSchema,
2542
2711
  /** Who it was assigned to (a participant ref, 'agent:<tokenId>'); null = unassigned. */
2543
- assignee: z4.string().nullable(),
2712
+ assignee: z5.string().nullable(),
2544
2713
  /** The request thread minted at assignment; null until assigned. */
2545
- parentId: z4.string().nullable(),
2714
+ parentId: z5.string().nullable(),
2546
2715
  /** REMINDERS (docs/model/notes/reminders-design.md): the NOT-BEFORE this becomes eligible to ride a
2547
2716
  * call — never a deadline. It only ever comes from the user's own words, so when it
2548
2717
  * passes Paigy rings ONCE (#1293, owner 2026-08-26: a time said out loud is consent to
@@ -2551,149 +2720,154 @@ var NoteSchema = z4.object({
2551
2720
  // Defaulted, not required: a Note from an API deploy older than the reminders
2552
2721
  // migration has none of these, and the defaults ARE what it means — no not-before,
2553
2722
  // one ride, never ridden. Parsing must not fail across a rolling deploy.
2554
- dueAt: z4.string().nullable().default(null),
2723
+ dueAt: z5.string().nullable().default(null),
2555
2724
  repeat: NoteRepeatSchema.default("once"),
2556
2725
  /** How many calls have already carried it — the fatigue cap counts rides, not days. */
2557
- rides: z4.number().int().default(0),
2558
- lastRideAt: z4.string().nullable().default(null),
2559
- createdAt: z4.string()
2726
+ rides: z5.number().int().default(0),
2727
+ lastRideAt: z5.string().nullable().default(null),
2728
+ createdAt: z5.string()
2560
2729
  });
2561
- var TriageItemSchema = z4.object({
2562
- noteId: z4.string(),
2730
+ var TriageItemSchema = z5.object({
2731
+ noteId: z5.string(),
2563
2732
  /** The note's headline at run time. */
2564
- title: z4.string(),
2733
+ title: z5.string(),
2565
2734
  /** WHY, in one short human line, evidence first — this is read on a phone underneath
2566
2735
  * the note's title: "no movement in 34 days", "worked 3 notes in this repo this week".
2567
2736
  * Never a model's reasoning transcript, never an id. */
2568
- why: z4.string()
2737
+ why: z5.string()
2569
2738
  });
2570
- var TriageAssignmentSchema = z4.object({
2739
+ var TriageAssignmentSchema = z5.object({
2571
2740
  /** The agent's token id — what `dispatchNote` resolves and what a request is addressed to. */
2572
- agent: z4.string(),
2741
+ agent: z5.string(),
2573
2742
  /** Its display name at run time (the name on the hatchling's card). Denormalized for the
2574
2743
  * same reason as `title`: the card must render from the proposal alone. */
2575
- agentName: z4.string(),
2576
- notes: z4.array(TriageItemSchema)
2744
+ agentName: z5.string(),
2745
+ notes: z5.array(TriageItemSchema)
2577
2746
  });
2578
- var TriageStatusSchema = z4.enum(["open", "superseded", "dismissed"]);
2579
- var SubmitTriageSchema = z4.object({
2747
+ var TriageStatusSchema = z5.enum(["open", "superseded", "dismissed"]);
2748
+ var SubmitTriageSchema = z5.object({
2580
2749
  /** Which runtime judged: "ollama" (inference never left the machine) or a harness the
2581
2750
  * user already runs under their own credentials ("claude" / "codex" / "agy"). Recorded
2582
2751
  * so the phone can say where the content went — an unattributed privacy claim is worth
2583
2752
  * nothing, and #1106's promise is precisely "Paigy's servers never see this". */
2584
- provider: z4.string().min(1).max(60),
2753
+ provider: z5.string().min(1).max(60),
2585
2754
  /** The concrete model when the provider names one (an ollama tag); null otherwise. */
2586
- model: z4.string().max(200).nullable().optional(),
2755
+ model: z5.string().max(200).nullable().optional(),
2587
2756
  /** How many open notes the run actually looked at — the denominator on the phone
2588
2757
  * ("6 of 50"), and the honest answer to "did it read the whole queue?". */
2589
- reviewed: z4.number().int().min(0).max(1e4).default(0),
2590
- close: z4.array(TriageItemSchema).max(200).default([]),
2591
- stale: z4.array(TriageItemSchema).max(200).default([]),
2592
- assign: z4.array(TriageAssignmentSchema).max(50).default([])
2758
+ reviewed: z5.number().int().min(0).max(1e4).default(0),
2759
+ close: z5.array(TriageItemSchema).max(200).default([]),
2760
+ stale: z5.array(TriageItemSchema).max(200).default([]),
2761
+ assign: z5.array(TriageAssignmentSchema).max(50).default([])
2593
2762
  });
2594
2763
  var TriageProposalSchema = SubmitTriageSchema.extend({
2595
- id: z4.string(),
2596
- runAt: z4.string(),
2764
+ id: z5.string(),
2765
+ runAt: z5.string(),
2597
2766
  status: TriageStatusSchema,
2598
- model: z4.string().nullable().default(null)
2767
+ model: z5.string().nullable().default(null)
2599
2768
  });
2600
- var AcceptTriageSchema = z4.discriminatedUnion("group", [
2601
- z4.object({ group: z4.literal("close"), noteIds: z4.array(z4.string()).max(200).optional() }),
2602
- z4.object({ group: z4.literal("stale"), noteIds: z4.array(z4.string()).max(200).optional() }),
2603
- z4.object({
2604
- group: z4.literal("assign"),
2605
- agent: z4.string().min(1),
2606
- noteIds: z4.array(z4.string()).max(200).optional()
2769
+ var AcceptTriageSchema = z5.discriminatedUnion("group", [
2770
+ z5.object({ group: z5.literal("close"), noteIds: z5.array(z5.string()).max(200).optional() }),
2771
+ z5.object({ group: z5.literal("stale"), noteIds: z5.array(z5.string()).max(200).optional() }),
2772
+ z5.object({
2773
+ group: z5.literal("assign"),
2774
+ agent: z5.string().min(1),
2775
+ noteIds: z5.array(z5.string()).max(200).optional()
2607
2776
  })
2608
2777
  ]);
2609
- var AcceptTriageResultSchema = z4.object({
2610
- accepted: z4.array(z4.string()),
2611
- failed: z4.array(z4.object({ noteId: z4.string(), reason: z4.string() }))
2778
+ var AcceptTriageResultSchema = z5.object({
2779
+ accepted: z5.array(z5.string()),
2780
+ failed: z5.array(z5.object({ noteId: z5.string(), reason: z5.string() }))
2612
2781
  });
2613
- var DeliveryModeSchema = z4.enum(["poll", "self_hosted"]);
2782
+ var DeliveryModeSchema = z5.enum(["poll", "self_hosted"]);
2614
2783
  var WAKE_EVENT = "wake";
2615
2784
  var wakeChannel = (tokenId) => `wake:${tokenId}`;
2616
- var RegisterDeliverySchema = z4.object({ mode: DeliveryModeSchema });
2617
- var OAuthStartSchema = z4.object({
2618
- provider: z4.enum(["cma"]),
2619
- returnTo: z4.string().min(1)
2785
+ var RegisterDeliverySchema = z5.object({ mode: DeliveryModeSchema });
2786
+ var OAuthStartSchema = z5.object({
2787
+ provider: z5.enum(["cma"]),
2788
+ returnTo: z5.string().min(1)
2620
2789
  });
2621
- var DeliveryConfigSchema = z4.object({
2622
- tokenId: z4.string(),
2790
+ var DeliveryConfigSchema = z5.object({
2791
+ tokenId: z5.string(),
2623
2792
  mode: DeliveryModeSchema,
2624
2793
  /** null when the deployment has no anon key configured. `self_hosted` is then REFUSED
2625
2794
  * (503 `self_hosted_unavailable`) rather than registered, so a self_hosted config always
2626
2795
  * carries credentials; only a `poll` registration can come back with null here. */
2627
- realtime: z4.object({ url: z4.string(), anonKey: z4.string() }).nullable()
2796
+ realtime: z5.object({ url: z5.string(), anonKey: z5.string() }).nullable()
2628
2797
  });
2629
- var HostDecisionSchema = z4.object({
2798
+ var HostDecisionSchema = z5.object({
2630
2799
  /** The agent's token id: the row's `recipient`. */
2631
- agent: z4.string().uuid(),
2632
- decision: z4.enum(["stood_back", "took_over"]),
2800
+ agent: z5.string().uuid(),
2801
+ decision: z5.enum(["stood_back", "took_over"]),
2633
2802
  /** The work it was about: the Goal `claim_goal` would hand that agent next. */
2634
- goalId: z4.string().uuid().nullable().optional(),
2803
+ goalId: z5.string().uuid().nullable().optional(),
2635
2804
  /** When the server last heard from the agent, as the host read it: the presence it stood back for. */
2636
- seenAt: z4.string().datetime().nullable().optional(),
2805
+ seenAt: z5.string().datetime().nullable().optional(),
2637
2806
  /** When that work last moved (`claimable.since` on `check_replies`), the fact the bound is judged on. */
2638
- since: z4.string().datetime().nullable().optional(),
2807
+ since: z5.string().datetime().nullable().optional(),
2639
2808
  /** What the host said, in its log's own words: why it stood back, or what the take-over did. */
2640
- said: z4.string().max(300).optional()
2809
+ said: z5.string().max(300).optional()
2641
2810
  });
2642
- var WakeNudgeSchema = z4.object({
2643
- kind: z4.enum(["reply", "request", "callback"]),
2644
- notificationId: z4.string().optional(),
2645
- parentId: z4.string()
2811
+ var WakeNudgeSchema = z5.object({
2812
+ kind: z5.enum(["reply", "request", "callback"]),
2813
+ notificationId: z5.string().optional(),
2814
+ parentId: z5.string()
2646
2815
  });
2647
- var PairingStatusSchema = z4.enum(["pending", "approved", "denied", "expired"]);
2648
- var DeviceCodeSchema = z4.object({
2649
- device_code: z4.string(),
2650
- user_code: z4.string(),
2651
- verification_uri: z4.string().url(),
2652
- verification_uri_complete: z4.string().url(),
2653
- interval: z4.number(),
2654
- expires_in: z4.number()
2816
+ var PairingStatusSchema = z5.enum(["pending", "approved", "denied", "expired"]);
2817
+ var DeviceCodeSchema = z5.object({
2818
+ device_code: z5.string(),
2819
+ user_code: z5.string(),
2820
+ verification_uri: z5.string().url(),
2821
+ verification_uri_complete: z5.string().url(),
2822
+ interval: z5.number(),
2823
+ expires_in: z5.number()
2655
2824
  });
2656
- var DeviceInfoSchema = z4.object({
2657
- code: z4.string(),
2825
+ var DeviceInfoSchema = z5.object({
2826
+ code: z5.string(),
2658
2827
  /** The agent's suggested name (from /device/code) — shown on the approval screen,
2659
2828
  * pre-filling the name field the human can edit. */
2660
- name: z4.string(),
2829
+ name: z5.string(),
2661
2830
  /** @deprecated Legacy alias of `name` for the pre-#531 embedded bundle in App Store
2662
2831
  * build 35, whose DeviceFlow renders `info.agent.slice(0, 2)` — without this a FRESH
2663
2832
  * install crashes on the pairing screen on first launch, before the OTA lands
2664
2833
  * (seen live: PAIGY-5T, 2026-07-21). Remove once a newer binary is the floor. */
2665
- agent: z4.string().optional(),
2666
- device: z4.string().nullable(),
2834
+ agent: z5.string().optional(),
2835
+ device: z5.string().nullable(),
2667
2836
  status: PairingStatusSchema
2668
2837
  });
2669
- var DeviceTokenSchema = z4.object({
2670
- access_token: z4.string(),
2838
+ var DeviceTokenSchema = z5.object({
2839
+ access_token: z5.string(),
2671
2840
  /** The pairing's single name (user-typed at approval, the agent's suggestion, or
2672
2841
  * a default silly name). */
2673
- name: z4.string(),
2674
- device: z4.string().nullable(),
2842
+ name: z5.string(),
2843
+ device: z5.string().nullable(),
2675
2844
  /** The pairing's assigned voice, cached so the desktop can seed the SAME face the phone
2676
2845
  * draws — voice is the third ingredient of a hatchling's build (party/traits.ts). */
2677
- voice: z4.string().nullable().optional(),
2846
+ voice: z5.string().nullable().optional(),
2678
2847
  /** The token's server-side id — the face's COLOUR anchor, and the only seed ingredient
2679
2848
  * that survives a rename. Cached by the host's identity beat. */
2680
- token_id: z4.string().nullable().optional(),
2849
+ token_id: z5.string().nullable().optional(),
2681
2850
  /** WHERE this identity works — the folder a wake should land it in. Written by the host
2682
2851
  * at spawn and by `paigy-harness handoff` from a live terminal. Without it every wake
2683
2852
  * landed in the FIRST granted workspace and the agent rediscovered its own repo from
2684
2853
  * the thread each time (host.ts, live catch 2026-08-06 — prompt-papered until now). */
2685
- workspace: z4.string().nullable().optional(),
2854
+ workspace: z5.string().nullable().optional(),
2686
2855
  /** Local host recovery must preserve the launch's runtime and Paigy identity. */
2687
- harness: z4.enum(["claude", "codex", "agy"]).optional(),
2688
- session_id: z4.string().uuid().optional(),
2689
- uik_pub: z4.string().nullable().optional()
2856
+ harness: z5.enum(["claude", "codex", "agy"]).optional(),
2857
+ session_id: z5.string().uuid().optional(),
2858
+ /** A conversation the host must not resume: its context is full, so every turn fails
2859
+ * ("Prompt is too long"). Written when a run hits it (`run.ts` `onFull`); the host skips a slot
2860
+ * whose resumable session is this one, so its Goals reach the dead-agent handoff instead of a
2861
+ * copy that types the person's words into a turn that cannot run (Calls, 2026-10-06). */
2862
+ full_session: z5.string().optional(),
2863
+ uik_pub: z5.string().nullable().optional()
2690
2864
  });
2691
- var SupportRequestSchema = z4.object({
2692
- email: z4.string().email().max(320),
2693
- message: z4.string().trim().min(1).max(5e3),
2694
- name: z4.string().trim().max(120).optional()
2865
+ var SupportRequestSchema = z5.object({
2866
+ email: z5.string().email().max(320),
2867
+ message: z5.string().trim().min(1).max(5e3),
2868
+ name: z5.string().trim().max(120).optional()
2695
2869
  });
2696
- var NotificationFeedbackKindSchema = z4.enum([
2870
+ var NotificationFeedbackKindSchema = z5.enum([
2697
2871
  "break_down",
2698
2872
  // "This should be more than one ask — break it down."
2699
2873
  "regenerate_options",
@@ -2708,116 +2882,116 @@ var NotificationFeedbackKindSchema = z4.enum([
2708
2882
  // anything else — the note carries it.
2709
2883
  ]);
2710
2884
  var SlimOptionSchema = OptionSchema.omit({ html: true });
2711
- var QuestionRowSchema = z4.object({
2885
+ var QuestionRowSchema = z5.object({
2712
2886
  /** The card's id (`deliveryId:needId`, or `deliveryId:entryId` for an update), as the inbox mints it. */
2713
- id: z4.string(),
2714
- deliveryId: z4.string(),
2715
- entryId: z4.string(),
2887
+ id: z5.string(),
2888
+ deliveryId: z5.string(),
2889
+ entryId: z5.string(),
2716
2890
  /** The decision it waits on; null for an update, which asks nothing. */
2717
- needId: z4.string().nullable(),
2718
- goalIds: z4.array(z4.string()),
2891
+ needId: z5.string().nullable(),
2892
+ goalIds: z5.array(z5.string()),
2719
2893
  /** The name of the work it is about, when the read could word it. */
2720
- goalTitle: z4.string().optional(),
2721
- tokenId: z4.string().optional(),
2722
- name: z4.string(),
2723
- title: z4.string(),
2724
- body: z4.string(),
2725
- select: z4.enum(["one", "many", "rank", "confirm", "text"]),
2726
- options: z4.array(SlimOptionSchema),
2727
- hasPreview: z4.boolean(),
2728
- blocking: z4.boolean(),
2729
- askedAt: z4.string().datetime(),
2894
+ goalTitle: z5.string().optional(),
2895
+ tokenId: z5.string().optional(),
2896
+ name: z5.string(),
2897
+ title: z5.string(),
2898
+ body: z5.string(),
2899
+ select: z5.enum(["one", "many", "rank", "confirm", "text"]),
2900
+ options: z5.array(SlimOptionSchema),
2901
+ hasPreview: z5.boolean(),
2902
+ blocking: z5.boolean(),
2903
+ askedAt: z5.string().datetime(),
2730
2904
  ring: InboxItemSchema.shape.ring,
2731
- onCall: z4.literal(true).optional(),
2732
- sealed: z4.boolean()
2905
+ onCall: z5.literal(true).optional(),
2906
+ sealed: z5.boolean()
2733
2907
  });
2734
- var WorkStateSchema = z4.enum(["ready", "active", "waiting", "done", "cancelled"]);
2735
- var WorkRowSchema = z4.object({
2736
- id: z4.string(),
2737
- parentId: z4.string().nullable(),
2738
- title: z4.string(),
2908
+ var WorkStateSchema = z5.enum(["ready", "active", "waiting", "done", "cancelled"]);
2909
+ var WorkRowSchema = z5.object({
2910
+ id: z5.string(),
2911
+ parentId: z5.string().nullable(),
2912
+ title: z5.string(),
2739
2913
  /** Straight off the Goal. */
2740
2914
  state: WorkStateSchema,
2741
- owner: z4.string().nullable(),
2742
- revision: z4.number().int(),
2915
+ owner: z5.string().nullable(),
2916
+ revision: z5.number().int(),
2743
2917
  /** Open questions on it, counted to 100. */
2744
- waiting: z4.number().int(),
2918
+ waiting: z5.number().int(),
2745
2919
  /** Held by a gate on work that is not done. */
2746
- blocked: z4.boolean(),
2747
- lastProgressAt: z4.string().datetime().nullable(),
2920
+ blocked: z5.boolean(),
2921
+ lastProgressAt: z5.string().datetime().nullable(),
2748
2922
  /** The line written for its newest progress note, else that note's first words. */
2749
- line: z4.string().nullable(),
2923
+ line: z5.string().nullable(),
2750
2924
  /** Work directly under it, counted to 100; the list carries up to 12 of them. */
2751
- children: z4.number().int(),
2752
- createdAt: z4.string().datetime(),
2753
- updatedAt: z4.string().datetime(),
2925
+ children: z5.number().int(),
2926
+ createdAt: z5.string().datetime(),
2927
+ updatedAt: z5.string().datetime(),
2754
2928
  /** When anything at or under it last moved — the order the list is in. */
2755
- activeAt: z4.string().datetime(),
2929
+ activeAt: z5.string().datetime(),
2756
2930
  /** A sealed outcome has no title here; the work's page opens it. */
2757
- sealed: z4.boolean()
2931
+ sealed: z5.boolean()
2758
2932
  });
2759
2933
  var ComputerRowSchema = ConnectionSummarySchema.omit({ activity: true });
2760
2934
  var AgentRowSchema = ComputerRowSchema.extend({
2761
2935
  /** Open questions it is asking the person, over every open card; null when that read failed. */
2762
- asking: z4.number().int().nullable(),
2763
- oldestAskAt: z4.string().datetime().nullable(),
2936
+ asking: z5.number().int().nullable(),
2937
+ oldestAskAt: z5.string().datetime().nullable(),
2764
2938
  /** Up to three of the live Goals it holds, oldest first (the order it picks them up), and how
2765
2939
  * many in all among the account's 200 most recently active agent-held live Goals
2766
2940
  * (`agent_holds`); null when that read failed. */
2767
- holds: z4.array(z4.object({ id: z4.string(), title: z4.string() })).nullable(),
2768
- held: z4.number().int().nullable(),
2941
+ holds: z5.array(z5.object({ id: z5.string(), title: z5.string() })).nullable(),
2942
+ held: z5.number().int().nullable(),
2769
2943
  /** The earliest instant any Goal it holds went quiet, by the one rule (`coldSince`); null
2770
2944
  * while none has, or when that read failed. */
2771
- cold: z4.string().datetime().nullable(),
2945
+ cold: z5.string().datetime().nullable(),
2772
2946
  /** The newest line of its working log, and when the harness saw it. */
2773
- line: z4.string().nullable(),
2774
- lineAt: z4.string().datetime().nullable()
2947
+ line: z5.string().nullable(),
2948
+ lineAt: z5.string().datetime().nullable()
2775
2949
  });
2776
- var SnapshotSchema = z4.object({
2950
+ var SnapshotSchema = z5.object({
2777
2951
  /** The API's clock, taken before the first read: what a later delta will start from. */
2778
- at: z4.string().datetime(),
2779
- questions: z4.object({
2952
+ at: z5.string().datetime(),
2953
+ questions: z5.object({
2780
2954
  /** The newest 30 open cards, questions before updates. */
2781
- items: z4.array(QuestionRowSchema),
2955
+ items: z5.array(QuestionRowSchema),
2782
2956
  /** Every open question, and apart from them every update, and what was put off. */
2783
- total: z4.number().int(),
2784
- updates: z4.number().int(),
2785
- putOff: z4.number().int()
2957
+ total: z5.number().int(),
2958
+ updates: z5.number().int(),
2959
+ putOff: z5.number().int()
2786
2960
  }).nullable(),
2787
- agents: z4.object({
2961
+ agents: z5.object({
2788
2962
  /** Up to 60, most recently seen first. */
2789
- items: z4.array(AgentRowSchema),
2790
- more: z4.boolean()
2963
+ items: z5.array(AgentRowSchema),
2964
+ more: z5.boolean()
2791
2965
  }).nullable(),
2792
- work: z4.object({
2966
+ work: z5.object({
2793
2967
  /** The 60 most recently active roots, each followed by up to 12 children; 240 rows at most. */
2794
- items: z4.array(WorkRowSchema),
2968
+ items: z5.array(WorkRowSchema),
2795
2969
  /** How much work is behind each of the Work tab's four filters, each counted to 100, read with
2796
2970
  * the rows. `work_list` (20260928023533) owns the predicates: Live is `ready`, `active` or
2797
2971
  * `waiting`; Waiting on you is live work with an open question or an unmet gate; Not started
2798
2972
  * is `ready`; Done is `done` or `cancelled`. */
2799
- counts: z4.object({ live: z4.number().int(), waiting: z4.number().int(), notStarted: z4.number().int(), done: z4.number().int() })
2973
+ counts: z5.object({ live: z5.number().int(), waiting: z5.number().int(), notStarted: z5.number().int(), done: z5.number().int() })
2800
2974
  }).nullable(),
2801
- you: z4.object({
2975
+ you: z5.object({
2802
2976
  settings: UserSettingsSchema,
2803
- callable: z4.boolean(),
2977
+ callable: z5.boolean(),
2804
2978
  /** Up to 20 paired computers; null when the roster read failed. */
2805
- computers: z4.array(ComputerRowSchema).nullable()
2979
+ computers: z5.array(ComputerRowSchema).nullable()
2806
2980
  }).nullable()
2807
2981
  });
2808
- var CallRecapSchema = z4.object({
2809
- call: z4.object({
2810
- status: z4.string(),
2811
- startedAt: z4.string(),
2812
- durationMs: z4.number().nullable(),
2813
- agents: z4.array(z4.object({ id: z4.string(), name: z4.string().nullable() }))
2982
+ var CallRecapSchema = z5.object({
2983
+ call: z5.object({
2984
+ status: z5.string(),
2985
+ startedAt: z5.string(),
2986
+ durationMs: z5.number().nullable(),
2987
+ agents: z5.array(z5.object({ id: z5.string(), name: z5.string().nullable() }))
2814
2988
  }),
2815
- topics: z4.array(z4.object({
2816
- goalId: z4.string().uuid(),
2817
- title: z4.string(),
2818
- owner: z4.string(),
2819
- state: z4.string(),
2820
- questions: z4.array(z4.object({ id: z4.string().uuid(), state: z4.string(), title: z4.string() })),
2989
+ topics: z5.array(z5.object({
2990
+ goalId: z5.string().uuid(),
2991
+ title: z5.string(),
2992
+ owner: z5.string(),
2993
+ state: z5.string(),
2994
+ questions: z5.array(z5.object({ id: z5.string().uuid(), state: z5.string(), title: z5.string() })),
2821
2995
  /** `words` is always what they SAID, verbatim — the record, never replaced. `headline` is
2822
2996
  * their answer on one line when the call's read wrote one (owner, 2026-10-01: "render them
2823
2997
  * summarized like a pre-made option is"), so the row scans like a chosen option and their
@@ -2825,14 +2999,14 @@ var CallRecapSchema = z4.object({
2825
2999
  * anything that is not an answer. */
2826
3000
  /** `about` is the request the line answered (its question), null for words that answered none —
2827
3001
  * the key the screen groups on, so one question is one row however many times it was answered. */
2828
- lines: z4.array(z4.object({ entryId: z4.string().uuid(), words: z4.string(), headline: z4.string().optional(), about: z4.string().nullable().optional() }))
3002
+ lines: z5.array(z5.object({ entryId: z5.string().uuid(), words: z5.string(), headline: z5.string().optional(), about: z5.string().nullable().optional() }))
2829
3003
  })),
2830
- unfiled: z4.array(z4.object({ lineId: z4.string().uuid(), words: z4.string(), atMs: z4.number() })),
2831
- more: z4.object({ lines: z4.number(), entries: z4.number(), topics: z4.number() })
3004
+ unfiled: z5.array(z5.object({ lineId: z5.string().uuid(), words: z5.string(), atMs: z5.number() })),
3005
+ more: z5.object({ lines: z5.number(), entries: z5.number(), topics: z5.number() })
2832
3006
  });
2833
3007
  function sessionSlot(sessionId2) {
2834
- const id = sessionId2 ?? sessionId();
2835
- return `session:${id.slice(0, 8)}`;
3008
+ const id2 = sessionId2 ?? sessionId();
3009
+ return `session:${id2.slice(0, 8)}`;
2836
3010
  }
2837
3011
  function agentName() {
2838
3012
  return process.env.PAIGY_AGENT || sessionSlot();
@@ -2890,13 +3064,16 @@ function updateSlot(agent2, patch) {
2890
3064
  }
2891
3065
  function slotIdentity(agent2) {
2892
3066
  const t = readTokenFile()[agent2];
2893
- 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 };
3067
+ 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 };
2894
3068
  }
2895
3069
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
2896
3070
  function readToken(agent2 = agentName()) {
2897
3071
  if (process.env.PAIGY_TOKEN) return process.env.PAIGY_TOKEN;
2898
3072
  return readTokenFile()[agent2]?.access_token ?? "";
2899
3073
  }
3074
+ function readSlot(agent2) {
3075
+ return readTokenFile()[agent2]?.access_token ?? "";
3076
+ }
2900
3077
  function listSlots() {
2901
3078
  return Object.keys(readTokenFile());
2902
3079
  }
@@ -3027,11 +3204,16 @@ async function updateGoal(goalId, input, opts = {}) {
3027
3204
  return await res.json();
3028
3205
  }
3029
3206
  var AWAIT_WINDOW_MS = 45e3;
3030
- async function hatch(name, voice = null) {
3031
- const res = await ensureAuthed(await reach(`${BACKEND_URL}/api/hatch`, {
3207
+ async function hatch(name, voice = null, opts = {}) {
3208
+ const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/hatch`, {
3032
3209
  method: "POST",
3033
3210
  headers: { "content-type": "application/json", authorization: `Bearer ${authToken()}` },
3034
- body: JSON.stringify({ name, voice })
3211
+ body: JSON.stringify({
3212
+ name,
3213
+ voice,
3214
+ ...opts.brief ? { brief: opts.brief } : {},
3215
+ ...opts.parentGoalId ? { parentGoalId: opts.parentGoalId } : {}
3216
+ })
3035
3217
  }));
3036
3218
  if (!res.ok) throw new Error(`hatch failed: ${res.status} ${await res.text()}`);
3037
3219
  return await res.json();
@@ -3104,7 +3286,9 @@ async function recordDecision(decision, opts = {}) {
3104
3286
  async function heartbeat(runtime, opts = {}) {
3105
3287
  const body = {
3106
3288
  ...runtime !== void 0 ? { runtime } : {},
3107
- ...opts.activity !== void 0 ? { activity: opts.activity } : {}
3289
+ ...opts.activity !== void 0 ? { activity: opts.activity } : {},
3290
+ // A listener's beat (`paigy-listen`): stamps when it last swept, and is not presence.
3291
+ ...opts.listening ? { listening: true } : {}
3108
3292
  };
3109
3293
  const res = await ensureAuthed(await reach(`${BACKEND_URL}/api/presence`, {
3110
3294
  method: "POST",
@@ -3169,6 +3353,13 @@ async function dismissTriage(proposalId, opts = {}) {
3169
3353
  if (!res.ok) throw new Error(`dismiss_triage failed: ${res.status} ${await res.text()}`);
3170
3354
  return await res.json();
3171
3355
  }
3356
+ async function whoIsWorking(opts = {}) {
3357
+ const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/agents/working`, {
3358
+ headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}`, "x-paigy-model": "goal-entry-v1" }
3359
+ }));
3360
+ if (!res.ok) await fail("who_is_working", res);
3361
+ return await res.json();
3362
+ }
3172
3363
  function repoFromRemote(url) {
3173
3364
  const said = (url ?? "").trim();
3174
3365
  if (!said) return null;
@@ -3196,6 +3387,7 @@ function currentRepo(cwd = process.cwd()) {
3196
3387
  return asked;
3197
3388
  }
3198
3389
  var NOT_SENT = "Recorded as progress, not sent: nobody was notified. If the person should see it (a result, something to try or check, a finding), contact again and say so; report progress with update_goal.";
3390
+ 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.";
3199
3391
  async function contact(input, opts = {}) {
3200
3392
  const parsed = ContactSchema.parse(input);
3201
3393
  opts.signal?.throwIfAborted();
@@ -3204,6 +3396,7 @@ async function contact(input, opts = {}) {
3204
3396
  let deliveryId;
3205
3397
  let deliveries = [];
3206
3398
  let recorded = [];
3399
+ let demoted;
3207
3400
  if ("deliveryId" in parsed) {
3208
3401
  deliveryId = parsed.deliveryId;
3209
3402
  } else {
@@ -3234,13 +3427,14 @@ async function contact(input, opts = {}) {
3234
3427
  if (!res.ok) await fail("batch contact", res);
3235
3428
  const receipt = await res.json();
3236
3429
  deliveries = receipt.deliveries ?? [];
3430
+ if (parsed.channel === "call" && receipt.channel && receipt.channel !== "call") demoted = DEMOTED;
3237
3431
  recorded = receipt.recorded ?? [];
3238
3432
  if (deliveries.length === 0 && recorded.length) return { sent: false, recorded, message: NOT_SENT };
3239
3433
  if (deliveries.length === 0) throw new Error("Goal contact returned no Delivery identities");
3240
3434
  deliveryId = deliveries[0]?.deliveryId ?? "";
3241
3435
  if (!deliveryId) throw new Error("Goal contact returned no valid Delivery identity");
3242
3436
  }
3243
- const all = deliveries.filter((d) => !!d.deliveryId && !!d.goalId).map(({ id, deliveryId: deliveryId2, goalId }) => ({ ...id ? { id } : {}, deliveryId: deliveryId2, goalId }));
3437
+ const all = deliveries.filter((d) => !!d.deliveryId && !!d.goalId).map(({ id: id2, deliveryId: deliveryId2, goalId }) => ({ ...id2 ? { id: id2 } : {}, deliveryId: deliveryId2, goalId }));
3244
3438
  const joinedCard = deliveries[0]?.joinedCard === true;
3245
3439
  const joinedCall = deliveries[0]?.joinedCall === true;
3246
3440
  const read = async (signal2) => {
@@ -3252,7 +3446,8 @@ async function contact(input, opts = {}) {
3252
3446
  ...all.length > 1 ? { deliveries: all } : {},
3253
3447
  ...recorded.length ? { recorded, notSent: NOT_SENT } : {},
3254
3448
  ...joinedCard ? { joinedCard: true } : {},
3255
- ...joinedCall ? { joinedCall: true } : {}
3449
+ ...joinedCall ? { joinedCall: true } : {},
3450
+ ...demoted ? { demoted } : {}
3256
3451
  };
3257
3452
  };
3258
3453
  const settled = (d) => d.kind === "notification" || d.state === "closed" || d.answers.length > 0 || d.entries.some((e) => e.kind === "contribution");
@@ -3283,7 +3478,7 @@ async function checkReplies(opts = {}) {
3283
3478
  function compact(o) {
3284
3479
  return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0 && v !== null && v !== "" && !(Array.isArray(v) && v.length === 0)));
3285
3480
  }
3286
- var who = (participant) => participant.startsWith("human:") ? "person" : isPaigy(participant) ? "paigy" : participant.startsWith("agent:") ? "agent" : participant;
3481
+ var who = (participant2) => participant2.startsWith("human:") ? "person" : isPaigy(participant2) ? "paigy" : participant2.startsWith("agent:") ? "agent" : participant2;
3287
3482
  var at = (iso) => {
3288
3483
  const t = Date.parse(iso);
3289
3484
  return Number.isFinite(t) ? new Date(t).toISOString().replace(/\.\d{3}Z$/, "Z") : iso;
@@ -3343,7 +3538,7 @@ function goalView(g) {
3343
3538
  if (!g.goalId) return compact({ state: g.state, next: g.message });
3344
3539
  const lines2 = conversation(g);
3345
3540
  const owed = lines2.filter((l) => l.from === "person" && l.decision?.state === "open" && l.id);
3346
- 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>" }] }). ` : "";
3541
+ 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>" }] }). ` : "";
3347
3542
  const newest = [...lines2].reverse().find((l) => l.from === "agent");
3348
3543
  return compact({
3349
3544
  goalId: g.goalId,
@@ -3352,7 +3547,9 @@ function goalView(g) {
3352
3547
  outcome: g.outcome,
3353
3548
  progress: g.progress && g.progress.trim() !== newest?.said ? g.progress : void 0,
3354
3549
  reviewPending: g.reviewPending || void 0,
3550
+ reviewSince: g.reviewSince,
3355
3551
  owner: g.ownerParticipant,
3552
+ others: g.others,
3356
3553
  parentGoalId: g.parentGoalId,
3357
3554
  // Every child is in the subtree below, with its goalId: listed again, it was said twice.
3358
3555
  childGoalIds: g.subtree?.length ? void 0 : g.childGoalIds,
@@ -3388,7 +3585,7 @@ function deliveryView(d) {
3388
3585
  notSent: d.notSent,
3389
3586
  joinedCard: d.joinedCard,
3390
3587
  joinedCall: d.joinedCall,
3391
- 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
3588
+ next: d.joinedCall ? `${JOINED_CALL}${d.message ? ` ${d.message}` : ""}` : d.kind === "notification" ? `${d.demoted ? `${d.demoted} ` : ""}${d.joinedCard ? `Added to the open report card on its Goal; no new push was sent. ${answers}` : answers}` : d.state === "open" && d.decisionNeeds.some((n) => n.state === "open") ? `Decision pending. Call contact(${JSON.stringify({ deliveryId: d.deliveryId })}) to continue this same Call \u2014 each call is ONE bounded window, and never resend the ask. When a window comes back with nothing new said, they are not typing: stop rereading, leave the question open, and collect the answer with check_replies or claim_goal on your next wake. ${d.message ?? ""}`.trim() : d.message
3392
3589
  });
3393
3590
  }
3394
3591
  function repliesView(r) {
@@ -3429,7 +3626,7 @@ function repliesView(r) {
3429
3626
  // sentence names the person and quotes the freshest one, because a count is not a message.
3430
3627
  ...review.length ? [spoke(review) ?? `${review.length} of your Goals have something new (review): read each with claim_goal or get_goal, then update_goal with reviewed: true.`] : [],
3431
3628
  ...stalled.length ? [`${stalled.length} of your Goals have had no progress for 3 days: update_goal each with progress, finish it, or cancel it.`] : [],
3432
- ...others.length ? [`${others.length} of other agents' Goals have gone quiet for 3 days: claim_goal({goalId}) takes one over, then finish or cancel it with update_goal.`] : [],
3629
+ ...others.length ? [`${others.length} of other agents' Goals have gone quiet for 3 days: claim_goal({goalId}) joins one without changing its owner, then finish or cancel it with update_goal.`] : [],
3433
3630
  // No name of its own (#2525): the API's ask, verbatim.
3434
3631
  ...r.unnamed ? [r.unnamed] : []
3435
3632
  ].join(" ")
@@ -3450,6 +3647,9 @@ async function runTool(name, args, opts) {
3450
3647
  const sent = await contact(input, { ...client, signal, waits });
3451
3648
  return "sent" in sent ? sent : deliveryView(sent);
3452
3649
  }
3650
+ case "who_is_working":
3651
+ CheckRepliesSchema.parse(input);
3652
+ return whoIsWorking(client);
3453
3653
  case "check_replies":
3454
3654
  CheckRepliesSchema.parse(input);
3455
3655
  return repliesView(await checkReplies(client));
@@ -3585,9 +3785,9 @@ function connect(line) {
3585
3785
  ws.addEventListener("error", again);
3586
3786
  }
3587
3787
  function subscribeRealtime(args) {
3588
- const key = `${args.url} ${args.anonKey}`;
3589
- let line = lines.get(key);
3590
- if (!line) lines.set(key, line = { url: args.url, anonKey: args.anonKey, open: false, attempt: 0, ref: 0, topics: /* @__PURE__ */ new Map() });
3788
+ const key2 = `${args.url} ${args.anonKey}`;
3789
+ let line = lines.get(key2);
3790
+ if (!line) lines.set(key2, line = { url: args.url, anonKey: args.anonKey, open: false, attempt: 0, ref: 0, topics: /* @__PURE__ */ new Map() });
3591
3791
  let t = line.topics.get(args.topic);
3592
3792
  if (!t) {
3593
3793
  line.topics.set(args.topic, t = { listeners: /* @__PURE__ */ new Set(), joined: false, attempt: 0 });
@@ -3609,7 +3809,7 @@ function subscribeRealtime(args) {
3609
3809
  if (line.open) frame(line, `realtime:${args.topic}`, "phx_leave", {});
3610
3810
  return;
3611
3811
  }
3612
- lines.delete(key);
3812
+ lines.delete(key2);
3613
3813
  stop(line);
3614
3814
  try {
3615
3815
  line.ws?.close();
@@ -3653,14 +3853,38 @@ function machineZone() {
3653
3853
  return null;
3654
3854
  }
3655
3855
  }
3856
+ function withCodexEnv(text) {
3857
+ const head = /^\[mcp_servers\.paigy\][ \t]*(?:#[^\n]*)?\r?\n/m.exec(text);
3858
+ if (!head) return null;
3859
+ const start = head.index + head[0].length;
3860
+ const next = /^\[/m.exec(text.slice(start));
3861
+ const end = next ? start + next.index : text.length;
3862
+ const block = text.slice(start, end);
3863
+ const assignment = /^[ \t]*env_vars[ \t]*=/m.exec(block);
3864
+ let updated;
3865
+ if (!assignment) updated = `env_vars = [${CODEX_ENV.map((v) => JSON.stringify(v)).join(", ")}]
3866
+ ${block}`;
3867
+ else {
3868
+ const array = /^[ \t]*env_vars[ \t]*=[ \t]*\[((?:#[^\n]*|[^\]"'#]|"[^"\n]*"|'[^'\n]*')*)\]/m.exec(block);
3869
+ if (!array) return null;
3870
+ const existing = [...array[1].matchAll(/"([^"\n]*)"|'([^'\n]*)'|#[^\n]*/g)].flatMap((m) => m[0].startsWith("#") ? [] : [m[1] ?? m[2]]);
3871
+ const missing = CODEX_ENV.filter((v) => !existing.includes(v));
3872
+ if (!missing.length) return text;
3873
+ const opening = array.index + array[0].indexOf("[") + 1;
3874
+ updated = block.slice(0, opening) + missing.map((v) => JSON.stringify(v)).join(", ") + (existing.length ? ", " : "") + block.slice(opening);
3875
+ }
3876
+ return text.slice(0, start) + updated + text.slice(end);
3877
+ }
3656
3878
 
3657
3879
  export {
3880
+ CODEX_ENV,
3658
3881
  BACKEND_URL,
3659
3882
  NETWORK_MSG,
3660
3883
  sessionId,
3661
3884
  isNetworkError,
3662
3885
  reach,
3663
3886
  reachAs,
3887
+ setClient,
3664
3888
  AGENT_TOOLS,
3665
3889
  AGENT_TOOL_NAMES,
3666
3890
  NotifyRequestSchema,
@@ -3674,6 +3898,7 @@ export {
3674
3898
  slotIdentity,
3675
3899
  sleep,
3676
3900
  readToken,
3901
+ readSlot,
3677
3902
  listSlots,
3678
3903
  slotName,
3679
3904
  deleteToken,
@@ -3706,11 +3931,13 @@ export {
3706
3931
  getTriage,
3707
3932
  acceptTriage,
3708
3933
  dismissTriage,
3934
+ whoIsWorking,
3709
3935
  repoFromRemote,
3710
3936
  currentRepo,
3711
3937
  contact,
3712
3938
  checkReplies,
3713
3939
  runTool,
3714
3940
  subscribeWake,
3715
- parseDue
3941
+ parseDue,
3942
+ withCodexEnv
3716
3943
  };