@paigy/mcp 0.40.16 → 0.40.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -3
- package/dist/{chunk-JWMR6K6D.js → chunk-76ILN3YV.js} +71 -2
- package/dist/{chunk-SFGGZLVO.js → chunk-HGXXGOGG.js} +140 -12
- package/dist/{chunk-OGTH55QH.js → chunk-TRMYDJ5H.js} +19 -1
- package/dist/{chunk-6OBUH2FB.js → chunk-VYWIPFEY.js} +302 -55
- package/dist/{dist-6F22VALF.js → dist-GJOOJ4FR.js} +3 -1
- package/dist/enable.js +3 -3
- package/dist/index.js +12 -20
- package/dist/listen.js +21 -9
- package/dist/onboard.js +4 -4
- package/dist/slot.js +1 -1
- package/dist/stalled.js +15 -7
- package/dist/statusline.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -215,19 +215,48 @@ script and branch on `$PAIGY_EVENT` there:
|
|
|
215
215
|
codex exec "Paigy goal $PAIGY_GOAL_ID. claim_goal it first. The user said: $PAIGY_TEXT. Reply with contact when done."
|
|
216
216
|
```
|
|
217
217
|
|
|
218
|
-
## Stalled work (Claude Code
|
|
218
|
+
## Stalled work (turn-end hook — Claude Code, Codex, Gemini CLI, Antigravity)
|
|
219
219
|
|
|
220
220
|
An agent that goes quiet leaves its Goals behind. Three things hand it that work back, all
|
|
221
221
|
reading one rule (no progress for 3 days, `COLD_AFTER_MS`): `check_replies` lists it under
|
|
222
222
|
`stalled`, `paigy-listen` carries it in `PAIGY_WORK` (and launches for it on boot), and this
|
|
223
|
-
hook holds a
|
|
223
|
+
hook holds a session's turn end once a day to list it. One program, four tools:
|
|
224
|
+
|
|
225
|
+
**Claude Code** (`~/.claude/settings.json`) — installed by the paigy plugin:
|
|
224
226
|
|
|
225
227
|
```json
|
|
226
228
|
{ "hooks": { "Stop": [{ "hooks": [{ "type": "command", "command": "npx -y -p @paigy/mcp paigy-stalled" }] }] } }
|
|
227
229
|
```
|
|
228
230
|
|
|
231
|
+
**Codex** (`~/.codex/config.toml`) — its `Stop` event takes the same `{ decision, reason }`, so
|
|
232
|
+
the same program serves it. Written for you when you pair from another tool:
|
|
233
|
+
|
|
234
|
+
```toml
|
|
235
|
+
[[hooks.Stop]]
|
|
236
|
+
|
|
237
|
+
[[hooks.Stop.hooks]]
|
|
238
|
+
type = "command"
|
|
239
|
+
command = "npx -y -p @paigy/mcp@latest paigy-stalled --agent codex"
|
|
240
|
+
timeout = 60
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Gemini CLI / Antigravity** (`~/.gemini/settings.json`, `~/.gemini/antigravity-cli/settings.json`)
|
|
244
|
+
— the turn-end event there is `AfterAgent`, and the reminder also arrives as a `systemMessage`,
|
|
245
|
+
which is the field that event promises will reach somebody:
|
|
246
|
+
|
|
247
|
+
```json
|
|
248
|
+
{ "hooks": { "AfterAgent": [{ "matcher": "*", "hooks": [{ "name": "paigy-stalled", "type": "command",
|
|
249
|
+
"command": "npx -y -p @paigy/mcp@latest paigy-stalled --agent antigravity", "timeout": 60000 }] }] } }
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
`--agent <slot>` is not optional outside Claude Code: a hook is not the MCP server, so it never
|
|
253
|
+
inherits the `PAIGY_AGENT` that server was registered with, and without the flag it reads this
|
|
254
|
+
session's own slot — a different identity, with no stalled work of its own. Pass the same value as
|
|
255
|
+
the tool's MCP registration (`codex`, `antigravity`, or whatever you set `PAIGY_AGENT` to).
|
|
256
|
+
|
|
229
257
|
It is silent when nothing is stalled, when the stop is already being continued by a hook, and
|
|
230
|
-
after it has reminded that session once today.
|
|
258
|
+
after it has reminded that session once today. Codex's older `notify` program is not a place for
|
|
259
|
+
this: it is spawned fire-and-forget with stdio null, so nothing it prints can reach the agent.
|
|
231
260
|
|
|
232
261
|
## Statusline (Claude Code)
|
|
233
262
|
|
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
serverInstructions,
|
|
5
5
|
sessionStartHook,
|
|
6
6
|
withSessionStartHook
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-HGXXGOGG.js";
|
|
8
8
|
import {
|
|
9
9
|
agentName
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-VYWIPFEY.js";
|
|
11
11
|
|
|
12
12
|
// src/toolset.ts
|
|
13
13
|
var ONBOARD_DESCRIPTION = "Get this agent talking to Paigy \u2014 call it FIRST, before contact, and any time you're unsure who you are. One call, and it does whatever the situation needs: NOT SET UP \u2192 hatches an identity instantly if this machine holds a device credential (the user ran the Paigy desktop app or harness), otherwise starts the code ceremony; ALREADY SET UP \u2192 returns your current identity and offers the two things left to decide, renaming it or unpairing; TOKEN NO LONGER VALID \u2192 says so, then re-pairs. Pass { name, voice } to choose who you are when hatching, or to RENAME yourself when already set up (voices: rachel, george, jessica, brian, lily). Safe to call any time: idempotent, and it never writes settings \u2014 the tool-allowlist state it reports is read-only. If it returns a `user_code`, print it to the user immediately and call onboard again with the `device_code`. If it returns `enable_command`, PRINT that command for the user to run \u2014 you cannot apply it yourself (it writes your own permission allowlist, which hosts block as privilege escalation), so print it, don't wait for it, and carry on.";
|
|
@@ -99,6 +99,55 @@ function withPaigyMcpToml(existing, agent) {
|
|
|
99
99
|
].join("\n");
|
|
100
100
|
return existing + entry;
|
|
101
101
|
}
|
|
102
|
+
function stalledHookCommand(agent) {
|
|
103
|
+
return `npx -y -p @paigy/mcp@latest paigy-stalled --agent ${agent}`;
|
|
104
|
+
}
|
|
105
|
+
function withPaigyStopHookToml(existing, agent) {
|
|
106
|
+
if (existing.includes("paigy-stalled")) return null;
|
|
107
|
+
const entry = [
|
|
108
|
+
"",
|
|
109
|
+
"[[hooks.Stop]]",
|
|
110
|
+
"",
|
|
111
|
+
"[[hooks.Stop.hooks]]",
|
|
112
|
+
'type = "command"',
|
|
113
|
+
`command = "${stalledHookCommand(agent)}"`,
|
|
114
|
+
"timeout = 60",
|
|
115
|
+
// seconds, in Codex — a cold `npx` resolve is slow the first time
|
|
116
|
+
""
|
|
117
|
+
].join("\n");
|
|
118
|
+
return existing + entry;
|
|
119
|
+
}
|
|
120
|
+
function withPaigyStopHookJson(existing, agent) {
|
|
121
|
+
let config = {};
|
|
122
|
+
if (existing) {
|
|
123
|
+
try {
|
|
124
|
+
config = JSON.parse(existing);
|
|
125
|
+
} catch {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const hooks = config.hooks ?? {};
|
|
130
|
+
const kept = (hooks.AfterAgent ?? []).map((group) => ({ ...group, hooks: (group.hooks ?? []).filter((hook) => hook.name !== "paigy-stalled") })).filter((group) => group.hooks.length > 0);
|
|
131
|
+
config.hooks = {
|
|
132
|
+
...hooks,
|
|
133
|
+
AfterAgent: [
|
|
134
|
+
...kept,
|
|
135
|
+
{
|
|
136
|
+
matcher: "*",
|
|
137
|
+
hooks: [
|
|
138
|
+
{
|
|
139
|
+
name: "paigy-stalled",
|
|
140
|
+
type: "command",
|
|
141
|
+
command: stalledHookCommand(agent),
|
|
142
|
+
timeout: 6e4
|
|
143
|
+
// milliseconds, in Gemini CLI
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
]
|
|
148
|
+
};
|
|
149
|
+
return JSON.stringify(config, null, 2);
|
|
150
|
+
}
|
|
102
151
|
function autoConfigureClients(skip = agentName()) {
|
|
103
152
|
const home = homedir();
|
|
104
153
|
const registered = [];
|
|
@@ -118,6 +167,20 @@ function autoConfigureClients(skip = agentName()) {
|
|
|
118
167
|
} catch {
|
|
119
168
|
}
|
|
120
169
|
}
|
|
170
|
+
for (const path of [
|
|
171
|
+
join(home, ".gemini", "settings.json"),
|
|
172
|
+
join(home, ".gemini", "antigravity-cli", "settings.json")
|
|
173
|
+
]) {
|
|
174
|
+
try {
|
|
175
|
+
if (!existsSync(dirname(path))) continue;
|
|
176
|
+
const existing = existsSync(path) ? readFileSync(path, "utf8") : null;
|
|
177
|
+
const updated = withPaigyStopHookJson(existing, "antigravity");
|
|
178
|
+
if (updated === null) continue;
|
|
179
|
+
writeFileSync(path, updated);
|
|
180
|
+
registered.push(`Paigy's turn-end reminder (${path})`);
|
|
181
|
+
} catch {
|
|
182
|
+
}
|
|
183
|
+
}
|
|
121
184
|
}
|
|
122
185
|
if (skip !== "codex") {
|
|
123
186
|
const codexConfig = join(home, ".codex", "config.toml");
|
|
@@ -129,6 +192,12 @@ function autoConfigureClients(skip = agentName()) {
|
|
|
129
192
|
writeFileSync(codexConfig, updated);
|
|
130
193
|
registered.push(`OpenAI Codex (${codexConfig})`);
|
|
131
194
|
}
|
|
195
|
+
const afterMcp = existsSync(codexConfig) ? readFileSync(codexConfig, "utf8") : "";
|
|
196
|
+
const withHook = withPaigyStopHookToml(afterMcp, "codex");
|
|
197
|
+
if (withHook !== null) {
|
|
198
|
+
writeFileSync(codexConfig, withHook);
|
|
199
|
+
registered.push(`Paigy's turn-end reminder (${codexConfig})`);
|
|
200
|
+
}
|
|
132
201
|
}
|
|
133
202
|
} catch {
|
|
134
203
|
}
|
|
@@ -68,7 +68,7 @@ var StartContactSchema = z2.object({
|
|
|
68
68
|
}).strict();
|
|
69
69
|
var ContactSchema = z2.union([StartContactSchema, z2.object({ deliveryId: z2.string().uuid() }).strict()]);
|
|
70
70
|
var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
|
|
71
|
-
var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. An ask that names a Goal (parentId) goes onto that Goal; one that names none is placed in the person's tree by Paigy. Notification returns immediately; collect durable answers with check_replies. On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. One question per 'ask'; several questions are several objects in 'asks'. A bundled ask is taken apart into its own cards by Paigy's one intake read \u2014 parts of one piece of work under one Goal, separate things as separate Goals.";
|
|
71
|
+
var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. An ask that names a Goal (parentId) goes onto that Goal; one that names none is placed in the person's tree by Paigy. Notification returns immediately; collect durable answers with check_replies. On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. One question per 'ask'; several questions are several objects in 'asks'. A bundled ask is taken apart into its own cards by Paigy's one intake read \u2014 parts of one piece of work under one Goal, separate things as separate Goals. An ask with no options that is not blocking is a report: on a Goal whose report card is still open it is added to that card, with no new push; one that only says where your work stands while under way is recorded as the Goal's progress, not sent (the result says so).";
|
|
72
72
|
var CreateGoalSchema = z3.object({
|
|
73
73
|
outcome: z3.string().trim().min(1).max(1e4),
|
|
74
74
|
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
|
|
@@ -113,11 +113,16 @@ var UpdateGoalSchema = z3.object({
|
|
|
113
113
|
}).strict();
|
|
114
114
|
var UpdateGoalToolSchema = UpdateGoalSchema.omit({ operationId: true }).extend({ goalId: z3.string().uuid() }).strict();
|
|
115
115
|
var ClaimGoalSchema = z3.object({ goalId: z3.string().uuid().optional() }).strict();
|
|
116
|
-
var GetGoalSchema = z3.object({
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
var GetGoalSchema = z3.object({
|
|
117
|
+
goalId: z3.string().uuid(),
|
|
118
|
+
/** Every entry in full. Without it the read carries the person's words, open questions and your
|
|
119
|
+
* newest entry, and counts what it left out (`apps/api/src/goal/collapse.ts`). */
|
|
120
|
+
history: z3.boolean().optional()
|
|
121
|
+
}).strict();
|
|
122
|
+
var GET_GOAL_DESCRIPTION = "Read one Goal without claiming it: its outcome, state, revision, progress, blockers, the conversation on it (each question with its options and what was decided), `next`, the one step to take, and `more`, where to read further. The conversation carries everything the person said, every open question and your newest entry; your older entries are left out and counted \u2014 history: true reads every entry in full. Another account's Goals, and another agent's Goal that is not below one of yours, are not disclosed.";
|
|
123
|
+
var UPDATE_GOAL_DESCRIPTION = `Update an owned Goal at an exact revision. State, ownership, dependencies, children, progress, title, and review acknowledgement are explicit; stale revisions are rejected. title is the work's name in one to five words, as a person would refer to it out loud (it is spoken on a call and heads every list); null clears it. reviewed: true acknowledges new evidence and closes the Deliveries addressed to you on that Goal, never over an open decision. dueAt (an ISO instant, or null) makes the Goal wait until then; when it passes you are woken for it \u2014 use it for a promise to follow up later. state: done means the work was accomplished, and it can be reopened: when the person says it is not done ("it's not working"), set state: active on that same Goal instead of starting a new one; its parent reopens with it. cancelled is final. Returns the Goal as get_goal reads it, at its new revision.`;
|
|
119
124
|
var CLAIM_GOAL_DESCRIPTION = "Claim the oldest runnable or review-pending Goal you own, or pass goalId to claim that Goal. Another agent's Goal that has gone quiet for 3 days (check_replies lists them as stalledOthers) is taken over when you claim it, and becomes yours to finish or cancel. Returns the Goal as get_goal reads it, and creates or renews the execution lease.";
|
|
120
|
-
var CHECK_REPLIES_DESCRIPTION = "What is waiting for you: your open Deliveries (a request the user started toward you, a handoff), one row each with its Goals, how many decisions are still open, and the newest words in brief; and `review`, your Goals with something new on them \u2014 the user's answers and notes are Entries on the Goal, not Deliveries. A pure read with no arguments: nothing is consumed, acknowledged or claimed by reading it, so call it on startup, after a long wait, or whenever you want to know what is outstanding. To act on one, claim its Goal (claim_goal) or reread a Delivery in full with contact({deliveryId}). Once you have acted on what arrived, update_goal with reviewed: true clears the Goal from `review` and closes the Deliveries addressed to you on it.";
|
|
125
|
+
var CHECK_REPLIES_DESCRIPTION = "What is waiting for you: your open Deliveries (a request the user started toward you, a handoff), one row each with its Goals, how many decisions are still open, and the newest words in brief; `assigned`, your Goals nobody has started yet, however they became yours (claim_goal({goalId}) starts one); and `review`, your Goals with something new on them \u2014 the user's answers and notes are Entries on the Goal, not Deliveries. A pure read with no arguments: nothing is consumed, acknowledged or claimed by reading it, so call it on startup, after a long wait, or whenever you want to know what is outstanding. To act on one, claim its Goal (claim_goal) or reread a Delivery in full with contact({deliveryId}). Once you have acted on what arrived, update_goal with reviewed: true clears the Goal from `review` and closes the Deliveries addressed to you on it.";
|
|
121
126
|
var CheckRepliesSchema = z3.object({}).strict();
|
|
122
127
|
var AGENT_TOOLS = [
|
|
123
128
|
{ name: "contact", description: CONTACT_DESCRIPTION, inputSchema: CONTACT_SCHEMA },
|
|
@@ -128,9 +133,10 @@ var AGENT_TOOLS = [
|
|
|
128
133
|
{ name: "update_goal", description: UPDATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(UpdateGoalToolSchema) }
|
|
129
134
|
];
|
|
130
135
|
var AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
|
|
136
|
+
var DONE_MESSAGE = "When the work is done, send one message through contact saying what is done and anything the person needs to do or check.";
|
|
131
137
|
function serverInstructions(opts) {
|
|
132
138
|
const calls = opts.waits ? "A Call holds one bounded window here; continue with contact({deliveryId}) to hold the next or reread that exact Delivery." : "A Call returns after one read; continue with contact({deliveryId}) to reread that exact Delivery.";
|
|
133
|
-
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 the conversation on it. get_goal rereads it without claiming. Every read ends in \`next\`, the one step to take. Notifications return immediately: keep working and collect answers through claim_goal/get_goal. ${calls} Evidence can repeat: reads do not acknowledge or hide it. Report progress and acknowledge review with update_goal, never as a contact. Never infer ringing from an open Call Delivery. Soft waiting and re-presentation are unsupported. Your user is remote. Always interact with the user through Paigy. For decisions, approvals, or questions, contact them with structured options. Never assume anyone is reading the terminal stdout.
|
|
139
|
+
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 the conversation on it. get_goal rereads it without claiming. Every read ends in \`next\`, the one step to take. Notifications return immediately: keep working and collect answers through claim_goal/get_goal. ${calls} Evidence can repeat: reads do not acknowledge or hide it. Report progress and acknowledge review with update_goal, never as a contact. ${DONE_MESSAGE} Never infer ringing from an open Call Delivery. Soft waiting and re-presentation are unsupported. Your user is remote. Always interact with the user through Paigy. For decisions, approvals, or questions, contact them with structured options. Never assume anyone is reading the terminal stdout.
|
|
134
140
|
|
|
135
141
|
HOW TO ASK:
|
|
136
142
|
1. One question per ask. Five questions are five objects in \`asks\`, in one contact, so each can be answered on its own; one question with five parts settles nothing until all five are answered.
|
|
@@ -472,9 +478,9 @@ var AgendaTurnSchema = z4.object({
|
|
|
472
478
|
/** Twin coverage (#1089): sibling claim ids this asking turn's answer ALSO settles —
|
|
473
479
|
* the planner declares duplicates instead of asking them twice. */
|
|
474
480
|
coveredIds: z4.array(z4.string()).optional(),
|
|
475
|
-
/**
|
|
476
|
-
*
|
|
477
|
-
info: z4.array(z4.string().min(1)).
|
|
481
|
+
/** The spoken sentences of the turn, in order. No count: how long a turn is is the brain's call
|
|
482
|
+
* (owner, 2026-09-25), and a count here refused whole plans. */
|
|
483
|
+
info: z4.array(z4.string().min(1)).default([]),
|
|
478
484
|
question: z4.string().min(1).nullable(),
|
|
479
485
|
/** True on the one turn carrying the agent's own declared question. */
|
|
480
486
|
asks: z4.boolean().optional(),
|
|
@@ -558,7 +564,13 @@ var InboxItemSchema = z4.object({
|
|
|
558
564
|
* request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
|
|
559
565
|
* carry for the call's one agent, so the phone draws it with the same seed. Absent when the
|
|
560
566
|
* author is not an agent this account holds (unpaired since, or a person). */
|
|
561
|
-
agent: z4.object({ tokenId: z4.string(), name: z4.string(), voice: VoiceKeySchema.optional() }).optional()
|
|
567
|
+
agent: z4.object({ tokenId: z4.string(), name: z4.string(), voice: VoiceKeySchema.optional() }).optional(),
|
|
568
|
+
/** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
|
|
569
|
+
* its need offers, exactly as its own card carries them, present only when one of them has a
|
|
570
|
+
* preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
|
|
571
|
+
* never re-sent as a second card to be seen mid-call. Words-only options are absent — the bot
|
|
572
|
+
* says those, and the list stays small (an `html` is up to 16 KB). */
|
|
573
|
+
options: z4.array(OptionSchema).optional()
|
|
562
574
|
})).optional(),
|
|
563
575
|
visuals: z4.array(VisualSchema).optional(),
|
|
564
576
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
@@ -585,6 +597,11 @@ var InboxItemSchema = z4.object({
|
|
|
585
597
|
* and "dismiss the stalled ones" cancelled 37 pieces of live work). */
|
|
586
598
|
cold: z4.string().datetime().optional(),
|
|
587
599
|
clarifies: z4.string().optional(),
|
|
600
|
+
/** THIS CARD'S QUESTION IS ON A LIVE CALL (owner, 2026-09-24: "Mark it while the call is
|
|
601
|
+
* live"). Present only while an open Call Delivery carries the card's request Entry — read
|
|
602
|
+
* off the same open list the card came from, so it clears when the Call does. A card is the
|
|
603
|
+
* backup for a call not taken; while the call has it, the call is where it is answered. */
|
|
604
|
+
onCall: z4.literal(true).optional(),
|
|
588
605
|
/** THE RING, ON THE ITEM (walk/design.md §12 §17, #2251): the last ring on this card was
|
|
589
606
|
* declined, and what the ladder will do next — read off the cron's own row, never computed
|
|
590
607
|
* on the phone. Present only while a `declined` receipt stands on the card's last Call.
|
|
@@ -949,7 +966,19 @@ var QueueItemSchema = z4.object({
|
|
|
949
966
|
/** When its owner last SAID something about it (`goals.last_progress_at`, written by every
|
|
950
967
|
* `update_goal` that changes `progress`). `updatedAt` moves for reasons nobody chose — a
|
|
951
968
|
* state recomputed, a review flag — so it cannot tell work in hand from work gone quiet. */
|
|
952
|
-
lastProgressAt: z4.string().nullable().optional()
|
|
969
|
+
lastProgressAt: z4.string().nullable().optional(),
|
|
970
|
+
/** THE GOAL'S NEWEST WORD, FROM EITHER SIDE (owner, 2026-09-27): the newest Entry on it, of any
|
|
971
|
+
* kind — what the person added ("Add to this"), their reply, the agent's ask or its progress
|
|
972
|
+
* note. A progress note is an Entry, so this is already the newer of the two: the person's note
|
|
973
|
+
* shows the moment it is written, and the agent's reply or next note replaces it by being newer.
|
|
974
|
+
* `said` is bounded to 280 characters server-side (a line, not the conversation). Null when the
|
|
975
|
+
* Goal carries no readable Entry; optional, so hand-built queues need not spell it. */
|
|
976
|
+
latest: z4.object({
|
|
977
|
+
from: z4.enum(["person", "agent"]),
|
|
978
|
+
said: z4.string(),
|
|
979
|
+
at: z4.string(),
|
|
980
|
+
entryId: z4.string()
|
|
981
|
+
}).nullable().optional()
|
|
953
982
|
});
|
|
954
983
|
var COLD_AFTER_MS = 3 * 24 * 60 * 60 * 1e3;
|
|
955
984
|
var NoteSourceSchema = z4.enum(["app", "call"]);
|
|
@@ -1125,6 +1154,104 @@ var NotificationFeedbackKindSchema = z4.enum([
|
|
|
1125
1154
|
"other"
|
|
1126
1155
|
// anything else — the note carries it.
|
|
1127
1156
|
]);
|
|
1157
|
+
var SlimOptionSchema = OptionSchema.omit({ html: true });
|
|
1158
|
+
var QuestionRowSchema = z4.object({
|
|
1159
|
+
/** The card's id (`deliveryId:needId`, or `deliveryId:entryId` for an update), as the inbox mints it. */
|
|
1160
|
+
id: z4.string(),
|
|
1161
|
+
deliveryId: z4.string(),
|
|
1162
|
+
entryId: z4.string(),
|
|
1163
|
+
/** The decision it waits on; null for an update, which asks nothing. */
|
|
1164
|
+
needId: z4.string().nullable(),
|
|
1165
|
+
goalIds: z4.array(z4.string()),
|
|
1166
|
+
/** The name of the work it is about, when the read could word it. */
|
|
1167
|
+
goalTitle: z4.string().optional(),
|
|
1168
|
+
tokenId: z4.string().optional(),
|
|
1169
|
+
name: z4.string(),
|
|
1170
|
+
title: z4.string(),
|
|
1171
|
+
body: z4.string(),
|
|
1172
|
+
select: z4.enum(["one", "many", "rank", "confirm", "text"]),
|
|
1173
|
+
options: z4.array(SlimOptionSchema),
|
|
1174
|
+
hasPreview: z4.boolean(),
|
|
1175
|
+
blocking: z4.boolean(),
|
|
1176
|
+
askedAt: z4.string().datetime(),
|
|
1177
|
+
ring: InboxItemSchema.shape.ring,
|
|
1178
|
+
onCall: z4.literal(true).optional(),
|
|
1179
|
+
sealed: z4.boolean()
|
|
1180
|
+
});
|
|
1181
|
+
var WorkStateSchema = z4.enum(["ready", "active", "waiting", "done", "cancelled"]);
|
|
1182
|
+
var WorkRowSchema = z4.object({
|
|
1183
|
+
id: z4.string(),
|
|
1184
|
+
parentId: z4.string().nullable(),
|
|
1185
|
+
title: z4.string(),
|
|
1186
|
+
/** Straight off the Goal. */
|
|
1187
|
+
state: WorkStateSchema,
|
|
1188
|
+
owner: z4.string().nullable(),
|
|
1189
|
+
revision: z4.number().int(),
|
|
1190
|
+
/** Open questions on it, counted to 100. */
|
|
1191
|
+
waiting: z4.number().int(),
|
|
1192
|
+
/** Held by a gate on work that is not done. */
|
|
1193
|
+
blocked: z4.boolean(),
|
|
1194
|
+
lastProgressAt: z4.string().datetime().nullable(),
|
|
1195
|
+
/** The line written for its newest progress note, else that note's first words. */
|
|
1196
|
+
line: z4.string().nullable(),
|
|
1197
|
+
/** Work directly under it, counted to 100; the list carries up to 12 of them. */
|
|
1198
|
+
children: z4.number().int(),
|
|
1199
|
+
createdAt: z4.string().datetime(),
|
|
1200
|
+
updatedAt: z4.string().datetime(),
|
|
1201
|
+
/** When anything at or under it last moved — the order the list is in. */
|
|
1202
|
+
activeAt: z4.string().datetime(),
|
|
1203
|
+
/** A sealed outcome has no title here; the work's page opens it. */
|
|
1204
|
+
sealed: z4.boolean()
|
|
1205
|
+
});
|
|
1206
|
+
var ComputerRowSchema = ConnectionSummarySchema.omit({ activity: true });
|
|
1207
|
+
var AgentRowSchema = ComputerRowSchema.extend({
|
|
1208
|
+
/** Open questions it is asking the person, over every open card; null when that read failed. */
|
|
1209
|
+
asking: z4.number().int().nullable(),
|
|
1210
|
+
oldestAskAt: z4.string().datetime().nullable(),
|
|
1211
|
+
/** Up to three of the live Goals it holds, oldest first (the order it picks them up), and how
|
|
1212
|
+
* many in all among the account's 200 most recently active agent-held live Goals
|
|
1213
|
+
* (`agent_holds`); null when that read failed. */
|
|
1214
|
+
holds: z4.array(z4.object({ id: z4.string(), title: z4.string() })).nullable(),
|
|
1215
|
+
held: z4.number().int().nullable(),
|
|
1216
|
+
/** The earliest instant any Goal it holds went quiet, by the one rule (`coldSince`); null
|
|
1217
|
+
* while none has, or when that read failed. */
|
|
1218
|
+
cold: z4.string().datetime().nullable(),
|
|
1219
|
+
/** The newest line of its working log, and when the harness saw it. */
|
|
1220
|
+
line: z4.string().nullable(),
|
|
1221
|
+
lineAt: z4.string().datetime().nullable()
|
|
1222
|
+
});
|
|
1223
|
+
var SnapshotSchema = z4.object({
|
|
1224
|
+
/** The API's clock, taken before the first read: what a later delta will start from. */
|
|
1225
|
+
at: z4.string().datetime(),
|
|
1226
|
+
questions: z4.object({
|
|
1227
|
+
/** The newest 30 open cards, questions before updates. */
|
|
1228
|
+
items: z4.array(QuestionRowSchema),
|
|
1229
|
+
/** Every open question, and apart from them every update, and what was put off. */
|
|
1230
|
+
total: z4.number().int(),
|
|
1231
|
+
updates: z4.number().int(),
|
|
1232
|
+
putOff: z4.number().int()
|
|
1233
|
+
}).nullable(),
|
|
1234
|
+
agents: z4.object({
|
|
1235
|
+
/** Up to 60, most recently seen first. */
|
|
1236
|
+
items: z4.array(AgentRowSchema),
|
|
1237
|
+
more: z4.boolean()
|
|
1238
|
+
}).nullable(),
|
|
1239
|
+
work: z4.object({
|
|
1240
|
+
/** The 60 most recently active roots, each followed by up to 12 children; 240 rows at most. */
|
|
1241
|
+
items: z4.array(WorkRowSchema),
|
|
1242
|
+
/** How much work is behind each of the Work tab's four filters, each counted to 100, read with
|
|
1243
|
+
* the rows. `work_list` (20260928023533) owns the predicates: Live is `ready`, `active` or
|
|
1244
|
+
* `waiting`; Waiting on you is live work with an open question or an unmet gate; Not started
|
|
1245
|
+
* is `ready`; Done is `done` or `cancelled`. */
|
|
1246
|
+
counts: z4.object({ live: z4.number().int(), waiting: z4.number().int(), notStarted: z4.number().int(), done: z4.number().int() })
|
|
1247
|
+
}).nullable(),
|
|
1248
|
+
you: z4.object({
|
|
1249
|
+
settings: UserSettingsSchema,
|
|
1250
|
+
callable: z4.boolean(),
|
|
1251
|
+
/** Up to 20 paired computers; null when the roster read failed. */
|
|
1252
|
+
computers: z4.array(ComputerRowSchema).nullable()
|
|
1253
|
+
}).nullable()
|
|
1254
|
+
});
|
|
1128
1255
|
|
|
1129
1256
|
// src/listening.ts
|
|
1130
1257
|
import { chmodSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
@@ -1149,7 +1276,8 @@ next check_replies. Start your own listener now, as a BACKGROUND task whose outp
|
|
|
1149
1276
|
npx -y -p @paigy/mcp@latest paigy-listen
|
|
1150
1277
|
|
|
1151
1278
|
Each line it prints is a Delivery waiting for you: read the Goal, and reply with contact when
|
|
1152
|
-
the answer needs a decision. Start it once; if it is already running, do nothing.
|
|
1279
|
+
the answer needs a decision. Start it once; if it is already running, do nothing. Then call
|
|
1280
|
+
claim_goal once: work handed to you before this session started waits there.
|
|
1153
1281
|
PAIGY
|
|
1154
1282
|
exit 0
|
|
1155
1283
|
`;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
+
NameTakenError,
|
|
2
3
|
isNetworkError,
|
|
3
4
|
pollToken,
|
|
4
5
|
requestCode,
|
|
5
6
|
saveToken,
|
|
7
|
+
setIdentity,
|
|
6
8
|
sleep
|
|
7
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-VYWIPFEY.js";
|
|
8
10
|
|
|
9
11
|
// src/identity.ts
|
|
10
12
|
var CLIENT_LABELS = {
|
|
@@ -25,6 +27,21 @@ function suggestedAgentName(clientName) {
|
|
|
25
27
|
if (!clientName) return void 0;
|
|
26
28
|
return CLIENT_LABELS[clientName.toLowerCase()] ?? clientName.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
27
29
|
}
|
|
30
|
+
async function identify(who, ask) {
|
|
31
|
+
let renamed = false;
|
|
32
|
+
if (ask.name || ask.voice) {
|
|
33
|
+
try {
|
|
34
|
+
who = await setIdentity({ ...ask.name ? { name: ask.name } : {}, ...ask.voice ? { voice: ask.voice } : {} });
|
|
35
|
+
renamed = true;
|
|
36
|
+
} catch (e) {
|
|
37
|
+
const message2 = e instanceof NameTakenError ? e.message : `The rename did not go through (${e.message}); nothing changed. Call onboard again to retry, or carry on.`;
|
|
38
|
+
return { name: who.name, voice: who.voice, message: message2 };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const voice = who.voice ? ` (voice: ${who.voice})` : "";
|
|
42
|
+
const message = !who.name ? [renamed && who.voice ? `Your voice is ${who.voice} now.` : "", who.unnamed ?? "Set up, with no name yet."].filter(Boolean).join(" ") : renamed ? `Renamed \u2014 this agent is "${who.name}"${voice} now.` : `Already set up as "${who.name}"${voice}. Ask the user if they want to rename it or unpair; otherwise carry on.`;
|
|
43
|
+
return { name: who.name, voice: who.voice, message };
|
|
44
|
+
}
|
|
28
45
|
|
|
29
46
|
// src/pairing.ts
|
|
30
47
|
async function resolvePairing(deviceCode, capMs, pollMs = 2e3) {
|
|
@@ -102,6 +119,7 @@ function clearPairing(deviceCode) {
|
|
|
102
119
|
|
|
103
120
|
export {
|
|
104
121
|
suggestedAgentName,
|
|
122
|
+
identify,
|
|
105
123
|
resolvePairing,
|
|
106
124
|
startPairing,
|
|
107
125
|
joinBackgroundPair,
|