@paigy/mcp 0.40.15 → 0.40.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-37FS33UP.js → chunk-7VLZIOLF.js} +2 -2
- package/dist/{chunk-SE2JCKDI.js → chunk-MWHYYLCE.js} +13 -7
- package/dist/{chunk-X6G7XXCP.js → chunk-UIZLBPRG.js} +1 -1
- package/dist/{chunk-PHTQITTC.js → chunk-V5AQASR7.js} +131 -37
- package/dist/{dist-DKP3DJQJ.js → dist-PJLF3B27.js} +1 -1
- package/dist/enable.js +3 -3
- package/dist/index.js +4 -4
- package/dist/listen.js +24 -9
- package/dist/onboard.js +4 -4
- package/dist/slot.js +1 -1
- package/dist/stalled.js +1 -1
- package/dist/statusline.js +1 -1
- package/package.json +1 -1
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
serverInstructions,
|
|
5
5
|
sessionStartHook,
|
|
6
6
|
withSessionStartHook
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-MWHYYLCE.js";
|
|
8
8
|
import {
|
|
9
9
|
agentName
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-V5AQASR7.js";
|
|
11
11
|
|
|
12
12
|
// src/toolset.ts
|
|
13
13
|
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.";
|
|
@@ -68,7 +68,7 @@ var StartContactSchema = z2.object({
|
|
|
68
68
|
}).strict();
|
|
69
69
|
var ContactSchema = z2.union([StartContactSchema, z2.object({ deliveryId: z2.string().uuid() }).strict()]);
|
|
70
70
|
var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
|
|
71
|
-
var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. An ask that names a Goal (parentId) goes onto that Goal; one that names none is placed in the person's tree by Paigy. Notification returns immediately; collect durable answers with check_replies. On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. One question per 'ask'; several questions are several objects in 'asks'. A bundled ask is taken apart into its own cards by Paigy's one intake read \u2014 parts of one piece of work under one Goal, separate things as separate Goals.";
|
|
71
|
+
var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. An ask that names a Goal (parentId) goes onto that Goal; one that names none is placed in the person's tree by Paigy. Notification returns immediately; collect durable answers with check_replies. On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. One question per 'ask'; several questions are several objects in 'asks'. A bundled ask is taken apart into its own cards by Paigy's one intake read \u2014 parts of one piece of work under one Goal, separate things as separate Goals. An ask with no options that is not blocking is a report: on a Goal whose report card is still open it is added to that card, with no new push; one that only says where your work stands while under way is recorded as the Goal's progress, not sent (the result says so).";
|
|
72
72
|
var CreateGoalSchema = z3.object({
|
|
73
73
|
outcome: z3.string().trim().min(1).max(1e4),
|
|
74
74
|
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
|
|
@@ -115,9 +115,9 @@ var UpdateGoalToolSchema = UpdateGoalSchema.omit({ operationId: true }).extend({
|
|
|
115
115
|
var ClaimGoalSchema = z3.object({ goalId: z3.string().uuid().optional() }).strict();
|
|
116
116
|
var GetGoalSchema = z3.object({ goalId: z3.string().uuid() }).strict();
|
|
117
117
|
var GET_GOAL_DESCRIPTION = "Read one Goal without claiming it: its outcome, state, revision, progress, blockers, the conversation on it (each question with its options and what was decided), and `next`, the one step to take. Foreign or sibling-owned Goals are not disclosed.";
|
|
118
|
-
var UPDATE_GOAL_DESCRIPTION =
|
|
118
|
+
var UPDATE_GOAL_DESCRIPTION = `Update an owned Goal at an exact revision. State, ownership, dependencies, children, progress, title, and review acknowledgement are explicit; stale revisions are rejected. title is the work's name in one to five words, as a person would refer to it out loud (it is spoken on a call and heads every list); null clears it. reviewed: true acknowledges new evidence and closes the Deliveries addressed to you on that Goal, never over an open decision. dueAt (an ISO instant, or null) makes the Goal wait until then; when it passes you are woken for it \u2014 use it for a promise to follow up later. state: done means the work was accomplished, and it can be reopened: when the person says it is not done ("it's not working"), set state: active on that same Goal instead of starting a new one; its parent reopens with it. cancelled is final. Returns the Goal as get_goal reads it, at its new revision.`;
|
|
119
119
|
var CLAIM_GOAL_DESCRIPTION = "Claim the oldest runnable or review-pending Goal you own, or pass goalId to claim that Goal. Another agent's Goal that has gone quiet for 3 days (check_replies lists them as stalledOthers) is taken over when you claim it, and becomes yours to finish or cancel. Returns the Goal as get_goal reads it, and creates or renews the execution lease.";
|
|
120
|
-
var CHECK_REPLIES_DESCRIPTION = "
|
|
120
|
+
var CHECK_REPLIES_DESCRIPTION = "What is waiting for you: your open Deliveries (a request the user started toward you, a handoff), one row each with its Goals, how many decisions are still open, and the newest words in brief; `assigned`, your Goals nobody has started yet, however they became yours (claim_goal({goalId}) starts one); and `review`, your Goals with something new on them \u2014 the user's answers and notes are Entries on the Goal, not Deliveries. A pure read with no arguments: nothing is consumed, acknowledged or claimed by reading it, so call it on startup, after a long wait, or whenever you want to know what is outstanding. To act on one, claim its Goal (claim_goal) or reread a Delivery in full with contact({deliveryId}). Once you have acted on what arrived, update_goal with reviewed: true clears the Goal from `review` and closes the Deliveries addressed to you on it.";
|
|
121
121
|
var CheckRepliesSchema = z3.object({}).strict();
|
|
122
122
|
var AGENT_TOOLS = [
|
|
123
123
|
{ name: "contact", description: CONTACT_DESCRIPTION, inputSchema: CONTACT_SCHEMA },
|
|
@@ -472,9 +472,9 @@ var AgendaTurnSchema = z4.object({
|
|
|
472
472
|
/** Twin coverage (#1089): sibling claim ids this asking turn's answer ALSO settles —
|
|
473
473
|
* the planner declares duplicates instead of asking them twice. */
|
|
474
474
|
coveredIds: z4.array(z4.string()).optional(),
|
|
475
|
-
/**
|
|
476
|
-
*
|
|
477
|
-
info: z4.array(z4.string().min(1)).
|
|
475
|
+
/** The spoken sentences of the turn, in order. No count: how long a turn is is the brain's call
|
|
476
|
+
* (owner, 2026-09-25), and a count here refused whole plans. */
|
|
477
|
+
info: z4.array(z4.string().min(1)).default([]),
|
|
478
478
|
question: z4.string().min(1).nullable(),
|
|
479
479
|
/** True on the one turn carrying the agent's own declared question. */
|
|
480
480
|
asks: z4.boolean().optional(),
|
|
@@ -585,6 +585,11 @@ var InboxItemSchema = z4.object({
|
|
|
585
585
|
* and "dismiss the stalled ones" cancelled 37 pieces of live work). */
|
|
586
586
|
cold: z4.string().datetime().optional(),
|
|
587
587
|
clarifies: z4.string().optional(),
|
|
588
|
+
/** THIS CARD'S QUESTION IS ON A LIVE CALL (owner, 2026-09-24: "Mark it while the call is
|
|
589
|
+
* live"). Present only while an open Call Delivery carries the card's request Entry — read
|
|
590
|
+
* off the same open list the card came from, so it clears when the Call does. A card is the
|
|
591
|
+
* backup for a call not taken; while the call has it, the call is where it is answered. */
|
|
592
|
+
onCall: z4.literal(true).optional(),
|
|
588
593
|
/** THE RING, ON THE ITEM (walk/design.md §12 §17, #2251): the last ring on this card was
|
|
589
594
|
* declined, and what the ladder will do next — read off the cron's own row, never computed
|
|
590
595
|
* on the phone. Present only while a `declined` receipt stands on the card's last Call.
|
|
@@ -1149,7 +1154,8 @@ next check_replies. Start your own listener now, as a BACKGROUND task whose outp
|
|
|
1149
1154
|
npx -y -p @paigy/mcp@latest paigy-listen
|
|
1150
1155
|
|
|
1151
1156
|
Each line it prints is a Delivery waiting for you: read the Goal, and reply with contact when
|
|
1152
|
-
the answer needs a decision. Start it once; if it is already running, do nothing.
|
|
1157
|
+
the answer needs a decision. Start it once; if it is already running, do nothing. Then call
|
|
1158
|
+
claim_goal once: work handed to you before this session started waits there.
|
|
1153
1159
|
PAIGY
|
|
1154
1160
|
exit 0
|
|
1155
1161
|
`;
|
|
@@ -1536,7 +1536,7 @@ var StartContactSchema = z2.object({
|
|
|
1536
1536
|
}).strict();
|
|
1537
1537
|
var ContactSchema = z2.union([StartContactSchema, z2.object({ deliveryId: z2.string().uuid() }).strict()]);
|
|
1538
1538
|
var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
|
|
1539
|
-
var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. An ask that names a Goal (parentId) goes onto that Goal; one that names none is placed in the person's tree by Paigy. Notification returns immediately; collect durable answers with check_replies. On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. One question per 'ask'; several questions are several objects in 'asks'. A bundled ask is taken apart into its own cards by Paigy's one intake read \u2014 parts of one piece of work under one Goal, separate things as separate Goals.";
|
|
1539
|
+
var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. An ask that names a Goal (parentId) goes onto that Goal; one that names none is placed in the person's tree by Paigy. Notification returns immediately; collect durable answers with check_replies. On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. One question per 'ask'; several questions are several objects in 'asks'. A bundled ask is taken apart into its own cards by Paigy's one intake read \u2014 parts of one piece of work under one Goal, separate things as separate Goals. An ask with no options that is not blocking is a report: on a Goal whose report card is still open it is added to that card, with no new push; one that only says where your work stands while under way is recorded as the Goal's progress, not sent (the result says so).";
|
|
1540
1540
|
var CreateGoalSchema = z3.object({
|
|
1541
1541
|
outcome: z3.string().trim().min(1).max(1e4),
|
|
1542
1542
|
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
|
|
@@ -1583,9 +1583,9 @@ var UpdateGoalToolSchema = UpdateGoalSchema.omit({ operationId: true }).extend({
|
|
|
1583
1583
|
var ClaimGoalSchema = z3.object({ goalId: z3.string().uuid().optional() }).strict();
|
|
1584
1584
|
var GetGoalSchema = z3.object({ goalId: z3.string().uuid() }).strict();
|
|
1585
1585
|
var GET_GOAL_DESCRIPTION = "Read one Goal without claiming it: its outcome, state, revision, progress, blockers, the conversation on it (each question with its options and what was decided), and `next`, the one step to take. Foreign or sibling-owned Goals are not disclosed.";
|
|
1586
|
-
var UPDATE_GOAL_DESCRIPTION =
|
|
1586
|
+
var UPDATE_GOAL_DESCRIPTION = `Update an owned Goal at an exact revision. State, ownership, dependencies, children, progress, title, and review acknowledgement are explicit; stale revisions are rejected. title is the work's name in one to five words, as a person would refer to it out loud (it is spoken on a call and heads every list); null clears it. reviewed: true acknowledges new evidence and closes the Deliveries addressed to you on that Goal, never over an open decision. dueAt (an ISO instant, or null) makes the Goal wait until then; when it passes you are woken for it \u2014 use it for a promise to follow up later. state: done means the work was accomplished, and it can be reopened: when the person says it is not done ("it's not working"), set state: active on that same Goal instead of starting a new one; its parent reopens with it. cancelled is final. Returns the Goal as get_goal reads it, at its new revision.`;
|
|
1587
1587
|
var CLAIM_GOAL_DESCRIPTION = "Claim the oldest runnable or review-pending Goal you own, or pass goalId to claim that Goal. Another agent's Goal that has gone quiet for 3 days (check_replies lists them as stalledOthers) is taken over when you claim it, and becomes yours to finish or cancel. Returns the Goal as get_goal reads it, and creates or renews the execution lease.";
|
|
1588
|
-
var CHECK_REPLIES_DESCRIPTION = "
|
|
1588
|
+
var CHECK_REPLIES_DESCRIPTION = "What is waiting for you: your open Deliveries (a request the user started toward you, a handoff), one row each with its Goals, how many decisions are still open, and the newest words in brief; `assigned`, your Goals nobody has started yet, however they became yours (claim_goal({goalId}) starts one); and `review`, your Goals with something new on them \u2014 the user's answers and notes are Entries on the Goal, not Deliveries. A pure read with no arguments: nothing is consumed, acknowledged or claimed by reading it, so call it on startup, after a long wait, or whenever you want to know what is outstanding. To act on one, claim its Goal (claim_goal) or reread a Delivery in full with contact({deliveryId}). Once you have acted on what arrived, update_goal with reviewed: true clears the Goal from `review` and closes the Deliveries addressed to you on it.";
|
|
1589
1589
|
var CheckRepliesSchema = z3.object({}).strict();
|
|
1590
1590
|
var AGENT_TOOLS = [
|
|
1591
1591
|
{ name: "contact", description: CONTACT_DESCRIPTION, inputSchema: CONTACT_SCHEMA },
|
|
@@ -1930,9 +1930,9 @@ var AgendaTurnSchema = z4.object({
|
|
|
1930
1930
|
/** Twin coverage (#1089): sibling claim ids this asking turn's answer ALSO settles —
|
|
1931
1931
|
* the planner declares duplicates instead of asking them twice. */
|
|
1932
1932
|
coveredIds: z4.array(z4.string()).optional(),
|
|
1933
|
-
/**
|
|
1934
|
-
*
|
|
1935
|
-
info: z4.array(z4.string().min(1)).
|
|
1933
|
+
/** The spoken sentences of the turn, in order. No count: how long a turn is is the brain's call
|
|
1934
|
+
* (owner, 2026-09-25), and a count here refused whole plans. */
|
|
1935
|
+
info: z4.array(z4.string().min(1)).default([]),
|
|
1936
1936
|
question: z4.string().min(1).nullable(),
|
|
1937
1937
|
/** True on the one turn carrying the agent's own declared question. */
|
|
1938
1938
|
asks: z4.boolean().optional(),
|
|
@@ -2043,6 +2043,11 @@ var InboxItemSchema = z4.object({
|
|
|
2043
2043
|
* and "dismiss the stalled ones" cancelled 37 pieces of live work). */
|
|
2044
2044
|
cold: z4.string().datetime().optional(),
|
|
2045
2045
|
clarifies: z4.string().optional(),
|
|
2046
|
+
/** THIS CARD'S QUESTION IS ON A LIVE CALL (owner, 2026-09-24: "Mark it while the call is
|
|
2047
|
+
* live"). Present only while an open Call Delivery carries the card's request Entry — read
|
|
2048
|
+
* off the same open list the card came from, so it clears when the Call does. A card is the
|
|
2049
|
+
* backup for a call not taken; while the call has it, the call is where it is answered. */
|
|
2050
|
+
onCall: z4.literal(true).optional(),
|
|
2046
2051
|
/** THE RING, ON THE ITEM (walk/design.md §12 §17, #2251): the last ring on this card was
|
|
2047
2052
|
* declined, and what the ladder will do next — read off the cron's own row, never computed
|
|
2048
2053
|
* on the phone. Present only while a `declined` receipt stands on the card's last Call.
|
|
@@ -2899,6 +2904,7 @@ function currentRepo(cwd = process.cwd()) {
|
|
|
2899
2904
|
}
|
|
2900
2905
|
return asked;
|
|
2901
2906
|
}
|
|
2907
|
+
var NOT_SENT = "Recorded as progress, not sent: nobody was notified. If the person should see it (a result, something to try or check, a finding), contact again and say so; report progress with update_goal.";
|
|
2902
2908
|
async function contact(input, opts = {}) {
|
|
2903
2909
|
const parsed = ContactSchema.parse(input);
|
|
2904
2910
|
opts.signal?.throwIfAborted();
|
|
@@ -2906,6 +2912,7 @@ async function contact(input, opts = {}) {
|
|
|
2906
2912
|
const headers = { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token) ?? ""}`, "x-paigy-model": "goal-entry-v1" };
|
|
2907
2913
|
let deliveryId;
|
|
2908
2914
|
let deliveries = [];
|
|
2915
|
+
let recorded = [];
|
|
2909
2916
|
if ("deliveryId" in parsed) {
|
|
2910
2917
|
deliveryId = parsed.deliveryId;
|
|
2911
2918
|
} else {
|
|
@@ -2934,19 +2941,24 @@ async function contact(input, opts = {}) {
|
|
|
2934
2941
|
if (!res.ok) await fail("batch contact", res);
|
|
2935
2942
|
const receipt = await res.json();
|
|
2936
2943
|
deliveries = receipt.deliveries ?? [];
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
}
|
|
2944
|
+
recorded = receipt.recorded ?? [];
|
|
2945
|
+
if (deliveries.length === 0 && recorded.length) return { sent: false, recorded, message: NOT_SENT };
|
|
2940
2946
|
if (deliveries.length === 0) throw new Error("Goal contact returned no Delivery identities");
|
|
2941
2947
|
deliveryId = deliveries[0]?.deliveryId ?? "";
|
|
2942
2948
|
if (!deliveryId) throw new Error("Goal contact returned no valid Delivery identity");
|
|
2943
2949
|
}
|
|
2944
|
-
const all = deliveries.filter((d) => !!d.deliveryId && !!d.goalId);
|
|
2950
|
+
const all = deliveries.filter((d) => !!d.deliveryId && !!d.goalId).map(({ id, deliveryId: deliveryId2, goalId }) => ({ ...id ? { id } : {}, deliveryId: deliveryId2, goalId }));
|
|
2951
|
+
const joinedCard = deliveries[0]?.joinedCard === true;
|
|
2945
2952
|
const read = async (signal2) => {
|
|
2946
2953
|
const res = ensureAuthed(await send2(`${BACKEND_URL}/api/deliveries/${encodeURIComponent(deliveryId)}`, { headers, signal: signal2 }));
|
|
2947
2954
|
if (!res.ok) await fail("read_delivery", res);
|
|
2948
2955
|
const one = await res.json();
|
|
2949
|
-
return
|
|
2956
|
+
return {
|
|
2957
|
+
...one,
|
|
2958
|
+
...all.length > 1 ? { deliveries: all } : {},
|
|
2959
|
+
...recorded.length ? { recorded, notSent: NOT_SENT } : {},
|
|
2960
|
+
...joinedCard ? { joinedCard: true } : {}
|
|
2961
|
+
};
|
|
2950
2962
|
};
|
|
2951
2963
|
const settled = (d) => d.kind === "notification" || d.state === "closed" || d.answers.length > 0 || d.entries.some((e) => e.kind === "contribution");
|
|
2952
2964
|
if (opts.waits === false) return read(opts.signal);
|
|
@@ -3044,6 +3056,7 @@ function goalView(g) {
|
|
|
3044
3056
|
});
|
|
3045
3057
|
}
|
|
3046
3058
|
function deliveryView(d) {
|
|
3059
|
+
const answers = "Answers land on the Goal: read them with claim_goal or get_goal. Do not poll this notification.";
|
|
3047
3060
|
return compact({
|
|
3048
3061
|
deliveryId: d.deliveryId,
|
|
3049
3062
|
kind: d.kind,
|
|
@@ -3052,11 +3065,17 @@ function deliveryView(d) {
|
|
|
3052
3065
|
conversation: conversation(d),
|
|
3053
3066
|
// Each ask's own Delivery when a contact carried several — the only handle on the others.
|
|
3054
3067
|
deliveries: d.deliveries,
|
|
3055
|
-
|
|
3068
|
+
// WHAT THIS CONTACT DID NOT SEND (#2450): asks the intake read kept as progress, by name.
|
|
3069
|
+
recorded: d.recorded,
|
|
3070
|
+
notSent: d.notSent,
|
|
3071
|
+
joinedCard: d.joinedCard,
|
|
3072
|
+
next: d.kind === "notification" ? d.joinedCard ? `Added to the open report card on its Goal; no new push was sent. ${answers}` : answers : d.message
|
|
3056
3073
|
});
|
|
3057
3074
|
}
|
|
3058
3075
|
function repliesView(r) {
|
|
3059
3076
|
const deliveries = r.deliveries ?? [];
|
|
3077
|
+
const assigned = r.assigned ?? [];
|
|
3078
|
+
const review = r.review ?? [];
|
|
3060
3079
|
const stalled = r.stalled ?? [];
|
|
3061
3080
|
const others = r.stalledOthers ?? [];
|
|
3062
3081
|
return {
|
|
@@ -3067,13 +3086,21 @@ function repliesView(r) {
|
|
|
3067
3086
|
openDecisions: d.decisionNeeds.filter((n) => n.state === "open").length || void 0,
|
|
3068
3087
|
latest: "about" in d ? d.about : void 0
|
|
3069
3088
|
})),
|
|
3089
|
+
// WORK ASSIGNED TO YOU (owner, 2026-09-24): your Goals nobody has started, however they
|
|
3090
|
+
// became yours — a Goal handed to you is not a Delivery, and must not wait for a claim to be seen.
|
|
3091
|
+
...assigned.length ? { assigned } : {},
|
|
3092
|
+
// NEW ON YOUR GOALS (owner, 2026-09-23): a reply is an Entry on the Goal, not a Delivery, so the
|
|
3093
|
+
// Goals flagged for your review are listed here until you acknowledge them.
|
|
3094
|
+
...review.length ? { review } : {},
|
|
3070
3095
|
// YOUR STALLED WORK (#2257): Goals you own with no progress for three days. Named here so an
|
|
3071
3096
|
// agent that reads its replies also sees what it left, and says what to do about each.
|
|
3072
3097
|
...stalled.length ? { stalled } : {},
|
|
3073
3098
|
// OTHER AGENTS' STALLED WORK (owner, 2026-09-23): any agent may take it over and close it.
|
|
3074
3099
|
...others.length ? { stalledOthers: others } : {},
|
|
3075
3100
|
next: [
|
|
3076
|
-
deliveries.length ? "Act on a Goal with claim_goal; reread one Delivery in full with contact({deliveryId})." : "Nothing is waiting.",
|
|
3101
|
+
deliveries.length || review.length || assigned.length ? "Act on a Goal with claim_goal; reread one Delivery in full with contact({deliveryId})." : "Nothing is waiting.",
|
|
3102
|
+
...assigned.length ? [`${assigned.length} of your Goals are assigned to you and not started (assigned): claim_goal({goalId}) to start one.`] : [],
|
|
3103
|
+
...review.length ? [`${review.length} of your Goals have something new (review): read each with claim_goal or get_goal, then update_goal with reviewed: true.`] : [],
|
|
3077
3104
|
...stalled.length ? [`${stalled.length} of your Goals have had no progress for 3 days: update_goal each with progress, finish it, or cancel it.`] : [],
|
|
3078
3105
|
...others.length ? [`${others.length} of other agents' Goals have gone quiet for 3 days: claim_goal({goalId}) takes one over, then finish or cancel it with update_goal.`] : []
|
|
3079
3106
|
].join(" ")
|
|
@@ -3085,7 +3112,7 @@ async function runTool(name, args, opts) {
|
|
|
3085
3112
|
switch (name) {
|
|
3086
3113
|
case "contact": {
|
|
3087
3114
|
const sent = await contact(input, { ...client, signal, waits });
|
|
3088
|
-
return "
|
|
3115
|
+
return "sent" in sent ? sent : deliveryView(sent);
|
|
3089
3116
|
}
|
|
3090
3117
|
case "check_replies":
|
|
3091
3118
|
CheckRepliesSchema.parse(input);
|
|
@@ -3113,47 +3140,108 @@ async function runTool(name, args, opts) {
|
|
|
3113
3140
|
var HEARTBEAT_MS = 25e3;
|
|
3114
3141
|
var RETRY_MS = [1e3, 2e3, 5e3, 1e4, 3e4];
|
|
3115
3142
|
var JOIN = { config: { broadcast: { self: false } } };
|
|
3143
|
+
var JOIN_TIMEOUT_MS = 1e4;
|
|
3116
3144
|
var lines = /* @__PURE__ */ new Map();
|
|
3117
3145
|
function frame(line, topic, event, payload) {
|
|
3146
|
+
const ref = String(++line.ref);
|
|
3118
3147
|
try {
|
|
3119
|
-
line.ws?.send(JSON.stringify({ topic, event, payload, ref
|
|
3148
|
+
line.ws?.send(JSON.stringify({ topic, event, payload, ref }));
|
|
3120
3149
|
} catch {
|
|
3121
3150
|
}
|
|
3151
|
+
return ref;
|
|
3122
3152
|
}
|
|
3123
3153
|
function stop(line) {
|
|
3124
3154
|
line.open = false;
|
|
3155
|
+
line.beat = void 0;
|
|
3125
3156
|
clearInterval(line.heart);
|
|
3126
3157
|
clearTimeout(line.retry);
|
|
3158
|
+
for (const t of line.topics.values()) {
|
|
3159
|
+
clearTimeout(t.timer);
|
|
3160
|
+
t.joined = false;
|
|
3161
|
+
t.ref = void 0;
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
function join3(line, name, t) {
|
|
3165
|
+
clearTimeout(t.timer);
|
|
3166
|
+
t.joined = false;
|
|
3167
|
+
t.ref = frame(line, `realtime:${name}`, "phx_join", JOIN);
|
|
3168
|
+
t.timer = setTimeout(() => rejoin(line, name, t), JOIN_TIMEOUT_MS);
|
|
3169
|
+
}
|
|
3170
|
+
function rejoin(line, name, t) {
|
|
3171
|
+
clearTimeout(t.timer);
|
|
3172
|
+
t.joined = false;
|
|
3173
|
+
t.ref = void 0;
|
|
3174
|
+
if (!line.open || line.topics.get(name) !== t) return;
|
|
3175
|
+
t.timer = setTimeout(() => {
|
|
3176
|
+
if (line.open && line.topics.get(name) === t) join3(line, name, t);
|
|
3177
|
+
}, RETRY_MS[Math.min(t.attempt++, RETRY_MS.length - 1)]);
|
|
3178
|
+
}
|
|
3179
|
+
function joined(t) {
|
|
3180
|
+
clearTimeout(t.timer);
|
|
3181
|
+
t.joined = true;
|
|
3182
|
+
t.attempt = 0;
|
|
3183
|
+
for (const l of t.listeners) l.onJoined?.(++l.joins);
|
|
3127
3184
|
}
|
|
3128
3185
|
function connect(line) {
|
|
3129
3186
|
const base = line.url.replace(/^http/, "ws").replace(/\/+$/, "");
|
|
3130
3187
|
const ws = line.ws = new WebSocket(`${base}/realtime/v1/websocket?apikey=${encodeURIComponent(line.anonKey)}&vsn=1.0.0`);
|
|
3188
|
+
let gone = false;
|
|
3189
|
+
const again = () => {
|
|
3190
|
+
if (gone) return;
|
|
3191
|
+
gone = true;
|
|
3192
|
+
stop(line);
|
|
3193
|
+
if (!line.topics.size) return;
|
|
3194
|
+
line.retry = setTimeout(() => connect(line), RETRY_MS[Math.min(line.attempt++, RETRY_MS.length - 1)]);
|
|
3195
|
+
};
|
|
3131
3196
|
ws.addEventListener("open", () => {
|
|
3197
|
+
if (gone) return;
|
|
3132
3198
|
line.open = true;
|
|
3133
3199
|
line.attempt = 0;
|
|
3134
|
-
for (const
|
|
3135
|
-
line.heart = setInterval(() =>
|
|
3200
|
+
for (const [name, t] of line.topics) join3(line, name, t);
|
|
3201
|
+
line.heart = setInterval(() => {
|
|
3202
|
+
if (line.beat) {
|
|
3203
|
+
try {
|
|
3204
|
+
ws.close();
|
|
3205
|
+
} catch {
|
|
3206
|
+
}
|
|
3207
|
+
again();
|
|
3208
|
+
return;
|
|
3209
|
+
}
|
|
3210
|
+
line.beat = frame(line, "phoenix", "heartbeat", {});
|
|
3211
|
+
}, HEARTBEAT_MS);
|
|
3136
3212
|
});
|
|
3137
3213
|
ws.addEventListener("message", (e) => {
|
|
3214
|
+
if (gone) return;
|
|
3138
3215
|
let m;
|
|
3139
3216
|
try {
|
|
3140
3217
|
m = JSON.parse(String(e.data));
|
|
3141
3218
|
} catch {
|
|
3142
3219
|
return;
|
|
3143
3220
|
}
|
|
3144
|
-
if (m.
|
|
3145
|
-
|
|
3221
|
+
if (m.topic === "phoenix") {
|
|
3222
|
+
if (m.event === "phx_reply" && m.ref === line.beat) line.beat = void 0;
|
|
3223
|
+
return;
|
|
3224
|
+
}
|
|
3225
|
+
if (!m.topic?.startsWith("realtime:")) return;
|
|
3226
|
+
const name = m.topic.slice("realtime:".length);
|
|
3227
|
+
const t = line.topics.get(name);
|
|
3228
|
+
if (!t) return;
|
|
3229
|
+
if (m.event === "phx_reply") {
|
|
3230
|
+
if (!t.ref || m.ref !== t.ref) return;
|
|
3231
|
+
if (m.payload?.status === "ok") joined(t);
|
|
3232
|
+
else rejoin(line, name, t);
|
|
3233
|
+
return;
|
|
3234
|
+
}
|
|
3235
|
+
if (m.event === "phx_error" || m.event === "phx_close") {
|
|
3236
|
+
if (m.ref && m.ref !== t.ref) return;
|
|
3237
|
+
rejoin(line, name, t);
|
|
3238
|
+
return;
|
|
3239
|
+
}
|
|
3240
|
+
if (m.event !== "broadcast") return;
|
|
3241
|
+
for (const l of t.listeners) {
|
|
3146
3242
|
if (m.payload?.event === l.event) l.onMessage(m.payload.payload);
|
|
3147
3243
|
}
|
|
3148
3244
|
});
|
|
3149
|
-
let gone = false;
|
|
3150
|
-
const again = () => {
|
|
3151
|
-
if (gone) return;
|
|
3152
|
-
gone = true;
|
|
3153
|
-
stop(line);
|
|
3154
|
-
if (!line.topics.size) return;
|
|
3155
|
-
line.retry = setTimeout(() => connect(line), RETRY_MS[Math.min(line.attempt++, RETRY_MS.length - 1)]);
|
|
3156
|
-
};
|
|
3157
3245
|
ws.addEventListener("close", again);
|
|
3158
3246
|
ws.addEventListener("error", again);
|
|
3159
3247
|
}
|
|
@@ -3161,17 +3249,22 @@ function subscribeRealtime(args) {
|
|
|
3161
3249
|
const key = `${args.url} ${args.anonKey}`;
|
|
3162
3250
|
let line = lines.get(key);
|
|
3163
3251
|
if (!line) lines.set(key, line = { url: args.url, anonKey: args.anonKey, open: false, attempt: 0, ref: 0, topics: /* @__PURE__ */ new Map() });
|
|
3164
|
-
let
|
|
3165
|
-
if (!
|
|
3166
|
-
line.topics.set(args.topic,
|
|
3167
|
-
if (line.open)
|
|
3168
|
-
}
|
|
3169
|
-
const
|
|
3170
|
-
|
|
3252
|
+
let t = line.topics.get(args.topic);
|
|
3253
|
+
if (!t) {
|
|
3254
|
+
line.topics.set(args.topic, t = { listeners: /* @__PURE__ */ new Set(), joined: false, attempt: 0 });
|
|
3255
|
+
if (line.open) join3(line, args.topic, t);
|
|
3256
|
+
}
|
|
3257
|
+
const topic = t;
|
|
3258
|
+
const me = { event: args.event, onMessage: args.onMessage, onJoined: args.onJoined, joins: 0 };
|
|
3259
|
+
topic.listeners.add(me);
|
|
3260
|
+
if (topic.joined && me.onJoined) queueMicrotask(() => {
|
|
3261
|
+
if (topic.joined && topic.listeners.has(me)) me.onJoined?.(++me.joins);
|
|
3262
|
+
});
|
|
3171
3263
|
if (!line.ws) connect(line);
|
|
3172
3264
|
return {
|
|
3173
3265
|
close: () => {
|
|
3174
|
-
if (!
|
|
3266
|
+
if (!topic.listeners.delete(me) || topic.listeners.size) return;
|
|
3267
|
+
clearTimeout(topic.timer);
|
|
3175
3268
|
line.topics.delete(args.topic);
|
|
3176
3269
|
if (line.topics.size) {
|
|
3177
3270
|
if (line.open) frame(line, `realtime:${args.topic}`, "phx_leave", {});
|
|
@@ -3187,7 +3280,7 @@ function subscribeRealtime(args) {
|
|
|
3187
3280
|
};
|
|
3188
3281
|
}
|
|
3189
3282
|
async function subscribeWake(onNudge, opts = {}) {
|
|
3190
|
-
const { release = true, ...client } = opts;
|
|
3283
|
+
const { release = true, onJoined, ...client } = opts;
|
|
3191
3284
|
const cfg = await registerDelivery("self_hosted", client);
|
|
3192
3285
|
const { url, anonKey } = cfg.realtime;
|
|
3193
3286
|
const channel = wakeChannel(cfg.tokenId);
|
|
@@ -3196,7 +3289,8 @@ async function subscribeWake(onNudge, opts = {}) {
|
|
|
3196
3289
|
anonKey,
|
|
3197
3290
|
topic: channel,
|
|
3198
3291
|
event: WAKE_EVENT,
|
|
3199
|
-
onMessage: (payload) => onNudge(payload ?? {})
|
|
3292
|
+
onMessage: (payload) => onNudge(payload ?? {}),
|
|
3293
|
+
onJoined
|
|
3200
3294
|
});
|
|
3201
3295
|
return {
|
|
3202
3296
|
tokenId: cfg.tokenId,
|
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-7VLZIOLF.js";
|
|
7
|
+
import "./chunk-MWHYYLCE.js";
|
|
8
|
+
import "./chunk-V5AQASR7.js";
|
|
9
9
|
|
|
10
10
|
// src/enable.ts
|
|
11
11
|
function main() {
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
resolvePairing,
|
|
6
6
|
startPairing,
|
|
7
7
|
suggestedAgentName
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-UIZLBPRG.js";
|
|
9
9
|
import {
|
|
10
10
|
ENABLE_COMMAND,
|
|
11
11
|
OnboardSchema,
|
|
@@ -14,11 +14,11 @@ import {
|
|
|
14
14
|
SERVER_INSTRUCTIONS,
|
|
15
15
|
TOOLS,
|
|
16
16
|
paigyToolsAllowlisted
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-7VLZIOLF.js";
|
|
18
18
|
import {
|
|
19
19
|
decideListen,
|
|
20
20
|
listenerAlive
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-MWHYYLCE.js";
|
|
22
22
|
import {
|
|
23
23
|
clearSurface,
|
|
24
24
|
writeSurface
|
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
setIdentity,
|
|
40
40
|
slotName,
|
|
41
41
|
whoAmI
|
|
42
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-V5AQASR7.js";
|
|
43
43
|
|
|
44
44
|
// src/index.ts
|
|
45
45
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
package/dist/listen.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
entryWords,
|
|
4
4
|
removeListenMark,
|
|
5
5
|
writeListenMark
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-MWHYYLCE.js";
|
|
7
7
|
import {
|
|
8
8
|
agentName,
|
|
9
9
|
checkReplies,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
getGoal,
|
|
12
12
|
slotName,
|
|
13
13
|
subscribeWake
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-V5AQASR7.js";
|
|
15
15
|
|
|
16
16
|
// src/listen.ts
|
|
17
17
|
import { spawn } from "child_process";
|
|
@@ -133,11 +133,16 @@ function leadOf(deliveries, goalId) {
|
|
|
133
133
|
function brief(waiting, reason, as, goalId) {
|
|
134
134
|
const lead = leadOf(waiting.deliveries, goalId);
|
|
135
135
|
const stalled = waiting.stalled?.length ? ` \xB7 ${waiting.stalled.length} of your Goals stalled 3+ days: update, finish or cancel each` : "";
|
|
136
|
-
|
|
136
|
+
const review = waiting.review ?? [];
|
|
137
|
+
const named = (goalId ? review.find((g) => g.goalId === goalId) : void 0) ?? review[0];
|
|
138
|
+
const fresh = review.length ? ` \xB7 new on ${review.length === 1 ? "your Goal" : `${review.length} of your Goals`}, first ${named.goalId} ("${named.title}"): claim_goal to read it` : "";
|
|
139
|
+
const assigned = waiting.assigned ?? [];
|
|
140
|
+
const mine = assigned.length ? ` \xB7 assigned to you: ${assigned.length === 1 ? "1 Goal" : `${assigned.length} Goals`}, first ${assigned[0].goalId} ("${assigned[0].title}"): claim_goal({goalId}) to start it` : "";
|
|
141
|
+
if (!lead) return `Paigy \xB7 ${reason === "boot" ? `listening as ${as}` : reason}${mine}${fresh || (mine ? "" : " \xB7 nothing waiting")}${stalled}`;
|
|
137
142
|
const said = saidIn(lead) || ("about" in lead ? lead.about : "");
|
|
138
143
|
const shown = said.length > 160 ? `${said.slice(0, 160)}\u2026` : said;
|
|
139
144
|
const goal = waiting.goal?.goalId ?? lead.goalIds[0];
|
|
140
|
-
return `Paigy \xB7 ${reason} \xB7 ${waiting.deliveries.length} waiting \xB7 lead ${lead.deliveryId} on Goal ${goal}: "${shown}"${stalled}`;
|
|
145
|
+
return `Paigy \xB7 ${reason} \xB7 ${waiting.deliveries.length} waiting \xB7 lead ${lead.deliveryId} on Goal ${goal}: "${shown}"${mine}${fresh}${stalled}`;
|
|
141
146
|
}
|
|
142
147
|
function launchEnv(waiting, reason, goalId) {
|
|
143
148
|
const env = { PAIGY_WORK: JSON.stringify(waiting), PAIGY_EVENT: reason };
|
|
@@ -154,16 +159,16 @@ function launchEnv(waiting, reason, goalId) {
|
|
|
154
159
|
async function sweep(reason, goalId) {
|
|
155
160
|
try {
|
|
156
161
|
const cmd = process.env.PAIGY_ON_WAKE;
|
|
157
|
-
const { deliveries: rows, stalled } = await checkReplies();
|
|
162
|
+
const { deliveries: rows, stalled, review, assigned } = await checkReplies();
|
|
158
163
|
const lead = leadOf(rows, goalId);
|
|
159
|
-
const full = lead ? await contact({ deliveryId: lead.deliveryId }, { waits: false }).then((d) => "
|
|
164
|
+
const full = lead ? await contact({ deliveryId: lead.deliveryId }, { waits: false }).then((d) => "sent" in d ? null : d).catch(() => null) : null;
|
|
160
165
|
const deliveries = rows.map((d) => d === lead && full ? full : d);
|
|
161
166
|
const leadGoalId = lead?.goalIds[0];
|
|
162
167
|
const goal = leadGoalId ? await getGoal(leadGoalId).then((g) => ({ goalId: leadGoalId, progress: g.progress })).catch(() => null) : null;
|
|
163
|
-
const waiting = { deliveries, goal, ...stalled?.length ? { stalled } : {} };
|
|
168
|
+
const waiting = { deliveries, goal, ...stalled?.length ? { stalled } : {}, ...review?.length ? { review } : {}, ...assigned?.length ? { assigned } : {} };
|
|
164
169
|
if (BRIEF) line(brief(waiting, reason, listeningAs(), goalId));
|
|
165
170
|
else emit({ type: "sweep", reason, ...goalId ? { about: goalId } : {}, ...waiting });
|
|
166
|
-
if (cmd && (deliveries.length || reason === "boot" && waiting.stalled?.length)) {
|
|
171
|
+
if (cmd && (deliveries.length || waiting.review?.length || waiting.assigned?.length || reason === "boot" && waiting.stalled?.length)) {
|
|
167
172
|
spawn(cmd, { shell: true, stdio: "inherit", env: { ...process.env, ...launchEnv(waiting, reason, goalId) } });
|
|
168
173
|
}
|
|
169
174
|
} catch (e) {
|
|
@@ -171,8 +176,17 @@ async function sweep(reason, goalId) {
|
|
|
171
176
|
else emit({ type: "error", reason, message: e.message });
|
|
172
177
|
}
|
|
173
178
|
}
|
|
179
|
+
function catchUpReason(joins) {
|
|
180
|
+
return joins > 1 ? "resubscribed" : null;
|
|
181
|
+
}
|
|
174
182
|
async function main() {
|
|
175
|
-
const sub = await subscribeWake((nudge) => void sweep(`wake:${nudge.kind ?? "?"}`, nudge.parentId), {
|
|
183
|
+
const sub = await subscribeWake((nudge) => void sweep(`wake:${nudge.kind ?? "?"}`, nudge.parentId), {
|
|
184
|
+
release: RELEASE,
|
|
185
|
+
onJoined: (joins) => {
|
|
186
|
+
const reason = catchUpReason(joins);
|
|
187
|
+
if (reason) void sweep(reason);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
176
190
|
const slot = agentName();
|
|
177
191
|
writeListenMark(slot, process.pid);
|
|
178
192
|
process.on("exit", () => removeListenMark(slot));
|
|
@@ -211,6 +225,7 @@ if (isEntry()) {
|
|
|
211
225
|
}
|
|
212
226
|
export {
|
|
213
227
|
brief,
|
|
228
|
+
catchUpReason,
|
|
214
229
|
launchEnv,
|
|
215
230
|
leadOf,
|
|
216
231
|
listenOptions,
|
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-UIZLBPRG.js";
|
|
7
7
|
import {
|
|
8
8
|
autoConfigureClients,
|
|
9
9
|
claudeInstallHint,
|
|
10
10
|
openBrowser
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import "./chunk-
|
|
11
|
+
} from "./chunk-7VLZIOLF.js";
|
|
12
|
+
import "./chunk-MWHYYLCE.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-V5AQASR7.js";
|
|
24
24
|
|
|
25
25
|
// src/onboard.ts
|
|
26
26
|
function reportRegistered() {
|
package/dist/slot.js
CHANGED
package/dist/stalled.js
CHANGED
|
@@ -22,7 +22,7 @@ async function main() {
|
|
|
22
22
|
const dir = join(homedir(), ".paigy", "stalled-reminded");
|
|
23
23
|
const mark = join(dir, `${session || "session"}-${day}`);
|
|
24
24
|
if (existsSync(mark)) return;
|
|
25
|
-
const { checkReplies } = await import("./dist-
|
|
25
|
+
const { checkReplies } = await import("./dist-PJLF3B27.js");
|
|
26
26
|
const reply = stopReply((await checkReplies()).stalled ?? []);
|
|
27
27
|
if (!reply) return;
|
|
28
28
|
mkdirSync(dir, { recursive: true });
|
package/dist/statusline.js
CHANGED
package/package.json
CHANGED