@paigy/mcp 0.40.24 → 0.40.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -7
- package/dist/{chunk-TP7DM2MP.js → chunk-2ROD77AW.js} +1 -1
- package/dist/{chunk-WR2M3RME.js → chunk-EJYXBEWN.js} +252 -112
- package/dist/{chunk-VUKR2E24.js → chunk-GHSB2VP6.js} +88 -31
- package/dist/{chunk-J7T3EB5Y.js → chunk-UAVXYNLV.js} +3 -3
- package/dist/{dist-EKLTLANV.js → dist-NMHU2UNG.js} +13 -5
- package/dist/enable.js +3 -3
- package/dist/index.js +4 -4
- package/dist/listen.js +5 -5
- package/dist/onboard.js +4 -4
- package/dist/slot.js +1 -1
- package/dist/stalled.js +3 -3
- package/dist/statusline.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -117,7 +117,7 @@ Then pair: `PAIGY_AGENT=gemini npx -p @paigy/mcp@latest paigy-mcp-onboard`.
|
|
|
117
117
|
## Tools
|
|
118
118
|
|
|
119
119
|
One catalog, on every transport (2026-09-11): the agent tools are `AGENT_TOOLS` in
|
|
120
|
-
`packages/schema/src/tools.ts` —
|
|
120
|
+
`packages/schema/src/tools.ts` — who_is_working, contact, manage_goals, claim_goal,
|
|
121
121
|
get_goal, update_goal — and both this server and the hosted MCP
|
|
122
122
|
(`apps/api/src/mcp`) publish that list and dispatch it through the SDK's one `runTool`.
|
|
123
123
|
This server adds only onboard, pair and unpair, the tools that mint and delete the token
|
|
@@ -125,10 +125,12 @@ file this machine holds. See the [SDK contract](../../packages/sdk/README.md) fo
|
|
|
125
125
|
single-Goal start shape. Notification returns immediately. Call holds one cancellable
|
|
126
126
|
~45-second window here (the hosted transport returns after one read); continue with
|
|
127
127
|
contact({deliveryId}) without sending another ask. Durable Entries and accepted answers can
|
|
128
|
-
repeat on reads;
|
|
129
|
-
user started toward you, an answer relayed to something you asked, a handoff) without
|
|
130
|
-
consuming any of it, and claim_goal is the catch-up for
|
|
131
|
-
|
|
128
|
+
repeat on reads; contact with nothing to send receives what is addressed to you (a request the
|
|
129
|
+
user started toward you, an answer relayed to something you asked, a handoff, a question you owe) without
|
|
130
|
+
consuming any of it until you acknowledge it (`ackEventIds`), and claim_goal is the catch-up for
|
|
131
|
+
your own Goals. manage_goals creates, edits, assigns, organizes and closes work, a change at a
|
|
132
|
+
time. Bookkeeping (`operationId`, the manage request id, the revision an edit was read at) is
|
|
133
|
+
minted here, never asked of the model. Retired
|
|
132
134
|
reply-lease/ACK/work/callback tools and hidden notification aliases are rejected.
|
|
133
135
|
|
|
134
136
|
The standalone listener below is still a **legacy host and target-release blocker**.
|
|
@@ -154,7 +156,7 @@ wake channel and reads what is waiting on every nudge. Keep it alive past the te
|
|
|
154
156
|
with `paigy-listen --install` (launchd on macOS, systemd user unit on Linux;
|
|
155
157
|
`--uninstall` removes it).
|
|
156
158
|
|
|
157
|
-
The read is `
|
|
159
|
+
The read is `receive` (behind `contact({})`) — the open Deliveries addressed to this agent — and it
|
|
158
160
|
**consumes nothing**: no lease, no acknowledgement, and the same read twice returns the
|
|
159
161
|
same list. So the daemon hands the launcher what it found and stops there; claiming is
|
|
160
162
|
the agent's own first act (`claim_goal`), because the daemon shares this machine's
|
|
@@ -231,7 +233,7 @@ codex exec "Paigy goal $PAIGY_GOAL_ID. claim_goal it first. The user said: $PAIG
|
|
|
231
233
|
## Stalled work (turn-end hook — Claude Code, Codex, Gemini CLI, Antigravity)
|
|
232
234
|
|
|
233
235
|
An agent that goes quiet leaves its Goals behind. Three things hand it that work back, all
|
|
234
|
-
reading one rule (no progress for 3 days, `COLD_AFTER_MS`): `
|
|
236
|
+
reading one rule (no progress for 3 days, `COLD_AFTER_MS`): `contact({})` lists it under
|
|
235
237
|
`stalled`, `paigy-listen` carries it in `PAIGY_WORK` (and launches for it on boot), and this
|
|
236
238
|
hook holds a session's turn end once a day to list it. One program, four tools:
|
|
237
239
|
|
|
@@ -1424,7 +1424,7 @@ var SPOKEN_HOUR = {
|
|
|
1424
1424
|
eleven: 11,
|
|
1425
1425
|
twelve: 12
|
|
1426
1426
|
};
|
|
1427
|
-
function parseWhen(said2, tz, nowMs,
|
|
1427
|
+
function parseWhen(said2, tz, nowMs, read22 = "earliest") {
|
|
1428
1428
|
const text = said2.trim();
|
|
1429
1429
|
if (!text) return null;
|
|
1430
1430
|
const rel = RELATIVE.exec(text);
|
|
@@ -1463,13 +1463,13 @@ function parseWhen(said2, tz, nowMs, read = "earliest") {
|
|
|
1463
1463
|
if (hour === 12) hour = 0;
|
|
1464
1464
|
} else if (hour < 12 && dayShift === 0 && hour <= today.h) hour += 12;
|
|
1465
1465
|
} else {
|
|
1466
|
-
const parts = PARTS[
|
|
1466
|
+
const parts = PARTS[read22];
|
|
1467
1467
|
const part = Object.keys(parts).find((k) => new RegExp(`\\b${k}\\b`, "i").test(text));
|
|
1468
1468
|
if (/\btonight\b/i.test(text)) hour = parts["night"];
|
|
1469
1469
|
else if (part) hour = parts[part];
|
|
1470
1470
|
else if (dayShift > 0) hour = 9;
|
|
1471
1471
|
else if (/\bend of (the )?day\b|\beod\b/i.test(text)) hour = END_OF_DAY;
|
|
1472
|
-
else if (
|
|
1472
|
+
else if (read22 === "ring" && VAGUE.test(text)) return new Date(nowMs + 30 * 6e4).toISOString();
|
|
1473
1473
|
else return null;
|
|
1474
1474
|
}
|
|
1475
1475
|
const at2 = instantAt(tz, {
|
|
@@ -1492,8 +1492,8 @@ function parseWhen(said2, tz, nowMs, read = "earliest") {
|
|
|
1492
1492
|
}) : at2;
|
|
1493
1493
|
return new Date(rolled ?? at2).toISOString();
|
|
1494
1494
|
}
|
|
1495
|
-
function parseDelay(said2, tz, nowMs,
|
|
1496
|
-
const at2 = parseWhen(said2, tz, nowMs,
|
|
1495
|
+
function parseDelay(said2, tz, nowMs, read22 = "earliest") {
|
|
1496
|
+
const at2 = parseWhen(said2, tz, nowMs, read22);
|
|
1497
1497
|
if (!at2) return null;
|
|
1498
1498
|
return Math.max(0, Math.round((Date.parse(at2) - nowMs) / 1e3));
|
|
1499
1499
|
}
|
|
@@ -1551,9 +1551,17 @@ var StartContactSchema = z2.object({
|
|
|
1551
1551
|
waiting: z2.enum(["none", "hard"]).default("none").describe("hard requests a call even when channel is notification; user permissions and ring cooldowns still apply."),
|
|
1552
1552
|
channel: z2.enum(["notification", "call"]).default("notification")
|
|
1553
1553
|
}).strict();
|
|
1554
|
-
var
|
|
1554
|
+
var ReceiveContactSchema = z2.object({
|
|
1555
|
+
wait: z2.boolean().optional().describe(
|
|
1556
|
+
"Only controls when this returns. true (the default when you send and acknowledge nothing): wait up to about 45 seconds for incoming communication addressed to you, and return as soon as some is available. false: return what is waiting now. It does not mean work is blocked, set urgency, or ask for a call. `waitOutcome` says which happened: `available`, `expired` (nothing arrived in the window; wait again without sending again), or `not_waited` (a hosted connection never holds a wait)."
|
|
1557
|
+
),
|
|
1558
|
+
ackEventIds: z2.array(z2.string().uuid()).min(1).max(100).optional().describe(
|
|
1559
|
+
"The exact eventIds of the events you have handled: an acknowledgment (ACK). Reading alone acknowledges nothing, so an event comes back until you acknowledge it; after that the next batch can come. Each recipient acknowledges separately, for itself only. A Question you owe is answered (an ask with `answers`), never acknowledged away: that id is refused until it is answered."
|
|
1560
|
+
)
|
|
1561
|
+
}).strict();
|
|
1562
|
+
var ContactSchema = z2.union([StartContactSchema, z2.object({ deliveryId: z2.string().uuid() }).strict(), ReceiveContactSchema]);
|
|
1555
1563
|
var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
|
|
1556
|
-
var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'select', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. An ask that names a Goal (parentId) goes onto that Goal; one that names none starts a new Goal of yours. Notification returns immediately; collect
|
|
1564
|
+
var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'select', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. An ask that names a Goal (parentId) goes onto that Goal; one that names none starts a new Goal of yours. Notification returns immediately; collect answers by receiving (below). On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. One question per 'ask'; several questions are several objects in 'asks'. Each ask is sent exactly as written, with no reading in between: a bundled ask arrives as one card, so separate questions are separate asks. An ask with no options that is not blocking is a report, never a claim on them, so no answer is owed and none should be awaited. A report reaches the person only when it answers something they asked you, or once the Goal is done; any other report is recorded as the Goal's progress and nobody is notified. So when the work is finished, mark the Goal done first, then send one message saying what is done and anything they need to do or check. On a Goal whose report card is still open, a report that does reach them is added to that card, with no new push. Send options (or waiting:'hard') when you actually need an answer. Each option's label must stand on its own \u2014 the person may see only the labels \u2014 so never a label that points into your text ('All three', 'Option 2', '1 and 3 only'). select:'many' makes the card a checklist, for independent options they may want several of; select:'one' (the default) a pick, for alternatives. The person can always answer in their own words, so never add an 'Other' option. If the person is already on a call, a contact that reaches them joins that call automatically \u2014 whatever channel you asked for, with no ring \u2014 and the Delivery it returns IS that call: reread it with contact({deliveryId}) to see everything answered on it so far, and contact again while it is live to add information or a further question to the same call.\n\nRECEIVING: contact with no asks sends nothing and returns `events`, a limited batch of what is addressed to you (not a history page): `question`, a Question you owe (answer it with an ask whose `answers` is its questionId); `update`, something new on one of your Goals (a reply, an answer: read it with get_goal); `instruction`, a request or note sent to you. `hasMore` says more are waiting. Reading acknowledges nothing: once you have handled events, confirm their eventIds with contact({ackEventIds}), and the next batch can come. contact({}) waits up to about 45 seconds for something to arrive; contact({wait:false}) returns at once. It also lists work given to you that nobody has started (`assigned`; claim_goal starts it) and your work gone quiet (`stalled`).";
|
|
1557
1565
|
var CreateGoalSchema = z3.object({
|
|
1558
1566
|
outcome: z3.string().trim().min(1).max(1e4),
|
|
1559
1567
|
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
|
|
@@ -1573,10 +1581,6 @@ var CreateGoalSchema = z3.object({
|
|
|
1573
1581
|
* repo name. Delegated work inherits this from its parent Goal when omitted. */
|
|
1574
1582
|
repo: z3.string().trim().min(1).max(200).optional()
|
|
1575
1583
|
});
|
|
1576
|
-
var CreateGoalToolSchema = CreateGoalSchema.extend({
|
|
1577
|
-
idempotencyKey: CreateGoalSchema.shape.idempotencyKey.optional().describe("Optional. One is minted per call; pass your own only so a retry lands on the same Goal.")
|
|
1578
|
-
}).strict();
|
|
1579
|
-
var CREATE_GOAL_DESCRIPTION = 'Create a durable Goal for an outcome. Without parentGoalId it is placed against your open Goals: if one already IS this work, that Goal comes back (existing: true) and nothing new is created \u2014 continue it; if the work belongs under one, it is created there (parentGoalId in the receipt); otherwise it is a root. Pass parentGoalId yourself to put it under a specific Goal. Pass repo to anchor the work to a specific repository ("owner/repo" or repo name); delegated children inherit it. Pass title to name it in one to five words, as a person would refer to it out loud ("the night rings") \u2014 it heads every list and is spoken on a call; without one the brain writes it. Admission only: claim it before doing work, then update it as it advances. Returns an admission receipt with goalId, current state, revision, ownerParticipant, and the next step; no Goal content or execution lease.';
|
|
1580
1584
|
var UpdateGoalSchema = z3.object({
|
|
1581
1585
|
revision: z3.number().int().positive(),
|
|
1582
1586
|
changes: z3.object({
|
|
@@ -1600,7 +1604,65 @@ var UpdateGoalSchema = z3.object({
|
|
|
1600
1604
|
reason: z3.string().trim().min(1).max(2e3),
|
|
1601
1605
|
operationId: z3.string().uuid().optional()
|
|
1602
1606
|
}).strict();
|
|
1603
|
-
var UpdateGoalToolSchema =
|
|
1607
|
+
var UpdateGoalToolSchema = z3.object({
|
|
1608
|
+
goalId: z3.string().uuid(),
|
|
1609
|
+
revision: UpdateGoalSchema.shape.revision,
|
|
1610
|
+
changes: UpdateGoalSchema.shape.changes.innerType().pick({ progress: true, reviewed: true, dueAt: true, withdraw: true }).strict().refine((v) => Object.keys(v).length > 0),
|
|
1611
|
+
reason: UpdateGoalSchema.shape.reason
|
|
1612
|
+
}).strict();
|
|
1613
|
+
var changedGoal = z3.string().uuid().describe("The Goal to change, as a read shows it.");
|
|
1614
|
+
var goalTitle = z3.string().trim().min(1).max(80).describe("The Goal's short display name: one to five words, how a person refers to it out loud.");
|
|
1615
|
+
var goalOutcome = z3.string().trim().min(1).max(1e4).describe("The full desired result. There is no third description field.");
|
|
1616
|
+
var CreateChange = z3.object({
|
|
1617
|
+
kind: z3.literal("create"),
|
|
1618
|
+
title: goalTitle,
|
|
1619
|
+
outcome: goalOutcome,
|
|
1620
|
+
ownerId: z3.string().trim().min(1).optional().describe("Who owns the work: an agent's participant (as who_is_working shows it) or the person's. Omitted: you."),
|
|
1621
|
+
parentGoalId: z3.string().uuid().optional().describe("The Goal it belongs under. Omitted: a root."),
|
|
1622
|
+
sourceEntryIds: z3.array(z3.string().uuid()).max(20).optional().describe("The whole Entries the work came from, ones you can read.")
|
|
1623
|
+
}).strict();
|
|
1624
|
+
var EditChange = z3.object({ kind: z3.literal("edit"), goalId: changedGoal, title: goalTitle.optional(), outcome: goalOutcome.optional() }).strict();
|
|
1625
|
+
var StateChange = z3.object({ kind: z3.literal("state"), goalId: changedGoal, state: z3.enum(["open", "completed", "canceled"]).describe(
|
|
1626
|
+
"completed: the Goal's own work is done now (refused while required children are open); open: reopen it; canceled: it will not be done."
|
|
1627
|
+
) }).strict();
|
|
1628
|
+
var AssignChange = z3.object({ kind: z3.literal("assign"), goalId: changedGoal, ownerId: z3.string().trim().min(1).describe("The new owner's participant.") }).strict();
|
|
1629
|
+
var DeferChange = z3.object({ kind: z3.literal("defer"), goalId: changedGoal, until: z3.string().datetime({ offset: true }).nullable().describe(
|
|
1630
|
+
"Postpone it until this instant (its owner is woken then), or null to take the postponement back."
|
|
1631
|
+
) }).strict();
|
|
1632
|
+
var MoveChange = z3.object({ kind: z3.literal("move"), goalId: changedGoal, parentGoalId: z3.string().uuid().nullable().describe(
|
|
1633
|
+
"Its new parent, or null for a root. Only this Goal moves; its new parent's other children stay."
|
|
1634
|
+
) }).strict();
|
|
1635
|
+
var DependencyChange = z3.object({
|
|
1636
|
+
kind: z3.literal("dependency"),
|
|
1637
|
+
change: z3.enum(["add", "remove"]),
|
|
1638
|
+
goalId: changedGoal,
|
|
1639
|
+
dependsOnGoalId: z3.string().uuid().describe("The Goal it waits on."),
|
|
1640
|
+
action: z3.enum(["start", "complete"]).describe("What waits: starting goalId, or completing it."),
|
|
1641
|
+
reason: z3.string().trim().min(1).max(2e3).optional().describe("Why, as an explanation, never as policy.")
|
|
1642
|
+
}).strict();
|
|
1643
|
+
var read = { revision: z3.number().int().positive().optional() };
|
|
1644
|
+
var GoalChangeSchema = z3.discriminatedUnion("kind", [
|
|
1645
|
+
CreateChange,
|
|
1646
|
+
EditChange.extend(read),
|
|
1647
|
+
StateChange.extend(read),
|
|
1648
|
+
AssignChange.extend(read),
|
|
1649
|
+
DeferChange.extend(read),
|
|
1650
|
+
MoveChange.extend(read),
|
|
1651
|
+
DependencyChange
|
|
1652
|
+
]);
|
|
1653
|
+
var editNames = (changes, ctx) => changes.forEach((c, i) => {
|
|
1654
|
+
if (c.kind === "edit" && c.title === void 0 && c.outcome === void 0) {
|
|
1655
|
+
ctx.addIssue({ code: z3.ZodIssueCode.custom, path: ["changes", i], message: "an edit supplies title, outcome, or both" });
|
|
1656
|
+
}
|
|
1657
|
+
});
|
|
1658
|
+
var ManageGoalsSchema = z3.object({
|
|
1659
|
+
requestId: z3.string().uuid().optional(),
|
|
1660
|
+
changes: z3.array(GoalChangeSchema).min(1).max(50)
|
|
1661
|
+
}).strict().superRefine((v, ctx) => editNames(v.changes, ctx));
|
|
1662
|
+
var ManageGoalsToolSchema = z3.object({
|
|
1663
|
+
changes: z3.array(z3.discriminatedUnion("kind", [CreateChange, EditChange, StateChange, AssignChange, DeferChange, MoveChange, DependencyChange])).min(1).max(50).describe("The changes, applied in order, each on its own.")
|
|
1664
|
+
}).strict().superRefine((v, ctx) => editNames(v.changes, ctx));
|
|
1665
|
+
var MANAGE_GOALS_DESCRIPTION = "Create, edit, assign, organize or close work. Every change here changes Goals; answering, withdrawing or asking Questions stays in contact. Each change applies independently: a refused one names why (`results[i].error`, e.g. goal_revision_conflict, goal_children_open, goal_not_joined, goal_not_found) and the rest still apply, so read every result: a partial result is never a complete success. Kinds: create (title, outcome, ownerId, parentGoalId, sourceEntryIds) returns the new Goal's id in `results[i].goalId`, in the order requested, to use in later calls; edit (title and/or outcome); state (open, completed, canceled); assign (ownerId); defer (until, or null); move (parentGoalId, or null for a root: only this Goal moves); dependency (add or remove: goalId waits on dependsOnGoalId to start or to complete). A Goal cannot be completed while its required children or dependencies remain open: finish or move them first. Finishing the children does not prove the parent's own work is done. An edit applies at the version you last read: if someone changed the Goal since, it is refused as goal_revision_conflict, so read it again (get_goal) and reconsider. You may change a Goal you own or have written on. Returns `ok` (every change applied), `results` per change (applied or failed), and `goals`, each changed Goal's id, state, title, outcome and revision.";
|
|
1604
1666
|
var ClaimGoalSchema = z3.object({ goalId: z3.string().uuid().optional() }).strict();
|
|
1605
1667
|
var GetGoalSchema = z3.object({
|
|
1606
1668
|
goalId: z3.string().uuid(),
|
|
@@ -1613,18 +1675,24 @@ var GetGoalSchema = z3.object({
|
|
|
1613
1675
|
diagnose: z3.boolean().optional()
|
|
1614
1676
|
}).strict();
|
|
1615
1677
|
var GET_GOAL_DESCRIPTION = "Read one Goal without claiming it, including others (the ten most recent other contributors, with names, latest entry headlines and times): its outcome, state, revision, progress, blockers, the conversation on it (each question with its options and what was decided), `next`, the one step to take, and `more`, where to read further. A question in that conversation reads `open` (nothing yet), `answered` (a choice was made, and `answer` carries it), `replied` (they said something and the read settled the question on their words \u2014 NO option of yours was chosen, and the words are the reply line beside it, so read that before you act), or `closed`. The conversation carries everything the person said, every open question and your newest entry; your older entries are left out and counted \u2014 history: true reads every entry in full. Every Goal of your person is readable, whichever of their agents owns it; another account's Goals are not disclosed.\n\ndiagnose: true is for when a read has CONFUSED you \u2014 not for the working loop. It adds `diagnosis`, which asks every reader that already answers a question about your work and NAMES the reader behind every value, so you never guess which of two places to look: what state your work is really in (the stored `goals.state` beside the derived `goal_execution_state`, which can differ); whether anything is armed to ring and when (`ladder_candidates`, with the ladder's own last decision); whether a wake fired for you and what it did (durable `wake.*` events, beside when your process was last heard from); and what has reached you (`list_waiting` \u2014 review flags, unstarted work, open questions, the person's newest words). Read `diagnosis.disagreements` FIRST: each one is two readers giving different values for the same fact, with both values and both sources, and it never chooses between them \u2014 that is yours to do, from the Goal's own history. `diagnosis.looked` names every reader asked, including any that could not answer, so an empty answer is never confused with a broken one.";
|
|
1616
|
-
var UPDATE_GOAL_DESCRIPTION =
|
|
1617
|
-
var CLAIM_GOAL_DESCRIPTION = "Claim a pending answer to your question or the oldest runnable or review-pending Goal you own. Pass goalId to join any Goal of your person; its assignment stays unchanged. Read others before overlapping another agent\u2019s work. Joining lets you contribute and change
|
|
1618
|
-
var
|
|
1619
|
-
|
|
1678
|
+
var UPDATE_GOAL_DESCRIPTION = "Report work on a Goal you are on, at an exact revision: progress, review acknowledgement, when to wake for it, and withdrawing a question of yours. What the Goal is (its title, outcome, state, owner, parent, dependencies) changes with manage_goals. You are on a Goal you own, or one you have written on (an update with progress, or a contact on it); any of your person's agents may write on any of their Goals, and one that never touched a Goal is refused (409 goal_not_joined). Stale revisions are rejected. A CONTACT ON THIS GOAL MOVES ITS REVISION: a question filed on a Goal is a change to it, so an update prepared before a contact and sent after it is refused as stale (409 goal_revision_conflict) \u2014 re-read the Goal, then write. progress says where the work stands. reviewed: true acknowledges new evidence and closes the Deliveries addressed to you on that Goal, never over an open decision (contact({ackEventIds}) does the same for an `update` event). dueAt (an ISO instant, or null) makes the Goal wait until then; when it passes you are woken for it \u2014 use it for a promise to follow up later. withdraw: [questionId] takes back a question YOU asked on this Goal that is still open \u2014 because you acted on it yourself, it no longer matters, or you asked it wrongly (e.g. waiting: hard when nothing was blocked): it is cancelled, not answered, its card closes and it stops ringing; the id is the one the conversation shows. Returns the Goal as get_goal reads it, at its new revision.";
|
|
1679
|
+
var CLAIM_GOAL_DESCRIPTION = "Claim a pending answer to your question or the oldest runnable or review-pending Goal you own. Pass goalId to join any Goal of your person; its assignment stays unchanged. Read others before overlapping another agent\u2019s work. Joining lets you contribute and change it; use manage_goals (assign) when the assignment itself should change. Returns the Goal as get_goal reads it, and marks you as on it, which never shuts another agent out: other agents of your person may write on it and change it too, and two changes at once are told apart by revision (409 goal_revision_conflict).";
|
|
1680
|
+
var SearchToolSchema = z3.object({
|
|
1681
|
+
query: z3.string().trim().min(1).max(500),
|
|
1682
|
+
types: z3.array(z3.enum(["entry", "goal", "answer"])).min(1).optional(),
|
|
1683
|
+
goalId: z3.string().uuid().optional(),
|
|
1684
|
+
limit: z3.number().int().min(1).max(20).optional()
|
|
1685
|
+
}).strict();
|
|
1686
|
+
var SEARCH_DESCRIPTION = "Search your person's history across all of their agents: Entries (what anyone said or wrote, typed or spoken on a call), Goals (by title and outcome) and Answers (found by their Question or by the words that gave them). query is words to look for; records sharing more of its words rank first, and exact names work. types narrows it to entry, goal and/or answer (default: all three). goalId searches under one Goal: its Entries, its Questions' Answers, and it and its immediate children. limit is matches per type, 1 to 20 (default 8). Read-only. Each match carries its whole saved words, its ID and its links (an Answer carries its Question, the choice made and the Entries that support it); `omitted` counts what matched but was left out, so narrow the words or add a goalId to see it. Nothing found is not proof that nothing exists; a refused search says why. Sealed (encrypted) content is never searched or returned.";
|
|
1687
|
+
var WhoIsWorkingSchema = z3.object({}).strict();
|
|
1620
1688
|
var AGENT_TOOLS = [
|
|
1621
|
-
{ name: "who_is_working", description: "Read your person's agents that contributed in the last 24 hours, newest first: at most 30 agents and their five most recently touched Goals, with their latest entry headline and time. Use get_goal to read a Goal and its other contributors before starting overlapping work. Read-only; no arguments.", inputSchema: mcpInputSchema(
|
|
1689
|
+
{ name: "who_is_working", description: "Read your person's agents that contributed in the last 24 hours, newest first: at most 30 agents and their five most recently touched Goals, with their latest entry headline and time. Use get_goal to read a Goal and its other contributors before starting overlapping work. Read-only; no arguments.", inputSchema: mcpInputSchema(WhoIsWorkingSchema) },
|
|
1622
1690
|
{ name: "contact", description: CONTACT_DESCRIPTION, inputSchema: CONTACT_SCHEMA },
|
|
1623
|
-
{ name: "
|
|
1624
|
-
{ name: "create_goal", description: CREATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(CreateGoalToolSchema) },
|
|
1691
|
+
{ name: "manage_goals", description: MANAGE_GOALS_DESCRIPTION, inputSchema: mcpInputSchema(ManageGoalsToolSchema) },
|
|
1625
1692
|
{ name: "claim_goal", description: CLAIM_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(ClaimGoalSchema) },
|
|
1626
1693
|
{ name: "get_goal", description: GET_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(GetGoalSchema) },
|
|
1627
|
-
{ name: "update_goal", description: UPDATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(UpdateGoalToolSchema) }
|
|
1694
|
+
{ name: "update_goal", description: UPDATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(UpdateGoalToolSchema) },
|
|
1695
|
+
{ name: "search", description: SEARCH_DESCRIPTION, inputSchema: mcpInputSchema(SearchToolSchema) }
|
|
1628
1696
|
];
|
|
1629
1697
|
var AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
|
|
1630
1698
|
var id = z4.string().uuid();
|
|
@@ -1655,17 +1723,6 @@ var existingGoalChanges = [
|
|
|
1655
1723
|
reason: z4.string().max(500).optional()
|
|
1656
1724
|
}).strict()
|
|
1657
1725
|
];
|
|
1658
|
-
var GoalChangeSchema = z4.union([
|
|
1659
|
-
z4.object({
|
|
1660
|
-
kind: z4.literal("create"),
|
|
1661
|
-
title: z4.string().min(1).max(120),
|
|
1662
|
-
outcome: z4.string().min(1),
|
|
1663
|
-
ownerId: participant,
|
|
1664
|
-
parentGoalId: id.optional(),
|
|
1665
|
-
sourceEntryIds: z4.array(id)
|
|
1666
|
-
}).strict(),
|
|
1667
|
-
...existingGoalChanges
|
|
1668
|
-
]);
|
|
1669
1726
|
var BrainGoalChangeSchema = z4.union([
|
|
1670
1727
|
// newSession: Paigy starts a session to own it (owner, 2026-10-06); until it has, the person owns it.
|
|
1671
1728
|
z4.object({
|
|
@@ -2073,7 +2130,7 @@ var AwaitItemSchema = z5.discriminatedUnion("type", [
|
|
|
2073
2130
|
/** The awaited ask was REPLACED by a newer notification on its thread (e.g. a
|
|
2074
2131
|
* post-feedback revision, #633) — the user will never answer this id. Stop
|
|
2075
2132
|
* awaiting it; the live ask is the thread's newest turn (await that one, or
|
|
2076
|
-
* re-orient via
|
|
2133
|
+
* re-orient via contact({})). */
|
|
2077
2134
|
z5.object({
|
|
2078
2135
|
type: z5.literal("superseded"),
|
|
2079
2136
|
parentId: z5.string(),
|
|
@@ -2803,7 +2860,7 @@ var HostDecisionSchema = z5.object({
|
|
|
2803
2860
|
goalId: z5.string().uuid().nullable().optional(),
|
|
2804
2861
|
/** When the server last heard from the agent, as the host read it: the presence it stood back for. */
|
|
2805
2862
|
seenAt: z5.string().datetime().nullable().optional(),
|
|
2806
|
-
/** When that work last moved (`claimable.since` on `
|
|
2863
|
+
/** When that work last moved (`claimable.since` on an agent's `contact({})` read), the fact the bound is judged on. */
|
|
2807
2864
|
since: z5.string().datetime().nullable().optional(),
|
|
2808
2865
|
/** What the host said, in its log's own words: why it stood back, or what the take-over did. */
|
|
2809
2866
|
said: z5.string().max(300).optional()
|
|
@@ -3170,28 +3227,28 @@ var authToken = (explicit) => explicit ?? tokenOverride ?? readToken();
|
|
|
3170
3227
|
async function fail(what, res) {
|
|
3171
3228
|
throw new ApiError(what, res.status, await res.text());
|
|
3172
3229
|
}
|
|
3173
|
-
async function
|
|
3230
|
+
async function manageGoals(input, opts = {}) {
|
|
3174
3231
|
const token = authToken(opts.token) ?? "";
|
|
3175
|
-
const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals`, {
|
|
3232
|
+
const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/manage`, {
|
|
3176
3233
|
method: "POST",
|
|
3177
|
-
headers: { "content-type": "application/json", authorization: `Bearer ${token}
|
|
3234
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
|
|
3178
3235
|
body: JSON.stringify(input)
|
|
3179
3236
|
}));
|
|
3180
|
-
if (!res.ok) await fail("
|
|
3237
|
+
if (!res.ok) await fail("manage_goals", res);
|
|
3181
3238
|
return await res.json();
|
|
3182
3239
|
}
|
|
3183
3240
|
async function claimGoal(goalId, opts = {}) {
|
|
3184
3241
|
const token = authToken(opts.token) ?? "";
|
|
3185
|
-
const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/claim`, { method: "POST", headers: { "content-type": "application/json", authorization: `Bearer ${token}
|
|
3242
|
+
const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/claim`, { method: "POST", headers: { "content-type": "application/json", authorization: `Bearer ${token}` }, body: JSON.stringify(goalId ? { goalId } : {}) }));
|
|
3186
3243
|
if (!res.ok) await fail("claim_goal", res);
|
|
3187
3244
|
return await res.json();
|
|
3188
3245
|
}
|
|
3189
|
-
async function getGoal(goalId, opts = {},
|
|
3246
|
+
async function getGoal(goalId, opts = {}, read3 = {}) {
|
|
3190
3247
|
const token = authToken(opts.token) ?? "";
|
|
3191
|
-
const query = [
|
|
3248
|
+
const query = [read3.history ? "history=1" : "", read3.diagnose ? "diagnose=1" : ""].filter(Boolean).join("&");
|
|
3192
3249
|
const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/${encodeURIComponent(goalId)}${query ? `?${query}` : ""}`, {
|
|
3193
3250
|
method: "GET",
|
|
3194
|
-
headers: { authorization: `Bearer ${token}
|
|
3251
|
+
headers: { authorization: `Bearer ${token}` }
|
|
3195
3252
|
}));
|
|
3196
3253
|
if (!res.ok) await fail("get_goal", res);
|
|
3197
3254
|
return await res.json();
|
|
@@ -3199,7 +3256,7 @@ async function getGoal(goalId, opts = {}, read = {}) {
|
|
|
3199
3256
|
async function updateGoal(goalId, input, opts = {}) {
|
|
3200
3257
|
const token = authToken(opts.token) ?? "";
|
|
3201
3258
|
const operationId = input.operationId ?? randomUUID2();
|
|
3202
|
-
const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/${encodeURIComponent(goalId)}`, { method: "PATCH", headers: { "content-type": "application/json", authorization: `Bearer ${token}
|
|
3259
|
+
const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/${encodeURIComponent(goalId)}`, { method: "PATCH", headers: { "content-type": "application/json", authorization: `Bearer ${token}` }, body: JSON.stringify({ ...input, operationId }) }));
|
|
3203
3260
|
if (!res.ok) await fail("update_goal", res);
|
|
3204
3261
|
return await res.json();
|
|
3205
3262
|
}
|
|
@@ -3315,7 +3372,7 @@ async function listNotes(opts = {}) {
|
|
|
3315
3372
|
}
|
|
3316
3373
|
async function listConnections(opts = {}) {
|
|
3317
3374
|
const res = await ensureAuthed(await reach(`${BACKEND_URL}/api/tokens`, {
|
|
3318
|
-
headers: { authorization: `Bearer ${authToken(opts.token)}
|
|
3375
|
+
headers: { authorization: `Bearer ${authToken(opts.token)}` }
|
|
3319
3376
|
}));
|
|
3320
3377
|
if (!res.ok) throw new Error(`list_connections failed: ${res.status} ${await res.text()}`);
|
|
3321
3378
|
return await res.json();
|
|
@@ -3353,9 +3410,18 @@ async function dismissTriage(proposalId, opts = {}) {
|
|
|
3353
3410
|
if (!res.ok) throw new Error(`dismiss_triage failed: ${res.status} ${await res.text()}`);
|
|
3354
3411
|
return await res.json();
|
|
3355
3412
|
}
|
|
3413
|
+
async function searchRecords(input, opts = {}) {
|
|
3414
|
+
const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/records/search`, {
|
|
3415
|
+
method: "POST",
|
|
3416
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token) ?? ""}` },
|
|
3417
|
+
body: JSON.stringify(input)
|
|
3418
|
+
}));
|
|
3419
|
+
if (!res.ok) await fail("search", res);
|
|
3420
|
+
return await res.json();
|
|
3421
|
+
}
|
|
3356
3422
|
async function whoIsWorking(opts = {}) {
|
|
3357
3423
|
const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/agents/working`, {
|
|
3358
|
-
headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}
|
|
3424
|
+
headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}` }
|
|
3359
3425
|
}));
|
|
3360
3426
|
if (!res.ok) await fail("who_is_working", res);
|
|
3361
3427
|
return await res.json();
|
|
@@ -3386,13 +3452,14 @@ function currentRepo(cwd = process.cwd()) {
|
|
|
3386
3452
|
}
|
|
3387
3453
|
return asked;
|
|
3388
3454
|
}
|
|
3389
|
-
var NOT_SENT = "Recorded as progress, not sent: nobody was notified.
|
|
3455
|
+
var NOT_SENT = "Recorded as the Goal's progress, not sent: nobody was notified. The person is told only a question, an answer to one they asked you, and what you say once the Goal is done. To close the work, mark the Goal done first, then send the one message saying what is done and anything they need to do or check.";
|
|
3390
3456
|
var DEMOTED = "Delivered as a card, not a call: the person's settings do not allow a call right now (silent, quiet hours, or a mode). It has reached them; do not send it again, with or without a Goal.";
|
|
3391
3457
|
async function contact(input, opts = {}) {
|
|
3392
3458
|
const parsed = ContactSchema.parse(input);
|
|
3459
|
+
if (!("asks" in parsed) && !("deliveryId" in parsed)) throw new Error("contact with nothing to send receives: call receiveContact");
|
|
3393
3460
|
opts.signal?.throwIfAborted();
|
|
3394
3461
|
const send2 = opts.reach ?? reach;
|
|
3395
|
-
const headers = { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token) ?? ""}
|
|
3462
|
+
const headers = { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token) ?? ""}` };
|
|
3396
3463
|
let deliveryId;
|
|
3397
3464
|
let deliveries = [];
|
|
3398
3465
|
let recorded = [];
|
|
@@ -3437,7 +3504,7 @@ async function contact(input, opts = {}) {
|
|
|
3437
3504
|
const all = deliveries.filter((d) => !!d.deliveryId && !!d.goalId).map(({ id: id2, deliveryId: deliveryId2, goalId }) => ({ ...id2 ? { id: id2 } : {}, deliveryId: deliveryId2, goalId }));
|
|
3438
3505
|
const joinedCard = deliveries[0]?.joinedCard === true;
|
|
3439
3506
|
const joinedCall = deliveries[0]?.joinedCall === true;
|
|
3440
|
-
const
|
|
3507
|
+
const read3 = async (signal2) => {
|
|
3441
3508
|
const res = await ensureAuthed(await send2(`${BACKEND_URL}/api/deliveries/${encodeURIComponent(deliveryId)}`, { headers, signal: signal2 }));
|
|
3442
3509
|
if (!res.ok) await fail("read_delivery", res);
|
|
3443
3510
|
const one = await res.json();
|
|
@@ -3451,14 +3518,14 @@ async function contact(input, opts = {}) {
|
|
|
3451
3518
|
};
|
|
3452
3519
|
};
|
|
3453
3520
|
const settled = (d) => d.kind === "notification" || d.state === "closed" || d.answers.length > 0 || d.entries.some((e) => e.kind === "contribution");
|
|
3454
|
-
if (opts.waits === false) return
|
|
3521
|
+
if (opts.waits === false) return read3(opts.signal);
|
|
3455
3522
|
const window = AbortSignal.timeout(AWAIT_WINDOW_MS);
|
|
3456
3523
|
const signal = opts.signal ? AbortSignal.any([opts.signal, window]) : window;
|
|
3457
3524
|
let latest;
|
|
3458
3525
|
try {
|
|
3459
3526
|
while (true) {
|
|
3460
3527
|
signal.throwIfAborted();
|
|
3461
|
-
latest = await
|
|
3528
|
+
latest = await read3(signal);
|
|
3462
3529
|
if (settled(latest)) return latest;
|
|
3463
3530
|
await sleep2(5e3, void 0, { signal });
|
|
3464
3531
|
}
|
|
@@ -3468,13 +3535,69 @@ async function contact(input, opts = {}) {
|
|
|
3468
3535
|
throw error;
|
|
3469
3536
|
}
|
|
3470
3537
|
}
|
|
3471
|
-
async function
|
|
3538
|
+
async function receive(opts = {}) {
|
|
3472
3539
|
const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/deliveries`, {
|
|
3473
|
-
headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}
|
|
3540
|
+
headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}` }
|
|
3474
3541
|
}));
|
|
3475
|
-
if (!res.ok) await fail("
|
|
3542
|
+
if (!res.ok) await fail("receive", res);
|
|
3476
3543
|
return await res.json();
|
|
3477
3544
|
}
|
|
3545
|
+
async function acknowledge(eventIds, opts = {}) {
|
|
3546
|
+
const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/deliveries/ack`, {
|
|
3547
|
+
method: "POST",
|
|
3548
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token) ?? ""}` },
|
|
3549
|
+
body: JSON.stringify({ eventIds })
|
|
3550
|
+
}));
|
|
3551
|
+
if (!res.ok) await fail("acknowledge", res);
|
|
3552
|
+
return (await res.json()).results ?? [];
|
|
3553
|
+
}
|
|
3554
|
+
var EVENT_BATCH = 20;
|
|
3555
|
+
function pendingEvents(w) {
|
|
3556
|
+
const fromDelivery = (d) => ({
|
|
3557
|
+
eventId: d.deliveryId,
|
|
3558
|
+
kind: d.questions?.length ? "question" : "instruction",
|
|
3559
|
+
...d.goalIds.length ? { goalIds: d.goalIds } : {},
|
|
3560
|
+
...d.about ? { text: d.about } : {},
|
|
3561
|
+
...d.questions?.length ? { questions: d.questions } : {}
|
|
3562
|
+
});
|
|
3563
|
+
const deliveries = (w.deliveries ?? []).map(fromDelivery);
|
|
3564
|
+
return [
|
|
3565
|
+
...deliveries.filter((e) => e.kind === "question"),
|
|
3566
|
+
...(w.review ?? []).map((r) => ({
|
|
3567
|
+
eventId: r.eventId ?? r.goalId,
|
|
3568
|
+
kind: "update",
|
|
3569
|
+
goalIds: [r.goalId],
|
|
3570
|
+
text: r.said ? `${r.title}: ${r.said}` : r.title,
|
|
3571
|
+
...r.saidAt ? { at: r.saidAt } : {}
|
|
3572
|
+
})),
|
|
3573
|
+
...deliveries.filter((e) => e.kind !== "question")
|
|
3574
|
+
];
|
|
3575
|
+
}
|
|
3576
|
+
async function receiveContact(input, opts = {}) {
|
|
3577
|
+
opts.signal?.throwIfAborted();
|
|
3578
|
+
const acknowledged = input.ackEventIds?.length ? await acknowledge(input.ackEventIds, opts) : void 0;
|
|
3579
|
+
const shaped = (waiting, waitOutcome) => {
|
|
3580
|
+
const all = pendingEvents(waiting);
|
|
3581
|
+
return { waiting, events: all.slice(0, EVENT_BATCH), hasMore: all.length > EVENT_BATCH, waitOutcome, ...acknowledged ? { acknowledged } : {} };
|
|
3582
|
+
};
|
|
3583
|
+
const wait = (input.wait ?? !input.ackEventIds?.length) && opts.waits !== false;
|
|
3584
|
+
if (!wait) return shaped(await receive(opts), "not_waited");
|
|
3585
|
+
const window = AbortSignal.timeout(AWAIT_WINDOW_MS);
|
|
3586
|
+
const signal = opts.signal ? AbortSignal.any([opts.signal, window]) : window;
|
|
3587
|
+
let latest;
|
|
3588
|
+
try {
|
|
3589
|
+
while (true) {
|
|
3590
|
+
signal.throwIfAborted();
|
|
3591
|
+
latest = await receive({ ...opts });
|
|
3592
|
+
if (pendingEvents(latest).length) return shaped(latest, "available");
|
|
3593
|
+
await sleep2(5e3, void 0, { signal });
|
|
3594
|
+
}
|
|
3595
|
+
} catch (error) {
|
|
3596
|
+
opts.signal?.throwIfAborted();
|
|
3597
|
+
if (window.aborted && latest) return shaped(latest, "expired");
|
|
3598
|
+
throw error;
|
|
3599
|
+
}
|
|
3600
|
+
}
|
|
3478
3601
|
function compact(o) {
|
|
3479
3602
|
return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0 && v !== null && v !== "" && !(Array.isArray(v) && v.length === 0)));
|
|
3480
3603
|
}
|
|
@@ -3531,7 +3654,7 @@ function more(g) {
|
|
|
3531
3654
|
earlier,
|
|
3532
3655
|
g.subtree?.length ? "A Goal below, in full: get_goal({ goalId }) with its goalId above." : null,
|
|
3533
3656
|
g.parentGoalId ? `The Goal above: get_goal({ goalId: "${g.parentGoalId}" }).` : null,
|
|
3534
|
-
"Anything else
|
|
3657
|
+
"Anything else addressed to you (requests, answers, questions you owe): contact({ wait: false })."
|
|
3535
3658
|
].filter((line) => !!line);
|
|
3536
3659
|
}
|
|
3537
3660
|
function goalView(g) {
|
|
@@ -3585,93 +3708,106 @@ function deliveryView(d) {
|
|
|
3585
3708
|
notSent: d.notSent,
|
|
3586
3709
|
joinedCard: d.joinedCard,
|
|
3587
3710
|
joinedCall: d.joinedCall,
|
|
3588
|
-
next: d.joinedCall ? `${JOINED_CALL}${d.message ? ` ${d.message}` : ""}` : d.kind === "notification" ? `${d.demoted ? `${d.demoted} ` : ""}${d.joinedCard ? `Added to the open report card on its Goal; no new push was sent. ${answers}` : answers}` : d.state === "open" && d.decisionNeeds.some((n) => n.state === "open") ? `Decision pending. Call contact(${JSON.stringify({ deliveryId: d.deliveryId })}) to continue this same Call \u2014 each call is ONE bounded window, and never resend the ask. When a window comes back with nothing new said, they are not typing: stop rereading, leave the question open, and collect the answer with
|
|
3711
|
+
next: d.joinedCall ? `${JOINED_CALL}${d.message ? ` ${d.message}` : ""}` : d.kind === "notification" ? `${d.demoted ? `${d.demoted} ` : ""}${d.joinedCard ? `Added to the open report card on its Goal; no new push was sent. ${answers}` : answers}` : d.state === "open" && d.decisionNeeds.some((n) => n.state === "open") ? `Decision pending. Call contact(${JSON.stringify({ deliveryId: d.deliveryId })}) to continue this same Call \u2014 each call is ONE bounded window, and never resend the ask. When a window comes back with nothing new said, they are not typing: stop rereading, leave the question open, and collect the answer with contact({ wait: false }) or claim_goal on your next wake. ${d.message ?? ""}`.trim() : d.message
|
|
3589
3712
|
});
|
|
3590
3713
|
}
|
|
3591
|
-
function
|
|
3592
|
-
const
|
|
3593
|
-
const assigned =
|
|
3594
|
-
const
|
|
3595
|
-
const
|
|
3596
|
-
const
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
//
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
//
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
// NEW ON YOUR GOALS (owner, 2026-09-23): a reply is an Entry on the Goal, not a Delivery, so the
|
|
3613
|
-
// Goals flagged for your review are listed here until you acknowledge them.
|
|
3614
|
-
...review.length ? { review } : {},
|
|
3615
|
-
// YOUR STALLED WORK (#2257): Goals you own with no progress for three days. Named here so an
|
|
3616
|
-
// agent that reads its replies also sees what it left, and says what to do about each.
|
|
3617
|
-
...stalled.length ? { stalled } : {},
|
|
3618
|
-
// OTHER AGENTS' STALLED WORK (owner, 2026-09-23): any agent may take it over and close it.
|
|
3619
|
-
...others.length ? { stalledOthers: others } : {},
|
|
3714
|
+
function receivedView(r) {
|
|
3715
|
+
const w = r.waiting;
|
|
3716
|
+
const assigned = w.assigned ?? [];
|
|
3717
|
+
const stalled = w.stalled ?? [];
|
|
3718
|
+
const others = w.stalledOthers ?? [];
|
|
3719
|
+
const owed = r.events.filter((e) => e.kind === "question");
|
|
3720
|
+
const refused = (r.acknowledged ?? []).filter((a) => a.refused);
|
|
3721
|
+
return compact({
|
|
3722
|
+
events: r.events.map((e) => compact({ ...e })),
|
|
3723
|
+
hasMore: r.hasMore || void 0,
|
|
3724
|
+
waitOutcome: r.waitOutcome,
|
|
3725
|
+
acknowledged: r.acknowledged?.filter((a) => a.acknowledged).map((a) => a.eventId),
|
|
3726
|
+
notAcknowledged: refused.length ? refused.map((a) => ({ eventId: a.eventId, refused: a.refused })) : void 0,
|
|
3727
|
+
// WORK ASSIGNED TO YOU (owner, 2026-09-24): your Goals nobody has started, however they became yours.
|
|
3728
|
+
assigned,
|
|
3729
|
+
// WHAT claim_goal HANDS YOU NEXT (owner, 2026-09-27: "Wake on any work"): the predicate that starts a
|
|
3730
|
+
// sleeping agent, so "nothing is waiting" is never said while a claim would hand you work.
|
|
3731
|
+
claimable: w.claimable,
|
|
3732
|
+
// YOUR STALLED WORK (#2257), and OTHER AGENTS' (owner, 2026-09-23): any agent may take it over.
|
|
3733
|
+
stalled,
|
|
3734
|
+
stalledOthers: others,
|
|
3620
3735
|
next: [
|
|
3621
|
-
|
|
3736
|
+
r.events.length ? `Handle each event, then acknowledge the ones you handled: contact({ ackEventIds: [${r.events.filter((e) => e.kind !== "question").map((e) => `"${e.eventId}"`).join(", ")}] }).` : w.claimable || assigned.length ? "No messages." : "Nothing is waiting.",
|
|
3737
|
+
...owed.length ? [`You owe ${owed.length === 1 ? "an answer" : `${owed.length} answers`}: contact({ asks: [{ ask: <your answer>, answers: "<questionId>" }] }) for each question; a question is answered, never acknowledged.`] : [],
|
|
3738
|
+
...r.hasMore ? ["More are waiting: acknowledge these, then receive again."] : [],
|
|
3739
|
+
...r.waitOutcome === "expired" ? ["Nothing arrived in the window; receive again to keep waiting, without sending again."] : [],
|
|
3622
3740
|
...assigned.length ? [`${assigned.length} of your Goals are assigned to you and not started (assigned): claim_goal({goalId}) to start one.`] : [],
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
// sentence names the person and quotes the freshest one, because a count is not a message.
|
|
3627
|
-
...review.length ? [spoke(review) ?? `${review.length} of your Goals have something new (review): read each with claim_goal or get_goal, then update_goal with reviewed: true.`] : [],
|
|
3628
|
-
...stalled.length ? [`${stalled.length} of your Goals have had no progress for 3 days: update_goal each with progress, finish it, or cancel it.`] : [],
|
|
3629
|
-
...others.length ? [`${others.length} of other agents' Goals have gone quiet for 3 days: claim_goal({goalId}) joins one without changing its owner, then finish or cancel it with update_goal.`] : [],
|
|
3741
|
+
...w.claimable && !assigned.length ? [`claim_goal() hands you ${w.claimable.title ?? w.claimable.goalId} next.`] : [],
|
|
3742
|
+
...stalled.length ? [`${stalled.length} of your Goals have had no progress for 3 days: update_goal each with progress, or finish or cancel it with manage_goals.`] : [],
|
|
3743
|
+
...others.length ? [`${others.length} of other agents' Goals have gone quiet for 3 days: claim_goal({goalId}) joins one without changing its owner.`] : [],
|
|
3630
3744
|
// No name of its own (#2525): the API's ask, verbatim.
|
|
3631
|
-
...
|
|
3745
|
+
...w.unnamed ? [w.unnamed] : []
|
|
3632
3746
|
].join(" ")
|
|
3633
|
-
};
|
|
3747
|
+
});
|
|
3634
3748
|
}
|
|
3635
|
-
function
|
|
3636
|
-
const
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3749
|
+
function managedView(m, changes) {
|
|
3750
|
+
const results = m.results ?? [];
|
|
3751
|
+
const failed = results.filter((r) => r.status !== "applied");
|
|
3752
|
+
return compact({
|
|
3753
|
+
ok: m.ok,
|
|
3754
|
+
results: results.map((r) => compact({ index: r.index, kind: changes[r.index]?.kind, status: r.status, goalId: r.goalId, error: r.error })),
|
|
3755
|
+
goals: m.goals,
|
|
3756
|
+
next: failed.length ? `${failed.length} of ${results.length} changes did not apply (see results[].error); the rest did. A goal_revision_conflict means the Goal changed since you read it: read it with get_goal and decide again.` : void 0
|
|
3757
|
+
});
|
|
3758
|
+
}
|
|
3759
|
+
var lastRead = /* @__PURE__ */ new Map();
|
|
3760
|
+
var readKey = (token, goalId) => `${token ?? ""}:${goalId}`;
|
|
3761
|
+
function remember(token, goalId, revision) {
|
|
3762
|
+
if (revision === void 0) return;
|
|
3763
|
+
if (lastRead.size >= 5e3) lastRead.clear();
|
|
3764
|
+
lastRead.set(readKey(token, goalId), revision);
|
|
3765
|
+
}
|
|
3766
|
+
function read2(token, goal) {
|
|
3767
|
+
if (goal.goalId) remember(token, goal.goalId, goal.revision);
|
|
3768
|
+
return goal;
|
|
3641
3769
|
}
|
|
3642
3770
|
async function runTool(name, args, opts) {
|
|
3643
3771
|
const { waits, signal, ...client } = opts;
|
|
3644
3772
|
const input = args ?? {};
|
|
3645
3773
|
switch (name) {
|
|
3646
3774
|
case "contact": {
|
|
3647
|
-
const
|
|
3775
|
+
const parsed = ContactSchema.parse(input);
|
|
3776
|
+
if (!("asks" in parsed) && !("deliveryId" in parsed)) return receivedView(await receiveContact(parsed, { ...client, signal, waits }));
|
|
3777
|
+
const sent = await contact(parsed, { ...client, signal, waits });
|
|
3648
3778
|
return "sent" in sent ? sent : deliveryView(sent);
|
|
3649
3779
|
}
|
|
3650
3780
|
case "who_is_working":
|
|
3651
|
-
|
|
3781
|
+
WhoIsWorkingSchema.parse(input);
|
|
3652
3782
|
return whoIsWorking(client);
|
|
3653
|
-
case "
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3783
|
+
case "manage_goals": {
|
|
3784
|
+
const { changes } = ManageGoalsToolSchema.parse(input);
|
|
3785
|
+
const versioned = changes.map((c) => {
|
|
3786
|
+
if (c.kind === "create" || c.kind === "dependency") return c;
|
|
3787
|
+
const revision = lastRead.get(readKey(client.token, c.goalId));
|
|
3788
|
+
return revision === void 0 ? c : { ...c, revision };
|
|
3789
|
+
});
|
|
3790
|
+
const managed = await manageGoals({ requestId: randomUUID4(), changes: versioned }, client);
|
|
3791
|
+
for (const g of managed.goals ?? []) remember(client.token, g.goalId, g.revision);
|
|
3792
|
+
return managedView(managed, changes);
|
|
3659
3793
|
}
|
|
3660
3794
|
case "claim_goal": {
|
|
3661
3795
|
const { goalId } = ClaimGoalSchema.parse(input);
|
|
3662
|
-
return goalView(await claimGoal(goalId, client));
|
|
3796
|
+
return goalView(read2(client.token, await claimGoal(goalId, client)));
|
|
3663
3797
|
}
|
|
3664
3798
|
case "get_goal": {
|
|
3665
3799
|
const { goalId, history, diagnose } = GetGoalSchema.parse(input);
|
|
3666
|
-
const goal = await getGoal(goalId, client, { history, diagnose });
|
|
3800
|
+
const goal = read2(client.token, await getGoal(goalId, client, { history, diagnose }));
|
|
3667
3801
|
const view = goalView(goal);
|
|
3668
3802
|
const diagnosis = goal.diagnosis;
|
|
3669
3803
|
return diagnosis === void 0 ? view : { ...view, diagnosis };
|
|
3670
3804
|
}
|
|
3671
3805
|
case "update_goal": {
|
|
3672
3806
|
const { goalId, ...body } = UpdateGoalToolSchema.parse(input);
|
|
3673
|
-
return goalView(await updateGoal(goalId, body, client));
|
|
3807
|
+
return goalView(read2(client.token, await updateGoal(goalId, body, client)));
|
|
3674
3808
|
}
|
|
3809
|
+
case "search":
|
|
3810
|
+
return searchRecords(SearchToolSchema.parse(input), client);
|
|
3675
3811
|
default:
|
|
3676
3812
|
throw new Error(`Unknown tool: ${name}`);
|
|
3677
3813
|
}
|
|
@@ -3910,7 +4046,7 @@ export {
|
|
|
3910
4046
|
ApiError,
|
|
3911
4047
|
overrideToken,
|
|
3912
4048
|
authToken,
|
|
3913
|
-
|
|
4049
|
+
manageGoals,
|
|
3914
4050
|
claimGoal,
|
|
3915
4051
|
getGoal,
|
|
3916
4052
|
updateGoal,
|
|
@@ -3931,11 +4067,15 @@ export {
|
|
|
3931
4067
|
getTriage,
|
|
3932
4068
|
acceptTriage,
|
|
3933
4069
|
dismissTriage,
|
|
4070
|
+
searchRecords,
|
|
3934
4071
|
whoIsWorking,
|
|
3935
4072
|
repoFromRemote,
|
|
3936
4073
|
currentRepo,
|
|
3937
4074
|
contact,
|
|
3938
|
-
|
|
4075
|
+
receive,
|
|
4076
|
+
acknowledge,
|
|
4077
|
+
pendingEvents,
|
|
4078
|
+
receiveContact,
|
|
3939
4079
|
runTool,
|
|
3940
4080
|
subscribeWake,
|
|
3941
4081
|
parseDue,
|
|
@@ -78,9 +78,17 @@ var StartContactSchema = z2.object({
|
|
|
78
78
|
waiting: z2.enum(["none", "hard"]).default("none").describe("hard requests a call even when channel is notification; user permissions and ring cooldowns still apply."),
|
|
79
79
|
channel: z2.enum(["notification", "call"]).default("notification")
|
|
80
80
|
}).strict();
|
|
81
|
-
var
|
|
81
|
+
var ReceiveContactSchema = z2.object({
|
|
82
|
+
wait: z2.boolean().optional().describe(
|
|
83
|
+
"Only controls when this returns. true (the default when you send and acknowledge nothing): wait up to about 45 seconds for incoming communication addressed to you, and return as soon as some is available. false: return what is waiting now. It does not mean work is blocked, set urgency, or ask for a call. `waitOutcome` says which happened: `available`, `expired` (nothing arrived in the window; wait again without sending again), or `not_waited` (a hosted connection never holds a wait)."
|
|
84
|
+
),
|
|
85
|
+
ackEventIds: z2.array(z2.string().uuid()).min(1).max(100).optional().describe(
|
|
86
|
+
"The exact eventIds of the events you have handled: an acknowledgment (ACK). Reading alone acknowledges nothing, so an event comes back until you acknowledge it; after that the next batch can come. Each recipient acknowledges separately, for itself only. A Question you owe is answered (an ask with `answers`), never acknowledged away: that id is refused until it is answered."
|
|
87
|
+
)
|
|
88
|
+
}).strict();
|
|
89
|
+
var ContactSchema = z2.union([StartContactSchema, z2.object({ deliveryId: z2.string().uuid() }).strict(), ReceiveContactSchema]);
|
|
82
90
|
var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
|
|
83
|
-
var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'select', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. An ask that names a Goal (parentId) goes onto that Goal; one that names none starts a new Goal of yours. Notification returns immediately; collect
|
|
91
|
+
var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'select', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. An ask that names a Goal (parentId) goes onto that Goal; one that names none starts a new Goal of yours. Notification returns immediately; collect answers by receiving (below). On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. One question per 'ask'; several questions are several objects in 'asks'. Each ask is sent exactly as written, with no reading in between: a bundled ask arrives as one card, so separate questions are separate asks. An ask with no options that is not blocking is a report, never a claim on them, so no answer is owed and none should be awaited. A report reaches the person only when it answers something they asked you, or once the Goal is done; any other report is recorded as the Goal's progress and nobody is notified. So when the work is finished, mark the Goal done first, then send one message saying what is done and anything they need to do or check. On a Goal whose report card is still open, a report that does reach them is added to that card, with no new push. Send options (or waiting:'hard') when you actually need an answer. Each option's label must stand on its own \u2014 the person may see only the labels \u2014 so never a label that points into your text ('All three', 'Option 2', '1 and 3 only'). select:'many' makes the card a checklist, for independent options they may want several of; select:'one' (the default) a pick, for alternatives. The person can always answer in their own words, so never add an 'Other' option. If the person is already on a call, a contact that reaches them joins that call automatically \u2014 whatever channel you asked for, with no ring \u2014 and the Delivery it returns IS that call: reread it with contact({deliveryId}) to see everything answered on it so far, and contact again while it is live to add information or a further question to the same call.\n\nRECEIVING: contact with no asks sends nothing and returns `events`, a limited batch of what is addressed to you (not a history page): `question`, a Question you owe (answer it with an ask whose `answers` is its questionId); `update`, something new on one of your Goals (a reply, an answer: read it with get_goal); `instruction`, a request or note sent to you. `hasMore` says more are waiting. Reading acknowledges nothing: once you have handled events, confirm their eventIds with contact({ackEventIds}), and the next batch can come. contact({}) waits up to about 45 seconds for something to arrive; contact({wait:false}) returns at once. It also lists work given to you that nobody has started (`assigned`; claim_goal starts it) and your work gone quiet (`stalled`).";
|
|
84
92
|
var CreateGoalSchema = z3.object({
|
|
85
93
|
outcome: z3.string().trim().min(1).max(1e4),
|
|
86
94
|
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
|
|
@@ -100,10 +108,6 @@ var CreateGoalSchema = z3.object({
|
|
|
100
108
|
* repo name. Delegated work inherits this from its parent Goal when omitted. */
|
|
101
109
|
repo: z3.string().trim().min(1).max(200).optional()
|
|
102
110
|
});
|
|
103
|
-
var CreateGoalToolSchema = CreateGoalSchema.extend({
|
|
104
|
-
idempotencyKey: CreateGoalSchema.shape.idempotencyKey.optional().describe("Optional. One is minted per call; pass your own only so a retry lands on the same Goal.")
|
|
105
|
-
}).strict();
|
|
106
|
-
var CREATE_GOAL_DESCRIPTION = 'Create a durable Goal for an outcome. Without parentGoalId it is placed against your open Goals: if one already IS this work, that Goal comes back (existing: true) and nothing new is created \u2014 continue it; if the work belongs under one, it is created there (parentGoalId in the receipt); otherwise it is a root. Pass parentGoalId yourself to put it under a specific Goal. Pass repo to anchor the work to a specific repository ("owner/repo" or repo name); delegated children inherit it. Pass title to name it in one to five words, as a person would refer to it out loud ("the night rings") \u2014 it heads every list and is spoken on a call; without one the brain writes it. Admission only: claim it before doing work, then update it as it advances. Returns an admission receipt with goalId, current state, revision, ownerParticipant, and the next step; no Goal content or execution lease.';
|
|
107
111
|
var UpdateGoalSchema = z3.object({
|
|
108
112
|
revision: z3.number().int().positive(),
|
|
109
113
|
changes: z3.object({
|
|
@@ -127,7 +131,65 @@ var UpdateGoalSchema = z3.object({
|
|
|
127
131
|
reason: z3.string().trim().min(1).max(2e3),
|
|
128
132
|
operationId: z3.string().uuid().optional()
|
|
129
133
|
}).strict();
|
|
130
|
-
var UpdateGoalToolSchema =
|
|
134
|
+
var UpdateGoalToolSchema = z3.object({
|
|
135
|
+
goalId: z3.string().uuid(),
|
|
136
|
+
revision: UpdateGoalSchema.shape.revision,
|
|
137
|
+
changes: UpdateGoalSchema.shape.changes.innerType().pick({ progress: true, reviewed: true, dueAt: true, withdraw: true }).strict().refine((v) => Object.keys(v).length > 0),
|
|
138
|
+
reason: UpdateGoalSchema.shape.reason
|
|
139
|
+
}).strict();
|
|
140
|
+
var changedGoal = z3.string().uuid().describe("The Goal to change, as a read shows it.");
|
|
141
|
+
var goalTitle = z3.string().trim().min(1).max(80).describe("The Goal's short display name: one to five words, how a person refers to it out loud.");
|
|
142
|
+
var goalOutcome = z3.string().trim().min(1).max(1e4).describe("The full desired result. There is no third description field.");
|
|
143
|
+
var CreateChange = z3.object({
|
|
144
|
+
kind: z3.literal("create"),
|
|
145
|
+
title: goalTitle,
|
|
146
|
+
outcome: goalOutcome,
|
|
147
|
+
ownerId: z3.string().trim().min(1).optional().describe("Who owns the work: an agent's participant (as who_is_working shows it) or the person's. Omitted: you."),
|
|
148
|
+
parentGoalId: z3.string().uuid().optional().describe("The Goal it belongs under. Omitted: a root."),
|
|
149
|
+
sourceEntryIds: z3.array(z3.string().uuid()).max(20).optional().describe("The whole Entries the work came from, ones you can read.")
|
|
150
|
+
}).strict();
|
|
151
|
+
var EditChange = z3.object({ kind: z3.literal("edit"), goalId: changedGoal, title: goalTitle.optional(), outcome: goalOutcome.optional() }).strict();
|
|
152
|
+
var StateChange = z3.object({ kind: z3.literal("state"), goalId: changedGoal, state: z3.enum(["open", "completed", "canceled"]).describe(
|
|
153
|
+
"completed: the Goal's own work is done now (refused while required children are open); open: reopen it; canceled: it will not be done."
|
|
154
|
+
) }).strict();
|
|
155
|
+
var AssignChange = z3.object({ kind: z3.literal("assign"), goalId: changedGoal, ownerId: z3.string().trim().min(1).describe("The new owner's participant.") }).strict();
|
|
156
|
+
var DeferChange = z3.object({ kind: z3.literal("defer"), goalId: changedGoal, until: z3.string().datetime({ offset: true }).nullable().describe(
|
|
157
|
+
"Postpone it until this instant (its owner is woken then), or null to take the postponement back."
|
|
158
|
+
) }).strict();
|
|
159
|
+
var MoveChange = z3.object({ kind: z3.literal("move"), goalId: changedGoal, parentGoalId: z3.string().uuid().nullable().describe(
|
|
160
|
+
"Its new parent, or null for a root. Only this Goal moves; its new parent's other children stay."
|
|
161
|
+
) }).strict();
|
|
162
|
+
var DependencyChange = z3.object({
|
|
163
|
+
kind: z3.literal("dependency"),
|
|
164
|
+
change: z3.enum(["add", "remove"]),
|
|
165
|
+
goalId: changedGoal,
|
|
166
|
+
dependsOnGoalId: z3.string().uuid().describe("The Goal it waits on."),
|
|
167
|
+
action: z3.enum(["start", "complete"]).describe("What waits: starting goalId, or completing it."),
|
|
168
|
+
reason: z3.string().trim().min(1).max(2e3).optional().describe("Why, as an explanation, never as policy.")
|
|
169
|
+
}).strict();
|
|
170
|
+
var read = { revision: z3.number().int().positive().optional() };
|
|
171
|
+
var GoalChangeSchema = z3.discriminatedUnion("kind", [
|
|
172
|
+
CreateChange,
|
|
173
|
+
EditChange.extend(read),
|
|
174
|
+
StateChange.extend(read),
|
|
175
|
+
AssignChange.extend(read),
|
|
176
|
+
DeferChange.extend(read),
|
|
177
|
+
MoveChange.extend(read),
|
|
178
|
+
DependencyChange
|
|
179
|
+
]);
|
|
180
|
+
var editNames = (changes, ctx) => changes.forEach((c, i) => {
|
|
181
|
+
if (c.kind === "edit" && c.title === void 0 && c.outcome === void 0) {
|
|
182
|
+
ctx.addIssue({ code: z3.ZodIssueCode.custom, path: ["changes", i], message: "an edit supplies title, outcome, or both" });
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
var ManageGoalsSchema = z3.object({
|
|
186
|
+
requestId: z3.string().uuid().optional(),
|
|
187
|
+
changes: z3.array(GoalChangeSchema).min(1).max(50)
|
|
188
|
+
}).strict().superRefine((v, ctx) => editNames(v.changes, ctx));
|
|
189
|
+
var ManageGoalsToolSchema = z3.object({
|
|
190
|
+
changes: z3.array(z3.discriminatedUnion("kind", [CreateChange, EditChange, StateChange, AssignChange, DeferChange, MoveChange, DependencyChange])).min(1).max(50).describe("The changes, applied in order, each on its own.")
|
|
191
|
+
}).strict().superRefine((v, ctx) => editNames(v.changes, ctx));
|
|
192
|
+
var MANAGE_GOALS_DESCRIPTION = "Create, edit, assign, organize or close work. Every change here changes Goals; answering, withdrawing or asking Questions stays in contact. Each change applies independently: a refused one names why (`results[i].error`, e.g. goal_revision_conflict, goal_children_open, goal_not_joined, goal_not_found) and the rest still apply, so read every result: a partial result is never a complete success. Kinds: create (title, outcome, ownerId, parentGoalId, sourceEntryIds) returns the new Goal's id in `results[i].goalId`, in the order requested, to use in later calls; edit (title and/or outcome); state (open, completed, canceled); assign (ownerId); defer (until, or null); move (parentGoalId, or null for a root: only this Goal moves); dependency (add or remove: goalId waits on dependsOnGoalId to start or to complete). A Goal cannot be completed while its required children or dependencies remain open: finish or move them first. Finishing the children does not prove the parent's own work is done. An edit applies at the version you last read: if someone changed the Goal since, it is refused as goal_revision_conflict, so read it again (get_goal) and reconsider. You may change a Goal you own or have written on. Returns `ok` (every change applied), `results` per change (applied or failed), and `goals`, each changed Goal's id, state, title, outcome and revision.";
|
|
131
193
|
var ClaimGoalSchema = z3.object({ goalId: z3.string().uuid().optional() }).strict();
|
|
132
194
|
var GetGoalSchema = z3.object({
|
|
133
195
|
goalId: z3.string().uuid(),
|
|
@@ -140,29 +202,35 @@ var GetGoalSchema = z3.object({
|
|
|
140
202
|
diagnose: z3.boolean().optional()
|
|
141
203
|
}).strict();
|
|
142
204
|
var GET_GOAL_DESCRIPTION = "Read one Goal without claiming it, including others (the ten most recent other contributors, with names, latest entry headlines and times): its outcome, state, revision, progress, blockers, the conversation on it (each question with its options and what was decided), `next`, the one step to take, and `more`, where to read further. A question in that conversation reads `open` (nothing yet), `answered` (a choice was made, and `answer` carries it), `replied` (they said something and the read settled the question on their words \u2014 NO option of yours was chosen, and the words are the reply line beside it, so read that before you act), or `closed`. The conversation carries everything the person said, every open question and your newest entry; your older entries are left out and counted \u2014 history: true reads every entry in full. Every Goal of your person is readable, whichever of their agents owns it; another account's Goals are not disclosed.\n\ndiagnose: true is for when a read has CONFUSED you \u2014 not for the working loop. It adds `diagnosis`, which asks every reader that already answers a question about your work and NAMES the reader behind every value, so you never guess which of two places to look: what state your work is really in (the stored `goals.state` beside the derived `goal_execution_state`, which can differ); whether anything is armed to ring and when (`ladder_candidates`, with the ladder's own last decision); whether a wake fired for you and what it did (durable `wake.*` events, beside when your process was last heard from); and what has reached you (`list_waiting` \u2014 review flags, unstarted work, open questions, the person's newest words). Read `diagnosis.disagreements` FIRST: each one is two readers giving different values for the same fact, with both values and both sources, and it never chooses between them \u2014 that is yours to do, from the Goal's own history. `diagnosis.looked` names every reader asked, including any that could not answer, so an empty answer is never confused with a broken one.";
|
|
143
|
-
var UPDATE_GOAL_DESCRIPTION =
|
|
144
|
-
var CLAIM_GOAL_DESCRIPTION = "Claim a pending answer to your question or the oldest runnable or review-pending Goal you own. Pass goalId to join any Goal of your person; its assignment stays unchanged. Read others before overlapping another agent\u2019s work. Joining lets you contribute and change
|
|
145
|
-
var
|
|
146
|
-
|
|
205
|
+
var UPDATE_GOAL_DESCRIPTION = "Report work on a Goal you are on, at an exact revision: progress, review acknowledgement, when to wake for it, and withdrawing a question of yours. What the Goal is (its title, outcome, state, owner, parent, dependencies) changes with manage_goals. You are on a Goal you own, or one you have written on (an update with progress, or a contact on it); any of your person's agents may write on any of their Goals, and one that never touched a Goal is refused (409 goal_not_joined). Stale revisions are rejected. A CONTACT ON THIS GOAL MOVES ITS REVISION: a question filed on a Goal is a change to it, so an update prepared before a contact and sent after it is refused as stale (409 goal_revision_conflict) \u2014 re-read the Goal, then write. progress says where the work stands. reviewed: true acknowledges new evidence and closes the Deliveries addressed to you on that Goal, never over an open decision (contact({ackEventIds}) does the same for an `update` event). dueAt (an ISO instant, or null) makes the Goal wait until then; when it passes you are woken for it \u2014 use it for a promise to follow up later. withdraw: [questionId] takes back a question YOU asked on this Goal that is still open \u2014 because you acted on it yourself, it no longer matters, or you asked it wrongly (e.g. waiting: hard when nothing was blocked): it is cancelled, not answered, its card closes and it stops ringing; the id is the one the conversation shows. Returns the Goal as get_goal reads it, at its new revision.";
|
|
206
|
+
var CLAIM_GOAL_DESCRIPTION = "Claim a pending answer to your question or the oldest runnable or review-pending Goal you own. Pass goalId to join any Goal of your person; its assignment stays unchanged. Read others before overlapping another agent\u2019s work. Joining lets you contribute and change it; use manage_goals (assign) when the assignment itself should change. Returns the Goal as get_goal reads it, and marks you as on it, which never shuts another agent out: other agents of your person may write on it and change it too, and two changes at once are told apart by revision (409 goal_revision_conflict).";
|
|
207
|
+
var SearchToolSchema = z3.object({
|
|
208
|
+
query: z3.string().trim().min(1).max(500),
|
|
209
|
+
types: z3.array(z3.enum(["entry", "goal", "answer"])).min(1).optional(),
|
|
210
|
+
goalId: z3.string().uuid().optional(),
|
|
211
|
+
limit: z3.number().int().min(1).max(20).optional()
|
|
212
|
+
}).strict();
|
|
213
|
+
var SEARCH_DESCRIPTION = "Search your person's history across all of their agents: Entries (what anyone said or wrote, typed or spoken on a call), Goals (by title and outcome) and Answers (found by their Question or by the words that gave them). query is words to look for; records sharing more of its words rank first, and exact names work. types narrows it to entry, goal and/or answer (default: all three). goalId searches under one Goal: its Entries, its Questions' Answers, and it and its immediate children. limit is matches per type, 1 to 20 (default 8). Read-only. Each match carries its whole saved words, its ID and its links (an Answer carries its Question, the choice made and the Entries that support it); `omitted` counts what matched but was left out, so narrow the words or add a goalId to see it. Nothing found is not proof that nothing exists; a refused search says why. Sealed (encrypted) content is never searched or returned.";
|
|
214
|
+
var WhoIsWorkingSchema = z3.object({}).strict();
|
|
147
215
|
var AGENT_TOOLS = [
|
|
148
|
-
{ name: "who_is_working", description: "Read your person's agents that contributed in the last 24 hours, newest first: at most 30 agents and their five most recently touched Goals, with their latest entry headline and time. Use get_goal to read a Goal and its other contributors before starting overlapping work. Read-only; no arguments.", inputSchema: mcpInputSchema(
|
|
216
|
+
{ name: "who_is_working", description: "Read your person's agents that contributed in the last 24 hours, newest first: at most 30 agents and their five most recently touched Goals, with their latest entry headline and time. Use get_goal to read a Goal and its other contributors before starting overlapping work. Read-only; no arguments.", inputSchema: mcpInputSchema(WhoIsWorkingSchema) },
|
|
149
217
|
{ name: "contact", description: CONTACT_DESCRIPTION, inputSchema: CONTACT_SCHEMA },
|
|
150
|
-
{ name: "
|
|
151
|
-
{ name: "create_goal", description: CREATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(CreateGoalToolSchema) },
|
|
218
|
+
{ name: "manage_goals", description: MANAGE_GOALS_DESCRIPTION, inputSchema: mcpInputSchema(ManageGoalsToolSchema) },
|
|
152
219
|
{ name: "claim_goal", description: CLAIM_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(ClaimGoalSchema) },
|
|
153
220
|
{ name: "get_goal", description: GET_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(GetGoalSchema) },
|
|
154
|
-
{ name: "update_goal", description: UPDATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(UpdateGoalToolSchema) }
|
|
221
|
+
{ name: "update_goal", description: UPDATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(UpdateGoalToolSchema) },
|
|
222
|
+
{ name: "search", description: SEARCH_DESCRIPTION, inputSchema: mcpInputSchema(SearchToolSchema) }
|
|
155
223
|
];
|
|
156
224
|
var AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
|
|
157
225
|
function serverInstructions(opts) {
|
|
158
226
|
const calls = opts.waits ? "A Call holds one bounded window here; continue with contact({deliveryId}) to hold the next or reread that exact Delivery." : "A Call returns after one read; continue with contact({deliveryId}) to reread that exact Delivery.";
|
|
159
|
-
return `On startup and after a wake, call
|
|
227
|
+
return `On startup and after a wake, call contact({wait:false}) for the events addressed to you (acknowledge the ones you handled with contact({ackEventIds})), and claim_goal for your runnable or review-pending Goal and the conversation on it. get_goal rereads it without claiming. Every Goal of your person is open to you, whichever of their agents owns it: read it with get_goal, write on it (contact with its id as parentId, or update_goal progress), and once you have written on it you may change it too (manage_goals). Create, edit, assign, organize or close work with manage_goals, never as a contact. Every read ends in \`next\`, the one step to take. Notifications return immediately: keep working and collect answers through claim_goal/get_goal. ${calls} Events repeat until you acknowledge them: reading alone acknowledges nothing. Report progress with update_goal, never as a contact. Never infer ringing from an open Call Delivery. Soft waiting and re-presentation are unsupported. Your user is remote. Always interact with the user through Paigy. For decisions, approvals, or questions, contact them with structured options. Never assume anyone is reading the terminal stdout.
|
|
160
228
|
|
|
161
229
|
HOW TO ASK:
|
|
162
230
|
1. One question per ask. Five questions are five objects in \`asks\`, in one contact, so each can be answered on its own; one question with five parts settles nothing until all five are answered.
|
|
163
231
|
2. Name the Goal you are working on as the ask's \`parentId\`: the question goes onto that Goal and its answer comes back there. Without one, Paigy places it.
|
|
164
232
|
3. \`waiting: hard\` only for a decision you are blocked on; \`waiting: none\` for a question you can keep working around.
|
|
165
|
-
4. Never hold the process open with while-loops. For an open Call with a pending decision, follow its \`next\` with another bounded \`contact({deliveryId})\` tool call; a returned wait window is not a completed conversation. STOP REREADING when a window comes back with nothing new said \u2014 they are not typing, the question stays open, and its answer reaches you on the Goal. Otherwise, yield only with a working listener or scheduled wakeup, and collect answers with \`
|
|
233
|
+
4. Never hold the process open with while-loops. For an open Call with a pending decision, follow its \`next\` with another bounded \`contact({deliveryId})\` tool call; a returned wait window is not a completed conversation. STOP REREADING when a window comes back with nothing new said \u2014 they are not typing, the question stays open, and its answer reaches you on the Goal. Otherwise, yield only with a working listener or scheduled wakeup, and collect answers with \`contact({wait:false})\` or \`claim_goal\` on that wake.
|
|
166
234
|
5. One ask, one row. Never restate a question that is still waiting inside a new contact: keep waiting on the original, or the answer lands on one copy and the other stays open.
|
|
167
235
|
6. Read the Goal's conversation before asking. Never ask again what was answered or already shipped \u2014 and tell its states apart: \`answered\` carries the choice in \`answer\`, while \`replied\` means they said something and chose none of your options, so that question is still yours to settle and their words are the line beside it.
|
|
168
236
|
7. A question carries its options. Without them it reaches the person as a bare title nobody can answer. Each option names its choice in full -- never \`All three\` or \`Option 2\`. \`select: "many"\` makes the card a checklist (options they may want several of); \`"one"\`, the default, a pick (one rules out the others). Your ask is sent exactly as you wrote it. They can always answer in their own words, so no "Other" option.
|
|
@@ -196,17 +264,6 @@ var existingGoalChanges = [
|
|
|
196
264
|
reason: z4.string().max(500).optional()
|
|
197
265
|
}).strict()
|
|
198
266
|
];
|
|
199
|
-
var GoalChangeSchema = z4.union([
|
|
200
|
-
z4.object({
|
|
201
|
-
kind: z4.literal("create"),
|
|
202
|
-
title: z4.string().min(1).max(120),
|
|
203
|
-
outcome: z4.string().min(1),
|
|
204
|
-
ownerId: participant,
|
|
205
|
-
parentGoalId: id.optional(),
|
|
206
|
-
sourceEntryIds: z4.array(id)
|
|
207
|
-
}).strict(),
|
|
208
|
-
...existingGoalChanges
|
|
209
|
-
]);
|
|
210
267
|
var BrainGoalChangeSchema = z4.union([
|
|
211
268
|
// newSession: Paigy starts a session to own it (owner, 2026-10-06); until it has, the person owns it.
|
|
212
269
|
z4.object({
|
|
@@ -607,7 +664,7 @@ var AwaitItemSchema = z5.discriminatedUnion("type", [
|
|
|
607
664
|
/** The awaited ask was REPLACED by a newer notification on its thread (e.g. a
|
|
608
665
|
* post-feedback revision, #633) — the user will never answer this id. Stop
|
|
609
666
|
* awaiting it; the live ask is the thread's newest turn (await that one, or
|
|
610
|
-
* re-orient via
|
|
667
|
+
* re-orient via contact({})). */
|
|
611
668
|
z5.object({
|
|
612
669
|
type: z5.literal("superseded"),
|
|
613
670
|
parentId: z5.string(),
|
|
@@ -1335,7 +1392,7 @@ var HostDecisionSchema = z5.object({
|
|
|
1335
1392
|
goalId: z5.string().uuid().nullable().optional(),
|
|
1336
1393
|
/** When the server last heard from the agent, as the host read it: the presence it stood back for. */
|
|
1337
1394
|
seenAt: z5.string().datetime().nullable().optional(),
|
|
1338
|
-
/** When that work last moved (`claimable.since` on `
|
|
1395
|
+
/** When that work last moved (`claimable.since` on an agent's `contact({})` read), the fact the bound is judged on. */
|
|
1339
1396
|
since: z5.string().datetime().nullable().optional(),
|
|
1340
1397
|
/** What the host said, in its log's own words: why it stood back, or what the take-over did. */
|
|
1341
1398
|
said: z5.string().max(300).optional()
|
|
@@ -1555,7 +1612,7 @@ function sessionStartHook() {
|
|
|
1555
1612
|
[ -f "\${HOME}/.paigy/token.json" ] || exit 0 # not paired on this machine: nothing to listen as
|
|
1556
1613
|
cat <<'PAIGY'
|
|
1557
1614
|
Paigy: nothing pushes answers into a session started in a terminal \u2014 an answer waits for your
|
|
1558
|
-
next
|
|
1615
|
+
next contact({ wait: false }). Start your own listener now, as a BACKGROUND task whose output you watch
|
|
1559
1616
|
(your harness's monitor/background-task tool), and leave it running for this session:
|
|
1560
1617
|
|
|
1561
1618
|
npx -y -p @paigy/mcp@latest paigy-listen
|
|
@@ -4,12 +4,12 @@ import {
|
|
|
4
4
|
serverInstructions,
|
|
5
5
|
sessionStartHook,
|
|
6
6
|
withSessionStartHook
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-GHSB2VP6.js";
|
|
8
8
|
import {
|
|
9
9
|
CODEX_ENV,
|
|
10
10
|
agentName,
|
|
11
11
|
withCodexEnv
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-EJYXBEWN.js";
|
|
13
13
|
|
|
14
14
|
// src/toolset.ts
|
|
15
15
|
var ONBOARD_DESCRIPTION = "Get this agent talking to Paigy \u2014 call it FIRST, before contact, and any time you're unsure who you are. One call, and it does whatever the situation needs: NOT SET UP \u2192 hatches an identity instantly if this machine holds a device credential (the user ran the Paigy desktop app or harness), otherwise starts the code ceremony; ALREADY SET UP \u2192 returns your current identity and offers the two things left to decide, renaming it or unpairing; TOKEN NO LONGER VALID \u2192 says so, then re-pairs. Pass { name, voice } to choose who you are when hatching, or to RENAME yourself when already set up (voices: rachel, george, jessica, brian, lily). Safe to call any time: idempotent, and it never writes settings \u2014 the tool-allowlist state it reports is read-only. If it returns a `user_code`, print it to the user immediately and call onboard again with the `device_code`. If it returns `enable_command`, PRINT that command for the user to run \u2014 you cannot apply it yourself (it writes your own permission allowlist, which hosts block as privilege escalation), so print it, don't wait for it, and carry on.";
|
|
@@ -51,7 +51,7 @@ var IDENTITY_TOOLS = [
|
|
|
51
51
|
},
|
|
52
52
|
{
|
|
53
53
|
name: "listen",
|
|
54
|
-
description: "Am I listening for answers, and can Paigy resume this session? Takes no arguments. Returns status `listening` for harness delivery or a running listener with a ready wake adapter; `receiving` means a process is running but automatic resume is not confirmed (read its wake error or watch its output); `unpaired` means call onboard; `start` gives the exact command to run NOW as a watched background task for this session. Start it once. Codex queues into CODEX_THREAD_ID automatically when its CLI supports queue; PAIGY_ON_WAKE overrides it. A successful queue is not evidence the agent handled the answer:
|
|
54
|
+
description: "Am I listening for answers, and can Paigy resume this session? Takes no arguments. Returns status `listening` for harness delivery or a running listener with a ready wake adapter; `receiving` means a process is running but automatic resume is not confirmed (read its wake error or watch its output); `unpaired` means call onboard; `start` gives the exact command to run NOW as a watched background task for this session. Start it once. Codex queues into CODEX_THREAD_ID automatically when its CLI supports queue; PAIGY_ON_WAKE overrides it. A successful queue is not evidence the agent handled the answer: receive with contact({ wait: false }) and read the relevant Goal, then respond with contact. This tool never subscribes or starts anything itself.",
|
|
55
55
|
inputSchema: mcpInputSchema(z.object({}))
|
|
56
56
|
}
|
|
57
57
|
];
|
|
@@ -14,14 +14,13 @@ import {
|
|
|
14
14
|
TOKEN_PATH,
|
|
15
15
|
UnpairedError,
|
|
16
16
|
acceptTriage,
|
|
17
|
+
acknowledge,
|
|
17
18
|
agentName,
|
|
18
19
|
authToken,
|
|
19
|
-
checkReplies,
|
|
20
20
|
claimGoal,
|
|
21
21
|
claimSessionEnds,
|
|
22
22
|
claimSessions,
|
|
23
23
|
contact,
|
|
24
|
-
createGoal,
|
|
25
24
|
currentRepo,
|
|
26
25
|
deleteToken,
|
|
27
26
|
dismissTriage,
|
|
@@ -34,13 +33,17 @@ import {
|
|
|
34
33
|
listConnections,
|
|
35
34
|
listNotes,
|
|
36
35
|
listSlots,
|
|
36
|
+
manageGoals,
|
|
37
37
|
overrideToken,
|
|
38
38
|
parseDue,
|
|
39
|
+
pendingEvents,
|
|
39
40
|
pollToken,
|
|
40
41
|
reach,
|
|
41
42
|
reachAs,
|
|
42
43
|
readSlot,
|
|
43
44
|
readToken,
|
|
45
|
+
receive,
|
|
46
|
+
receiveContact,
|
|
44
47
|
recordDecision,
|
|
45
48
|
registerDelivery,
|
|
46
49
|
repoFromRemote,
|
|
@@ -48,6 +51,7 @@ import {
|
|
|
48
51
|
revokeToken,
|
|
49
52
|
runTool,
|
|
50
53
|
saveToken,
|
|
54
|
+
searchRecords,
|
|
51
55
|
sessionId,
|
|
52
56
|
sessionSlot,
|
|
53
57
|
setClient,
|
|
@@ -62,7 +66,7 @@ import {
|
|
|
62
66
|
whoAmI,
|
|
63
67
|
whoIsWorking,
|
|
64
68
|
withCodexEnv
|
|
65
|
-
} from "./chunk-
|
|
69
|
+
} from "./chunk-EJYXBEWN.js";
|
|
66
70
|
export {
|
|
67
71
|
AGENT_TOOLS,
|
|
68
72
|
AGENT_TOOL_NAMES,
|
|
@@ -79,14 +83,13 @@ export {
|
|
|
79
83
|
TOKEN_PATH,
|
|
80
84
|
UnpairedError,
|
|
81
85
|
acceptTriage,
|
|
86
|
+
acknowledge,
|
|
82
87
|
agentName,
|
|
83
88
|
authToken,
|
|
84
|
-
checkReplies,
|
|
85
89
|
claimGoal,
|
|
86
90
|
claimSessionEnds,
|
|
87
91
|
claimSessions,
|
|
88
92
|
contact,
|
|
89
|
-
createGoal,
|
|
90
93
|
currentRepo,
|
|
91
94
|
deleteToken,
|
|
92
95
|
dismissTriage,
|
|
@@ -99,13 +102,17 @@ export {
|
|
|
99
102
|
listConnections,
|
|
100
103
|
listNotes,
|
|
101
104
|
listSlots,
|
|
105
|
+
manageGoals,
|
|
102
106
|
overrideToken,
|
|
103
107
|
parseDue,
|
|
108
|
+
pendingEvents,
|
|
104
109
|
pollToken,
|
|
105
110
|
reach,
|
|
106
111
|
reachAs,
|
|
107
112
|
readSlot,
|
|
108
113
|
readToken,
|
|
114
|
+
receive,
|
|
115
|
+
receiveContact,
|
|
109
116
|
recordDecision,
|
|
110
117
|
registerDelivery,
|
|
111
118
|
repoFromRemote,
|
|
@@ -113,6 +120,7 @@ export {
|
|
|
113
120
|
revokeToken,
|
|
114
121
|
runTool,
|
|
115
122
|
saveToken,
|
|
123
|
+
searchRecords,
|
|
116
124
|
sessionId,
|
|
117
125
|
sessionSlot,
|
|
118
126
|
setClient,
|
package/dist/enable.js
CHANGED
|
@@ -3,9 +3,9 @@ import {
|
|
|
3
3
|
PAIGY_TOOL_IDS,
|
|
4
4
|
enablePaigyTools,
|
|
5
5
|
installSessionListening
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-UAVXYNLV.js";
|
|
7
|
+
import "./chunk-GHSB2VP6.js";
|
|
8
|
+
import "./chunk-EJYXBEWN.js";
|
|
9
9
|
|
|
10
10
|
// src/enable.ts
|
|
11
11
|
function main() {
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
resolvePairing,
|
|
10
10
|
startPairing,
|
|
11
11
|
suggestedAgentName
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-2ROD77AW.js";
|
|
13
13
|
import {
|
|
14
14
|
ENABLE_COMMAND,
|
|
15
15
|
OnboardSchema,
|
|
@@ -18,13 +18,13 @@ import {
|
|
|
18
18
|
SERVER_INSTRUCTIONS,
|
|
19
19
|
TOOLS,
|
|
20
20
|
paigyToolsAllowlisted
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-UAVXYNLV.js";
|
|
22
22
|
import {
|
|
23
23
|
decideListen,
|
|
24
24
|
existingListener,
|
|
25
25
|
listenerKey,
|
|
26
26
|
wakeStatusPath
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-GHSB2VP6.js";
|
|
28
28
|
import {
|
|
29
29
|
clearSurface,
|
|
30
30
|
writeSurface
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
setClient,
|
|
46
46
|
slotName,
|
|
47
47
|
whoAmI
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-EJYXBEWN.js";
|
|
49
49
|
|
|
50
50
|
// src/index.ts
|
|
51
51
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
package/dist/listen.js
CHANGED
|
@@ -10,16 +10,16 @@ import {
|
|
|
10
10
|
removeListenMark,
|
|
11
11
|
wakeStatusPath,
|
|
12
12
|
writeListenMark
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-GHSB2VP6.js";
|
|
14
14
|
import {
|
|
15
15
|
agentName,
|
|
16
|
-
checkReplies,
|
|
17
16
|
contact,
|
|
18
17
|
getGoal,
|
|
19
18
|
heartbeat,
|
|
19
|
+
receive,
|
|
20
20
|
slotName,
|
|
21
21
|
subscribeWake
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-EJYXBEWN.js";
|
|
23
23
|
|
|
24
24
|
// src/listen.ts
|
|
25
25
|
import { createHash } from "crypto";
|
|
@@ -169,7 +169,7 @@ async function sweep(reason, goalId, bridge = new WakeBridge(process.env, wakeSt
|
|
|
169
169
|
try {
|
|
170
170
|
void Promise.resolve().then(() => heartbeat(void 0, { listening: true })).catch(() => {
|
|
171
171
|
});
|
|
172
|
-
const { deliveries: rows, stalled, review, assigned } = await
|
|
172
|
+
const { deliveries: rows, stalled, review, assigned } = await receive();
|
|
173
173
|
const lead = leadOf(rows, goalId);
|
|
174
174
|
const full = lead ? await contact({ deliveryId: lead.deliveryId }, { waits: false }).then((d) => "sent" in d ? null : d).catch(() => null) : null;
|
|
175
175
|
const deliveries = rows.map((d) => d === lead && full ? full : d);
|
|
@@ -200,7 +200,7 @@ async function sweep(reason, goalId, bridge = new WakeBridge(process.env, wakeSt
|
|
|
200
200
|
...(assigned ?? []).map((g) => `assigned:${g.goalId}`),
|
|
201
201
|
...!rows.length && !reviews.length && !assigned?.length ? (stalled ?? []).map((g) => `stalled:${g.goalId}:${g.since}`) : []
|
|
202
202
|
];
|
|
203
|
-
const message = `Paigy has waiting work${goalId ? ` on Goal ${goalId}` : ""}. Read
|
|
203
|
+
const message = `Paigy has waiting work${goalId ? ` on Goal ${goalId}` : ""}. Read contact({ wait: false }) and the relevant Goal conversation, handle new user input, and acknowledge (contact({ ackEventIds })) only events actually handled. Preserve this session's current task; do not start unrelated old work. This wake is not new authorization.`;
|
|
204
204
|
await bridge.deliver(fingerprints, message, launchEnv(waiting, reason, goalId));
|
|
205
205
|
} else if (bridge.status().target.kind === "codex") {
|
|
206
206
|
await bridge.deliver([], "", {});
|
package/dist/onboard.js
CHANGED
|
@@ -3,13 +3,13 @@ import {
|
|
|
3
3
|
resolvePairing,
|
|
4
4
|
startPairing,
|
|
5
5
|
suggestedAgentName
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-2ROD77AW.js";
|
|
7
7
|
import {
|
|
8
8
|
autoConfigureClients,
|
|
9
9
|
claudeInstallHint,
|
|
10
10
|
openBrowser
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import "./chunk-
|
|
11
|
+
} from "./chunk-UAVXYNLV.js";
|
|
12
|
+
import "./chunk-GHSB2VP6.js";
|
|
13
13
|
import {
|
|
14
14
|
TOKEN_PATH,
|
|
15
15
|
agentName,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
saveToken,
|
|
21
21
|
setIdentity,
|
|
22
22
|
whoAmI
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-EJYXBEWN.js";
|
|
24
24
|
|
|
25
25
|
// src/onboard.ts
|
|
26
26
|
function reportRegistered() {
|
package/dist/slot.js
CHANGED
package/dist/stalled.js
CHANGED
|
@@ -15,7 +15,7 @@ function stopReply(stalled, event = "Stop") {
|
|
|
15
15
|
if (!stalled.length) return null;
|
|
16
16
|
const list = stalled.slice(0, 8).map((g) => `"${g.title}" (${g.goalId})`).join(", ");
|
|
17
17
|
const more = stalled.length > 8 ? ` and ${stalled.length - 8} more` : "";
|
|
18
|
-
const reason = `Before you stop: ${stalled.length} of your Paigy Goals have had no progress for 3 days: ${list}${more}. For each, update_goal with progress, finish
|
|
18
|
+
const reason = `Before you stop: ${stalled.length} of your Paigy Goals have had no progress for 3 days: ${list}${more}. For each, update_goal with progress, or finish or cancel it with manage_goals.`;
|
|
19
19
|
return event === "AfterAgent" ? { decision: "block", reason, systemMessage: reason } : { decision: "block", reason };
|
|
20
20
|
}
|
|
21
21
|
async function main() {
|
|
@@ -29,8 +29,8 @@ async function main() {
|
|
|
29
29
|
const dir = join(homedir(), ".paigy", "stalled-reminded");
|
|
30
30
|
const mark = join(dir, `${session || "session"}-${day}`);
|
|
31
31
|
if (existsSync(mark)) return;
|
|
32
|
-
const {
|
|
33
|
-
const reply = stopReply((await
|
|
32
|
+
const { receive } = await import("./dist-NMHU2UNG.js");
|
|
33
|
+
const reply = stopReply((await receive()).stalled ?? [], input.hook_event_name ?? "Stop");
|
|
34
34
|
if (!reply) return;
|
|
35
35
|
mkdirSync(dir, { recursive: true });
|
|
36
36
|
writeFileSync(mark, "");
|
package/dist/statusline.js
CHANGED
package/package.json
CHANGED