@paigy/mcp 0.40.16 → 0.40.19

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.
@@ -1536,7 +1536,7 @@ var StartContactSchema = z2.object({
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.";
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).";
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
@@ -1581,11 +1581,16 @@ var UpdateGoalSchema = z3.object({
1581
1581
  }).strict();
1582
1582
  var UpdateGoalToolSchema = UpdateGoalSchema.omit({ operationId: true }).extend({ goalId: z3.string().uuid() }).strict();
1583
1583
  var ClaimGoalSchema = z3.object({ goalId: z3.string().uuid().optional() }).strict();
1584
- var GetGoalSchema = z3.object({ goalId: z3.string().uuid() }).strict();
1585
- 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), and `next`, the one step to take. Foreign or sibling-owned Goals are not disclosed.";
1586
- 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. Returns the Goal as get_goal reads it, at its new revision.";
1584
+ var GetGoalSchema = z3.object({
1585
+ goalId: z3.string().uuid(),
1586
+ /** Every entry in full. Without it the read carries the person's words, open questions and your
1587
+ * newest entry, and counts what it left out (`apps/api/src/goal/collapse.ts`). */
1588
+ history: z3.boolean().optional()
1589
+ }).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.`;
1587
1592
  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.";
1588
- 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; 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.";
1593
+ 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.";
1589
1594
  var CheckRepliesSchema = z3.object({}).strict();
1590
1595
  var AGENT_TOOLS = [
1591
1596
  { name: "contact", description: CONTACT_DESCRIPTION, inputSchema: CONTACT_SCHEMA },
@@ -1930,9 +1935,9 @@ var AgendaTurnSchema = z4.object({
1930
1935
  /** Twin coverage (#1089): sibling claim ids this asking turn's answer ALSO settles —
1931
1936
  * the planner declares duplicates instead of asking them twice. */
1932
1937
  coveredIds: z4.array(z4.string()).optional(),
1933
- /** At most three short spoken sentences. Capped because a turn is a breath: a 1031-char
1934
- * line went out on 2026-07-28 and the caller could not answer it at all. */
1935
- info: z4.array(z4.string().min(1)).max(3).default([]),
1938
+ /** The spoken sentences of the turn, in order. No count: how long a turn is is the brain's call
1939
+ * (owner, 2026-09-25), and a count here refused whole plans. */
1940
+ info: z4.array(z4.string().min(1)).default([]),
1936
1941
  question: z4.string().min(1).nullable(),
1937
1942
  /** True on the one turn carrying the agent's own declared question. */
1938
1943
  asks: z4.boolean().optional(),
@@ -2016,7 +2021,13 @@ var InboxItemSchema = z4.object({
2016
2021
  * request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
2017
2022
  * carry for the call's one agent, so the phone draws it with the same seed. Absent when the
2018
2023
  * author is not an agent this account holds (unpaired since, or a person). */
2019
- agent: z4.object({ tokenId: z4.string(), name: z4.string(), voice: VoiceKeySchema.optional() }).optional()
2024
+ agent: z4.object({ tokenId: z4.string(), name: z4.string(), voice: VoiceKeySchema.optional() }).optional(),
2025
+ /** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
2026
+ * its need offers, exactly as its own card carries them, present only when one of them has a
2027
+ * preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
2028
+ * never re-sent as a second card to be seen mid-call. Words-only options are absent — the bot
2029
+ * says those, and the list stays small (an `html` is up to 16 KB). */
2030
+ options: z4.array(OptionSchema).optional()
2020
2031
  })).optional(),
2021
2032
  visuals: z4.array(VisualSchema).optional(),
2022
2033
  /** The connected agent's name (the single pairing name — user-typed, or the
@@ -2043,6 +2054,11 @@ var InboxItemSchema = z4.object({
2043
2054
  * and "dismiss the stalled ones" cancelled 37 pieces of live work). */
2044
2055
  cold: z4.string().datetime().optional(),
2045
2056
  clarifies: z4.string().optional(),
2057
+ /** THIS CARD'S QUESTION IS ON A LIVE CALL (owner, 2026-09-24: "Mark it while the call is
2058
+ * live"). Present only while an open Call Delivery carries the card's request Entry — read
2059
+ * off the same open list the card came from, so it clears when the Call does. A card is the
2060
+ * backup for a call not taken; while the call has it, the call is where it is answered. */
2061
+ onCall: z4.literal(true).optional(),
2046
2062
  /** THE RING, ON THE ITEM (walk/design.md §12 §17, #2251): the last ring on this card was
2047
2063
  * declined, and what the ladder will do next — read off the cron's own row, never computed
2048
2064
  * on the phone. Present only while a `declined` receipt stands on the card's last Call.
@@ -2407,7 +2423,19 @@ var QueueItemSchema = z4.object({
2407
2423
  /** When its owner last SAID something about it (`goals.last_progress_at`, written by every
2408
2424
  * `update_goal` that changes `progress`). `updatedAt` moves for reasons nobody chose — a
2409
2425
  * state recomputed, a review flag — so it cannot tell work in hand from work gone quiet. */
2410
- lastProgressAt: z4.string().nullable().optional()
2426
+ lastProgressAt: z4.string().nullable().optional(),
2427
+ /** THE GOAL'S NEWEST WORD, FROM EITHER SIDE (owner, 2026-09-27): the newest Entry on it, of any
2428
+ * kind — what the person added ("Add to this"), their reply, the agent's ask or its progress
2429
+ * note. A progress note is an Entry, so this is already the newer of the two: the person's note
2430
+ * shows the moment it is written, and the agent's reply or next note replaces it by being newer.
2431
+ * `said` is bounded to 280 characters server-side (a line, not the conversation). Null when the
2432
+ * Goal carries no readable Entry; optional, so hand-built queues need not spell it. */
2433
+ latest: z4.object({
2434
+ from: z4.enum(["person", "agent"]),
2435
+ said: z4.string(),
2436
+ at: z4.string(),
2437
+ entryId: z4.string()
2438
+ }).nullable().optional()
2411
2439
  });
2412
2440
  var COLD_AFTER_MS = 3 * 24 * 60 * 60 * 1e3;
2413
2441
  var NoteSourceSchema = z4.enum(["app", "call"]);
@@ -2585,6 +2613,104 @@ var NotificationFeedbackKindSchema = z4.enum([
2585
2613
  "other"
2586
2614
  // anything else — the note carries it.
2587
2615
  ]);
2616
+ var SlimOptionSchema = OptionSchema.omit({ html: true });
2617
+ var QuestionRowSchema = z4.object({
2618
+ /** The card's id (`deliveryId:needId`, or `deliveryId:entryId` for an update), as the inbox mints it. */
2619
+ id: z4.string(),
2620
+ deliveryId: z4.string(),
2621
+ entryId: z4.string(),
2622
+ /** The decision it waits on; null for an update, which asks nothing. */
2623
+ needId: z4.string().nullable(),
2624
+ goalIds: z4.array(z4.string()),
2625
+ /** The name of the work it is about, when the read could word it. */
2626
+ goalTitle: z4.string().optional(),
2627
+ tokenId: z4.string().optional(),
2628
+ name: z4.string(),
2629
+ title: z4.string(),
2630
+ body: z4.string(),
2631
+ select: z4.enum(["one", "many", "rank", "confirm", "text"]),
2632
+ options: z4.array(SlimOptionSchema),
2633
+ hasPreview: z4.boolean(),
2634
+ blocking: z4.boolean(),
2635
+ askedAt: z4.string().datetime(),
2636
+ ring: InboxItemSchema.shape.ring,
2637
+ onCall: z4.literal(true).optional(),
2638
+ sealed: z4.boolean()
2639
+ });
2640
+ var WorkStateSchema = z4.enum(["ready", "active", "waiting", "done", "cancelled"]);
2641
+ var WorkRowSchema = z4.object({
2642
+ id: z4.string(),
2643
+ parentId: z4.string().nullable(),
2644
+ title: z4.string(),
2645
+ /** Straight off the Goal. */
2646
+ state: WorkStateSchema,
2647
+ owner: z4.string().nullable(),
2648
+ revision: z4.number().int(),
2649
+ /** Open questions on it, counted to 100. */
2650
+ waiting: z4.number().int(),
2651
+ /** Held by a gate on work that is not done. */
2652
+ blocked: z4.boolean(),
2653
+ lastProgressAt: z4.string().datetime().nullable(),
2654
+ /** The line written for its newest progress note, else that note's first words. */
2655
+ line: z4.string().nullable(),
2656
+ /** Work directly under it, counted to 100; the list carries up to 12 of them. */
2657
+ children: z4.number().int(),
2658
+ createdAt: z4.string().datetime(),
2659
+ updatedAt: z4.string().datetime(),
2660
+ /** When anything at or under it last moved — the order the list is in. */
2661
+ activeAt: z4.string().datetime(),
2662
+ /** A sealed outcome has no title here; the work's page opens it. */
2663
+ sealed: z4.boolean()
2664
+ });
2665
+ var ComputerRowSchema = ConnectionSummarySchema.omit({ activity: true });
2666
+ var AgentRowSchema = ComputerRowSchema.extend({
2667
+ /** Open questions it is asking the person, over every open card; null when that read failed. */
2668
+ asking: z4.number().int().nullable(),
2669
+ oldestAskAt: z4.string().datetime().nullable(),
2670
+ /** Up to three of the live Goals it holds, oldest first (the order it picks them up), and how
2671
+ * many in all among the account's 200 most recently active agent-held live Goals
2672
+ * (`agent_holds`); null when that read failed. */
2673
+ holds: z4.array(z4.object({ id: z4.string(), title: z4.string() })).nullable(),
2674
+ held: z4.number().int().nullable(),
2675
+ /** The earliest instant any Goal it holds went quiet, by the one rule (`coldSince`); null
2676
+ * while none has, or when that read failed. */
2677
+ cold: z4.string().datetime().nullable(),
2678
+ /** The newest line of its working log, and when the harness saw it. */
2679
+ line: z4.string().nullable(),
2680
+ lineAt: z4.string().datetime().nullable()
2681
+ });
2682
+ var SnapshotSchema = z4.object({
2683
+ /** The API's clock, taken before the first read: what a later delta will start from. */
2684
+ at: z4.string().datetime(),
2685
+ questions: z4.object({
2686
+ /** The newest 30 open cards, questions before updates. */
2687
+ items: z4.array(QuestionRowSchema),
2688
+ /** Every open question, and apart from them every update, and what was put off. */
2689
+ total: z4.number().int(),
2690
+ updates: z4.number().int(),
2691
+ putOff: z4.number().int()
2692
+ }).nullable(),
2693
+ agents: z4.object({
2694
+ /** Up to 60, most recently seen first. */
2695
+ items: z4.array(AgentRowSchema),
2696
+ more: z4.boolean()
2697
+ }).nullable(),
2698
+ work: z4.object({
2699
+ /** The 60 most recently active roots, each followed by up to 12 children; 240 rows at most. */
2700
+ items: z4.array(WorkRowSchema),
2701
+ /** How much work is behind each of the Work tab's four filters, each counted to 100, read with
2702
+ * the rows. `work_list` (20260928023533) owns the predicates: Live is `ready`, `active` or
2703
+ * `waiting`; Waiting on you is live work with an open question or an unmet gate; Not started
2704
+ * is `ready`; Done is `done` or `cancelled`. */
2705
+ counts: z4.object({ live: z4.number().int(), waiting: z4.number().int(), notStarted: z4.number().int(), done: z4.number().int() })
2706
+ }).nullable(),
2707
+ you: z4.object({
2708
+ settings: UserSettingsSchema,
2709
+ callable: z4.boolean(),
2710
+ /** Up to 20 paired computers; null when the roster read failed. */
2711
+ computers: z4.array(ComputerRowSchema).nullable()
2712
+ }).nullable()
2713
+ });
2588
2714
  function sessionSlot(sessionId2) {
2589
2715
  const id = sessionId2 ?? sessionId();
2590
2716
  return `session:${id.slice(0, 8)}`;
@@ -2752,9 +2878,9 @@ async function claimGoal(goalId, opts = {}) {
2752
2878
  if (!res.ok) await fail("claim_goal", res);
2753
2879
  return await res.json();
2754
2880
  }
2755
- async function getGoal(goalId, opts = {}) {
2881
+ async function getGoal(goalId, opts = {}, read = {}) {
2756
2882
  const token = authToken(opts.token) ?? "";
2757
- const res = ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/${encodeURIComponent(goalId)}`, {
2883
+ const res = ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/${encodeURIComponent(goalId)}${read.history ? "?history=1" : ""}`, {
2758
2884
  method: "GET",
2759
2885
  headers: { authorization: `Bearer ${token}`, "x-paigy-model": "goal-entry-v1" }
2760
2886
  }));
@@ -2788,13 +2914,27 @@ async function whoAmI(opts = {}) {
2788
2914
  return null;
2789
2915
  }
2790
2916
  }
2917
+ var NameTakenError = class extends Error {
2918
+ code = "agent_name_taken";
2919
+ };
2791
2920
  async function setIdentity(patch, opts = {}) {
2792
2921
  const res = ensureAuthed(await reach(`${BACKEND_URL}/api/identity`, {
2793
2922
  method: "PATCH",
2794
2923
  headers: { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token)}` },
2795
2924
  body: JSON.stringify(patch)
2796
2925
  }));
2797
- if (!res.ok) throw new Error(`set_identity failed: ${res.status} ${await res.text()}`);
2926
+ if (!res.ok) {
2927
+ const text = await res.text();
2928
+ const refusal = (() => {
2929
+ try {
2930
+ return JSON.parse(text);
2931
+ } catch {
2932
+ return null;
2933
+ }
2934
+ })();
2935
+ if (res.status === 409 && refusal?.error === "agent_name_taken" && refusal.message) throw new NameTakenError(refusal.message);
2936
+ throw new Error(`set_identity failed: ${res.status} ${text}`);
2937
+ }
2798
2938
  return await res.json();
2799
2939
  }
2800
2940
  async function claimSessions(opts = {}) {
@@ -2899,6 +3039,7 @@ function currentRepo(cwd = process.cwd()) {
2899
3039
  }
2900
3040
  return asked;
2901
3041
  }
3042
+ var NOT_SENT = "Recorded as progress, not sent: nobody was notified. If the person should see it (a result, something to try or check, a finding), contact again and say so; report progress with update_goal.";
2902
3043
  async function contact(input, opts = {}) {
2903
3044
  const parsed = ContactSchema.parse(input);
2904
3045
  opts.signal?.throwIfAborted();
@@ -2906,6 +3047,7 @@ async function contact(input, opts = {}) {
2906
3047
  const headers = { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token) ?? ""}`, "x-paigy-model": "goal-entry-v1" };
2907
3048
  let deliveryId;
2908
3049
  let deliveries = [];
3050
+ let recorded = [];
2909
3051
  if ("deliveryId" in parsed) {
2910
3052
  deliveryId = parsed.deliveryId;
2911
3053
  } else {
@@ -2934,19 +3076,24 @@ async function contact(input, opts = {}) {
2934
3076
  if (!res.ok) await fail("batch contact", res);
2935
3077
  const receipt = await res.json();
2936
3078
  deliveries = receipt.deliveries ?? [];
2937
- if (deliveries.length === 0 && receipt.recorded?.length) {
2938
- return { recorded: receipt.recorded, message: receipt.message ?? "Recorded as progress on its Goal; nothing was sent to the person." };
2939
- }
3079
+ recorded = receipt.recorded ?? [];
3080
+ if (deliveries.length === 0 && recorded.length) return { sent: false, recorded, message: NOT_SENT };
2940
3081
  if (deliveries.length === 0) throw new Error("Goal contact returned no Delivery identities");
2941
3082
  deliveryId = deliveries[0]?.deliveryId ?? "";
2942
3083
  if (!deliveryId) throw new Error("Goal contact returned no valid Delivery identity");
2943
3084
  }
2944
- const all = deliveries.filter((d) => !!d.deliveryId && !!d.goalId);
3085
+ const all = deliveries.filter((d) => !!d.deliveryId && !!d.goalId).map(({ id, deliveryId: deliveryId2, goalId }) => ({ ...id ? { id } : {}, deliveryId: deliveryId2, goalId }));
3086
+ const joinedCard = deliveries[0]?.joinedCard === true;
2945
3087
  const read = async (signal2) => {
2946
3088
  const res = ensureAuthed(await send2(`${BACKEND_URL}/api/deliveries/${encodeURIComponent(deliveryId)}`, { headers, signal: signal2 }));
2947
3089
  if (!res.ok) await fail("read_delivery", res);
2948
3090
  const one = await res.json();
2949
- return all.length > 1 ? { ...one, deliveries: all } : one;
3091
+ return {
3092
+ ...one,
3093
+ ...all.length > 1 ? { deliveries: all } : {},
3094
+ ...recorded.length ? { recorded, notSent: NOT_SENT } : {},
3095
+ ...joinedCard ? { joinedCard: true } : {}
3096
+ };
2950
3097
  };
2951
3098
  const settled = (d) => d.kind === "notification" || d.state === "closed" || d.answers.length > 0 || d.entries.some((e) => e.kind === "contribution");
2952
3099
  if (opts.waits === false) return read(opts.signal);
@@ -2996,38 +3143,53 @@ function decided(result) {
2996
3143
  return void 0;
2997
3144
  }
2998
3145
  }
3146
+ var handle = (entryId) => entryId.slice(0, 8);
2999
3147
  function conversation(e) {
3000
3148
  const answers = new Map((e.answers ?? []).map((a) => [a.decisionNeedId, a]));
3149
+ const shown = new Set((e.entries ?? []).map((entry) => entry.entryId));
3150
+ const repliedTo = new Set((e.entries ?? []).flatMap((entry) => entry.aboutId && shown.has(entry.aboutId) ? [entry.aboutId] : []));
3001
3151
  return (e.entries ?? []).map((entry) => {
3002
3152
  const sealed = !!entry.content && "sealed" in entry.content;
3003
3153
  const plain = entry.content && "plain" in entry.content ? entry.content.plain : null;
3004
3154
  const options = plain && typeof plain === "object" && Array.isArray(plain.options) ? plain.options.map((o) => o.label).filter((l) => typeof l === "string") : [];
3005
3155
  const need = (e.decisionNeeds ?? []).find((n) => n.requestEntryId === entry.entryId);
3006
3156
  const answer = need ? answers.get(need.decisionNeedId) : void 0;
3007
- const answered = answer ? decided(answer.result) : void 0;
3157
+ const decision = answer ? decided(answer.result) : void 0;
3008
3158
  return compact({
3009
- id: entry.entryId,
3159
+ id: repliedTo.has(entry.entryId) ? handle(entry.entryId) : void 0,
3010
3160
  from: who(entry.authorParticipant),
3011
3161
  at: at(entry.createdAt),
3012
3162
  said: sealed ? "[encrypted]" : entryWords(entry).trim(),
3013
- re: entry.aboutId ?? void 0,
3163
+ re: entry.aboutId && shown.has(entry.aboutId) ? handle(entry.aboutId) : void 0,
3014
3164
  options,
3015
- decision: need ? compact({ id: need.decisionNeedId, state: need.state, answer: answered }) : void 0
3165
+ decision: need ? compact({ state: need.state, answer: decision }) : void 0
3016
3166
  });
3017
3167
  });
3018
3168
  }
3169
+ function more(g) {
3170
+ const earlier = g.earlier && g.earlier.omitted + g.earlier.shortened ? `${[g.earlier.omitted ? `${g.earlier.omitted} earlier entries left out` : "", g.earlier.shortened ? `${g.earlier.shortened} shortened to their headline` : ""].filter(Boolean).join(", ")}: get_goal({ goalId: "${g.goalId}", history: true }) reads every one in full.` : null;
3171
+ return [
3172
+ earlier,
3173
+ g.subtree?.length ? "A Goal below, in full: get_goal({ goalId }) with its goalId above." : null,
3174
+ g.parentGoalId ? `The Goal above: get_goal({ goalId: "${g.parentGoalId}" }).` : null,
3175
+ "Anything else waiting for you (other requests, answers, reviews): check_replies()."
3176
+ ].filter((line) => !!line);
3177
+ }
3019
3178
  function goalView(g) {
3020
3179
  if (!g.goalId) return compact({ state: g.state, next: g.message });
3180
+ const lines2 = conversation(g);
3181
+ const newest = [...lines2].reverse().find((l) => l.from !== "person");
3021
3182
  return compact({
3022
3183
  goalId: g.goalId,
3023
3184
  revision: g.revision,
3024
3185
  state: g.state,
3025
3186
  outcome: g.outcome,
3026
- progress: g.progress,
3187
+ progress: g.progress && g.progress.trim() !== newest?.said ? g.progress : void 0,
3027
3188
  reviewPending: g.reviewPending || void 0,
3028
3189
  owner: g.ownerParticipant,
3029
3190
  parentGoalId: g.parentGoalId,
3030
- childGoalIds: g.childGoalIds,
3191
+ // Every child is in the subtree below, with its goalId: listed again, it was said twice.
3192
+ childGoalIds: g.subtree?.length ? void 0 : g.childGoalIds,
3031
3193
  // EVERYTHING BELOW, read through this Goal (umbrella reads): one line per Goal, indented by
3032
3194
  // depth, then each question with its answer — so an agent that asked through an umbrella
3033
3195
  // reads every answer in one call, whoever owns the child it landed on.
@@ -3039,11 +3201,13 @@ function goalView(g) {
3039
3201
  blockedBy: g.blockedDependencyGoalIds,
3040
3202
  dueAt: g.dueAt,
3041
3203
  leaseExpiresAt: g.leaseExpiresAt,
3042
- conversation: conversation(g),
3043
- next: g.message
3204
+ conversation: lines2,
3205
+ next: g.message,
3206
+ more: more(g)
3044
3207
  });
3045
3208
  }
3046
3209
  function deliveryView(d) {
3210
+ const answers = "Answers land on the Goal: read them with claim_goal or get_goal. Do not poll this notification.";
3047
3211
  return compact({
3048
3212
  deliveryId: d.deliveryId,
3049
3213
  kind: d.kind,
@@ -3052,11 +3216,16 @@ function deliveryView(d) {
3052
3216
  conversation: conversation(d),
3053
3217
  // Each ask's own Delivery when a contact carried several — the only handle on the others.
3054
3218
  deliveries: d.deliveries,
3055
- next: d.kind === "notification" ? "Answers land on the Goal: read them with claim_goal or get_goal. Do not poll this notification." : d.message
3219
+ // WHAT THIS CONTACT DID NOT SEND (#2450): asks the intake read kept as progress, by name.
3220
+ recorded: d.recorded,
3221
+ notSent: d.notSent,
3222
+ 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
3056
3224
  });
3057
3225
  }
3058
3226
  function repliesView(r) {
3059
3227
  const deliveries = r.deliveries ?? [];
3228
+ const assigned = r.assigned ?? [];
3060
3229
  const review = r.review ?? [];
3061
3230
  const stalled = r.stalled ?? [];
3062
3231
  const others = r.stalledOthers ?? [];
@@ -3068,6 +3237,13 @@ function repliesView(r) {
3068
3237
  openDecisions: d.decisionNeeds.filter((n) => n.state === "open").length || void 0,
3069
3238
  latest: "about" in d ? d.about : void 0
3070
3239
  })),
3240
+ // WORK ASSIGNED TO YOU (owner, 2026-09-24): your Goals nobody has started, however they
3241
+ // became yours — a Goal handed to you is not a Delivery, and must not wait for a claim to be seen.
3242
+ ...assigned.length ? { assigned } : {},
3243
+ // WHAT claim_goal HANDS YOU NEXT (owner, 2026-09-27: "Wake on any work"): the same predicate that
3244
+ // starts a sleeping agent, so "nothing is waiting" is never said while a claim would hand you work
3245
+ // — a Goal you started in a process that died is on no other list.
3246
+ ...r.claimable ? { claimable: r.claimable } : {},
3071
3247
  // NEW ON YOUR GOALS (owner, 2026-09-23): a reply is an Entry on the Goal, not a Delivery, so the
3072
3248
  // Goals flagged for your review are listed here until you acknowledge them.
3073
3249
  ...review.length ? { review } : {},
@@ -3077,10 +3253,13 @@ function repliesView(r) {
3077
3253
  // OTHER AGENTS' STALLED WORK (owner, 2026-09-23): any agent may take it over and close it.
3078
3254
  ...others.length ? { stalledOthers: others } : {},
3079
3255
  next: [
3080
- deliveries.length || review.length ? "Act on a Goal with claim_goal; reread one Delivery in full with contact({deliveryId})." : "Nothing is waiting.",
3256
+ 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
+ ...assigned.length ? [`${assigned.length} of your Goals are assigned to you and not started (assigned): claim_goal({goalId}) to start one.`] : [],
3081
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.`] : [],
3082
3259
  ...stalled.length ? [`${stalled.length} of your Goals have had no progress for 3 days: update_goal each with progress, finish it, or cancel it.`] : [],
3083
- ...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.`] : []
3260
+ ...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
+ // No name of its own (#2525): the API's ask, verbatim.
3262
+ ...r.unnamed ? [r.unnamed] : []
3084
3263
  ].join(" ")
3085
3264
  };
3086
3265
  }
@@ -3090,7 +3269,7 @@ async function runTool(name, args, opts) {
3090
3269
  switch (name) {
3091
3270
  case "contact": {
3092
3271
  const sent = await contact(input, { ...client, signal, waits });
3093
- return "recorded" in sent ? sent : deliveryView(sent);
3272
+ return "sent" in sent ? sent : deliveryView(sent);
3094
3273
  }
3095
3274
  case "check_replies":
3096
3275
  CheckRepliesSchema.parse(input);
@@ -3104,8 +3283,8 @@ async function runTool(name, args, opts) {
3104
3283
  return goalView(await claimGoal(goalId, client));
3105
3284
  }
3106
3285
  case "get_goal": {
3107
- const { goalId } = GetGoalSchema.parse(input);
3108
- return goalView(await getGoal(goalId, client));
3286
+ const { goalId, history } = GetGoalSchema.parse(input);
3287
+ return goalView(await getGoal(goalId, client, { history }));
3109
3288
  }
3110
3289
  case "update_goal": {
3111
3290
  const { goalId, ...body } = UpdateGoalToolSchema.parse(input);
@@ -3118,47 +3297,108 @@ async function runTool(name, args, opts) {
3118
3297
  var HEARTBEAT_MS = 25e3;
3119
3298
  var RETRY_MS = [1e3, 2e3, 5e3, 1e4, 3e4];
3120
3299
  var JOIN = { config: { broadcast: { self: false } } };
3300
+ var JOIN_TIMEOUT_MS = 1e4;
3121
3301
  var lines = /* @__PURE__ */ new Map();
3122
3302
  function frame(line, topic, event, payload) {
3303
+ const ref = String(++line.ref);
3123
3304
  try {
3124
- line.ws?.send(JSON.stringify({ topic, event, payload, ref: String(++line.ref) }));
3305
+ line.ws?.send(JSON.stringify({ topic, event, payload, ref }));
3125
3306
  } catch {
3126
3307
  }
3308
+ return ref;
3127
3309
  }
3128
3310
  function stop(line) {
3129
3311
  line.open = false;
3312
+ line.beat = void 0;
3130
3313
  clearInterval(line.heart);
3131
3314
  clearTimeout(line.retry);
3315
+ for (const t of line.topics.values()) {
3316
+ clearTimeout(t.timer);
3317
+ t.joined = false;
3318
+ t.ref = void 0;
3319
+ }
3320
+ }
3321
+ function join3(line, name, t) {
3322
+ clearTimeout(t.timer);
3323
+ t.joined = false;
3324
+ t.ref = frame(line, `realtime:${name}`, "phx_join", JOIN);
3325
+ t.timer = setTimeout(() => rejoin(line, name, t), JOIN_TIMEOUT_MS);
3326
+ }
3327
+ function rejoin(line, name, t) {
3328
+ clearTimeout(t.timer);
3329
+ t.joined = false;
3330
+ t.ref = void 0;
3331
+ if (!line.open || line.topics.get(name) !== t) return;
3332
+ t.timer = setTimeout(() => {
3333
+ if (line.open && line.topics.get(name) === t) join3(line, name, t);
3334
+ }, RETRY_MS[Math.min(t.attempt++, RETRY_MS.length - 1)]);
3335
+ }
3336
+ function joined(t) {
3337
+ clearTimeout(t.timer);
3338
+ t.joined = true;
3339
+ t.attempt = 0;
3340
+ for (const l of t.listeners) l.onJoined?.(++l.joins);
3132
3341
  }
3133
3342
  function connect(line) {
3134
3343
  const base = line.url.replace(/^http/, "ws").replace(/\/+$/, "");
3135
3344
  const ws = line.ws = new WebSocket(`${base}/realtime/v1/websocket?apikey=${encodeURIComponent(line.anonKey)}&vsn=1.0.0`);
3345
+ let gone = false;
3346
+ const again = () => {
3347
+ if (gone) return;
3348
+ gone = true;
3349
+ stop(line);
3350
+ if (!line.topics.size) return;
3351
+ line.retry = setTimeout(() => connect(line), RETRY_MS[Math.min(line.attempt++, RETRY_MS.length - 1)]);
3352
+ };
3136
3353
  ws.addEventListener("open", () => {
3354
+ if (gone) return;
3137
3355
  line.open = true;
3138
3356
  line.attempt = 0;
3139
- for (const topic of line.topics.keys()) frame(line, `realtime:${topic}`, "phx_join", JOIN);
3140
- line.heart = setInterval(() => frame(line, "phoenix", "heartbeat", {}), HEARTBEAT_MS);
3357
+ for (const [name, t] of line.topics) join3(line, name, t);
3358
+ line.heart = setInterval(() => {
3359
+ if (line.beat) {
3360
+ try {
3361
+ ws.close();
3362
+ } catch {
3363
+ }
3364
+ again();
3365
+ return;
3366
+ }
3367
+ line.beat = frame(line, "phoenix", "heartbeat", {});
3368
+ }, HEARTBEAT_MS);
3141
3369
  });
3142
3370
  ws.addEventListener("message", (e) => {
3371
+ if (gone) return;
3143
3372
  let m;
3144
3373
  try {
3145
3374
  m = JSON.parse(String(e.data));
3146
3375
  } catch {
3147
3376
  return;
3148
3377
  }
3149
- if (m.event !== "broadcast" || !m.topic?.startsWith("realtime:")) return;
3150
- for (const l of line.topics.get(m.topic.slice("realtime:".length)) ?? []) {
3378
+ if (m.topic === "phoenix") {
3379
+ if (m.event === "phx_reply" && m.ref === line.beat) line.beat = void 0;
3380
+ return;
3381
+ }
3382
+ if (!m.topic?.startsWith("realtime:")) return;
3383
+ const name = m.topic.slice("realtime:".length);
3384
+ const t = line.topics.get(name);
3385
+ if (!t) return;
3386
+ if (m.event === "phx_reply") {
3387
+ if (!t.ref || m.ref !== t.ref) return;
3388
+ if (m.payload?.status === "ok") joined(t);
3389
+ else rejoin(line, name, t);
3390
+ return;
3391
+ }
3392
+ if (m.event === "phx_error" || m.event === "phx_close") {
3393
+ if (m.ref && m.ref !== t.ref) return;
3394
+ rejoin(line, name, t);
3395
+ return;
3396
+ }
3397
+ if (m.event !== "broadcast") return;
3398
+ for (const l of t.listeners) {
3151
3399
  if (m.payload?.event === l.event) l.onMessage(m.payload.payload);
3152
3400
  }
3153
3401
  });
3154
- let gone = false;
3155
- const again = () => {
3156
- if (gone) return;
3157
- gone = true;
3158
- stop(line);
3159
- if (!line.topics.size) return;
3160
- line.retry = setTimeout(() => connect(line), RETRY_MS[Math.min(line.attempt++, RETRY_MS.length - 1)]);
3161
- };
3162
3402
  ws.addEventListener("close", again);
3163
3403
  ws.addEventListener("error", again);
3164
3404
  }
@@ -3166,17 +3406,22 @@ function subscribeRealtime(args) {
3166
3406
  const key = `${args.url} ${args.anonKey}`;
3167
3407
  let line = lines.get(key);
3168
3408
  if (!line) lines.set(key, line = { url: args.url, anonKey: args.anonKey, open: false, attempt: 0, ref: 0, topics: /* @__PURE__ */ new Map() });
3169
- let heard = line.topics.get(args.topic);
3170
- if (!heard) {
3171
- line.topics.set(args.topic, heard = /* @__PURE__ */ new Set());
3172
- if (line.open) frame(line, `realtime:${args.topic}`, "phx_join", JOIN);
3173
- }
3174
- const me = { event: args.event, onMessage: args.onMessage };
3175
- heard.add(me);
3409
+ let t = line.topics.get(args.topic);
3410
+ if (!t) {
3411
+ line.topics.set(args.topic, t = { listeners: /* @__PURE__ */ new Set(), joined: false, attempt: 0 });
3412
+ if (line.open) join3(line, args.topic, t);
3413
+ }
3414
+ const topic = t;
3415
+ const me = { event: args.event, onMessage: args.onMessage, onJoined: args.onJoined, joins: 0 };
3416
+ topic.listeners.add(me);
3417
+ if (topic.joined && me.onJoined) queueMicrotask(() => {
3418
+ if (topic.joined && topic.listeners.has(me)) me.onJoined?.(++me.joins);
3419
+ });
3176
3420
  if (!line.ws) connect(line);
3177
3421
  return {
3178
3422
  close: () => {
3179
- if (!heard.delete(me) || heard.size) return;
3423
+ if (!topic.listeners.delete(me) || topic.listeners.size) return;
3424
+ clearTimeout(topic.timer);
3180
3425
  line.topics.delete(args.topic);
3181
3426
  if (line.topics.size) {
3182
3427
  if (line.open) frame(line, `realtime:${args.topic}`, "phx_leave", {});
@@ -3192,7 +3437,7 @@ function subscribeRealtime(args) {
3192
3437
  };
3193
3438
  }
3194
3439
  async function subscribeWake(onNudge, opts = {}) {
3195
- const { release = true, ...client } = opts;
3440
+ const { release = true, onJoined, ...client } = opts;
3196
3441
  const cfg = await registerDelivery("self_hosted", client);
3197
3442
  const { url, anonKey } = cfg.realtime;
3198
3443
  const channel = wakeChannel(cfg.tokenId);
@@ -3201,7 +3446,8 @@ async function subscribeWake(onNudge, opts = {}) {
3201
3446
  anonKey,
3202
3447
  topic: channel,
3203
3448
  event: WAKE_EVENT,
3204
- onMessage: (payload) => onNudge(payload ?? {})
3449
+ onMessage: (payload) => onNudge(payload ?? {}),
3450
+ onJoined
3205
3451
  });
3206
3452
  return {
3207
3453
  tokenId: cfg.tokenId,
@@ -3263,6 +3509,7 @@ export {
3263
3509
  AWAIT_WINDOW_MS,
3264
3510
  hatch,
3265
3511
  whoAmI,
3512
+ NameTakenError,
3266
3513
  setIdentity,
3267
3514
  claimSessions,
3268
3515
  heartbeat,
@@ -6,6 +6,7 @@ import {
6
6
  BACKEND_URL,
7
7
  KEY_PATH,
8
8
  NETWORK_MSG,
9
+ NameTakenError,
9
10
  NotifyRequestSchema,
10
11
  PROTO,
11
12
  TOKEN_PATH,
@@ -52,7 +53,7 @@ import {
52
53
  updateGoal,
53
54
  updateSlot,
54
55
  whoAmI
55
- } from "./chunk-6OBUH2FB.js";
56
+ } from "./chunk-VYWIPFEY.js";
56
57
  export {
57
58
  AGENT_TOOLS,
58
59
  AGENT_TOOL_NAMES,
@@ -61,6 +62,7 @@ export {
61
62
  BACKEND_URL,
62
63
  KEY_PATH,
63
64
  NETWORK_MSG,
65
+ NameTakenError,
64
66
  NotifyRequestSchema,
65
67
  PROTO,
66
68
  TOKEN_PATH,
package/dist/enable.js CHANGED
@@ -3,9 +3,9 @@ import {
3
3
  PAIGY_TOOL_IDS,
4
4
  enablePaigyTools,
5
5
  installSessionListening
6
- } from "./chunk-JWMR6K6D.js";
7
- import "./chunk-SFGGZLVO.js";
8
- import "./chunk-6OBUH2FB.js";
6
+ } from "./chunk-76ILN3YV.js";
7
+ import "./chunk-HGXXGOGG.js";
8
+ import "./chunk-VYWIPFEY.js";
9
9
 
10
10
  // src/enable.ts
11
11
  function main() {