@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.
- package/dist/{chunk-VYWIPFEY.js → chunk-2UEIJRRC.js} +206 -70
- package/dist/{chunk-76ILN3YV.js → chunk-B7SEHASI.js} +2 -2
- package/dist/{chunk-TRMYDJ5H.js → chunk-LRN27BMO.js} +1 -1
- package/dist/{chunk-HGXXGOGG.js → chunk-OW65TWMX.js} +144 -46
- package/dist/{dist-GJOOJ4FR.js → dist-VVU24KB2.js} +5 -1
- package/dist/enable.js +3 -3
- package/dist/index.js +40 -4
- package/dist/listen.js +7 -2
- package/dist/onboard.js +4 -4
- package/dist/slot.js +1 -1
- package/dist/stalled.js +1 -1
- package/dist/statusline.js +1 -1
- package/package.json +1 -1
|
@@ -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
|
|
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(
|
|
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
|
|
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 (
|
|
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
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
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
|
-
|
|
2169
|
-
|
|
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
|
|
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
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
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
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|