@paigy/mcp 0.40.1 → 0.40.2
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 +25 -22
- package/dist/{chunk-TLASZUJD.js → chunk-647HS22P.js} +2 -2
- package/dist/{chunk-CXABA27N.js → chunk-BUTCP2ED.js} +1 -1
- package/dist/{chunk-3QUIB42M.js → chunk-FFI3AIZE.js} +113 -44
- package/dist/{chunk-4UZAPBKK.js → chunk-YJEJZT56.js} +125 -236
- package/dist/enable.js +3 -3
- package/dist/index.js +4 -4
- package/dist/listen.js +34 -102
- package/dist/onboard.js +4 -4
- package/dist/slot.js +1 -1
- package/dist/statusline.js +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -145,42 +145,48 @@ not a compatibility fallback for target MCP tools.
|
|
|
145
145
|
no extra setup. Two edges: with `HTTP_PROXY` set, an `http://localhost` backend
|
|
146
146
|
proxies too unless `NO_PROXY=localhost` (hostname entries work; CIDR ranges are
|
|
147
147
|
ignored), and `paigy-listen`'s realtime wake channel is a websocket that doesn't
|
|
148
|
-
proxy — its REST
|
|
148
|
+
proxy — its REST reads do.
|
|
149
149
|
|
|
150
150
|
## Wake any harness (`paigy-listen`)
|
|
151
151
|
|
|
152
152
|
`paigy-listen` is the self-hosted push daemon: it subscribes to this connection's
|
|
153
|
-
wake channel and
|
|
154
|
-
`paigy-listen --install` (launchd on macOS, systemd user unit on Linux;
|
|
155
|
-
removes it).
|
|
153
|
+
wake channel and reads what is waiting on every nudge. Keep it alive past the terminal
|
|
154
|
+
with `paigy-listen --install` (launchd on macOS, systemd user unit on Linux;
|
|
155
|
+
`--uninstall` removes it).
|
|
156
|
+
|
|
157
|
+
The read is `check_replies` — the open Deliveries addressed to this agent — and it
|
|
158
|
+
**consumes nothing**: no lease, no acknowledgement, and the same read twice returns the
|
|
159
|
+
same list. So the daemon hands the launcher what it found and stops there; claiming is
|
|
160
|
+
the agent's own first act (`claim_goal`), because the daemon shares this machine's
|
|
161
|
+
token with the agent it launches and a second claimer on one identity eats the first
|
|
162
|
+
one's claim.
|
|
156
163
|
|
|
157
164
|
To launch an agent — any agent, not just Claude — when work arrives, set
|
|
158
|
-
`PAIGY_ON_WAKE` to a command before `--install
|
|
159
|
-
work, so the launcher reads it from the environment rather than calling
|
|
160
|
-
`check_replies` again:
|
|
165
|
+
`PAIGY_ON_WAKE` to a command before `--install`:
|
|
161
166
|
|
|
162
167
|
| Variable | What it holds |
|
|
163
168
|
| --- | --- |
|
|
164
|
-
| `PAIGY_WORK` | the
|
|
165
|
-
| `PAIGY_EVENT` | the wake that caused this run — `boot`, `wake:reply`, `
|
|
166
|
-
| `
|
|
167
|
-
| `
|
|
168
|
-
| `
|
|
169
|
-
| `
|
|
169
|
+
| `PAIGY_WORK` | everything the wake found, as JSON (`deliveries`, `goal`) |
|
|
170
|
+
| `PAIGY_EVENT` | the wake that caused this run — `boot`, `wake:reply`, `wake:request`… |
|
|
171
|
+
| `PAIGY_PARENT_ID` | the thread to continue on (`PAIGY_THREAD_ID` is the same value, for existing scripts) |
|
|
172
|
+
| `PAIGY_DELIVERY_ID` | the Delivery being acted on — `contact({deliveryId})` rereads it |
|
|
173
|
+
| `PAIGY_GOAL_ID` | the Goal it belongs to — `claim_goal` it **first** |
|
|
174
|
+
| `PAIGY_CONTEXT_THREAD_ID` | a past conversation this Goal names — `get_thread` it **first** |
|
|
175
|
+
| `PAIGY_TEXT` | what the person said, in prose |
|
|
170
176
|
|
|
171
177
|
Hand `$PAIGY_WORK` to a harness that can read JSON and decide for itself; use the
|
|
172
178
|
scalars for a plain shell launcher that shouldn't need `jq`. They describe **one**
|
|
173
|
-
item — the oldest
|
|
174
|
-
|
|
175
|
-
|
|
179
|
+
item — the oldest open Delivery — because the contract is one thread at a time. An
|
|
180
|
+
absent fact is unset rather than empty, so `${PAIGY_CONTEXT_THREAD_ID:-}`
|
|
181
|
+
distinguishes "no seed" from "seeded with nothing".
|
|
176
182
|
|
|
177
183
|
```sh
|
|
178
184
|
# Claude Code — hand it everything and let it plan:
|
|
179
|
-
PAIGY_ON_WAKE='claude -p "Handle the Paigy work in $PAIGY_WORK — rehydrate threads you do not recognize via get_thread
|
|
185
|
+
PAIGY_ON_WAKE='claude -p "Handle the Paigy work in $PAIGY_WORK — claim_goal first, and rehydrate threads you do not recognize via get_thread."' \
|
|
180
186
|
npx -y -p @paigy/mcp paigy-listen --install
|
|
181
187
|
|
|
182
188
|
# Codex (or any CLI harness) — the scalars are enough for a one-liner:
|
|
183
|
-
PAIGY_ON_WAKE='codex exec "
|
|
189
|
+
PAIGY_ON_WAKE='codex exec "Claim Paigy goal $PAIGY_GOAL_ID and continue thread $PAIGY_THREAD_ID. The user said: $PAIGY_TEXT. Reply with contact."' \
|
|
184
190
|
npx -y -p @paigy/mcp paigy-listen --install
|
|
185
191
|
```
|
|
186
192
|
|
|
@@ -194,10 +200,7 @@ script and branch on `$PAIGY_EVENT` there:
|
|
|
194
200
|
seed=""
|
|
195
201
|
[ -n "${PAIGY_CONTEXT_THREAD_ID:-}" ] && seed="It continues thread $PAIGY_CONTEXT_THREAD_ID — get_thread that first."
|
|
196
202
|
|
|
197
|
-
|
|
198
|
-
cron:callback*) codex exec "You owe the user a callback on thread $PAIGY_THREAD_ID: $PAIGY_TEXT. Deliver it with contact." ;;
|
|
199
|
-
*) codex exec "Paigy thread $PAIGY_THREAD_ID. The user said: $PAIGY_TEXT. $seed Reply with contact when done." ;;
|
|
200
|
-
esac
|
|
203
|
+
codex exec "Paigy goal $PAIGY_GOAL_ID on thread $PAIGY_THREAD_ID. claim_goal it first. The user said: $PAIGY_TEXT. $seed Reply with contact when done."
|
|
201
204
|
```
|
|
202
205
|
|
|
203
206
|
## Statusline (Claude Code)
|
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
AGENT_TOOLS,
|
|
3
3
|
mcpInputSchema,
|
|
4
4
|
serverInstructions
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-FFI3AIZE.js";
|
|
6
6
|
import {
|
|
7
7
|
AGENT_NAME
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-YJEJZT56.js";
|
|
9
9
|
|
|
10
10
|
// src/toolset.ts
|
|
11
11
|
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.";
|
|
@@ -36,7 +36,22 @@ function mcpInputSchema(s) {
|
|
|
36
36
|
}
|
|
37
37
|
var StartContactSchema = z.object({
|
|
38
38
|
goalIds: z.tuple([z.string().uuid()]),
|
|
39
|
-
|
|
39
|
+
// THE ASK IS THE QUESTION, not a bulletin with a question at the end (2026-09-16).
|
|
40
|
+
//
|
|
41
|
+
// A DecisionNeed is settled only by an answer in the shape THIS ask declares. Bundle news,
|
|
42
|
+
// findings and a decision into one, and the person answers whichever part engaged them —
|
|
43
|
+
// which settles nothing, leaves the need open, and gets them asked again. On call 619c5a92
|
|
44
|
+
// one ask carried a briefing, a side question ("the ring should show the agent's name now —
|
|
45
|
+
// what did you actually see?") and a one-of-four about how far an erase reaches. The owner
|
|
46
|
+
// answered the side question. The need stayed open; the card asked again. Of the 45 call
|
|
47
|
+
// asks carrying a decision, 27 are over 600 characters.
|
|
48
|
+
//
|
|
49
|
+
// Sending them separately costs nothing, because a CALL contact joins the call already
|
|
50
|
+
// happening (`goal/store.ts`, JOIN BEFORE MINTING) — several contacts on one Goal arrive as
|
|
51
|
+
// one call, planned as its turns, and each keeps its own settleable need.
|
|
52
|
+
ask: z.string().trim().min(1).max(1e4).describe(
|
|
53
|
+
"The question, and only what is needed to answer it. News, progress and findings are their own contact \u2014 a call contact JOINS a call already happening, so several arrive as one call. Do not bundle: a DecisionNeed is settled only by an answer in the shape this ask declares, so someone who answers the part that interested them settles nothing and is asked again."
|
|
54
|
+
),
|
|
40
55
|
waiting: z.enum(["none", "hard"]).default("none"),
|
|
41
56
|
channel: z.enum(["notification", "call"]).default("notification"),
|
|
42
57
|
options: z.array(z.object({ label: z.string().trim().min(1).max(1e3), image: z.string().url().optional(), html: z.string().max(16384).optional() }).strict()).min(2).max(6).optional(),
|
|
@@ -48,12 +63,25 @@ var CONTACT_DESCRIPTION = "Contact the user about exactly one existing Goal: pas
|
|
|
48
63
|
var CreateGoalSchema = z2.object({
|
|
49
64
|
outcome: z2.string().trim().min(1).max(1e4),
|
|
50
65
|
ownerParticipant: z2.string().trim().min(1).optional(),
|
|
51
|
-
idempotencyKey: z2.string().trim().min(1).max(200)
|
|
66
|
+
idempotencyKey: z2.string().trim().min(1).max(200),
|
|
67
|
+
/** A past conversation this Goal should be read against — History's "new session from this"
|
|
68
|
+
* (owner, on the call of 2026-09-14: "let's do the reference with the threading"). A
|
|
69
|
+
* reference only: the owner reads it through `get_thread`, which does its own scoping, and
|
|
70
|
+
* the writer refuses a thread belonging to another account. */
|
|
71
|
+
contextThreadId: z2.string().uuid().optional(),
|
|
72
|
+
/** THE GOAL THIS ONE BELONGS UNDER (owner, 2026-09-15: "the ask I gave for the design doc
|
|
73
|
+
* didn't get created as a child goal of the voice UI goal, which is how it should've
|
|
74
|
+
* worked"). It could not have been: this door took no parent, so the only route was
|
|
75
|
+
* create -> claim -> `update_goal`, three calls with a lease in the middle, and every agent
|
|
76
|
+
* took the short one. The hierarchy has been modelled since Goals existed and had been used
|
|
77
|
+
* ZERO times in 2,031 of them. Absent still means a root, which is what every caller meant
|
|
78
|
+
* before this existed. The writer refuses a Goal belonging to another account. */
|
|
79
|
+
parentGoalId: z2.string().uuid().optional()
|
|
52
80
|
});
|
|
53
81
|
var CreateGoalToolSchema = CreateGoalSchema.extend({
|
|
54
82
|
idempotencyKey: CreateGoalSchema.shape.idempotencyKey.optional().describe("Optional. One is minted per call; pass your own only so a retry lands on the same Goal.")
|
|
55
83
|
}).strict();
|
|
56
|
-
var CREATE_GOAL_DESCRIPTION = "Create a durable
|
|
84
|
+
var CREATE_GOAL_DESCRIPTION = "Create a durable Goal for an outcome. Pass parentGoalId to create it UNDER an existing Goal \u2014 do that whenever the work is part of something already being tracked, rather than minting a root and leaving the relationship unsaid; absent means a root. Admission only: the owner must 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.";
|
|
57
85
|
var UpdateGoalSchema = z2.object({
|
|
58
86
|
revision: z2.number().int().positive(),
|
|
59
87
|
changes: z2.object({
|
|
@@ -99,6 +127,21 @@ var AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
|
|
|
99
127
|
function serverInstructions(_opts) {
|
|
100
128
|
return "On startup and after a wake, call check_replies for the open Deliveries addressed to you, and claim_goal for your runnable or review-pending Goal and its durable Entries and accepted decisions. get_goal rereads the same evidence without consuming it. Contact only for an existing Goal. Notifications return immediately: keep working and collect answers through claim_goal/get_goal. On stdio, Calls hold one bounded window; on hosted MCP, Calls return after one read; continue with contact({deliveryId}) to reread that exact Delivery. Evidence can repeat: reads do not acknowledge or hide it. Use update_goal to report progress and explicitly acknowledge review. Never infer ringing from an open Call Delivery. Soft waiting, multipart contact, and re-presentation are unsupported. In an interactive session, ask the user directly; use Paigy when they asked for it or are away.";
|
|
101
129
|
}
|
|
130
|
+
function entryWords(entry) {
|
|
131
|
+
const content = entry.content;
|
|
132
|
+
if (content && "sealed" in content) return "";
|
|
133
|
+
const plain = content?.plain;
|
|
134
|
+
if (typeof plain === "string") return plain;
|
|
135
|
+
if (plain && typeof plain === "object") {
|
|
136
|
+
const text = plain.text;
|
|
137
|
+
if (typeof text === "string") return text;
|
|
138
|
+
const title = plain.title;
|
|
139
|
+
const description = plain.description;
|
|
140
|
+
const parts = [title, ...Array.isArray(description) ? description : []].filter((v) => typeof v === "string");
|
|
141
|
+
if (parts.length) return parts.join("\n\n");
|
|
142
|
+
}
|
|
143
|
+
return entry.sources.map((source) => source.text).join("\n");
|
|
144
|
+
}
|
|
102
145
|
var ContextSchema = z3.object({
|
|
103
146
|
title: z3.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
104
147
|
description: z3.array(z3.string().min(1)).describe(
|
|
@@ -332,13 +375,6 @@ var AgentStateSchema = z3.enum(["idle", "in_progress", "completed", "needs_input
|
|
|
332
375
|
var SetTaskStateSchema = z3.object({
|
|
333
376
|
state: z3.enum(["in_progress", "completed", "needs_input"])
|
|
334
377
|
});
|
|
335
|
-
var SetWorkStateSchema = z3.object({
|
|
336
|
-
workId: z3.string().uuid().optional(),
|
|
337
|
-
notificationId: z3.string().optional(),
|
|
338
|
-
state: SetTaskStateSchema.shape.state
|
|
339
|
-
}).refine((value) => Number(Boolean(value.workId)) + Number(Boolean(value.notificationId)) === 1, {
|
|
340
|
-
message: "exactly one of workId or notificationId is required"
|
|
341
|
-
});
|
|
342
378
|
var TurnSchema = z3.object({
|
|
343
379
|
prompt: z3.string(),
|
|
344
380
|
reply: z3.string()
|
|
@@ -468,13 +504,6 @@ var AwaitItemSchema = z3.discriminatedUnion("type", [
|
|
|
468
504
|
})
|
|
469
505
|
]);
|
|
470
506
|
var CallbackTriggerSchema = z3.enum(["on_done", "on_blocked", "scheduled"]);
|
|
471
|
-
var ScheduleCallbackSchema = z3.object({
|
|
472
|
-
parentId: z3.string().describe("The thread to call back on (from a prior contact / reply / request)."),
|
|
473
|
-
goalId: z3.string().uuid().optional().describe("The Goal this callback advances; preferred for Goal-owned work."),
|
|
474
|
-
trigger: CallbackTriggerSchema,
|
|
475
|
-
dueInSeconds: z3.number().int().positive().optional().describe("For 'scheduled' only: how many seconds from now to fire."),
|
|
476
|
-
note: z3.string().optional().describe("What to tell the user when you follow up.")
|
|
477
|
-
});
|
|
478
507
|
var PendingRepliesSchema = z3.object({
|
|
479
508
|
replies: z3.array(
|
|
480
509
|
z3.object({
|
|
@@ -797,7 +826,21 @@ var InboxItemSchema = z3.object({
|
|
|
797
826
|
/** E2EE: the agent's device X25519 public key to seal the user's answer BACK to (the
|
|
798
827
|
* sender the phone replies to). Sourced server-side from this item's pairing credential.
|
|
799
828
|
* Present only alongside `envelope`; the phone seals via sealAnswer(answer, this, id). */
|
|
800
|
-
agentX25519: z3.string().optional()
|
|
829
|
+
agentX25519: z3.string().optional(),
|
|
830
|
+
/** THE TARGET FACTS A CARD RENDERS (#1796 point 5, 2026-09-11): the Delivery it is a view of,
|
|
831
|
+
* that Delivery's kind, the request Entry, the Goals it answers for, the exact DecisionNeed (none
|
|
832
|
+
* for a request that asks nothing), whether its content is sealed, and that Goal's state. The
|
|
833
|
+
* answer writer (`POST /api/entries`) and the disposition (`close_delivery`) take their ids from
|
|
834
|
+
* here. The server projects it (`apps/api/src/inbox/project.ts`); a client never builds it. */
|
|
835
|
+
communication: z3.object({
|
|
836
|
+
deliveryId: z3.string(),
|
|
837
|
+
kind: z3.enum(["notification", "call"]),
|
|
838
|
+
entryId: z3.string(),
|
|
839
|
+
goalIds: z3.array(z3.string()),
|
|
840
|
+
decisionNeedId: z3.string().optional(),
|
|
841
|
+
sealed: z3.boolean(),
|
|
842
|
+
goalState: z3.string().optional()
|
|
843
|
+
}).optional()
|
|
801
844
|
});
|
|
802
845
|
var SnoozeRequestSchema = z3.object({
|
|
803
846
|
requestId: z3.string(),
|
|
@@ -1033,26 +1076,54 @@ var CreateRequestSchema = z3.object({
|
|
|
1033
1076
|
* the agent reads it via get_thread. Must belong to the requesting user. */
|
|
1034
1077
|
contextParentId: z3.string().optional()
|
|
1035
1078
|
});
|
|
1036
|
-
var
|
|
1037
|
-
/**
|
|
1038
|
-
|
|
1039
|
-
/**
|
|
1040
|
-
|
|
1041
|
-
/**
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
/**
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1079
|
+
var QueueQuestionSchema = z3.object({
|
|
1080
|
+
/** The decision need's id — what an answer is accepted against. */
|
|
1081
|
+
id: z3.string(),
|
|
1082
|
+
/** The words that were asked, from the request Entry that asked them. */
|
|
1083
|
+
question: z3.string(),
|
|
1084
|
+
/** Where it was asked — which is where the ruling goes (`POST /api/entries`). Null only
|
|
1085
|
+
* for a need whose request Entry is carried by no interactive Delivery, which nothing
|
|
1086
|
+
* can answer. */
|
|
1087
|
+
deliveryId: z3.string().nullable().default(null),
|
|
1088
|
+
/** The Entry the ruling is about. */
|
|
1089
|
+
aboutId: z3.string().nullable().default(null),
|
|
1090
|
+
/** Empty for a free-text question. */
|
|
1091
|
+
options: z3.array(OptionSchema).default([]),
|
|
1092
|
+
select: z3.enum(["one", "many", "rank", "confirm", "text"]).default("text"),
|
|
1093
|
+
askedAt: z3.string(),
|
|
1094
|
+
/** Null while the question is open — which is how the page tells the two apart. */
|
|
1095
|
+
answeredAt: z3.string().nullable().default(null),
|
|
1096
|
+
/** The ruling in the person's own words, from the contribution that replied — not the
|
|
1097
|
+
* option id, which is not something anyone reads back. Null while it is open, and null
|
|
1098
|
+
* for a settled question whose reply carried nothing readable. */
|
|
1099
|
+
answer: z3.string().nullable().default(null)
|
|
1100
|
+
});
|
|
1101
|
+
var QueueItemSchema = z3.object({
|
|
1102
|
+
id: z3.string(),
|
|
1103
|
+
/** One-line headline — the first sentence of the outcome. */
|
|
1104
|
+
title: z3.string(),
|
|
1105
|
+
/** The outcome in full, verbatim: the person's own words are what an assignee sees. */
|
|
1106
|
+
intent: z3.string(),
|
|
1107
|
+
/** `ready` | `active` | `waiting` | `done` | `cancelled`, straight off the Goal. */
|
|
1108
|
+
state: z3.string(),
|
|
1109
|
+
/** Who holds it (a participant ref); null when nobody does yet. */
|
|
1110
|
+
assignee: z3.string().nullable().default(null),
|
|
1111
|
+
/** What the agent last said it was doing; null if it has said nothing. */
|
|
1112
|
+
progress: z3.string().nullable().default(null),
|
|
1113
|
+
reviewPending: z3.boolean().default(false),
|
|
1114
|
+
dueAt: z3.string().nullable().default(null),
|
|
1115
|
+
/** The Goal this one was opened under; null at the root. */
|
|
1116
|
+
parentGoalId: z3.string().nullable().default(null),
|
|
1117
|
+
/** Goals opened under this one — only those the same list holds. */
|
|
1118
|
+
childGoalIds: z3.array(z3.string()).default([]),
|
|
1119
|
+
/** Goals this one waits on (start or finish gates). */
|
|
1120
|
+
dependencyGoalIds: z3.array(z3.string()).default([]),
|
|
1121
|
+
/** True while any gate is on a Goal that is not done — the walk draws it dashed. */
|
|
1122
|
+
blocked: z3.boolean().default(false),
|
|
1123
|
+
/** Every decision need on it, open or settled — the page decides which to show. */
|
|
1124
|
+
questions: z3.array(QueueQuestionSchema).default([]),
|
|
1125
|
+
createdAt: z3.string(),
|
|
1126
|
+
updatedAt: z3.string().nullable().default(null)
|
|
1056
1127
|
});
|
|
1057
1128
|
var NoteSourceSchema = z3.enum(["app", "call"]);
|
|
1058
1129
|
var NoteStatusSchema = z3.enum(["open", "assigned", "in_progress", "done"]);
|
|
@@ -1181,8 +1252,6 @@ var AcceptTriageResultSchema = z3.object({
|
|
|
1181
1252
|
failed: z3.array(z3.object({ noteId: z3.string(), reason: z3.string() }))
|
|
1182
1253
|
});
|
|
1183
1254
|
var DeliveryModeSchema = z3.enum(["poll", "self_hosted"]);
|
|
1184
|
-
var WAKE_EVENT = "wake";
|
|
1185
|
-
var wakeChannel = (tokenId) => `wake:${tokenId}`;
|
|
1186
1255
|
var RegisterDeliverySchema = z3.object({ mode: DeliveryModeSchema });
|
|
1187
1256
|
var OAuthStartSchema = z3.object({
|
|
1188
1257
|
provider: z3.enum(["cma"]),
|
|
@@ -1191,8 +1260,9 @@ var OAuthStartSchema = z3.object({
|
|
|
1191
1260
|
var DeliveryConfigSchema = z3.object({
|
|
1192
1261
|
tokenId: z3.string(),
|
|
1193
1262
|
mode: DeliveryModeSchema,
|
|
1194
|
-
/** null when the
|
|
1195
|
-
*
|
|
1263
|
+
/** null when the deployment has no anon key configured. `self_hosted` is then REFUSED
|
|
1264
|
+
* (503 `self_hosted_unavailable`) rather than registered, so a self_hosted config always
|
|
1265
|
+
* carries credentials; only a `poll` registration can come back with null here. */
|
|
1196
1266
|
realtime: z3.object({ url: z3.string(), anonKey: z3.string() }).nullable()
|
|
1197
1267
|
});
|
|
1198
1268
|
var StatusSchema = z3.object({
|
|
@@ -1383,6 +1453,5 @@ export {
|
|
|
1383
1453
|
mcpInputSchema,
|
|
1384
1454
|
AGENT_TOOLS,
|
|
1385
1455
|
serverInstructions,
|
|
1386
|
-
|
|
1387
|
-
wakeChannel
|
|
1456
|
+
entryWords
|
|
1388
1457
|
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { createRequire as __sdkCreateRequire } from "module";
|
|
3
3
|
import { createHash, randomUUID } from "crypto";
|
|
4
4
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
5
|
-
import { setTimeout as
|
|
5
|
+
import { setTimeout as sleep2 } from "timers/promises";
|
|
6
6
|
import { z as z3 } from "zod";
|
|
7
7
|
import { z as z2 } from "zod";
|
|
8
8
|
import { ZodFirstPartyTypeKind as ZodFirstPartyTypeKind3 } from "zod/v3";
|
|
@@ -14,6 +14,7 @@ import { closeSync, existsSync, mkdirSync, openSync, readFileSync, rmSync, statS
|
|
|
14
14
|
import { homedir } from "os";
|
|
15
15
|
import { join } from "path";
|
|
16
16
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
17
|
+
import { createClient } from "@supabase/supabase-js";
|
|
17
18
|
var require2 = __sdkCreateRequire(import.meta.url);
|
|
18
19
|
var __create = Object.create;
|
|
19
20
|
var __defProp = Object.defineProperty;
|
|
@@ -3551,7 +3552,22 @@ function mcpInputSchema(s) {
|
|
|
3551
3552
|
}
|
|
3552
3553
|
var StartContactSchema = z.object({
|
|
3553
3554
|
goalIds: z.tuple([z.string().uuid()]),
|
|
3554
|
-
|
|
3555
|
+
// THE ASK IS THE QUESTION, not a bulletin with a question at the end (2026-09-16).
|
|
3556
|
+
//
|
|
3557
|
+
// A DecisionNeed is settled only by an answer in the shape THIS ask declares. Bundle news,
|
|
3558
|
+
// findings and a decision into one, and the person answers whichever part engaged them —
|
|
3559
|
+
// which settles nothing, leaves the need open, and gets them asked again. On call 619c5a92
|
|
3560
|
+
// one ask carried a briefing, a side question ("the ring should show the agent's name now —
|
|
3561
|
+
// what did you actually see?") and a one-of-four about how far an erase reaches. The owner
|
|
3562
|
+
// answered the side question. The need stayed open; the card asked again. Of the 45 call
|
|
3563
|
+
// asks carrying a decision, 27 are over 600 characters.
|
|
3564
|
+
//
|
|
3565
|
+
// Sending them separately costs nothing, because a CALL contact joins the call already
|
|
3566
|
+
// happening (`goal/store.ts`, JOIN BEFORE MINTING) — several contacts on one Goal arrive as
|
|
3567
|
+
// one call, planned as its turns, and each keeps its own settleable need.
|
|
3568
|
+
ask: z.string().trim().min(1).max(1e4).describe(
|
|
3569
|
+
"The question, and only what is needed to answer it. News, progress and findings are their own contact \u2014 a call contact JOINS a call already happening, so several arrive as one call. Do not bundle: a DecisionNeed is settled only by an answer in the shape this ask declares, so someone who answers the part that interested them settles nothing and is asked again."
|
|
3570
|
+
),
|
|
3555
3571
|
waiting: z.enum(["none", "hard"]).default("none"),
|
|
3556
3572
|
channel: z.enum(["notification", "call"]).default("notification"),
|
|
3557
3573
|
options: z.array(z.object({ label: z.string().trim().min(1).max(1e3), image: z.string().url().optional(), html: z.string().max(16384).optional() }).strict()).min(2).max(6).optional(),
|
|
@@ -3563,12 +3579,25 @@ var CONTACT_DESCRIPTION = "Contact the user about exactly one existing Goal: pas
|
|
|
3563
3579
|
var CreateGoalSchema = z2.object({
|
|
3564
3580
|
outcome: z2.string().trim().min(1).max(1e4),
|
|
3565
3581
|
ownerParticipant: z2.string().trim().min(1).optional(),
|
|
3566
|
-
idempotencyKey: z2.string().trim().min(1).max(200)
|
|
3582
|
+
idempotencyKey: z2.string().trim().min(1).max(200),
|
|
3583
|
+
/** A past conversation this Goal should be read against — History's "new session from this"
|
|
3584
|
+
* (owner, on the call of 2026-09-14: "let's do the reference with the threading"). A
|
|
3585
|
+
* reference only: the owner reads it through `get_thread`, which does its own scoping, and
|
|
3586
|
+
* the writer refuses a thread belonging to another account. */
|
|
3587
|
+
contextThreadId: z2.string().uuid().optional(),
|
|
3588
|
+
/** THE GOAL THIS ONE BELONGS UNDER (owner, 2026-09-15: "the ask I gave for the design doc
|
|
3589
|
+
* didn't get created as a child goal of the voice UI goal, which is how it should've
|
|
3590
|
+
* worked"). It could not have been: this door took no parent, so the only route was
|
|
3591
|
+
* create -> claim -> `update_goal`, three calls with a lease in the middle, and every agent
|
|
3592
|
+
* took the short one. The hierarchy has been modelled since Goals existed and had been used
|
|
3593
|
+
* ZERO times in 2,031 of them. Absent still means a root, which is what every caller meant
|
|
3594
|
+
* before this existed. The writer refuses a Goal belonging to another account. */
|
|
3595
|
+
parentGoalId: z2.string().uuid().optional()
|
|
3567
3596
|
});
|
|
3568
3597
|
var CreateGoalToolSchema = CreateGoalSchema.extend({
|
|
3569
3598
|
idempotencyKey: CreateGoalSchema.shape.idempotencyKey.optional().describe("Optional. One is minted per call; pass your own only so a retry lands on the same Goal.")
|
|
3570
3599
|
}).strict();
|
|
3571
|
-
var CREATE_GOAL_DESCRIPTION = "Create a durable
|
|
3600
|
+
var CREATE_GOAL_DESCRIPTION = "Create a durable Goal for an outcome. Pass parentGoalId to create it UNDER an existing Goal \u2014 do that whenever the work is part of something already being tracked, rather than minting a root and leaving the relationship unsaid; absent means a root. Admission only: the owner must 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.";
|
|
3572
3601
|
var UpdateGoalSchema = z2.object({
|
|
3573
3602
|
revision: z2.number().int().positive(),
|
|
3574
3603
|
changes: z2.object({
|
|
@@ -3899,13 +3928,6 @@ var AgentStateSchema = z3.enum(["idle", "in_progress", "completed", "needs_input
|
|
|
3899
3928
|
var SetTaskStateSchema = z3.object({
|
|
3900
3929
|
state: z3.enum(["in_progress", "completed", "needs_input"])
|
|
3901
3930
|
});
|
|
3902
|
-
var SetWorkStateSchema = z3.object({
|
|
3903
|
-
workId: z3.string().uuid().optional(),
|
|
3904
|
-
notificationId: z3.string().optional(),
|
|
3905
|
-
state: SetTaskStateSchema.shape.state
|
|
3906
|
-
}).refine((value) => Number(Boolean(value.workId)) + Number(Boolean(value.notificationId)) === 1, {
|
|
3907
|
-
message: "exactly one of workId or notificationId is required"
|
|
3908
|
-
});
|
|
3909
3931
|
var TurnSchema = z3.object({
|
|
3910
3932
|
prompt: z3.string(),
|
|
3911
3933
|
reply: z3.string()
|
|
@@ -4035,13 +4057,6 @@ var AwaitItemSchema = z3.discriminatedUnion("type", [
|
|
|
4035
4057
|
})
|
|
4036
4058
|
]);
|
|
4037
4059
|
var CallbackTriggerSchema = z3.enum(["on_done", "on_blocked", "scheduled"]);
|
|
4038
|
-
var ScheduleCallbackSchema = z3.object({
|
|
4039
|
-
parentId: z3.string().describe("The thread to call back on (from a prior contact / reply / request)."),
|
|
4040
|
-
goalId: z3.string().uuid().optional().describe("The Goal this callback advances; preferred for Goal-owned work."),
|
|
4041
|
-
trigger: CallbackTriggerSchema,
|
|
4042
|
-
dueInSeconds: z3.number().int().positive().optional().describe("For 'scheduled' only: how many seconds from now to fire."),
|
|
4043
|
-
note: z3.string().optional().describe("What to tell the user when you follow up.")
|
|
4044
|
-
});
|
|
4045
4060
|
var PendingRepliesSchema = z3.object({
|
|
4046
4061
|
replies: z3.array(
|
|
4047
4062
|
z3.object({
|
|
@@ -4364,7 +4379,21 @@ var InboxItemSchema = z3.object({
|
|
|
4364
4379
|
/** E2EE: the agent's device X25519 public key to seal the user's answer BACK to (the
|
|
4365
4380
|
* sender the phone replies to). Sourced server-side from this item's pairing credential.
|
|
4366
4381
|
* Present only alongside `envelope`; the phone seals via sealAnswer(answer, this, id). */
|
|
4367
|
-
agentX25519: z3.string().optional()
|
|
4382
|
+
agentX25519: z3.string().optional(),
|
|
4383
|
+
/** THE TARGET FACTS A CARD RENDERS (#1796 point 5, 2026-09-11): the Delivery it is a view of,
|
|
4384
|
+
* that Delivery's kind, the request Entry, the Goals it answers for, the exact DecisionNeed (none
|
|
4385
|
+
* for a request that asks nothing), whether its content is sealed, and that Goal's state. The
|
|
4386
|
+
* answer writer (`POST /api/entries`) and the disposition (`close_delivery`) take their ids from
|
|
4387
|
+
* here. The server projects it (`apps/api/src/inbox/project.ts`); a client never builds it. */
|
|
4388
|
+
communication: z3.object({
|
|
4389
|
+
deliveryId: z3.string(),
|
|
4390
|
+
kind: z3.enum(["notification", "call"]),
|
|
4391
|
+
entryId: z3.string(),
|
|
4392
|
+
goalIds: z3.array(z3.string()),
|
|
4393
|
+
decisionNeedId: z3.string().optional(),
|
|
4394
|
+
sealed: z3.boolean(),
|
|
4395
|
+
goalState: z3.string().optional()
|
|
4396
|
+
}).optional()
|
|
4368
4397
|
});
|
|
4369
4398
|
var SnoozeRequestSchema = z3.object({
|
|
4370
4399
|
requestId: z3.string(),
|
|
@@ -4600,26 +4629,54 @@ var CreateRequestSchema = z3.object({
|
|
|
4600
4629
|
* the agent reads it via get_thread. Must belong to the requesting user. */
|
|
4601
4630
|
contextParentId: z3.string().optional()
|
|
4602
4631
|
});
|
|
4603
|
-
var
|
|
4604
|
-
/**
|
|
4605
|
-
|
|
4606
|
-
/**
|
|
4607
|
-
|
|
4608
|
-
/**
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
/**
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4632
|
+
var QueueQuestionSchema = z3.object({
|
|
4633
|
+
/** The decision need's id — what an answer is accepted against. */
|
|
4634
|
+
id: z3.string(),
|
|
4635
|
+
/** The words that were asked, from the request Entry that asked them. */
|
|
4636
|
+
question: z3.string(),
|
|
4637
|
+
/** Where it was asked — which is where the ruling goes (`POST /api/entries`). Null only
|
|
4638
|
+
* for a need whose request Entry is carried by no interactive Delivery, which nothing
|
|
4639
|
+
* can answer. */
|
|
4640
|
+
deliveryId: z3.string().nullable().default(null),
|
|
4641
|
+
/** The Entry the ruling is about. */
|
|
4642
|
+
aboutId: z3.string().nullable().default(null),
|
|
4643
|
+
/** Empty for a free-text question. */
|
|
4644
|
+
options: z3.array(OptionSchema).default([]),
|
|
4645
|
+
select: z3.enum(["one", "many", "rank", "confirm", "text"]).default("text"),
|
|
4646
|
+
askedAt: z3.string(),
|
|
4647
|
+
/** Null while the question is open — which is how the page tells the two apart. */
|
|
4648
|
+
answeredAt: z3.string().nullable().default(null),
|
|
4649
|
+
/** The ruling in the person's own words, from the contribution that replied — not the
|
|
4650
|
+
* option id, which is not something anyone reads back. Null while it is open, and null
|
|
4651
|
+
* for a settled question whose reply carried nothing readable. */
|
|
4652
|
+
answer: z3.string().nullable().default(null)
|
|
4653
|
+
});
|
|
4654
|
+
var QueueItemSchema = z3.object({
|
|
4655
|
+
id: z3.string(),
|
|
4656
|
+
/** One-line headline — the first sentence of the outcome. */
|
|
4657
|
+
title: z3.string(),
|
|
4658
|
+
/** The outcome in full, verbatim: the person's own words are what an assignee sees. */
|
|
4659
|
+
intent: z3.string(),
|
|
4660
|
+
/** `ready` | `active` | `waiting` | `done` | `cancelled`, straight off the Goal. */
|
|
4661
|
+
state: z3.string(),
|
|
4662
|
+
/** Who holds it (a participant ref); null when nobody does yet. */
|
|
4663
|
+
assignee: z3.string().nullable().default(null),
|
|
4664
|
+
/** What the agent last said it was doing; null if it has said nothing. */
|
|
4665
|
+
progress: z3.string().nullable().default(null),
|
|
4666
|
+
reviewPending: z3.boolean().default(false),
|
|
4667
|
+
dueAt: z3.string().nullable().default(null),
|
|
4668
|
+
/** The Goal this one was opened under; null at the root. */
|
|
4669
|
+
parentGoalId: z3.string().nullable().default(null),
|
|
4670
|
+
/** Goals opened under this one — only those the same list holds. */
|
|
4671
|
+
childGoalIds: z3.array(z3.string()).default([]),
|
|
4672
|
+
/** Goals this one waits on (start or finish gates). */
|
|
4673
|
+
dependencyGoalIds: z3.array(z3.string()).default([]),
|
|
4674
|
+
/** True while any gate is on a Goal that is not done — the walk draws it dashed. */
|
|
4675
|
+
blocked: z3.boolean().default(false),
|
|
4676
|
+
/** Every decision need on it, open or settled — the page decides which to show. */
|
|
4677
|
+
questions: z3.array(QueueQuestionSchema).default([]),
|
|
4678
|
+
createdAt: z3.string(),
|
|
4679
|
+
updatedAt: z3.string().nullable().default(null)
|
|
4623
4680
|
});
|
|
4624
4681
|
var NoteSourceSchema = z3.enum(["app", "call"]);
|
|
4625
4682
|
var NoteStatusSchema = z3.enum(["open", "assigned", "in_progress", "done"]);
|
|
@@ -4748,6 +4805,8 @@ var AcceptTriageResultSchema = z3.object({
|
|
|
4748
4805
|
failed: z3.array(z3.object({ noteId: z3.string(), reason: z3.string() }))
|
|
4749
4806
|
});
|
|
4750
4807
|
var DeliveryModeSchema = z3.enum(["poll", "self_hosted"]);
|
|
4808
|
+
var WAKE_EVENT = "wake";
|
|
4809
|
+
var wakeChannel = (tokenId) => `wake:${tokenId}`;
|
|
4751
4810
|
var RegisterDeliverySchema = z3.object({ mode: DeliveryModeSchema });
|
|
4752
4811
|
var OAuthStartSchema = z3.object({
|
|
4753
4812
|
provider: z3.enum(["cma"]),
|
|
@@ -4756,8 +4815,9 @@ var OAuthStartSchema = z3.object({
|
|
|
4756
4815
|
var DeliveryConfigSchema = z3.object({
|
|
4757
4816
|
tokenId: z3.string(),
|
|
4758
4817
|
mode: DeliveryModeSchema,
|
|
4759
|
-
/** null when the
|
|
4760
|
-
*
|
|
4818
|
+
/** null when the deployment has no anon key configured. `self_hosted` is then REFUSED
|
|
4819
|
+
* (503 `self_hosted_unavailable`) rather than registered, so a self_hosted config always
|
|
4820
|
+
* carries credentials; only a `poll` registration can come back with null here. */
|
|
4761
4821
|
realtime: z3.object({ url: z3.string(), anonKey: z3.string() }).nullable()
|
|
4762
4822
|
});
|
|
4763
4823
|
var StatusSchema = z3.object({
|
|
@@ -4966,21 +5026,6 @@ function utf8ToBytes(s) {
|
|
|
4966
5026
|
}
|
|
4967
5027
|
return Uint8Array.from(out);
|
|
4968
5028
|
}
|
|
4969
|
-
function bytesToUtf8(b) {
|
|
4970
|
-
let out = "";
|
|
4971
|
-
for (let i = 0; i < b.length; ) {
|
|
4972
|
-
const c = b[i++];
|
|
4973
|
-
if (c < 128) out += String.fromCharCode(c);
|
|
4974
|
-
else if (c < 224) out += String.fromCharCode((c & 31) << 6 | b[i++] & 63);
|
|
4975
|
-
else if (c < 240) out += String.fromCharCode((c & 15) << 12 | (b[i++] & 63) << 6 | b[i++] & 63);
|
|
4976
|
-
else {
|
|
4977
|
-
const cp = (c & 7) << 18 | (b[i++] & 63) << 12 | (b[i++] & 63) << 6 | b[i++] & 63;
|
|
4978
|
-
const u = cp - 65536;
|
|
4979
|
-
out += String.fromCharCode(55296 + (u >> 10), 56320 + (u & 1023));
|
|
4980
|
-
}
|
|
4981
|
-
}
|
|
4982
|
-
return out;
|
|
4983
|
-
}
|
|
4984
5029
|
var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
4985
5030
|
function toB64(bytes) {
|
|
4986
5031
|
let out = "";
|
|
@@ -5026,15 +5071,9 @@ function concat(parts) {
|
|
|
5026
5071
|
}
|
|
5027
5072
|
return out;
|
|
5028
5073
|
}
|
|
5029
|
-
function u16be(n) {
|
|
5030
|
-
return Uint8Array.of(n >>> 8 & 255, n & 255);
|
|
5031
|
-
}
|
|
5032
5074
|
function u32be(n) {
|
|
5033
5075
|
return Uint8Array.of(n >>> 24 & 255, n >>> 16 & 255, n >>> 8 & 255, n & 255);
|
|
5034
5076
|
}
|
|
5035
|
-
function readU32be(b, o) {
|
|
5036
|
-
return (b[o] << 24 | b[o + 1] << 16 | b[o + 2] << 8 | b[o + 3]) >>> 0;
|
|
5037
|
-
}
|
|
5038
5077
|
function lp(b) {
|
|
5039
5078
|
return concat([u32be(b.length), b]);
|
|
5040
5079
|
}
|
|
@@ -5076,9 +5115,6 @@ function sharedSecret(peerX25519PubB64, mySecretB64) {
|
|
|
5076
5115
|
if (raw.every((b) => b === 0)) throw new Error("low-order X25519 point \u2014 shared secret rejected");
|
|
5077
5116
|
return toB64(raw);
|
|
5078
5117
|
}
|
|
5079
|
-
function keyId(publicKeyB64) {
|
|
5080
|
-
return toB64(import_tweetnacl2.default.hash(fromB64(publicKeyB64)).slice(0, 8));
|
|
5081
|
-
}
|
|
5082
5118
|
function credentialBody(c) {
|
|
5083
5119
|
return concat([
|
|
5084
5120
|
lp(utf8ToBytes("paigy-device-credential-v1")),
|
|
@@ -5096,43 +5132,6 @@ function verifyDeviceCredential(cred, uikPublicB64) {
|
|
|
5096
5132
|
return false;
|
|
5097
5133
|
}
|
|
5098
5134
|
}
|
|
5099
|
-
var ENVELOPE_VERSION = 1;
|
|
5100
|
-
var ENVELOPE_ALG = "x25519-xsalsa20poly1305";
|
|
5101
|
-
function canonicalHeader(msgId, hdr) {
|
|
5102
|
-
const parts = [
|
|
5103
|
-
u16be(ENVELOPE_VERSION),
|
|
5104
|
-
lp(utf8ToBytes(ENVELOPE_ALG)),
|
|
5105
|
-
lp(msgId),
|
|
5106
|
-
lp(utf8ToBytes(hdr.field)),
|
|
5107
|
-
lp(utf8ToBytes(hdr.kind)),
|
|
5108
|
-
lp(utf8ToBytes(hdr.senderRole)),
|
|
5109
|
-
u32be(hdr.seq),
|
|
5110
|
-
u32be(hdr.recipientKeyIds.length),
|
|
5111
|
-
...hdr.recipientKeyIds.map((k) => lp(utf8ToBytes(k)))
|
|
5112
|
-
];
|
|
5113
|
-
return concat(parts);
|
|
5114
|
-
}
|
|
5115
|
-
function open(envelope, myKeyId, mySecretKeyB64) {
|
|
5116
|
-
if (envelope.v !== ENVELOPE_VERSION) throw new Error(`unsupported envelope version ${String(envelope.v)}`);
|
|
5117
|
-
if (envelope.alg !== ENVELOPE_ALG) throw new Error(`unsupported alg ${String(envelope.alg)}`);
|
|
5118
|
-
const rec = envelope.recipients.find((r) => r.keyId === myKeyId);
|
|
5119
|
-
if (!rec) throw new Error("not a recipient of this envelope");
|
|
5120
|
-
const cek = import_tweetnacl3.default.box.open(fromB64(rec.wrap), fromB64(rec.wnonce), fromB64(rec.epk), fromB64(mySecretKeyB64));
|
|
5121
|
-
if (!cek) throw new Error("CEK unwrap failed (wrong key or tampered)");
|
|
5122
|
-
const padded = import_tweetnacl3.default.secretbox.open(fromB64(envelope.ct), fromB64(envelope.nonce), cek);
|
|
5123
|
-
if (!padded) throw new Error("body authentication failed (tampered ciphertext)");
|
|
5124
|
-
if (padded.length < 8) throw new Error("malformed sealed plaintext");
|
|
5125
|
-
const canonLen = readU32be(padded, 0);
|
|
5126
|
-
if (canonLen < 0 || 8 + canonLen > padded.length) throw new Error("malformed sealed plaintext");
|
|
5127
|
-
const sealedCanon = padded.slice(4, 4 + canonLen);
|
|
5128
|
-
const bodyLen = readU32be(padded, 4 + canonLen);
|
|
5129
|
-
const bodyStart = 8 + canonLen;
|
|
5130
|
-
if (bodyStart + bodyLen > padded.length) throw new Error("malformed sealed plaintext");
|
|
5131
|
-
const body = padded.slice(bodyStart, bodyStart + bodyLen);
|
|
5132
|
-
const expected = canonicalHeader(fromB64(envelope.msgId), envelope.hdr);
|
|
5133
|
-
if (!eqCt(sealedCanon, expected)) throw new Error("header mismatch (tampered metadata)");
|
|
5134
|
-
return { header: envelope.hdr, body };
|
|
5135
|
-
}
|
|
5136
5135
|
var SAS_DOMAIN = "paigy-sas-v2";
|
|
5137
5136
|
function deriveSAS(input, bits = 24) {
|
|
5138
5137
|
const bundle = concat([
|
|
@@ -5360,21 +5359,6 @@ async function pairStep(deviceCode, keyFile) {
|
|
|
5360
5359
|
return { kind: "e2ee_aborted", warning: e.message };
|
|
5361
5360
|
}
|
|
5362
5361
|
}
|
|
5363
|
-
function openSealedAnswer(sealed, keypair) {
|
|
5364
|
-
const myKeyId = keyId(keypair.x25519.publicKey);
|
|
5365
|
-
const { header, body } = open(sealed.envelope, myKeyId, keypair.x25519.secretKey);
|
|
5366
|
-
if (header.field !== "answer" || header.senderRole !== "user") {
|
|
5367
|
-
throw new Error("sealed answer has the wrong envelope field/role \u2014 refusing (possible replay/misattribution)");
|
|
5368
|
-
}
|
|
5369
|
-
const parsed = UserAnswerSchema.safeParse(JSON.parse(bytesToUtf8(body)));
|
|
5370
|
-
if (!parsed.success) throw new Error("sealed answer body is not a valid UserAnswer");
|
|
5371
|
-
const answer = parsed.data;
|
|
5372
|
-
const bodyIsIgnored = answer.kind === "ignored";
|
|
5373
|
-
if (sealed.ignored !== bodyIsIgnored) {
|
|
5374
|
-
throw new Error("sealed answer tamper: `ignored` hint disagrees with the decrypted body");
|
|
5375
|
-
}
|
|
5376
|
-
return answer;
|
|
5377
|
-
}
|
|
5378
5362
|
var UnpairedError = class extends Error {
|
|
5379
5363
|
constructor() {
|
|
5380
5364
|
super("Not paired with Paigy \u2014 no valid token (the API answered 401). Run the pairing flow, then retry.");
|
|
@@ -5435,6 +5419,7 @@ async function updateGoal(goalId, input, opts = {}) {
|
|
|
5435
5419
|
if (!res.ok) await fail("update_goal", res);
|
|
5436
5420
|
return await res.json();
|
|
5437
5421
|
}
|
|
5422
|
+
var AWAIT_WINDOW_MS = 45e3;
|
|
5438
5423
|
async function getThread(parentId, opts = {}) {
|
|
5439
5424
|
const res = ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/thread/${encodeURIComponent(parentId)}`, {
|
|
5440
5425
|
headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}`, "x-paigy-model": "goal-entry-v1" }
|
|
@@ -5449,17 +5434,6 @@ async function searchThreads(q, opts = {}) {
|
|
|
5449
5434
|
if (!res.ok) await fail("search_threads", res);
|
|
5450
5435
|
return await res.json();
|
|
5451
5436
|
}
|
|
5452
|
-
async function sweepLeased(opts = {}) {
|
|
5453
|
-
const token = authToken(opts.token);
|
|
5454
|
-
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/pending`, {
|
|
5455
|
-
headers: {
|
|
5456
|
-
authorization: `Bearer ${token}`,
|
|
5457
|
-
"x-paigy-await-ack": "1"
|
|
5458
|
-
}
|
|
5459
|
-
}));
|
|
5460
|
-
if (!res.ok) throw new Error(`check_replies failed: ${res.status} ${await res.text()}`);
|
|
5461
|
-
return await res.json();
|
|
5462
|
-
}
|
|
5463
5437
|
var tokenOverride = null;
|
|
5464
5438
|
function overrideToken(secret) {
|
|
5465
5439
|
tokenOverride = secret;
|
|
@@ -5505,10 +5479,10 @@ async function heartbeat(runtime, opts = {}) {
|
|
|
5505
5479
|
}));
|
|
5506
5480
|
if (!res.ok) throw new Error(`heartbeat failed: ${res.status}`);
|
|
5507
5481
|
}
|
|
5508
|
-
async function registerDelivery(mode) {
|
|
5482
|
+
async function registerDelivery(mode, opts = {}) {
|
|
5509
5483
|
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/delivery`, {
|
|
5510
5484
|
method: "POST",
|
|
5511
|
-
headers: { "content-type": "application/json", authorization: `Bearer ${authToken()}` },
|
|
5485
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token)}` },
|
|
5512
5486
|
body: JSON.stringify({ mode })
|
|
5513
5487
|
}));
|
|
5514
5488
|
if (!res.ok) throw new Error(`register_delivery failed: ${res.status} ${await res.text()}`);
|
|
@@ -5544,7 +5518,7 @@ Inbox delivery is asynchronous; use claim_goal/get_goal to collect durable answe
|
|
|
5544
5518
|
};
|
|
5545
5519
|
const settled = (d) => d.kind === "notification" || d.state === "closed" || d.answers.length > 0 || d.entries.some((e) => e.kind === "contribution");
|
|
5546
5520
|
if (opts.waits === false) return read(opts.signal);
|
|
5547
|
-
const window = AbortSignal.timeout(
|
|
5521
|
+
const window = AbortSignal.timeout(AWAIT_WINDOW_MS);
|
|
5548
5522
|
const signal = opts.signal ? AbortSignal.any([opts.signal, window]) : window;
|
|
5549
5523
|
let latest;
|
|
5550
5524
|
try {
|
|
@@ -5552,7 +5526,7 @@ Inbox delivery is asynchronous; use claim_goal/get_goal to collect durable answe
|
|
|
5552
5526
|
signal.throwIfAborted();
|
|
5553
5527
|
latest = await read(signal);
|
|
5554
5528
|
if (settled(latest)) return latest;
|
|
5555
|
-
await
|
|
5529
|
+
await sleep2(5e3, void 0, { signal });
|
|
5556
5530
|
}
|
|
5557
5531
|
} catch (error) {
|
|
5558
5532
|
opts.signal?.throwIfAborted();
|
|
@@ -5604,106 +5578,23 @@ async function runTool(name, args, opts) {
|
|
|
5604
5578
|
throw new Error(`Unknown tool: ${name}`);
|
|
5605
5579
|
}
|
|
5606
5580
|
}
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
retryable;
|
|
5623
|
-
};
|
|
5624
|
-
function decryptAnswer(reply, deps = {}) {
|
|
5625
|
-
if (!reply.sealed) return reply.answer;
|
|
5626
|
-
const keyFile = (deps.readKeyFile ?? readKeyFile)();
|
|
5627
|
-
if (!keyFile?.keypair) {
|
|
5628
|
-
throw new Error(
|
|
5629
|
-
"received a sealed answer but this agent has no device key to open it \u2014 re-pair."
|
|
5630
|
-
);
|
|
5631
|
-
}
|
|
5632
|
-
return openSealedAnswer(reply.sealed, keyFile.keypair);
|
|
5633
|
-
}
|
|
5634
|
-
function decodeReply(item, deps = {}) {
|
|
5635
|
-
const { delivery: _delivery, ...withoutDelivery } = item;
|
|
5636
|
-
const enriched = { ...withoutDelivery };
|
|
5637
|
-
if (!enriched.sealed) return enriched;
|
|
5638
|
-
const answer = decryptAnswer(enriched, deps);
|
|
5639
|
-
const { sealed: _sealed, ...plaintext } = enriched;
|
|
5640
|
-
return { ...plaintext, answer };
|
|
5641
|
-
}
|
|
5642
|
-
function deliveryOf(item) {
|
|
5643
|
-
if (item.type !== "reply" || !("delivery" in item) || item.delivery === void 0) {
|
|
5644
|
-
return null;
|
|
5645
|
-
}
|
|
5646
|
-
const delivery = item.delivery;
|
|
5647
|
-
if (!delivery || typeof delivery.leaseId !== "string" || !delivery.leaseId || typeof delivery.expiresAt !== "string" || !delivery.expiresAt) {
|
|
5648
|
-
throw new Error(
|
|
5649
|
-
"Paigy returned invalid reply-delivery metadata; the answer was not acknowledged and remains recoverable."
|
|
5650
|
-
);
|
|
5651
|
-
}
|
|
5652
|
-
return delivery;
|
|
5653
|
-
}
|
|
5654
|
-
async function responseDetail(res) {
|
|
5655
|
-
try {
|
|
5656
|
-
return (await res.text()).slice(0, 300);
|
|
5657
|
-
} catch {
|
|
5658
|
-
return "";
|
|
5659
|
-
}
|
|
5660
|
-
}
|
|
5661
|
-
async function acknowledge(token, leaseId, opts = {}) {
|
|
5662
|
-
const attempts = Math.max(1, Math.min(opts.attempts ?? 3, 5));
|
|
5663
|
-
const backoffMs = Math.max(0, opts.backoffMs ?? 100);
|
|
5664
|
-
const doSleep = opts.doSleep ?? sleep4;
|
|
5665
|
-
let lastError;
|
|
5666
|
-
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
5667
|
-
try {
|
|
5668
|
-
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/await/ack`, {
|
|
5669
|
-
method: "POST",
|
|
5670
|
-
headers: {
|
|
5671
|
-
authorization: `Bearer ${token}`,
|
|
5672
|
-
"content-type": "application/json"
|
|
5673
|
-
},
|
|
5674
|
-
body: JSON.stringify({ leaseId })
|
|
5675
|
-
}));
|
|
5676
|
-
if (res.ok) return;
|
|
5677
|
-
const detail2 = await responseDetail(res);
|
|
5678
|
-
if (res.status === 409) throw new ReplyLeaseExpiredError();
|
|
5679
|
-
const retryable = res.status === 408 || res.status === 425 || res.status === 429 || res.status >= 500;
|
|
5680
|
-
throw new ReplyAckError(
|
|
5681
|
-
`Paigy reply acknowledgement failed: ${res.status}${detail2 ? ` ${detail2}` : ""}`,
|
|
5682
|
-
retryable
|
|
5683
|
-
);
|
|
5684
|
-
} catch (error) {
|
|
5685
|
-
if (error instanceof UnpairedError || error instanceof ReplyLeaseExpiredError || error instanceof ReplyAckError && !error.retryable) {
|
|
5686
|
-
throw error;
|
|
5687
|
-
}
|
|
5688
|
-
lastError = error;
|
|
5689
|
-
if (attempt + 1 < attempts) {
|
|
5690
|
-
await doSleep(backoffMs * 2 ** attempt);
|
|
5691
|
-
}
|
|
5581
|
+
async function subscribeWake(onNudge, opts = {}) {
|
|
5582
|
+
const cfg = await registerDelivery("self_hosted", opts);
|
|
5583
|
+
const { url, anonKey } = cfg.realtime;
|
|
5584
|
+
const channel = wakeChannel(cfg.tokenId);
|
|
5585
|
+
const sb = createClient(url, anonKey, { auth: { persistSession: false } });
|
|
5586
|
+
const ch = sb.channel(channel).on("broadcast", { event: WAKE_EVENT }, (msg) => onNudge(msg.payload ?? {}));
|
|
5587
|
+
ch.subscribe();
|
|
5588
|
+
return {
|
|
5589
|
+
tokenId: cfg.tokenId,
|
|
5590
|
+
channel,
|
|
5591
|
+
close: async () => {
|
|
5592
|
+
await sb.removeChannel(ch).catch(() => {
|
|
5593
|
+
});
|
|
5594
|
+
await registerDelivery("poll", opts).catch(() => {
|
|
5595
|
+
});
|
|
5692
5596
|
}
|
|
5693
|
-
}
|
|
5694
|
-
const detail = lastError instanceof Error ? lastError.message : String(lastError);
|
|
5695
|
-
throw new Error(
|
|
5696
|
-
`Could not acknowledge the Paigy reply after ${attempts} attempts (${detail}). The answer was not returned to the agent and remains recoverable after the lease expires.`
|
|
5697
|
-
);
|
|
5698
|
-
}
|
|
5699
|
-
async function ackReply(item, token, opts = {}) {
|
|
5700
|
-
const delivery = deliveryOf(item);
|
|
5701
|
-
if (!delivery) throw new Error("Paigy returned a reply without a delivery lease; nothing to acknowledge.");
|
|
5702
|
-
await acknowledge(token, delivery.leaseId, {
|
|
5703
|
-
attempts: opts.ackAttempts,
|
|
5704
|
-
backoffMs: opts.ackBackoffMs,
|
|
5705
|
-
doSleep: opts.ackSleep
|
|
5706
|
-
});
|
|
5597
|
+
};
|
|
5707
5598
|
}
|
|
5708
5599
|
|
|
5709
5600
|
export {
|
|
@@ -5729,15 +5620,13 @@ export {
|
|
|
5729
5620
|
fetchCredential,
|
|
5730
5621
|
pairStep,
|
|
5731
5622
|
UnpairedError,
|
|
5732
|
-
|
|
5623
|
+
getGoal,
|
|
5733
5624
|
overrideToken,
|
|
5734
|
-
authToken,
|
|
5735
5625
|
hatch,
|
|
5736
5626
|
whoAmI,
|
|
5737
5627
|
setIdentity,
|
|
5738
5628
|
heartbeat,
|
|
5739
|
-
|
|
5629
|
+
checkReplies,
|
|
5740
5630
|
runTool,
|
|
5741
|
-
|
|
5742
|
-
ackReply
|
|
5631
|
+
subscribeWake
|
|
5743
5632
|
};
|
package/dist/enable.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import {
|
|
3
3
|
PAIGY_TOOL_IDS,
|
|
4
4
|
enablePaigyTools
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-647HS22P.js";
|
|
6
|
+
import "./chunk-FFI3AIZE.js";
|
|
7
|
+
import "./chunk-YJEJZT56.js";
|
|
8
8
|
|
|
9
9
|
// src/enable.ts
|
|
10
10
|
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-BUTCP2ED.js";
|
|
9
9
|
import {
|
|
10
10
|
ENABLE_COMMAND,
|
|
11
11
|
OnboardSchema,
|
|
@@ -16,8 +16,8 @@ import {
|
|
|
16
16
|
autoConfigureClients,
|
|
17
17
|
claudeInstallHint,
|
|
18
18
|
paigyToolsAllowlisted
|
|
19
|
-
} from "./chunk-
|
|
20
|
-
import "./chunk-
|
|
19
|
+
} from "./chunk-647HS22P.js";
|
|
20
|
+
import "./chunk-FFI3AIZE.js";
|
|
21
21
|
import {
|
|
22
22
|
clearSurface,
|
|
23
23
|
writeSurface
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
setIdentity,
|
|
39
39
|
slotName,
|
|
40
40
|
whoAmI
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-YJEJZT56.js";
|
|
42
42
|
|
|
43
43
|
// src/index.ts
|
|
44
44
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
package/dist/listen.js
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "./chunk-3QUIB42M.js";
|
|
3
|
+
entryWords
|
|
4
|
+
} from "./chunk-FFI3AIZE.js";
|
|
6
5
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
sweepLeased
|
|
12
|
-
} from "./chunk-4UZAPBKK.js";
|
|
6
|
+
checkReplies,
|
|
7
|
+
getGoal,
|
|
8
|
+
subscribeWake
|
|
9
|
+
} from "./chunk-YJEJZT56.js";
|
|
13
10
|
|
|
14
11
|
// src/listen.ts
|
|
15
|
-
import { createClient } from "@supabase/supabase-js";
|
|
16
12
|
import { spawn } from "child_process";
|
|
17
13
|
import { realpathSync } from "fs";
|
|
18
14
|
import { fileURLToPath } from "url";
|
|
@@ -24,7 +20,7 @@ import { homedir } from "os";
|
|
|
24
20
|
import { join } from "path";
|
|
25
21
|
var LABEL = "ai.paigy.listen";
|
|
26
22
|
function frozenEnv() {
|
|
27
|
-
const keys = ["PAIGY_BACKEND_URL", "PAIGY_ON_WAKE"
|
|
23
|
+
const keys = ["PAIGY_BACKEND_URL", "PAIGY_ON_WAKE"];
|
|
28
24
|
return Object.fromEntries(keys.flatMap((k) => process.env[k] ? [[k, process.env[k]]] : []));
|
|
29
25
|
}
|
|
30
26
|
function renderLaunchd(nodePath, scriptPath, env) {
|
|
@@ -114,114 +110,49 @@ function uninstallService() {
|
|
|
114
110
|
return unit;
|
|
115
111
|
}
|
|
116
112
|
|
|
117
|
-
// src/message.ts
|
|
118
|
-
function answerText(answer) {
|
|
119
|
-
switch (answer.kind) {
|
|
120
|
-
case "text":
|
|
121
|
-
return answer.text;
|
|
122
|
-
case "option":
|
|
123
|
-
return answer.label ?? answer.optionId;
|
|
124
|
-
case "multi":
|
|
125
|
-
case "ranked":
|
|
126
|
-
return (answer.labels ?? answer.optionIds).join(", ");
|
|
127
|
-
case "confirm":
|
|
128
|
-
return answer.approved ? "approved" : "declined";
|
|
129
|
-
case "clarify":
|
|
130
|
-
return answer.chunks.join(" ");
|
|
131
|
-
case "turns":
|
|
132
|
-
return answer.turns.map((turn) => turn.reply).join(" ");
|
|
133
|
-
case "precedent":
|
|
134
|
-
return answer.answer;
|
|
135
|
-
case "ignored":
|
|
136
|
-
return "";
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
113
|
// src/listen.ts
|
|
141
114
|
var emit = (obj) => void process.stdout.write(JSON.stringify(obj) + "\n");
|
|
142
|
-
function
|
|
143
|
-
|
|
115
|
+
function saidIn(delivery) {
|
|
116
|
+
return delivery.entries.filter((entry) => entry.kind === "contribution").map((entry) => entryWords(entry)).filter(Boolean).join("\n");
|
|
117
|
+
}
|
|
118
|
+
function launchEnv(waiting, reason, goalId) {
|
|
119
|
+
const env = { PAIGY_WORK: JSON.stringify(waiting), PAIGY_EVENT: reason };
|
|
144
120
|
const put = (key, value) => {
|
|
145
121
|
if (value) env[key] = value;
|
|
146
122
|
};
|
|
147
|
-
const
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
if (request) {
|
|
158
|
-
put("PAIGY_THREAD_ID", request.parentId);
|
|
159
|
-
put("PAIGY_PARENT_ID", request.parentId);
|
|
160
|
-
put("PAIGY_NOTIFICATION_ID", request.notificationId);
|
|
161
|
-
put("PAIGY_CONTEXT_THREAD_ID", request.contextParentId);
|
|
162
|
-
put("PAIGY_TEXT", request.text);
|
|
163
|
-
return env;
|
|
164
|
-
}
|
|
165
|
-
const callback = work.owedCallbacks[0];
|
|
166
|
-
if (callback) {
|
|
167
|
-
put("PAIGY_THREAD_ID", callback.parentId);
|
|
168
|
-
put("PAIGY_PARENT_ID", callback.parentId);
|
|
169
|
-
put("PAIGY_TEXT", callback.note);
|
|
170
|
-
}
|
|
123
|
+
const named = goalId ? waiting.deliveries.find((d) => d.goalIds.includes(goalId)) : void 0;
|
|
124
|
+
const lead = named ?? waiting.deliveries[0];
|
|
125
|
+
if (!lead) return env;
|
|
126
|
+
put("PAIGY_THREAD_ID", lead.threadId);
|
|
127
|
+
put("PAIGY_PARENT_ID", lead.threadId);
|
|
128
|
+
put("PAIGY_DELIVERY_ID", lead.deliveryId);
|
|
129
|
+
put("PAIGY_GOAL_ID", waiting.goal?.goalId ?? lead.goalIds[0]);
|
|
130
|
+
put("PAIGY_CONTEXT_THREAD_ID", waiting.goal?.contextThreadId);
|
|
131
|
+
put("PAIGY_TEXT", saidIn(lead) || lead.message);
|
|
171
132
|
return env;
|
|
172
133
|
}
|
|
173
|
-
|
|
174
|
-
async function sweep(reason) {
|
|
134
|
+
async function sweep(reason, goalId) {
|
|
175
135
|
try {
|
|
176
136
|
const cmd = process.env.PAIGY_ON_WAKE;
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const { type: _type, ...reply } = decodeReply({ ...r, type: "reply" });
|
|
186
|
-
return reply;
|
|
187
|
-
})
|
|
188
|
-
};
|
|
189
|
-
emit({ type: "sweep", reason, ...work });
|
|
190
|
-
const inbound = work.replies.length > 0 || work.requests.length > 0;
|
|
191
|
-
const reengage = work.owedCallbacks.length > 0 && (reason === "boot" || reason.includes("callback"));
|
|
192
|
-
if (cmd && (inbound || reengage)) {
|
|
193
|
-
spawn(cmd, { shell: true, stdio: "inherit", env: { ...process.env, ...launchEnv(work, reason) } });
|
|
194
|
-
}
|
|
195
|
-
if (cmd) for (const r of leased) await ackReply({ ...r, type: "reply" }, token);
|
|
196
|
-
if (leased.length === 0) return;
|
|
137
|
+
const { deliveries } = await checkReplies();
|
|
138
|
+
const lead = (goalId ? deliveries.find((d) => d.goalIds.includes(goalId)) : void 0) ?? deliveries[0];
|
|
139
|
+
const leadGoalId = lead?.goalIds[0];
|
|
140
|
+
const goal = leadGoalId ? await getGoal(leadGoalId).then((g) => ({ goalId: leadGoalId, contextThreadId: g.contextThreadId, progress: g.progress })).catch(() => null) : null;
|
|
141
|
+
const waiting = { deliveries, goal };
|
|
142
|
+
emit({ type: "sweep", reason, ...goalId ? { about: goalId } : {}, ...waiting });
|
|
143
|
+
if (cmd && deliveries.length) {
|
|
144
|
+
spawn(cmd, { shell: true, stdio: "inherit", env: { ...process.env, ...launchEnv(waiting, reason, goalId) } });
|
|
197
145
|
}
|
|
198
146
|
} catch (e) {
|
|
199
147
|
emit({ type: "error", reason, message: e.message });
|
|
200
148
|
}
|
|
201
149
|
}
|
|
202
150
|
async function main() {
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
const anonKey = cfg.realtime?.anonKey ?? process.env.PAIGY_SUPABASE_ANON_KEY;
|
|
206
|
-
if (!url || !anonKey) {
|
|
207
|
-
throw new Error(
|
|
208
|
-
"No Realtime credentials \u2014 set SUPABASE_ANON_KEY on the server, or PAIGY_SUPABASE_URL + PAIGY_SUPABASE_ANON_KEY locally."
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
const channel = wakeChannel(cfg.tokenId);
|
|
212
|
-
emit({ type: "listening", channel });
|
|
151
|
+
const sub = await subscribeWake((nudge) => void sweep(`wake:${nudge.kind ?? "?"}`, nudge.parentId));
|
|
152
|
+
emit({ type: "listening", channel: sub.channel });
|
|
213
153
|
await sweep("boot");
|
|
214
|
-
const sb = createClient(url, anonKey, { auth: { persistSession: false } });
|
|
215
|
-
sb.channel(channel).on(
|
|
216
|
-
"broadcast",
|
|
217
|
-
{ event: WAKE_EVENT },
|
|
218
|
-
(msg) => void sweep(`wake:${msg.payload?.kind ?? "?"}`)
|
|
219
|
-
).subscribe();
|
|
220
154
|
const shutdown = async () => {
|
|
221
|
-
|
|
222
|
-
await registerDelivery("poll");
|
|
223
|
-
} catch {
|
|
224
|
-
}
|
|
155
|
+
await sub.close();
|
|
225
156
|
process.exit(0);
|
|
226
157
|
};
|
|
227
158
|
process.on("SIGINT", () => void shutdown());
|
|
@@ -251,5 +182,6 @@ if (isEntry()) {
|
|
|
251
182
|
}
|
|
252
183
|
export {
|
|
253
184
|
launchEnv,
|
|
185
|
+
saidIn,
|
|
254
186
|
sweep
|
|
255
187
|
};
|
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-BUTCP2ED.js";
|
|
7
7
|
import {
|
|
8
8
|
autoConfigureClients,
|
|
9
9
|
claudeInstallHint,
|
|
10
10
|
openBrowser
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import "./chunk-
|
|
11
|
+
} from "./chunk-647HS22P.js";
|
|
12
|
+
import "./chunk-FFI3AIZE.js";
|
|
13
13
|
import {
|
|
14
14
|
AGENT_NAME,
|
|
15
15
|
TOKEN_PATH,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
saveToken,
|
|
21
21
|
setIdentity,
|
|
22
22
|
whoAmI
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-YJEJZT56.js";
|
|
24
24
|
|
|
25
25
|
// src/onboard.ts
|
|
26
26
|
function reportRegistered() {
|
package/dist/slot.js
CHANGED
package/dist/statusline.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@paigy/mcp",
|
|
3
|
-
"version": "0.40.
|
|
3
|
+
"version": "0.40.2",
|
|
4
4
|
"description": "Paigy MCP server — the AI agent harness that calls you. Lets an agent notify a user and await their reply.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"typescript": "^5.7.2",
|
|
40
40
|
"vitest": "^2.1.8",
|
|
41
41
|
"@paigy/crypto": "0.0.0",
|
|
42
|
-
"@paigy/
|
|
43
|
-
"@paigy/
|
|
42
|
+
"@paigy/sdk": "0.2.1",
|
|
43
|
+
"@paigy/schema": "0.0.0"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "tsup",
|