@paigy/mcp 0.40.19 → 0.40.21

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.
@@ -1525,18 +1525,18 @@ var AskInputSchema = z2.object({
1525
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."),
1526
1526
  repo: z2.string().optional().describe("Optional repository context."),
1527
1527
  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 parts of one piece of work under one Goal, separate things as separate Goals \u2014 but the words it splits are its reading, not yours. News, progress and findings are their own contact; a call contact JOINS a call already happening, so several arrive as one call."
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."
1529
1529
  ),
1530
- options: z2.array(OptionInputSchema).min(2).max(6).optional()
1530
+ options: z2.array(OptionInputSchema).min(1).max(6).optional()
1531
1531
  }).strict();
1532
1532
  var StartContactSchema = z2.object({
1533
1533
  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."),
1534
- waiting: z2.enum(["none", "hard"]).default("none"),
1534
+ waiting: z2.enum(["none", "hard"]).default("none").describe("hard requests a call even when channel is notification; user permissions and ring cooldowns still apply."),
1535
1535
  channel: z2.enum(["notification", "call"]).default("notification")
1536
1536
  }).strict();
1537
1537
  var ContactSchema = z2.union([StartContactSchema, z2.object({ deliveryId: z2.string().uuid() }).strict()]);
1538
1538
  var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
1539
- var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', '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 \u2014 parts of one piece of work under one Goal, separate things as separate Goals. An ask with no options that is not blocking is a report: 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).";
1539
+ var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', '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. 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.";
1540
1540
  var CreateGoalSchema = z3.object({
1541
1541
  outcome: z3.string().trim().min(1).max(1e4),
1542
1542
  /** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
@@ -1574,7 +1574,11 @@ var UpdateGoalSchema = z3.object({
1574
1574
  state: z3.enum(["active", "done", "cancelled"]).optional(),
1575
1575
  progress: z3.string().trim().min(1).max(1e4).optional(),
1576
1576
  reviewed: z3.literal(true).optional(),
1577
- dueAt: z3.string().datetime({ offset: true }).nullable().optional()
1577
+ dueAt: z3.string().datetime({ offset: true }).nullable().optional(),
1578
+ /** WITHDRAW YOUR OWN QUESTION (#2777, owner 2026-09-30). The question's id as every read shows it
1579
+ * (the conversation's `id`, eight characters, or the whole request Entry id), on THIS Goal, asked
1580
+ * by you and still open. It is cancelled, not answered: its cards close and nothing rings for it. */
1581
+ withdraw: z3.array(z3.string().trim().regex(/^[0-9a-fA-F-]{8,36}$/)).min(1).max(10).optional()
1578
1582
  }).strict().refine((v) => Object.keys(v).length > 0),
1579
1583
  reason: z3.string().trim().min(1).max(2e3),
1580
1584
  operationId: z3.string().uuid().optional()
@@ -1585,10 +1589,14 @@ var GetGoalSchema = z3.object({
1585
1589
  goalId: z3.string().uuid(),
1586
1590
  /** Every entry in full. Without it the read carries the person's words, open questions and your
1587
1591
  * newest entry, and counts what it left out (`apps/api/src/goal/collapse.ts`). */
1588
- history: z3.boolean().optional()
1592
+ history: z3.boolean().optional(),
1593
+ /** WHEN A READ HAS CONFUSED YOU. Adds `diagnosis`: every reader that already answers a question
1594
+ * about this work, each answer attributed to the reader that gave it, and every disagreement
1595
+ * between two of them named. Off by default; `docs/model/goal/diagnose-design.md`. */
1596
+ diagnose: z3.boolean().optional()
1589
1597
  }).strict();
1590
- 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. 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.";
1591
- 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. 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. 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. cancelled is final. Returns the Goal as get_goal reads it, at its new revision.`;
1598
+ 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. 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.";
1599
+ 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. 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.`;
1592
1600
  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.";
1593
1601
  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.";
1594
1602
  var CheckRepliesSchema = z3.object({}).strict();
@@ -1617,6 +1625,7 @@ function entryWords(entry) {
1617
1625
  return entry.sources.map((source) => source.text).join("\n");
1618
1626
  }
1619
1627
  var LIVE_MS = 3 * 6e4;
1628
+ var WORKING_MS = 30 * 6e4;
1620
1629
  var ContextSchema = z4.object({
1621
1630
  title: z4.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
1622
1631
  description: z4.array(z4.string().min(1)).describe(
@@ -1687,7 +1696,7 @@ var AttentionSchema = z4.object({
1687
1696
  points: z4.array(z4.string()).nullable(),
1688
1697
  /** Whether the ask blocks the sender — what lets arbitration escalate it on silence. */
1689
1698
  blocking: z4.boolean(),
1690
- /** Reserved (MODEL.md lists it): a response deadline. No row column yet — a later Phase 2
1699
+ /** Reserved (docs/model/model.md lists it): a response deadline. No row column yet — a later Phase 2
1691
1700
  * slice wires it; optional so today's rows/callers project cleanly. */
1692
1701
  deadline: z4.string().datetime().nullable().optional()
1693
1702
  });
@@ -1738,7 +1747,7 @@ var NotifyRequestFields = z4.object({
1738
1747
  * derive agent-side before sealing, so the server only ever shapes plaintext). */
1739
1748
  // 10k, not a sentence budget. What the human hears is bounded by the BROKER — it splits
1740
1749
  // the ask into topics and gives each one at most three sentences and one question
1741
- // (broker/agenda-design.md) — not by a wire cap the agent has to pre-summarize under.
1750
+ // (docs/brain/broker/agenda-design.md) — not by a wire cap the agent has to pre-summarize under.
1742
1751
  // Owner, 2026-07-28: "our actual limitation on how long something is to the user should
1743
1752
  // come from the broker splitting and summarizing." The cap that remains is a size guard.
1744
1753
  ask: z4.string().min(1).max(1e4).optional().describe(
@@ -1815,7 +1824,7 @@ var UserAnswerSchema = z4.discriminatedUnion("kind", [
1815
1824
  z4.object({ kind: z4.literal("clarify"), chunks: z4.array(z4.string()).min(1) }),
1816
1825
  z4.object({ kind: z4.literal("confirm"), approved: z4.boolean() }),
1817
1826
  z4.object({ kind: z4.literal("turns"), turns: z4.array(TurnSchema).min(1) }),
1818
- /** An auto-answer derived from the user's PAST decisions (broker/precedent-design.md §2):
1827
+ /** An auto-answer derived from the user's PAST decisions (docs/brain/broker/precedent-design.md §2):
1819
1828
  * delivered through the same settle/await path as a human answer, carrying the judge's
1820
1829
  * derivation and the precedent ids it grew from. Always paired with a visible trail
1821
1830
  * card the user can reply to — the broker never overrides the user. */
@@ -1864,7 +1873,7 @@ var AwaitItemSchema = z4.discriminatedUnion("type", [
1864
1873
  * and now knows exactly which call to make. Absent when either half is missing —
1865
1874
  * a sentence with a hole in it is worse than no sentence. */
1866
1875
  note: z4.string().optional(),
1867
- /** The call record rendered for THIS agent (`voice/record-design.md`): the words the
1876
+ /** The call record rendered for THIS agent (`docs/brain/voice/record-design.md`): the words the
1868
1877
  * shaped answer was mapped from, filtered to its own claims. There is no second list
1869
1878
  * of labels beside it — the acts went 2026-09-04 and `intents` went with them (owner,
1870
1879
  * 2026-09-04): the agent reads the sentence and decides. */
@@ -1960,10 +1969,11 @@ var AgendaTurnSchema = z4.object({
1960
1969
  agentId: z4.string().optional(),
1961
1970
  select: SelectShapeSchema.optional(),
1962
1971
  options: z4.array(OptionSchema.omit({ id: true })).optional(),
1963
- /** Pacing (#826, owner 2026-08-03: "how fast we move through them ... are parameters"):
1964
- * seconds the floor stays open after this turn speaks. Absent = the bot's defaults
1965
- * (the beat for context, the answer window for asks). Clamped bot-side. */
1966
- pace: z4.number().positive().optional(),
1972
+ /* `pace` STOOD HERE (#826). A turn could carry seconds and the model chose them. The walk
1973
+ paces itself now — a short beat between the sentences of a turn, the longer one at its end
1974
+ (owner, 2026-09-30: "remove the bot deciding pace") — and it does that where the words are
1975
+ spoken, not where the plan is written, so nothing between the brain and the walk decides
1976
+ anything. A caller's own `beat_s` tuning is what it used to override. */
1967
1977
  /** Whether the walk WAITS for an answer before moving on. Absent = derived as today
1968
1978
  * (a question blocks, context flows). blocking:false on a question = ask and move
1969
1979
  * on, the claim stays pending; blocking:true on context = hold for a reply. */
@@ -1998,7 +2008,7 @@ var InboxItemSchema = z4.object({
1998
2008
  * instant the agent picks it up (#997). Absent = pre-trigger row; fall back to
1999
2009
  * `createdAt`. */
2000
2010
  agentStateAt: z4.string().datetime().optional(),
2001
- /** THE QUESTIONS A CALL CARRIES — the call screen's agenda spine (walk/design.md §11, owner
2011
+ /** THE QUESTIONS A CALL CARRIES — the call screen's agenda spine (docs/clients/app/walk/design.md §11, owner
2002
2012
  * 2026-09-22). One per DecisionNeed on the Call, in the Call's order, answered or open (a
2003
2013
  * superseded or cancelled need is no longer a question anyone is asked). Present only on a
2004
2014
  * Call's cards, and every card of that Call carries the same list: the call screen reads it
@@ -2059,7 +2069,7 @@ var InboxItemSchema = z4.object({
2059
2069
  * off the same open list the card came from, so it clears when the Call does. A card is the
2060
2070
  * backup for a call not taken; while the call has it, the call is where it is answered. */
2061
2071
  onCall: z4.literal(true).optional(),
2062
- /** THE RING, ON THE ITEM (walk/design.md §12 §17, #2251): the last ring on this card was
2072
+ /** THE RING, ON THE ITEM (docs/clients/app/walk/design.md §12 §17, #2251): the last ring on this card was
2063
2073
  * declined, and what the ladder will do next — read off the cron's own row, never computed
2064
2074
  * on the phone. Present only while a `declined` receipt stands on the card's last Call.
2065
2075
  * The ladder is ACCOUNT-WIDE (#2259): `anchorAt` and `step` are the account's position;
@@ -2090,7 +2100,7 @@ var InboxItemSchema = z4.object({
2090
2100
  "Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
2091
2101
  ),
2092
2102
  /** Real downstream work is stuck behind this one — set by the agent, independent of
2093
- * urgency (see the main README's "premier use case" + notify/states.md). Drives the
2103
+ * urgency (see the main README's "premier use case" + docs/delivery/notify/states.md). Drives the
2094
2104
  * inbox's blocking badge and the extra confirm step before dismissing it. */
2095
2105
  blocking: z4.boolean().default(false),
2096
2106
  /** The user's locked-in answer; present only for replied items (GET /api/replied/:id). */
@@ -2164,9 +2174,16 @@ var UserSettingsSchema = z4.object({
2164
2174
  banner: z4.boolean(),
2165
2175
  push: z4.boolean()
2166
2176
  }),
2177
+ /** LockedIn / Default / DateNight on screen; the stored words are unchanged on purpose —
2178
+ * they are an enum on a live column across every account, and the rename is a rename of
2179
+ * what people read (owner, 2026-09-30). */
2167
2180
  sessionMode: z4.enum(["default", "all_calls", "silent"]),
2168
- silentPush: z4.boolean(),
2169
- autoCallback: z4.boolean(),
2181
+ /** DEAD, AND ACCEPTED ANYWAY (#2813). DateNight is inbox-only with no way back, so nothing
2182
+ * reads this. It stays OPTIONAL rather than deleted because a phone on the old bundle
2183
+ * PATCHes the whole settings object and would be refused for sending a key we stopped
2184
+ * wanting — the same trap `voiceMode` below already documents. #2813 drops the field and
2185
+ * its column once no installed build still sends it. */
2186
+ silentPush: z4.boolean().optional(),
2170
2187
  /** Opt-in (default false) to using your content to improve Paigy and train models. */
2171
2188
  improveConsent: z4.boolean(),
2172
2189
  missedCall: MissedCallSchema.default("backoff_standard"),
@@ -2177,12 +2194,16 @@ var UserSettingsSchema = z4.object({
2177
2194
  * must not silently reset this privacy choice. Absent = leave unchanged on
2178
2195
  * write, 'hosted' on read (see store.ts). */
2179
2196
  voiceMode: z4.enum(["hosted", "on_device"]).optional(),
2180
- /** Talk — after you answer, the next step is read aloud (walk/design.md §6). ALWAYS ON until
2197
+ /** Talk — after you answer, the next step is read aloud (docs/clients/app/walk/design.md §6). ALWAYS ON until
2181
2198
  * turned off (owner, 2026-09-18, #2249): a setting, not a per-walk toggle. Optional, NOT
2182
2199
  * defaulted, for the same reason `voiceMode` is: a stale client PATCHing the full settings
2183
2200
  * object must not silently turn it back on. Absent = leave unchanged on write, true on
2184
2201
  * read (see store.ts). */
2185
2202
  talk: z4.boolean().optional(),
2203
+ /** CALL DIAGNOSTICS (owner, 2026-10-01): the call report carries each listen and the bot's own
2204
+ * load timings. SERVER-SET, no UI — on for every account that existed on 2026-10-01, off for
2205
+ * newer ones (migration 20261001132859). Read-only here: the settings PATCH never writes it. */
2206
+ callDiagnostics: z4.boolean().optional(),
2186
2207
  /** Per-user ring budget (#603): calls per rolling day before further calls
2187
2208
  * degrade to banner. Absent = the global default (25). A number, never a
2188
2209
  * bypass — every account keeps a ceiling. No UI; set per user for testing. */
@@ -2191,9 +2212,6 @@ var UserSettingsSchema = z4.object({
2191
2212
  * slower speaker). No API-side semantics; the bot resolves each key with its
2192
2213
  * own defaults. Set per user (no UI yet); absent = bot defaults. */
2193
2214
  voiceTuning: z4.record(z4.string(), z4.union([z4.number(), z4.string()])).optional(),
2194
- /** Opt-in to real-phone (PSTN) calls when the app can't ring. Optional, not
2195
- * defaulted — an older client PATCHing the full object must not clobber it. */
2196
- pstnCalls: z4.boolean().optional(),
2197
2215
  /** The user's IANA timezone (e.g. "America/Bogota"), recorded by the app — it is the
2198
2216
  * only party that knows it. REMINDERS are why it exists: "remind me at ten" becomes
2199
2217
  * an absolute `due_at` only if we know whose ten. Optional and never defaulted, for
@@ -2207,18 +2225,20 @@ var UserSettingsSchema = z4.object({
2207
2225
  * clobber guard as voiceMode: absent = leave unchanged on write. */
2208
2226
  broker: BrokerTuningSchema.optional()
2209
2227
  });
2210
- var HistoryItemSchema = z4.object({
2228
+ var HistoryWorkSchema = z4.object({
2211
2229
  id: z4.string(),
2212
- /** 'user' = a request you sent; 'agent' = a notification an agent sent you. */
2213
- initiator: z4.enum(["user", "agent"]),
2214
2230
  title: z4.string(),
2215
- /** The agent on the other end (its name). */
2216
- name: z4.string(),
2217
- createdAt: z4.string(),
2218
- /** When the agent fetched your request (user→agent only). */
2219
- agentAckedAt: z4.string().nullable(),
2220
- /** When you answered the agent's notification (agent→user only). */
2221
- humanAckedAt: z4.string().nullable()
2231
+ state: z4.enum(["done", "cancelled"]),
2232
+ /** Who held it (`agent:<tokenId>` or `human:<userId>`). */
2233
+ assignee: z4.string()
2234
+ });
2235
+ var HistoryEntrySchema = z4.union([
2236
+ z4.object({ at: z4.string(), card: InboxItemSchema }),
2237
+ z4.object({ at: z4.string(), work: HistoryWorkSchema })
2238
+ ]);
2239
+ var HistoryPageSchema = z4.object({
2240
+ entries: z4.array(HistoryEntrySchema),
2241
+ next: z4.string().nullable()
2222
2242
  });
2223
2243
  var ACTIVITY_LINES = 2;
2224
2244
  var ACTIVITY_LINE_MAX = 80;
@@ -2235,7 +2255,7 @@ var ConnectionSummarySchema = z4.object({
2235
2255
  /** The credential kind: "device" = a paired machine (mint-only — it hosts and mints, it
2236
2256
  * never talks); "agent" = an identity that sends. The roster and devices surfaces split
2237
2257
  * on this. Optional/absent reads as "agent" (a row predating the kind column). See
2238
- * apps/api/src/tokens/devices-vs-agents-design.md. */
2258
+ * docs/server/tokens/devices-vs-agents-design.md. */
2239
2259
  kind: z4.enum(["device", "agent"]).optional(),
2240
2260
  /** For an agent, the token id of the DEVICE that minted it — so agents group under their
2241
2261
  * machine, and revoking a device cascades to them. Null on devices, and on unlinked
@@ -2253,7 +2273,7 @@ var ConnectionSummarySchema = z4.object({
2253
2273
  * user on the agent's own page. null = no ceiling (today's behaviour for every
2254
2274
  * connection). Android binds importance to a relationship rather than to each message,
2255
2275
  * and that is the thing our roster could not say: "Marlow may always call me; Otto
2256
- * never may" (navigation-design.md, gap 2). Clamped in `arbitrateLevel`, so it binds
2276
+ * never may" (docs/clients/app/party/navigation-design.md, gap 2). Clamped in `arbitrateLevel`, so it binds
2257
2277
  * every surface at once and outranks even `sessionMode: all_calls` — a mode the user
2258
2278
  * set once must not overrule a rule they set about one agent. */
2259
2279
  reach: NotifyLevelSchema.nullable().optional(),
@@ -2264,7 +2284,14 @@ var ConnectionSummarySchema = z4.object({
2264
2284
  /** Last presence heartbeat from a running agent process (POST /api/presence) — the
2265
2285
  * desktop app while open. Null = never seen; stale = offline. */
2266
2286
  lastSeenAt: z4.string().datetime().nullable().optional(),
2267
- /** What a live desktop can run (companion.md §2.2), advertised on its heartbeat:
2287
+ /** WORKING, NOT JUST CONNECTED (owner, 2026-09-30): the last time the agent itself acted on one of
2288
+ * its Goals — took its lease or recorded an operation (`tokens.last_worked_at`). Within
2289
+ * `WORKING_MS` it is working; otherwise it is connected but idle. Null = not seen working yet. */
2290
+ lastWorkedAt: z4.string().datetime().nullable().optional(),
2291
+ /** The oldest of its Goals that is `ready` for it — work handed to it that nobody has started.
2292
+ * With no work of its own for `WORKING_MS`, an agent sitting on this is not taking its work. */
2293
+ oldestReadyAt: z4.string().datetime().nullable().optional(),
2294
+ /** What a live desktop can run (docs/clients/desktop/companion.md §2.2), advertised on its heartbeat:
2268
2295
  * harness availabilities + granted workspaces — the option set the phone's
2269
2296
  * "new session" sheet offers. Absent for ordinary MCP agents. */
2270
2297
  runtime: z4.object({
@@ -2272,7 +2299,12 @@ var ConnectionSummarySchema = z4.object({
2272
2299
  * shows its age here (`apps/desktop/src/update.ts`). */
2273
2300
  version: z4.string().optional(),
2274
2301
  harnesses: z4.array(z4.object({ name: z4.string(), label: z4.string(), status: z4.string() })).optional(),
2275
- workspaces: z4.array(z4.string()).optional()
2302
+ workspaces: z4.array(z4.string()).optional(),
2303
+ /** THE GIT REPOS IN THOSE FOLDERS (2026-10-01, Goal 26982211): each granted folder that is a
2304
+ * repo, and each repo directly inside one, with its `origin` remote. A session started for
2305
+ * work on `mauurda/paigy` opens in that repo rather than the folder above it, where the repo's
2306
+ * own AGENTS.md is never read (`workspaceForRepo`). Absent on hosts that predate it. */
2307
+ repos: z4.array(z4.object({ path: z4.string(), remote: z4.string() })).optional()
2276
2308
  }).optional(),
2277
2309
  /** The tail of this agent's working log, when a harness is driving it — the agent page's
2278
2310
  * live strip. Absent for anything the desktop harness isn't running (a hatched identity
@@ -2355,7 +2387,7 @@ var QueueQuestionSchema = z4.object({
2355
2387
  * for a settled question whose reply carried nothing readable. */
2356
2388
  answer: z4.string().nullable().default(null),
2357
2389
  /** The Goal this question belongs to — a step knows its Goal on its own, not only through
2358
- * an `InboxItem`'s `communication.goalIds[0]` (walk/design.md §12 item 3).
2390
+ * an `InboxItem`'s `communication.goalIds[0]` (docs/clients/app/walk/design.md §12 item 3).
2359
2391
  * READ BY `apps/client/src/walk/order.ts`, which stamps it onto every `WalkStep`: the walk's
2360
2392
  * order, its route, home's trees and the list of steps all take a step's Goal from here, so
2361
2393
  * this is the field they agree through rather than each re-deriving it from the row it
@@ -2402,6 +2434,9 @@ var QueueItemSchema = z4.object({
2402
2434
  progressLine: z4.string().nullable().optional(),
2403
2435
  reviewPending: z4.boolean().default(false),
2404
2436
  dueAt: z4.string().nullable().default(null),
2437
+ /** WHEN ITS OWNER SAID DONE WHILE CHILDREN WERE OPEN (#2704): its own work is finished and it closes
2438
+ * with its last open child. Null otherwise; optional, so hand-built queues need not spell it. */
2439
+ finishedAt: z4.string().nullable().optional(),
2405
2440
  /** The Goal this one was opened under; null at the root. */
2406
2441
  parentGoalId: z4.string().nullable().default(null),
2407
2442
  /** Goals opened under this one — only those the same list holds. */
@@ -2410,8 +2445,17 @@ var QueueItemSchema = z4.object({
2410
2445
  dependencyGoalIds: z4.array(z4.string()).default([]),
2411
2446
  /** True while any gate is on a Goal that is not done — the walk draws it dashed. */
2412
2447
  blocked: z4.boolean().default(false),
2413
- /** Every decision need on it, open or settled — the page decides which to show. */
2448
+ /** Its questions: every OPEN one, and at most ten settled, newest settled first
2449
+ * (20260929133308) — the page decides which of them to show. NOT the whole set: `asked` and
2450
+ * `answered` are, and a settled one's words are a line (280 characters), its body read when the
2451
+ * question is opened. */
2414
2452
  questions: z4.array(QueueQuestionSchema).default([]),
2453
+ /** HOW MANY QUESTIONS THIS WORK HAS ASKED, and how many are answered — the Goal's own totals,
2454
+ * bounded at 100 server-side. A tally counted off `questions` is a wrong number that looks
2455
+ * right once the cap bites (`walk/trees.ts` `tallyOf`). Optional, and defaulted from the array
2456
+ * by the projection, so hand-built queues (fixtures, the demo) need not spell them. */
2457
+ asked: z4.number().optional(),
2458
+ answered: z4.number().optional(),
2415
2459
  /** Every note on it the person replied to (`QueueReplySchema`) — the page decides which to show.
2416
2460
  * Optional, not defaulted: absent is none, and every hand-built queue (fixtures, the demo) need
2417
2461
  * not spell an empty list. */
@@ -2435,7 +2479,19 @@ var QueueItemSchema = z4.object({
2435
2479
  said: z4.string(),
2436
2480
  at: z4.string(),
2437
2481
  entryId: z4.string()
2438
- }).nullable().optional()
2482
+ }).nullable().optional(),
2483
+ /** WHEN THIS PERSON LAST PUT A HAND ON IT THEMSELVES (owner, Paigy Goal 16d18f51, 2026-09-30):
2484
+ * the newest Entry on the Goal they wrote, of any kind — a line they added, a reply to a note, an
2485
+ * answer to a question, a voice note filed as work. Null when the only hands on it have been its
2486
+ * agent's; optional, so a hand-built queue (fixtures, the demo) and a door older than
2487
+ * 20260930222356 need not spell it — `work/list.ts`'s `yoursAt` keeps its other sources, so the
2488
+ * Work tab orders as it did before the migration rather than throwing on the missing key.
2489
+ *
2490
+ * It is a FACT, not a reconstruction: `latest` holds one Entry, so an agent's progress note a
2491
+ * minute after the person speaks erases their instant from it, and the durable traces the client
2492
+ * can see (`replies`, `questions[].answeredAt`) miss a spontaneous note entirely — a `request`
2493
+ * Entry with no `about_id` is in neither. */
2494
+ lastPersonAt: z4.string().nullable().optional()
2439
2495
  });
2440
2496
  var COLD_AFTER_MS = 3 * 24 * 60 * 60 * 1e3;
2441
2497
  var NoteSourceSchema = z4.enum(["app", "call"]);
@@ -2465,7 +2521,7 @@ var NoteSchema = z4.object({
2465
2521
  assignee: z4.string().nullable(),
2466
2522
  /** The request thread minted at assignment; null until assigned. */
2467
2523
  parentId: z4.string().nullable(),
2468
- /** REMINDERS (reminders-design.md): the NOT-BEFORE this becomes eligible to ride a
2524
+ /** REMINDERS (docs/model/notes/reminders-design.md): the NOT-BEFORE this becomes eligible to ride a
2469
2525
  * call — never a deadline. It only ever comes from the user's own words, so when it
2470
2526
  * passes Paigy rings ONCE (#1293, owner 2026-08-26: a time said out loud is consent to
2471
2527
  * a ring at that time); after that it rides like any other. Null = "the very next
@@ -2548,6 +2604,19 @@ var DeliveryConfigSchema = z4.object({
2548
2604
  * carries credentials; only a `poll` registration can come back with null here. */
2549
2605
  realtime: z4.object({ url: z4.string(), anonKey: z4.string() }).nullable()
2550
2606
  });
2607
+ var HostDecisionSchema = z4.object({
2608
+ /** The agent's token id: the row's `recipient`. */
2609
+ agent: z4.string().uuid(),
2610
+ decision: z4.enum(["stood_back", "took_over"]),
2611
+ /** The work it was about: the Goal `claim_goal` would hand that agent next. */
2612
+ goalId: z4.string().uuid().nullable().optional(),
2613
+ /** When the server last heard from the agent, as the host read it: the presence it stood back for. */
2614
+ seenAt: z4.string().datetime().nullable().optional(),
2615
+ /** When that work last moved (`claimable.since` on `check_replies`), the fact the bound is judged on. */
2616
+ since: z4.string().datetime().nullable().optional(),
2617
+ /** What the host said, in its log's own words: why it stood back, or what the take-over did. */
2618
+ said: z4.string().max(300).optional()
2619
+ });
2551
2620
  var WakeNudgeSchema = z4.object({
2552
2621
  kind: z4.enum(["reply", "request", "callback"]),
2553
2622
  notificationId: z4.string().optional(),
@@ -2592,6 +2661,9 @@ var DeviceTokenSchema = z4.object({
2592
2661
  * landed in the FIRST granted workspace and the agent rediscovered its own repo from
2593
2662
  * the thread each time (host.ts, live catch 2026-08-06 — prompt-papered until now). */
2594
2663
  workspace: z4.string().nullable().optional(),
2664
+ /** Local host recovery must preserve the launch's runtime and Paigy identity. */
2665
+ harness: z4.enum(["claude", "codex", "agy"]).optional(),
2666
+ session_id: z4.string().uuid().optional(),
2595
2667
  uik_pub: z4.string().nullable().optional()
2596
2668
  });
2597
2669
  var SupportRequestSchema = z4.object({
@@ -2711,6 +2783,29 @@ var SnapshotSchema = z4.object({
2711
2783
  computers: z4.array(ComputerRowSchema).nullable()
2712
2784
  }).nullable()
2713
2785
  });
2786
+ var CallRecapSchema = z4.object({
2787
+ call: z4.object({
2788
+ status: z4.string(),
2789
+ startedAt: z4.string(),
2790
+ durationMs: z4.number().nullable(),
2791
+ agents: z4.array(z4.object({ id: z4.string(), name: z4.string().nullable() }))
2792
+ }),
2793
+ topics: z4.array(z4.object({
2794
+ goalId: z4.string().uuid(),
2795
+ title: z4.string(),
2796
+ owner: z4.string(),
2797
+ state: z4.string(),
2798
+ questions: z4.array(z4.object({ id: z4.string().uuid(), state: z4.string(), title: z4.string() })),
2799
+ /** `words` is always what they SAID, verbatim — the record, never replaced. `headline` is
2800
+ * their answer on one line when the call's read wrote one (owner, 2026-10-01: "render them
2801
+ * summarized like a pre-made option is"), so the row scans like a chosen option and their
2802
+ * own words stay under it. Absent on every line stored before the read wrote them, and on
2803
+ * anything that is not an answer. */
2804
+ lines: z4.array(z4.object({ entryId: z4.string().uuid(), words: z4.string(), headline: z4.string().optional() }))
2805
+ })),
2806
+ unfiled: z4.array(z4.object({ lineId: z4.string().uuid(), words: z4.string(), atMs: z4.number() })),
2807
+ more: z4.object({ lines: z4.number(), entries: z4.number(), topics: z4.number() })
2808
+ });
2714
2809
  function sessionSlot(sessionId2) {
2715
2810
  const id = sessionId2 ?? sessionId();
2716
2811
  return `session:${id.slice(0, 8)}`;
@@ -2771,7 +2866,7 @@ function updateSlot(agent2, patch) {
2771
2866
  }
2772
2867
  function slotIdentity(agent2) {
2773
2868
  const t = readTokenFile()[agent2];
2774
- return { name: t?.name ?? null, voice: t?.voice ?? null, tokenId: t?.token_id ?? null, workspace: t?.workspace ?? null };
2869
+ 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 };
2775
2870
  }
2776
2871
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
2777
2872
  function readToken(agent2 = agentName()) {
@@ -2835,9 +2930,21 @@ var UnpairedError = class extends Error {
2835
2930
  this.name = "UnpairedError";
2836
2931
  }
2837
2932
  };
2838
- function ensureAuthed(res) {
2839
- if (res.status === 401) throw new UnpairedError();
2840
- return res;
2933
+ var RevokedError = class extends UnpairedError {
2934
+ constructor() {
2935
+ super();
2936
+ this.message = "This identity was revoked (the API answered 401 revoked): its credential is gone for good. Pairing again makes a new identity.";
2937
+ this.name = "RevokedError";
2938
+ }
2939
+ };
2940
+ async function ensureAuthed(res) {
2941
+ if (res.status !== 401) return res;
2942
+ let body = null;
2943
+ try {
2944
+ body = await res.json();
2945
+ } catch {
2946
+ }
2947
+ throw body?.error === "revoked" ? new RevokedError() : new UnpairedError();
2841
2948
  }
2842
2949
  var ApiError = class extends Error {
2843
2950
  status;
@@ -2864,7 +2971,7 @@ async function fail(what, res) {
2864
2971
  }
2865
2972
  async function createGoal(input, opts = {}) {
2866
2973
  const token = authToken(opts.token) ?? "";
2867
- const res = ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals`, {
2974
+ const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals`, {
2868
2975
  method: "POST",
2869
2976
  headers: { "content-type": "application/json", authorization: `Bearer ${token}`, "x-paigy-model": "goal-entry-v1" },
2870
2977
  body: JSON.stringify(input)
@@ -2874,13 +2981,14 @@ async function createGoal(input, opts = {}) {
2874
2981
  }
2875
2982
  async function claimGoal(goalId, opts = {}) {
2876
2983
  const token = authToken(opts.token) ?? "";
2877
- const res = ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/claim`, { method: "POST", headers: { "content-type": "application/json", authorization: `Bearer ${token}`, "x-paigy-model": "goal-entry-v1" }, body: JSON.stringify(goalId ? { goalId } : {}) }));
2984
+ const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/claim`, { method: "POST", headers: { "content-type": "application/json", authorization: `Bearer ${token}`, "x-paigy-model": "goal-entry-v1" }, body: JSON.stringify(goalId ? { goalId } : {}) }));
2878
2985
  if (!res.ok) await fail("claim_goal", res);
2879
2986
  return await res.json();
2880
2987
  }
2881
2988
  async function getGoal(goalId, opts = {}, read = {}) {
2882
2989
  const token = authToken(opts.token) ?? "";
2883
- const res = ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/${encodeURIComponent(goalId)}${read.history ? "?history=1" : ""}`, {
2990
+ const query = [read.history ? "history=1" : "", read.diagnose ? "diagnose=1" : ""].filter(Boolean).join("&");
2991
+ const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/${encodeURIComponent(goalId)}${query ? `?${query}` : ""}`, {
2884
2992
  method: "GET",
2885
2993
  headers: { authorization: `Bearer ${token}`, "x-paigy-model": "goal-entry-v1" }
2886
2994
  }));
@@ -2890,13 +2998,13 @@ async function getGoal(goalId, opts = {}, read = {}) {
2890
2998
  async function updateGoal(goalId, input, opts = {}) {
2891
2999
  const token = authToken(opts.token) ?? "";
2892
3000
  const operationId = input.operationId ?? randomUUID2();
2893
- const res = ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/${encodeURIComponent(goalId)}`, { method: "PATCH", headers: { "content-type": "application/json", authorization: `Bearer ${token}`, "x-paigy-model": "goal-entry-v1" }, body: JSON.stringify({ ...input, operationId }) }));
3001
+ const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/${encodeURIComponent(goalId)}`, { method: "PATCH", headers: { "content-type": "application/json", authorization: `Bearer ${token}`, "x-paigy-model": "goal-entry-v1" }, body: JSON.stringify({ ...input, operationId }) }));
2894
3002
  if (!res.ok) await fail("update_goal", res);
2895
3003
  return await res.json();
2896
3004
  }
2897
3005
  var AWAIT_WINDOW_MS = 45e3;
2898
3006
  async function hatch(name, voice = null) {
2899
- const res = ensureAuthed(await reach(`${BACKEND_URL}/api/hatch`, {
3007
+ const res = await ensureAuthed(await reach(`${BACKEND_URL}/api/hatch`, {
2900
3008
  method: "POST",
2901
3009
  headers: { "content-type": "application/json", authorization: `Bearer ${authToken()}` },
2902
3010
  body: JSON.stringify({ name, voice })
@@ -2918,7 +3026,7 @@ var NameTakenError = class extends Error {
2918
3026
  code = "agent_name_taken";
2919
3027
  };
2920
3028
  async function setIdentity(patch, opts = {}) {
2921
- const res = ensureAuthed(await reach(`${BACKEND_URL}/api/identity`, {
3029
+ const res = await ensureAuthed(await reach(`${BACKEND_URL}/api/identity`, {
2922
3030
  method: "PATCH",
2923
3031
  headers: { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token)}` },
2924
3032
  body: JSON.stringify(patch)
@@ -2938,19 +3046,27 @@ async function setIdentity(patch, opts = {}) {
2938
3046
  return await res.json();
2939
3047
  }
2940
3048
  async function claimSessions(opts = {}) {
2941
- const res = ensureAuthed(await reach(`${BACKEND_URL}/api/sessions/claim`, {
3049
+ const res = await ensureAuthed(await reach(`${BACKEND_URL}/api/sessions/claim`, {
2942
3050
  headers: { authorization: `Bearer ${authToken(opts.token)}` }
2943
3051
  // the HOST's identity
2944
3052
  }));
2945
3053
  if (!res.ok) throw new Error(`claim_sessions failed: ${res.status}`);
2946
3054
  return (await res.json()).sessions;
2947
3055
  }
3056
+ async function recordDecision(decision, opts = {}) {
3057
+ const res = await ensureAuthed(await reach(`${BACKEND_URL}/api/host/decisions`, {
3058
+ method: "POST",
3059
+ headers: { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token)}` },
3060
+ body: JSON.stringify(decision)
3061
+ }));
3062
+ if (!res.ok) await fail("record_decision", res);
3063
+ }
2948
3064
  async function heartbeat(runtime, opts = {}) {
2949
3065
  const body = {
2950
3066
  ...runtime !== void 0 ? { runtime } : {},
2951
3067
  ...opts.activity !== void 0 ? { activity: opts.activity } : {}
2952
3068
  };
2953
- const res = ensureAuthed(await reach(`${BACKEND_URL}/api/presence`, {
3069
+ const res = await ensureAuthed(await reach(`${BACKEND_URL}/api/presence`, {
2954
3070
  method: "POST",
2955
3071
  headers: { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token)}` },
2956
3072
  ...Object.keys(body).length > 0 ? { body: JSON.stringify(body) } : {}
@@ -2958,7 +3074,7 @@ async function heartbeat(runtime, opts = {}) {
2958
3074
  if (!res.ok) throw new Error(`heartbeat failed: ${res.status}`);
2959
3075
  }
2960
3076
  async function registerDelivery(mode, opts = {}) {
2961
- const res = ensureAuthed(await reach(`${BACKEND_URL}/api/delivery`, {
3077
+ const res = await ensureAuthed(await reach(`${BACKEND_URL}/api/delivery`, {
2962
3078
  method: "POST",
2963
3079
  headers: { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token)}` },
2964
3080
  body: JSON.stringify({ mode })
@@ -2967,21 +3083,21 @@ async function registerDelivery(mode, opts = {}) {
2967
3083
  return await res.json();
2968
3084
  }
2969
3085
  async function listNotes(opts = {}) {
2970
- const res = ensureAuthed(await reach(`${BACKEND_URL}/api/notes`, {
3086
+ const res = await ensureAuthed(await reach(`${BACKEND_URL}/api/notes`, {
2971
3087
  headers: { authorization: `Bearer ${authToken(opts.token)}` }
2972
3088
  }));
2973
3089
  if (!res.ok) throw new Error(`list_notes failed: ${res.status} ${await res.text()}`);
2974
3090
  return await res.json();
2975
3091
  }
2976
3092
  async function listConnections(opts = {}) {
2977
- const res = ensureAuthed(await reach(`${BACKEND_URL}/api/tokens`, {
3093
+ const res = await ensureAuthed(await reach(`${BACKEND_URL}/api/tokens`, {
2978
3094
  headers: { authorization: `Bearer ${authToken(opts.token)}`, "x-paigy-model": "goal-entry-v1" }
2979
3095
  }));
2980
3096
  if (!res.ok) throw new Error(`list_connections failed: ${res.status} ${await res.text()}`);
2981
3097
  return await res.json();
2982
3098
  }
2983
3099
  async function submitTriage(run, opts = {}) {
2984
- const res = ensureAuthed(await reach(`${BACKEND_URL}/api/triage`, {
3100
+ const res = await ensureAuthed(await reach(`${BACKEND_URL}/api/triage`, {
2985
3101
  method: "POST",
2986
3102
  headers: { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token)}` },
2987
3103
  body: JSON.stringify(run)
@@ -2990,14 +3106,14 @@ async function submitTriage(run, opts = {}) {
2990
3106
  return await res.json();
2991
3107
  }
2992
3108
  async function getTriage(opts = {}) {
2993
- const res = ensureAuthed(await reach(`${BACKEND_URL}/api/triage`, {
3109
+ const res = await ensureAuthed(await reach(`${BACKEND_URL}/api/triage`, {
2994
3110
  headers: { authorization: `Bearer ${authToken(opts.token)}` }
2995
3111
  }));
2996
3112
  if (!res.ok) throw new Error(`get_triage failed: ${res.status} ${await res.text()}`);
2997
3113
  return await res.json();
2998
3114
  }
2999
3115
  async function acceptTriage(proposalId, body, opts = {}) {
3000
- const res = ensureAuthed(await reach(`${BACKEND_URL}/api/triage/${encodeURIComponent(proposalId)}/accept`, {
3116
+ const res = await ensureAuthed(await reach(`${BACKEND_URL}/api/triage/${encodeURIComponent(proposalId)}/accept`, {
3001
3117
  method: "POST",
3002
3118
  headers: { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token)}` },
3003
3119
  body: JSON.stringify(body)
@@ -3006,7 +3122,7 @@ async function acceptTriage(proposalId, body, opts = {}) {
3006
3122
  return await res.json();
3007
3123
  }
3008
3124
  async function dismissTriage(proposalId, opts = {}) {
3009
- const res = ensureAuthed(await reach(`${BACKEND_URL}/api/triage/${encodeURIComponent(proposalId)}/dismiss`, {
3125
+ const res = await ensureAuthed(await reach(`${BACKEND_URL}/api/triage/${encodeURIComponent(proposalId)}/dismiss`, {
3010
3126
  method: "POST",
3011
3127
  headers: { authorization: `Bearer ${authToken(opts.token)}` }
3012
3128
  }));
@@ -3062,7 +3178,7 @@ async function contact(input, opts = {}) {
3062
3178
  ask: a.ask,
3063
3179
  options: a.options ?? []
3064
3180
  }));
3065
- const res = ensureAuthed(await send2(`${BACKEND_URL}/api/goals/contact`, {
3181
+ const res = await ensureAuthed(await send2(`${BACKEND_URL}/api/goals/contact`, {
3066
3182
  method: "POST",
3067
3183
  headers,
3068
3184
  signal: opts.signal,
@@ -3084,15 +3200,17 @@ async function contact(input, opts = {}) {
3084
3200
  }
3085
3201
  const all = deliveries.filter((d) => !!d.deliveryId && !!d.goalId).map(({ id, deliveryId: deliveryId2, goalId }) => ({ ...id ? { id } : {}, deliveryId: deliveryId2, goalId }));
3086
3202
  const joinedCard = deliveries[0]?.joinedCard === true;
3203
+ const joinedCall = deliveries[0]?.joinedCall === true;
3087
3204
  const read = async (signal2) => {
3088
- const res = ensureAuthed(await send2(`${BACKEND_URL}/api/deliveries/${encodeURIComponent(deliveryId)}`, { headers, signal: signal2 }));
3205
+ const res = await ensureAuthed(await send2(`${BACKEND_URL}/api/deliveries/${encodeURIComponent(deliveryId)}`, { headers, signal: signal2 }));
3089
3206
  if (!res.ok) await fail("read_delivery", res);
3090
3207
  const one = await res.json();
3091
3208
  return {
3092
3209
  ...one,
3093
3210
  ...all.length > 1 ? { deliveries: all } : {},
3094
3211
  ...recorded.length ? { recorded, notSent: NOT_SENT } : {},
3095
- ...joinedCard ? { joinedCard: true } : {}
3212
+ ...joinedCard ? { joinedCard: true } : {},
3213
+ ...joinedCall ? { joinedCall: true } : {}
3096
3214
  };
3097
3215
  };
3098
3216
  const settled = (d) => d.kind === "notification" || d.state === "closed" || d.answers.length > 0 || d.entries.some((e) => e.kind === "contribution");
@@ -3114,7 +3232,7 @@ async function contact(input, opts = {}) {
3114
3232
  }
3115
3233
  }
3116
3234
  async function checkReplies(opts = {}) {
3117
- const res = ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/deliveries`, {
3235
+ const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/deliveries`, {
3118
3236
  headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}`, "x-paigy-model": "goal-entry-v1" }
3119
3237
  }));
3120
3238
  if (!res.ok) await fail("check_replies", res);
@@ -3206,6 +3324,7 @@ function goalView(g) {
3206
3324
  more: more(g)
3207
3325
  });
3208
3326
  }
3327
+ var JOINED_CALL = "This ask joined the call the person is ALREADY ON \u2014 no ring, no card of its own. `conversation` above is that whole call, so the answers already given on it before your ask arrived are in it. Yours is queued for the bot's next turn: call contact({deliveryId}) again to keep reading this same call until your answer lands \u2014 do not resend the ask. To add information or a further question to the same call, contact again while it is live; that joins it too.";
3209
3328
  function deliveryView(d) {
3210
3329
  const answers = "Answers land on the Goal: read them with claim_goal or get_goal. Do not poll this notification.";
3211
3330
  return compact({
@@ -3220,7 +3339,8 @@ function deliveryView(d) {
3220
3339
  recorded: d.recorded,
3221
3340
  notSent: d.notSent,
3222
3341
  joinedCard: d.joinedCard,
3223
- next: d.kind === "notification" ? d.joinedCard ? `Added to the open report card on its Goal; no new push was sent. ${answers}` : answers : d.message
3342
+ joinedCall: d.joinedCall,
3343
+ 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 })}) now to continue this same Call; do not resend the ask or end your turn merely because this wait window returned. ${d.message ?? ""}`.trim() : d.message
3224
3344
  });
3225
3345
  }
3226
3346
  function repliesView(r) {
@@ -3255,7 +3375,11 @@ function repliesView(r) {
3255
3375
  next: [
3256
3376
  deliveries.length || r.claimable || review.length || assigned.length ? "Act on a Goal with claim_goal; reread one Delivery in full with contact({deliveryId})." : "Nothing is waiting.",
3257
3377
  ...assigned.length ? [`${assigned.length} of your Goals are assigned to you and not started (assigned): claim_goal({goalId}) to start one.`] : [],
3258
- ...review.length ? [`${review.length} of your Goals have something new (review): read each with claim_goal or get_goal, then update_goal with reviewed: true.`] : [],
3378
+ // THE PERSON SPOKE, AND THAT IS NOT "SOMETHING NEW" (2026-09-29). This line said only how
3379
+ // many Goals were flagged, so four things the owner said on a call read as four chores. The
3380
+ // rows carry their words now (`said`, `saidAt`, newest first, `delivery/read.ts`); the
3381
+ // sentence names the person and quotes the freshest one, because a count is not a message.
3382
+ ...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.`] : [],
3259
3383
  ...stalled.length ? [`${stalled.length} of your Goals have had no progress for 3 days: update_goal each with progress, finish it, or cancel it.`] : [],
3260
3384
  ...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.`] : [],
3261
3385
  // No name of its own (#2525): the API's ask, verbatim.
@@ -3263,6 +3387,13 @@ function repliesView(r) {
3263
3387
  ].join(" ")
3264
3388
  };
3265
3389
  }
3390
+ function spoke(review) {
3391
+ const withWords = review.filter((r) => r.said?.trim());
3392
+ if (!withWords.length) return null;
3393
+ const newest = withWords[0];
3394
+ const rest = review.length - 1;
3395
+ return `The person has spoken on ${withWords.length === 1 ? "one of your Goals" : `${withWords.length} of your Goals`} \u2014 newest, on ${newest.title ?? newest.goalId}: "${newest.said}". Read it with claim_goal or get_goal, answer what it asks, then update_goal with reviewed: true.${rest > 0 ? ` (${rest} more flagged; see review.)` : ""}`;
3396
+ }
3266
3397
  async function runTool(name, args, opts) {
3267
3398
  const { waits, signal, ...client } = opts;
3268
3399
  const input = args ?? {};
@@ -3283,8 +3414,11 @@ async function runTool(name, args, opts) {
3283
3414
  return goalView(await claimGoal(goalId, client));
3284
3415
  }
3285
3416
  case "get_goal": {
3286
- const { goalId, history } = GetGoalSchema.parse(input);
3287
- return goalView(await getGoal(goalId, client, { history }));
3417
+ const { goalId, history, diagnose } = GetGoalSchema.parse(input);
3418
+ const goal = await getGoal(goalId, client, { history, diagnose });
3419
+ const view = goalView(goal);
3420
+ const diagnosis = goal.diagnosis;
3421
+ return diagnosis === void 0 ? view : { ...view, diagnosis };
3288
3422
  }
3289
3423
  case "update_goal": {
3290
3424
  const { goalId, ...body } = UpdateGoalToolSchema.parse(input);
@@ -3499,6 +3633,7 @@ export {
3499
3633
  requestCode,
3500
3634
  pollToken,
3501
3635
  UnpairedError,
3636
+ RevokedError,
3502
3637
  ApiError,
3503
3638
  overrideToken,
3504
3639
  authToken,
@@ -3512,6 +3647,7 @@ export {
3512
3647
  NameTakenError,
3513
3648
  setIdentity,
3514
3649
  claimSessions,
3650
+ recordDecision,
3515
3651
  heartbeat,
3516
3652
  registerDelivery,
3517
3653
  listNotes,