@paigy/mcp 0.25.2 → 0.26.0
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-2374V6WK.js → chunk-B7SCZUYX.js} +123 -8
- package/dist/{chunk-FEAIZ6DA.js → chunk-BZPE2ZFE.js} +1 -1
- package/dist/{chunk-LUY5MXDM.js → chunk-KAEJRPN5.js} +83 -6
- package/dist/index.js +5 -5
- package/dist/listen.js +2 -2
- package/dist/onboard.js +2 -2
- package/dist/statusline.js +1 -1
- package/package.json +3 -3
|
@@ -2277,7 +2277,7 @@ async function reach(url, init) {
|
|
|
2277
2277
|
var TITLE_MAX = 90;
|
|
2278
2278
|
var CHUNKS_MAX = 8;
|
|
2279
2279
|
var CHUNK_MAX = 300;
|
|
2280
|
-
var ASK_MAX =
|
|
2280
|
+
var ASK_MAX = 1e4;
|
|
2281
2281
|
var OPTION_MAX = 80;
|
|
2282
2282
|
var NEEDS_MAX = 6;
|
|
2283
2283
|
var UNSPEAKABLE = /```|\n/;
|
|
@@ -2438,8 +2438,13 @@ var NotifyRequestSchema = z.object({
|
|
|
2438
2438
|
* matters now — the broker derives the optimal shape and channel. Mutually
|
|
2439
2439
|
* exclusive with `context` (and never sent alongside `envelope`: E2EE pairings
|
|
2440
2440
|
* derive agent-side before sealing, so the server only ever shapes plaintext). */
|
|
2441
|
-
|
|
2442
|
-
|
|
2441
|
+
// 10k, not a sentence budget. What the human hears is bounded by the BROKER — it splits
|
|
2442
|
+
// the ask into topics and gives each one at most three sentences and one question
|
|
2443
|
+
// (broker/agenda-design.md) — not by a wire cap the agent has to pre-summarize under.
|
|
2444
|
+
// Owner, 2026-07-28: "our actual limitation on how long something is to the user should
|
|
2445
|
+
// come from the broker splitting and summarizing." The cap that remains is a size guard.
|
|
2446
|
+
ask: z.string().min(1).max(1e4).optional().describe(
|
|
2447
|
+
'SIMPLIFIED FORM \u2014 state in plain prose what you need to learn from the user and why it matters now (e.g. "I need to know whether to deploy the auth fix \u2014 tests are green, staging verified"). Write as much as the situation needs (up to 10k characters) \u2014 Paigy breaks it into topics and reads it back a few sentences at a time; do NOT pre-summarize it into one line. Paigy derives the title, answer shape, options, and delivery channel for you. Mutually exclusive with context/select/options \u2014 send one form or the other.'
|
|
2443
2448
|
),
|
|
2444
2449
|
needs: z.array(z.string().min(1)).optional().describe(
|
|
2445
2450
|
"With `ask` only: the distinct things you need answered when the ask is multi-part \u2014 becomes the coverage contract (`points`), so a half-answer is never silently final."
|
|
@@ -2512,6 +2517,23 @@ function normalizeWaiting(req) {
|
|
|
2512
2517
|
blocking: req.blocking || waiting === "hard"
|
|
2513
2518
|
};
|
|
2514
2519
|
}
|
|
2520
|
+
var DERIVE_CHUNKS_MAX = 8;
|
|
2521
|
+
var DERIVE_CHUNK_MAX = 300;
|
|
2522
|
+
function chunkAsk(text, max = DERIVE_CHUNK_MAX, cap = DERIVE_CHUNKS_MAX) {
|
|
2523
|
+
const body = text.trim();
|
|
2524
|
+
if (!body) return [];
|
|
2525
|
+
const sentences = body.match(/[^.!?]+[.!?]*\s*/g) ?? [body];
|
|
2526
|
+
const chunks = [];
|
|
2527
|
+
for (const raw of sentences) {
|
|
2528
|
+
const s = raw.trim();
|
|
2529
|
+
if (!s) continue;
|
|
2530
|
+
const last = chunks[chunks.length - 1];
|
|
2531
|
+
if (last !== void 0 && `${last} ${s}`.length <= max) chunks[chunks.length - 1] = `${last} ${s}`;
|
|
2532
|
+
else chunks.push(s);
|
|
2533
|
+
}
|
|
2534
|
+
if (chunks.length <= cap) return chunks;
|
|
2535
|
+
return [...chunks.slice(0, cap - 1), chunks.slice(cap - 1).join(" ")];
|
|
2536
|
+
}
|
|
2515
2537
|
function deriveAsk(req) {
|
|
2516
2538
|
req = normalizeWaiting(req);
|
|
2517
2539
|
if (!req.ask) return req;
|
|
@@ -2523,7 +2545,7 @@ function deriveAsk(req) {
|
|
|
2523
2545
|
const { ask: _ask, needs, urgencyHint: _hint, channel: _channel, ...rest } = req;
|
|
2524
2546
|
return {
|
|
2525
2547
|
...rest,
|
|
2526
|
-
context: { title, description:
|
|
2548
|
+
context: { title, description: chunkAsk(text) },
|
|
2527
2549
|
// Options riding alongside the ask (#575: pixels can't be prose) floor to a
|
|
2528
2550
|
// single pick — the model broker may upgrade to many/rank from the wording.
|
|
2529
2551
|
select: req.options?.length ? "one" : "text",
|
|
@@ -2598,6 +2620,22 @@ var AwaitItemSchema = z.discriminatedUnion("type", [
|
|
|
2598
2620
|
threadId: z.string(),
|
|
2599
2621
|
notificationId: z.string()
|
|
2600
2622
|
}),
|
|
2623
|
+
/** A LIVE call's turn, streamed as it lands (#783). PROVISIONAL: the user can still
|
|
2624
|
+
* revise any of these until the final reply arrives — partial = intelligence,
|
|
2625
|
+
* settled = authorization. Use it to PREPARE (fetch, draft, warm), never to act
|
|
2626
|
+
* irreversibly. If `acts` carries a question aimed at you and you know the answer,
|
|
2627
|
+
* contact on the same thread right away — the caller hears it on the same call. */
|
|
2628
|
+
z.object({
|
|
2629
|
+
type: z.literal("partial"),
|
|
2630
|
+
notificationId: z.string(),
|
|
2631
|
+
inFlight: z.literal(true),
|
|
2632
|
+
turn: z.object({
|
|
2633
|
+
idx: z.number(),
|
|
2634
|
+
prompt: z.string(),
|
|
2635
|
+
reply: z.string(),
|
|
2636
|
+
acts: z.array(IntentSchema).nullable().optional()
|
|
2637
|
+
})
|
|
2638
|
+
}),
|
|
2601
2639
|
z.object({ type: z.literal("idle") })
|
|
2602
2640
|
]);
|
|
2603
2641
|
var CallbackTriggerSchema = z.enum(["on_done", "on_blocked", "scheduled"]);
|
|
@@ -2698,6 +2736,16 @@ var UserResponseSchema = z.object({
|
|
|
2698
2736
|
covered: z.array(z.string()).optional()
|
|
2699
2737
|
});
|
|
2700
2738
|
var VoiceKeySchema = z.enum(["rachel", "george", "jessica", "brian", "lily"]);
|
|
2739
|
+
var AgendaTurnSchema = z.object({
|
|
2740
|
+
/** At most three short spoken sentences. Capped because a turn is a breath: a 1031-char
|
|
2741
|
+
* line went out on 2026-07-28 and the caller could not answer it at all. */
|
|
2742
|
+
info: z.array(z.string().min(1)).max(3).default([]),
|
|
2743
|
+
question: z.string().min(1).nullable(),
|
|
2744
|
+
/** True on the one turn carrying the agent's own declared question. */
|
|
2745
|
+
asks: z.boolean().optional(),
|
|
2746
|
+
select: SelectShapeSchema.optional(),
|
|
2747
|
+
options: z.array(OptionSchema.omit({ id: true })).optional()
|
|
2748
|
+
});
|
|
2701
2749
|
var InboxItemSchema = z.object({
|
|
2702
2750
|
id: z.string(),
|
|
2703
2751
|
/** The conversation thread + connection this item lives on. Present on the replied
|
|
@@ -2709,9 +2757,11 @@ var InboxItemSchema = z.object({
|
|
|
2709
2757
|
options: z.array(OptionSchema).optional(),
|
|
2710
2758
|
/** The ask's declared coverage points (#396), when the agent sent them. */
|
|
2711
2759
|
points: z.array(z.string()).optional(),
|
|
2712
|
-
/**
|
|
2713
|
-
*
|
|
2714
|
-
|
|
2760
|
+
/** The call's AGENDA (broker/agenda-design.md): the ordered turns it is made of, built at
|
|
2761
|
+
* ring/enqueue time. Replaces the condensed line + index-aligned phrased points, which
|
|
2762
|
+
* between them could not express a call as a sequence. `question: null` is a real turn —
|
|
2763
|
+
* a status update stays a statement instead of being shaped into a yes/no. */
|
|
2764
|
+
agenda: z.array(AgendaTurnSchema).optional(),
|
|
2715
2765
|
/** On a replied detail (#397): the next steps the user attached to the answer
|
|
2716
2766
|
* ("call back after lunch") — shown so they can see the commitment was captured. */
|
|
2717
2767
|
intents: z.array(IntentSchema).optional(),
|
|
@@ -2812,7 +2862,6 @@ var UserSettingsSchema = z.object({
|
|
|
2812
2862
|
/** Per-user ring budget (#603): calls per rolling day before further calls
|
|
2813
2863
|
* degrade to banner. Absent = the global default (25). A number, never a
|
|
2814
2864
|
* bypass — every account keeps a ceiling. No UI; set per user for testing. */
|
|
2815
|
-
callBudget: z.number().int().min(1).max(500).optional(),
|
|
2816
2865
|
/** Per-user voice-call tuning (#318): raw knobs forwarded to the call bot's
|
|
2817
2866
|
* payload['tuning'] (e.g. { silence_s: 3.5 } — a longer pause window for a
|
|
2818
2867
|
* slower speaker). No API-side semantics; the bot resolves each key with its
|
|
@@ -2894,6 +2943,49 @@ var HandoffSchema = z.object({
|
|
|
2894
2943
|
* same primitive; a recap is one whose audience includes you. */
|
|
2895
2944
|
recap: z.boolean().optional()
|
|
2896
2945
|
});
|
|
2946
|
+
var NoteSourceSchema = z.enum(["app", "call"]);
|
|
2947
|
+
var NoteStatusSchema = z.enum(["open", "assigned", "done"]);
|
|
2948
|
+
var DecisionSchema = z.object({
|
|
2949
|
+
id: z.string(),
|
|
2950
|
+
/** The note this decision refines; null = recorded on a bare thread (the
|
|
2951
|
+
* extensibility seam — any conversation can accrue decisions). */
|
|
2952
|
+
noteId: z.string().nullable(),
|
|
2953
|
+
/** What was ambiguous — the broker's (or the user's own) question. */
|
|
2954
|
+
question: z.string(),
|
|
2955
|
+
/** The user's ruling; null while the question is open. */
|
|
2956
|
+
answer: z.string().nullable(),
|
|
2957
|
+
decidedAt: z.string().nullable(),
|
|
2958
|
+
createdAt: z.string()
|
|
2959
|
+
});
|
|
2960
|
+
var NoteSchema = z.object({
|
|
2961
|
+
id: z.string(),
|
|
2962
|
+
/** One-line headline (broker-titled; deterministic floor). */
|
|
2963
|
+
title: z.string(),
|
|
2964
|
+
/** The original intent, verbatim — assignees always see the user's own words. */
|
|
2965
|
+
intent: z.string(),
|
|
2966
|
+
source: NoteSourceSchema,
|
|
2967
|
+
status: NoteStatusSchema,
|
|
2968
|
+
/** Who it was assigned to (a participant ref, 'agent:<tokenId>'); null = unassigned. */
|
|
2969
|
+
assignee: z.string().nullable(),
|
|
2970
|
+
/** The request thread minted at assignment; null until assigned. */
|
|
2971
|
+
threadId: z.string().nullable(),
|
|
2972
|
+
createdAt: z.string()
|
|
2973
|
+
});
|
|
2974
|
+
var CreateNoteSchema = z.object({
|
|
2975
|
+
/** The intent, in the user's own words. Stored verbatim; the broker only titles it. */
|
|
2976
|
+
text: z.string().min(1).max(4e3)
|
|
2977
|
+
});
|
|
2978
|
+
var RecordDecisionSchema = z.object({
|
|
2979
|
+
/** An open decision (from /clarify) to answer. */
|
|
2980
|
+
decisionId: z.string().optional(),
|
|
2981
|
+
/** For an ad-hoc decision: what was ambiguous. Required without `decisionId`. */
|
|
2982
|
+
question: z.string().min(1).max(500).optional(),
|
|
2983
|
+
/** The ruling. */
|
|
2984
|
+
answer: z.string().min(1).max(2e3)
|
|
2985
|
+
}).refine((d) => d.decisionId || d.question, { message: "decisionId or question required" });
|
|
2986
|
+
var AssignNoteSchema = z.object({
|
|
2987
|
+
target: z.string().min(1)
|
|
2988
|
+
});
|
|
2897
2989
|
var DeliveryModeSchema = z.enum(["poll", "self_hosted"]);
|
|
2898
2990
|
var RegisterDeliverySchema = z.object({ mode: DeliveryModeSchema });
|
|
2899
2991
|
var OAuthStartSchema = z.object({
|
|
@@ -3043,6 +3135,8 @@ var NotificationFeedbackKindSchema = z.enum([
|
|
|
3043
3135
|
// "These recommended answers aren't good — give me new ones."
|
|
3044
3136
|
"needs_visual",
|
|
3045
3137
|
// "There should be a picture or design here."
|
|
3138
|
+
"should_have_called",
|
|
3139
|
+
// "Don't put this in a banner — ring me for something like this."
|
|
3046
3140
|
"other"
|
|
3047
3141
|
// anything else — the note carries it.
|
|
3048
3142
|
]);
|
|
@@ -3645,6 +3739,25 @@ async function pollAnswer(notificationId) {
|
|
|
3645
3739
|
const item = await res.json();
|
|
3646
3740
|
return item.type === "idle" ? item : decryptItem(item);
|
|
3647
3741
|
}
|
|
3742
|
+
var _partialSeen = /* @__PURE__ */ new Map();
|
|
3743
|
+
async function pollPartials(notificationId) {
|
|
3744
|
+
const token = readToken();
|
|
3745
|
+
const after = _partialSeen.get(notificationId) ?? 0;
|
|
3746
|
+
try {
|
|
3747
|
+
const res = await reach(
|
|
3748
|
+
`${BACKEND_URL}/api/await/partials?notificationId=${encodeURIComponent(notificationId)}&afterIdx=${after}`,
|
|
3749
|
+
{ headers: { authorization: `Bearer ${token}` } }
|
|
3750
|
+
);
|
|
3751
|
+
if (!res.ok) return null;
|
|
3752
|
+
const body = await res.json();
|
|
3753
|
+
const next = body.turns?.[0];
|
|
3754
|
+
if (!next) return null;
|
|
3755
|
+
_partialSeen.set(notificationId, next.idx);
|
|
3756
|
+
return { type: "partial", notificationId, inFlight: true, turn: next };
|
|
3757
|
+
} catch {
|
|
3758
|
+
return null;
|
|
3759
|
+
}
|
|
3760
|
+
}
|
|
3648
3761
|
async function awaitReply(notificationId, opts = {}) {
|
|
3649
3762
|
const intervalMs = opts.intervalMs ?? 5e3;
|
|
3650
3763
|
const windowMs = opts.windowMs ?? 3e5;
|
|
@@ -3654,6 +3767,8 @@ async function awaitReply(notificationId, opts = {}) {
|
|
|
3654
3767
|
while (true) {
|
|
3655
3768
|
const item = await pollAnswer(notificationId);
|
|
3656
3769
|
if (item.type !== "idle") return item;
|
|
3770
|
+
const partial = await pollPartials(notificationId);
|
|
3771
|
+
if (partial) return partial;
|
|
3657
3772
|
if (now() - start >= windowMs) return { type: "idle" };
|
|
3658
3773
|
await doSleep(intervalMs);
|
|
3659
3774
|
}
|
|
@@ -122,8 +122,13 @@ var NotifyRequestSchema = z.object({
|
|
|
122
122
|
* matters now — the broker derives the optimal shape and channel. Mutually
|
|
123
123
|
* exclusive with `context` (and never sent alongside `envelope`: E2EE pairings
|
|
124
124
|
* derive agent-side before sealing, so the server only ever shapes plaintext). */
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
// 10k, not a sentence budget. What the human hears is bounded by the BROKER — it splits
|
|
126
|
+
// the ask into topics and gives each one at most three sentences and one question
|
|
127
|
+
// (broker/agenda-design.md) — not by a wire cap the agent has to pre-summarize under.
|
|
128
|
+
// Owner, 2026-07-28: "our actual limitation on how long something is to the user should
|
|
129
|
+
// come from the broker splitting and summarizing." The cap that remains is a size guard.
|
|
130
|
+
ask: z.string().min(1).max(1e4).optional().describe(
|
|
131
|
+
'SIMPLIFIED FORM \u2014 state in plain prose what you need to learn from the user and why it matters now (e.g. "I need to know whether to deploy the auth fix \u2014 tests are green, staging verified"). Write as much as the situation needs (up to 10k characters) \u2014 Paigy breaks it into topics and reads it back a few sentences at a time; do NOT pre-summarize it into one line. Paigy derives the title, answer shape, options, and delivery channel for you. Mutually exclusive with context/select/options \u2014 send one form or the other.'
|
|
127
132
|
),
|
|
128
133
|
needs: z.array(z.string().min(1)).optional().describe(
|
|
129
134
|
"With `ask` only: the distinct things you need answered when the ask is multi-part \u2014 becomes the coverage contract (`points`), so a half-answer is never silently final."
|
|
@@ -254,6 +259,22 @@ var AwaitItemSchema = z.discriminatedUnion("type", [
|
|
|
254
259
|
threadId: z.string(),
|
|
255
260
|
notificationId: z.string()
|
|
256
261
|
}),
|
|
262
|
+
/** A LIVE call's turn, streamed as it lands (#783). PROVISIONAL: the user can still
|
|
263
|
+
* revise any of these until the final reply arrives — partial = intelligence,
|
|
264
|
+
* settled = authorization. Use it to PREPARE (fetch, draft, warm), never to act
|
|
265
|
+
* irreversibly. If `acts` carries a question aimed at you and you know the answer,
|
|
266
|
+
* contact on the same thread right away — the caller hears it on the same call. */
|
|
267
|
+
z.object({
|
|
268
|
+
type: z.literal("partial"),
|
|
269
|
+
notificationId: z.string(),
|
|
270
|
+
inFlight: z.literal(true),
|
|
271
|
+
turn: z.object({
|
|
272
|
+
idx: z.number(),
|
|
273
|
+
prompt: z.string(),
|
|
274
|
+
reply: z.string(),
|
|
275
|
+
acts: z.array(IntentSchema).nullable().optional()
|
|
276
|
+
})
|
|
277
|
+
}),
|
|
257
278
|
z.object({ type: z.literal("idle") })
|
|
258
279
|
]);
|
|
259
280
|
var CallbackTriggerSchema = z.enum(["on_done", "on_blocked", "scheduled"]);
|
|
@@ -354,6 +375,16 @@ var UserResponseSchema = z.object({
|
|
|
354
375
|
covered: z.array(z.string()).optional()
|
|
355
376
|
});
|
|
356
377
|
var VoiceKeySchema = z.enum(["rachel", "george", "jessica", "brian", "lily"]);
|
|
378
|
+
var AgendaTurnSchema = z.object({
|
|
379
|
+
/** At most three short spoken sentences. Capped because a turn is a breath: a 1031-char
|
|
380
|
+
* line went out on 2026-07-28 and the caller could not answer it at all. */
|
|
381
|
+
info: z.array(z.string().min(1)).max(3).default([]),
|
|
382
|
+
question: z.string().min(1).nullable(),
|
|
383
|
+
/** True on the one turn carrying the agent's own declared question. */
|
|
384
|
+
asks: z.boolean().optional(),
|
|
385
|
+
select: SelectShapeSchema.optional(),
|
|
386
|
+
options: z.array(OptionSchema.omit({ id: true })).optional()
|
|
387
|
+
});
|
|
357
388
|
var InboxItemSchema = z.object({
|
|
358
389
|
id: z.string(),
|
|
359
390
|
/** The conversation thread + connection this item lives on. Present on the replied
|
|
@@ -365,9 +396,11 @@ var InboxItemSchema = z.object({
|
|
|
365
396
|
options: z.array(OptionSchema).optional(),
|
|
366
397
|
/** The ask's declared coverage points (#396), when the agent sent them. */
|
|
367
398
|
points: z.array(z.string()).optional(),
|
|
368
|
-
/**
|
|
369
|
-
*
|
|
370
|
-
|
|
399
|
+
/** The call's AGENDA (broker/agenda-design.md): the ordered turns it is made of, built at
|
|
400
|
+
* ring/enqueue time. Replaces the condensed line + index-aligned phrased points, which
|
|
401
|
+
* between them could not express a call as a sequence. `question: null` is a real turn —
|
|
402
|
+
* a status update stays a statement instead of being shaped into a yes/no. */
|
|
403
|
+
agenda: z.array(AgendaTurnSchema).optional(),
|
|
371
404
|
/** On a replied detail (#397): the next steps the user attached to the answer
|
|
372
405
|
* ("call back after lunch") — shown so they can see the commitment was captured. */
|
|
373
406
|
intents: z.array(IntentSchema).optional(),
|
|
@@ -468,7 +501,6 @@ var UserSettingsSchema = z.object({
|
|
|
468
501
|
/** Per-user ring budget (#603): calls per rolling day before further calls
|
|
469
502
|
* degrade to banner. Absent = the global default (25). A number, never a
|
|
470
503
|
* bypass — every account keeps a ceiling. No UI; set per user for testing. */
|
|
471
|
-
callBudget: z.number().int().min(1).max(500).optional(),
|
|
472
504
|
/** Per-user voice-call tuning (#318): raw knobs forwarded to the call bot's
|
|
473
505
|
* payload['tuning'] (e.g. { silence_s: 3.5 } — a longer pause window for a
|
|
474
506
|
* slower speaker). No API-side semantics; the bot resolves each key with its
|
|
@@ -550,6 +582,49 @@ var HandoffSchema = z.object({
|
|
|
550
582
|
* same primitive; a recap is one whose audience includes you. */
|
|
551
583
|
recap: z.boolean().optional()
|
|
552
584
|
});
|
|
585
|
+
var NoteSourceSchema = z.enum(["app", "call"]);
|
|
586
|
+
var NoteStatusSchema = z.enum(["open", "assigned", "done"]);
|
|
587
|
+
var DecisionSchema = z.object({
|
|
588
|
+
id: z.string(),
|
|
589
|
+
/** The note this decision refines; null = recorded on a bare thread (the
|
|
590
|
+
* extensibility seam — any conversation can accrue decisions). */
|
|
591
|
+
noteId: z.string().nullable(),
|
|
592
|
+
/** What was ambiguous — the broker's (or the user's own) question. */
|
|
593
|
+
question: z.string(),
|
|
594
|
+
/** The user's ruling; null while the question is open. */
|
|
595
|
+
answer: z.string().nullable(),
|
|
596
|
+
decidedAt: z.string().nullable(),
|
|
597
|
+
createdAt: z.string()
|
|
598
|
+
});
|
|
599
|
+
var NoteSchema = z.object({
|
|
600
|
+
id: z.string(),
|
|
601
|
+
/** One-line headline (broker-titled; deterministic floor). */
|
|
602
|
+
title: z.string(),
|
|
603
|
+
/** The original intent, verbatim — assignees always see the user's own words. */
|
|
604
|
+
intent: z.string(),
|
|
605
|
+
source: NoteSourceSchema,
|
|
606
|
+
status: NoteStatusSchema,
|
|
607
|
+
/** Who it was assigned to (a participant ref, 'agent:<tokenId>'); null = unassigned. */
|
|
608
|
+
assignee: z.string().nullable(),
|
|
609
|
+
/** The request thread minted at assignment; null until assigned. */
|
|
610
|
+
threadId: z.string().nullable(),
|
|
611
|
+
createdAt: z.string()
|
|
612
|
+
});
|
|
613
|
+
var CreateNoteSchema = z.object({
|
|
614
|
+
/** The intent, in the user's own words. Stored verbatim; the broker only titles it. */
|
|
615
|
+
text: z.string().min(1).max(4e3)
|
|
616
|
+
});
|
|
617
|
+
var RecordDecisionSchema = z.object({
|
|
618
|
+
/** An open decision (from /clarify) to answer. */
|
|
619
|
+
decisionId: z.string().optional(),
|
|
620
|
+
/** For an ad-hoc decision: what was ambiguous. Required without `decisionId`. */
|
|
621
|
+
question: z.string().min(1).max(500).optional(),
|
|
622
|
+
/** The ruling. */
|
|
623
|
+
answer: z.string().min(1).max(2e3)
|
|
624
|
+
}).refine((d) => d.decisionId || d.question, { message: "decisionId or question required" });
|
|
625
|
+
var AssignNoteSchema = z.object({
|
|
626
|
+
target: z.string().min(1)
|
|
627
|
+
});
|
|
553
628
|
var DeliveryModeSchema = z.enum(["poll", "self_hosted"]);
|
|
554
629
|
var WAKE_EVENT = "wake";
|
|
555
630
|
var wakeChannel = (tokenId) => `wake:${tokenId}`;
|
|
@@ -701,6 +776,8 @@ var NotificationFeedbackKindSchema = z.enum([
|
|
|
701
776
|
// "These recommended answers aren't good — give me new ones."
|
|
702
777
|
"needs_visual",
|
|
703
778
|
// "There should be a picture or design here."
|
|
779
|
+
"should_have_called",
|
|
780
|
+
// "Don't put this in a banner — ring me for something like this."
|
|
704
781
|
"other"
|
|
705
782
|
// anything else — the note carries it.
|
|
706
783
|
]);
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
HandoffSchema
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-KAEJRPN5.js";
|
|
5
5
|
import {
|
|
6
6
|
PAIGY_TOOL_IDS,
|
|
7
7
|
autoConfigureClients,
|
|
8
8
|
claudeInstallHint,
|
|
9
9
|
enablePaigyTools
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-BZPE2ZFE.js";
|
|
11
11
|
import {
|
|
12
12
|
clearSurface,
|
|
13
13
|
writeSurface
|
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
sleep,
|
|
40
40
|
startE2ee,
|
|
41
41
|
submitNotification
|
|
42
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-B7SCZUYX.js";
|
|
43
43
|
|
|
44
44
|
// src/index.ts
|
|
45
45
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -83,7 +83,7 @@ var CONTACT_SCHEMA = {
|
|
|
83
83
|
properties: {
|
|
84
84
|
ask: {
|
|
85
85
|
type: "string",
|
|
86
|
-
description: "What to tell the user, or what you need to find out from them. Plain prose
|
|
86
|
+
description: "What to tell the user, or what you need to find out from them. Plain prose \u2014 as long as it needs to be (up to 10k characters); Paigy splits it into topics and reads back a few sentences at a time, so do NOT compress a briefing into one line. May be spoken aloud on a call, so write natural speech and name things (not IDs)."
|
|
87
87
|
},
|
|
88
88
|
waiting: {
|
|
89
89
|
type: "string",
|
|
@@ -381,7 +381,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
381
381
|
},
|
|
382
382
|
{
|
|
383
383
|
name: "await_reply",
|
|
384
|
-
description: "Wait for the user's reply to a specific notification you sent (pass the notificationId from contact). This is how you wait for your answer in-context. Polls ~5 min; returns { type:'reply', answer } when they respond, { type:'remind', remindInSeconds } on snooze (ScheduleWakeup then await_reply again), or { type:'idle' } (timed out this window, no answer yet). On idle, if this is genuinely still blocking you and you have nothing else useful to do meanwhile, just call await_reply again immediately \u2014 keep looping. This is how you actually deliver on the point of calling: the user steps away for a while and comes back to find you'd already continued the moment they answered, not idle waiting to be checked on. Don't give up after one window. Only stop looping to do other work (and check back later), or after an unreasonably long stretch (tens of minutes to hours) worth telling the user about instead. Scoped to that one notification \u2014 it NEVER returns replies meant for other notifications, so concurrent contact calls don't cross. A CALL answer can come back as {kind:'turns', turns:[{prompt,reply}]} \u2014 the ordered log of that call. Read turns[0].reply as the user's main instruction. Usually that's the only turn; if there are more (e.g. an end-of-call 'call me back when it's done / I have a blocking question'), read each one in order as a further follow-up instruction, not a single combined one. If they asked for a callback, re-engage in the SAME thread (contact with the reply's threadId) when the task is done or you hit a blocker \u2014 waiting:'hard' for a blocker, waiting:'none' for done. Paigy has no scheduler; the callback is yours to send (use ScheduleWakeup/cron for timing). A call-mapped answer may carry `intents` \u2014 next steps the user attached, each { kind, detail } with detail quoting their words. ACT on them, don't just read them: 'defer' (\"call me after lunch\") \u2192 register it NOW with schedule_callback \u2014 when the intent carries `dueInSeconds` (Paigy pre-parsed the spoken time against the user's clock) pass it straight through; otherwise derive it from the detail yourself \u2014 then follow up on the same thread; 'delegate' (\"you pick\") \u2192 make the call yourself and tell them what you chose; 'channel' (\"text me next time\") \u2192 honor it on your next contact (channel:'message'); 'question' (an open question aimed back at you that the call couldn't answer) \u2192 you OWE them the answer \u2014 work it out and follow up on the same thread without being asked, the call deliberately skipped \"should I call you back?\" because the follow-up is implied. `transcript` is the user's raw words behind a shaped answer \u2014 read it for hedges and conditions (\"yes, IF tests pass\") before acting. If your ask declared `points`, the reply carries `covered` \u2014 the points actually addressed. Compare against what you declared: a missing point is STILL unanswered \u2014 re-ask it (contact on the same threadId) or proceed knowingly partial; never treat a partial answer as complete.",
|
|
384
|
+
description: "Wait for the user's reply to a specific notification you sent (pass the notificationId from contact). This is how you wait for your answer in-context. Polls ~5 min; returns { type:'reply', answer } when they respond, { type:'remind', remindInSeconds } on snooze (ScheduleWakeup then await_reply again), or { type:'idle' } (timed out this window, no answer yet). While your contact is being handled on a LIVE call, you may receive { type:'partial', inFlight:true, turn } results: what the user said to each turn, as they say it. Use partials to PREPARE \u2014 fetch the data, draft the thing, warm the build \u2014 never to act irreversibly: the user can still revise any of them until the final reply arrives. Partial = intelligence, settled = authorization. If a partial's acts carry a question aimed at you and you know the answer, call contact on the SAME threadId right away \u2014 the caller hears your answer on the same call instead of waiting for a callback. Keep calling await_reply until you get the final reply \u2014 THAT one is the decision. On idle, if this is genuinely still blocking you and you have nothing else useful to do meanwhile, just call await_reply again immediately \u2014 keep looping. This is how you actually deliver on the point of calling: the user steps away for a while and comes back to find you'd already continued the moment they answered, not idle waiting to be checked on. Don't give up after one window. Only stop looping to do other work (and check back later), or after an unreasonably long stretch (tens of minutes to hours) worth telling the user about instead. Scoped to that one notification \u2014 it NEVER returns replies meant for other notifications, so concurrent contact calls don't cross. A CALL answer can come back as {kind:'turns', turns:[{prompt,reply}]} \u2014 the ordered log of that call. Read turns[0].reply as the user's main instruction. Usually that's the only turn; if there are more (e.g. an end-of-call 'call me back when it's done / I have a blocking question'), read each one in order as a further follow-up instruction, not a single combined one. If they asked for a callback, re-engage in the SAME thread (contact with the reply's threadId) when the task is done or you hit a blocker \u2014 waiting:'hard' for a blocker, waiting:'none' for done. Paigy has no scheduler; the callback is yours to send (use ScheduleWakeup/cron for timing). A call-mapped answer may carry `intents` \u2014 next steps the user attached, each { kind, detail } with detail quoting their words. ACT on them, don't just read them: 'defer' (\"call me after lunch\") \u2192 register it NOW with schedule_callback \u2014 when the intent carries `dueInSeconds` (Paigy pre-parsed the spoken time against the user's clock) pass it straight through; otherwise derive it from the detail yourself \u2014 then follow up on the same thread; 'delegate' (\"you pick\") \u2192 make the call yourself and tell them what you chose; 'channel' (\"text me next time\") \u2192 honor it on your next contact (channel:'message'); 'question' (an open question aimed back at you that the call couldn't answer) \u2192 you OWE them the answer \u2014 work it out and follow up on the same thread without being asked, the call deliberately skipped \"should I call you back?\" because the follow-up is implied. `transcript` is the user's raw words behind a shaped answer \u2014 read it for hedges and conditions (\"yes, IF tests pass\") before acting. If your ask declared `points`, the reply carries `covered` \u2014 the points actually addressed. Compare against what you declared: a missing point is STILL unanswered \u2014 re-ask it (contact on the same threadId) or proceed knowingly partial; never treat a partial answer as complete.",
|
|
385
385
|
inputSchema: json(AwaitReplySchema)
|
|
386
386
|
},
|
|
387
387
|
{
|
package/dist/listen.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import {
|
|
3
3
|
WAKE_EVENT,
|
|
4
4
|
wakeChannel
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-KAEJRPN5.js";
|
|
6
6
|
import {
|
|
7
7
|
checkReplies,
|
|
8
8
|
registerDelivery
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-B7SCZUYX.js";
|
|
10
10
|
|
|
11
11
|
// src/listen.ts
|
|
12
12
|
import { createClient } from "@supabase/supabase-js";
|
package/dist/onboard.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
autoConfigureClients,
|
|
4
4
|
claudeInstallHint,
|
|
5
5
|
openBrowser
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-BZPE2ZFE.js";
|
|
7
7
|
import {
|
|
8
8
|
AGENT_NAME,
|
|
9
9
|
TOKEN_PATH,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
requestCode,
|
|
12
12
|
saveToken,
|
|
13
13
|
sleep
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-B7SCZUYX.js";
|
|
15
15
|
|
|
16
16
|
// src/onboard.ts
|
|
17
17
|
async function main() {
|
package/dist/statusline.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@paigy/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Paigy MCP server — a voice inbox for your AI agents. Lets an agent notify a user and await their reply.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"tsup": "^8.3.5",
|
|
37
37
|
"typescript": "^5.7.2",
|
|
38
38
|
"vitest": "^2.1.8",
|
|
39
|
-
"@paigy/
|
|
39
|
+
"@paigy/sdk": "0.1.0",
|
|
40
40
|
"@paigy/schema": "0.0.0",
|
|
41
|
-
"@paigy/
|
|
41
|
+
"@paigy/crypto": "0.0.0"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsup",
|