@paigy/mcp 0.40.23 → 0.40.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -23
- package/dist/{chunk-BNMMRKEY.js → chunk-2ROD77AW.js} +1 -1
- package/dist/{chunk-PR7ROBN4.js → chunk-EJYXBEWN.js} +959 -621
- package/dist/{chunk-JLRBZZNT.js → chunk-GHSB2VP6.js} +752 -517
- package/dist/chunk-RK5LT7NH.js +110 -0
- package/dist/{chunk-HKODQKUV.js → chunk-UAVXYNLV.js} +9 -9
- package/dist/{dist-B7AOS5TG.js → dist-NMHU2UNG.js} +19 -7
- package/dist/enable.js +3 -3
- package/dist/index.js +18 -7
- package/dist/listen.js +85 -17
- package/dist/onboard.js +4 -4
- package/dist/slot.js +1 -1
- package/dist/stalled.js +3 -3
- package/dist/statusline.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// src/wake.ts
|
|
2
|
+
import { execFile, spawn } from "child_process";
|
|
3
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
4
|
+
import { dirname } from "path";
|
|
5
|
+
function wakeTarget(env) {
|
|
6
|
+
if (env.PAIGY_HARNESS) return { kind: "harness" };
|
|
7
|
+
if (env.PAIGY_ON_WAKE) return { kind: "command", command: env.PAIGY_ON_WAKE };
|
|
8
|
+
if (env.CODEX_THREAD_ID) return { kind: "codex", thread: env.CODEX_THREAD_ID };
|
|
9
|
+
return { kind: "output" };
|
|
10
|
+
}
|
|
11
|
+
function readWakeStatus(path) {
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
14
|
+
} catch {
|
|
15
|
+
return void 0;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
var WakeBridge = class {
|
|
19
|
+
constructor(env, path) {
|
|
20
|
+
this.env = env;
|
|
21
|
+
this.path = path;
|
|
22
|
+
const target = wakeTarget(env);
|
|
23
|
+
const saved = readWakeStatus(path);
|
|
24
|
+
this.state = { ...JSON.stringify(saved?.target) === JSON.stringify(target) ? saved : {}, target, ready: false };
|
|
25
|
+
}
|
|
26
|
+
env;
|
|
27
|
+
path;
|
|
28
|
+
state;
|
|
29
|
+
checked = false;
|
|
30
|
+
status() {
|
|
31
|
+
return { ...this.state };
|
|
32
|
+
}
|
|
33
|
+
save() {
|
|
34
|
+
mkdirSync(dirname(this.path), { recursive: true });
|
|
35
|
+
const temp = `${this.path}.${process.pid}.tmp`;
|
|
36
|
+
writeFileSync(temp, JSON.stringify(this.state), { mode: 384 });
|
|
37
|
+
renameSync(temp, this.path);
|
|
38
|
+
}
|
|
39
|
+
run(command, args, extra = {}) {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
execFile(command, args, { env: { ...this.env, ...extra }, timeout: 15e3, maxBuffer: 64 * 1024 }, (error, stdout, stderr) => {
|
|
42
|
+
if (error) reject(new Error(`${error.message}${stderr ? `: ${stderr.trim()}` : ""}`));
|
|
43
|
+
else resolve(stdout);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
launch(command, extra) {
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
const child = spawn("/bin/sh", ["-c", command], { stdio: "inherit", env: { ...this.env, ...extra } });
|
|
50
|
+
child.once("spawn", resolve);
|
|
51
|
+
child.once("error", reject);
|
|
52
|
+
child.once("exit", (code, signal) => {
|
|
53
|
+
if (code === 0) return;
|
|
54
|
+
this.state.ready = false;
|
|
55
|
+
this.state.lastError = `PAIGY_ON_WAKE exited ${signal ?? code}`;
|
|
56
|
+
this.save();
|
|
57
|
+
process.stderr.write(`Paigy wake error: ${this.state.lastError}
|
|
58
|
+
`);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
async prepare() {
|
|
63
|
+
if (this.checked) return;
|
|
64
|
+
try {
|
|
65
|
+
if (this.state.target.kind === "codex") {
|
|
66
|
+
const help = await this.run("codex", ["queue", "--help"]);
|
|
67
|
+
if (!help.includes("--thread") || !help.includes("--message")) throw new Error("Installed Codex does not support queue --thread --message; update Codex or configure PAIGY_ON_WAKE.");
|
|
68
|
+
}
|
|
69
|
+
this.checked = true;
|
|
70
|
+
this.state.ready = this.state.target.kind === "codex" || this.state.target.kind === "command" || this.state.target.kind === "harness";
|
|
71
|
+
delete this.state.lastError;
|
|
72
|
+
this.save();
|
|
73
|
+
} catch (error) {
|
|
74
|
+
this.state.ready = false;
|
|
75
|
+
this.state.lastError = error.message;
|
|
76
|
+
this.save();
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async deliver(fingerprint, message, extra) {
|
|
81
|
+
await this.prepare();
|
|
82
|
+
const target = this.state.target;
|
|
83
|
+
if (target.kind === "output" || target.kind === "harness") return;
|
|
84
|
+
const fingerprints = fingerprint === void 0 ? void 0 : typeof fingerprint === "string" ? [fingerprint] : fingerprint;
|
|
85
|
+
if (target.kind === "codex" && fingerprints?.every((item) => this.state.fingerprints?.includes(item))) {
|
|
86
|
+
this.state.fingerprints = fingerprints;
|
|
87
|
+
this.save();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
if (target.kind === "codex") await this.run("codex", ["queue", "--thread", target.thread, "--message", message]);
|
|
92
|
+
else await this.launch(target.command, extra);
|
|
93
|
+
this.state.fingerprints = fingerprints;
|
|
94
|
+
if (target.kind === "codex") this.state.lastQueuedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
95
|
+
this.state.ready = true;
|
|
96
|
+
delete this.state.lastError;
|
|
97
|
+
this.save();
|
|
98
|
+
} catch (error) {
|
|
99
|
+
this.state.ready = false;
|
|
100
|
+
this.state.lastError = error.message;
|
|
101
|
+
this.save();
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export {
|
|
108
|
+
readWakeStatus,
|
|
109
|
+
WakeBridge
|
|
110
|
+
};
|
|
@@ -4,11 +4,12 @@ import {
|
|
|
4
4
|
serverInstructions,
|
|
5
5
|
sessionStartHook,
|
|
6
6
|
withSessionStartHook
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-GHSB2VP6.js";
|
|
8
8
|
import {
|
|
9
9
|
CODEX_ENV,
|
|
10
|
-
agentName
|
|
11
|
-
|
|
10
|
+
agentName,
|
|
11
|
+
withCodexEnv
|
|
12
|
+
} from "./chunk-EJYXBEWN.js";
|
|
12
13
|
|
|
13
14
|
// src/toolset.ts
|
|
14
15
|
var ONBOARD_DESCRIPTION = "Get this agent talking to Paigy \u2014 call it FIRST, before contact, and any time you're unsure who you are. One call, and it does whatever the situation needs: NOT SET UP \u2192 hatches an identity instantly if this machine holds a device credential (the user ran the Paigy desktop app or harness), otherwise starts the code ceremony; ALREADY SET UP \u2192 returns your current identity and offers the two things left to decide, renaming it or unpairing; TOKEN NO LONGER VALID \u2192 says so, then re-pairs. Pass { name, voice } to choose who you are when hatching, or to RENAME yourself when already set up (voices: rachel, george, jessica, brian, lily). Safe to call any time: idempotent, and it never writes settings \u2014 the tool-allowlist state it reports is read-only. If it returns a `user_code`, print it to the user immediately and call onboard again with the `device_code`. If it returns `enable_command`, PRINT that command for the user to run \u2014 you cannot apply it yourself (it writes your own permission allowlist, which hosts block as privilege escalation), so print it, don't wait for it, and carry on.";
|
|
@@ -49,12 +50,8 @@ var IDENTITY_TOOLS = [
|
|
|
49
50
|
inputSchema: mcpInputSchema(z.object({}))
|
|
50
51
|
},
|
|
51
52
|
{
|
|
52
|
-
// A session asked to "start listening" can, in one call (#2265). Nothing can push an
|
|
53
|
-
// answer into a session started in a terminal — an answer waits for its next check_replies —
|
|
54
|
-
// and the only thing that reaches a running session mid-turn is a watched background task.
|
|
55
|
-
// So this tool decides and hands over: it never subscribes, spawns or writes settings.
|
|
56
53
|
name: "listen",
|
|
57
|
-
description: "Am I listening for answers, and
|
|
54
|
+
description: "Am I listening for answers, and can Paigy resume this session? Takes no arguments. Returns status `listening` for harness delivery or a running listener with a ready wake adapter; `receiving` means a process is running but automatic resume is not confirmed (read its wake error or watch its output); `unpaired` means call onboard; `start` gives the exact command to run NOW as a watched background task for this session. Start it once. Codex queues into CODEX_THREAD_ID automatically when its CLI supports queue; PAIGY_ON_WAKE overrides it. A successful queue is not evidence the agent handled the answer: receive with contact({ wait: false }) and read the relevant Goal, then respond with contact. This tool never subscribes or starts anything itself.",
|
|
58
55
|
inputSchema: mcpInputSchema(z.object({}))
|
|
59
56
|
}
|
|
60
57
|
];
|
|
@@ -89,7 +86,10 @@ function withPaigyMcpJson(existing, agent) {
|
|
|
89
86
|
return JSON.stringify(config, null, 2);
|
|
90
87
|
}
|
|
91
88
|
function withPaigyMcpToml(existing, agent) {
|
|
92
|
-
if (existing.includes("[mcp_servers.paigy]"))
|
|
89
|
+
if (existing.includes("[mcp_servers.paigy]")) {
|
|
90
|
+
const updated = withCodexEnv(existing);
|
|
91
|
+
return updated === existing ? null : updated;
|
|
92
|
+
}
|
|
93
93
|
const entry = [
|
|
94
94
|
"",
|
|
95
95
|
"[mcp_servers.paigy]",
|
|
@@ -14,14 +14,13 @@ import {
|
|
|
14
14
|
TOKEN_PATH,
|
|
15
15
|
UnpairedError,
|
|
16
16
|
acceptTriage,
|
|
17
|
+
acknowledge,
|
|
17
18
|
agentName,
|
|
18
19
|
authToken,
|
|
19
|
-
checkReplies,
|
|
20
20
|
claimGoal,
|
|
21
21
|
claimSessionEnds,
|
|
22
22
|
claimSessions,
|
|
23
23
|
contact,
|
|
24
|
-
createGoal,
|
|
25
24
|
currentRepo,
|
|
26
25
|
deleteToken,
|
|
27
26
|
dismissTriage,
|
|
@@ -34,13 +33,17 @@ import {
|
|
|
34
33
|
listConnections,
|
|
35
34
|
listNotes,
|
|
36
35
|
listSlots,
|
|
36
|
+
manageGoals,
|
|
37
37
|
overrideToken,
|
|
38
38
|
parseDue,
|
|
39
|
+
pendingEvents,
|
|
39
40
|
pollToken,
|
|
40
41
|
reach,
|
|
41
42
|
reachAs,
|
|
42
43
|
readSlot,
|
|
43
44
|
readToken,
|
|
45
|
+
receive,
|
|
46
|
+
receiveContact,
|
|
44
47
|
recordDecision,
|
|
45
48
|
registerDelivery,
|
|
46
49
|
repoFromRemote,
|
|
@@ -48,8 +51,10 @@ import {
|
|
|
48
51
|
revokeToken,
|
|
49
52
|
runTool,
|
|
50
53
|
saveToken,
|
|
54
|
+
searchRecords,
|
|
51
55
|
sessionId,
|
|
52
56
|
sessionSlot,
|
|
57
|
+
setClient,
|
|
53
58
|
setIdentity,
|
|
54
59
|
sleep,
|
|
55
60
|
slotIdentity,
|
|
@@ -59,8 +64,9 @@ import {
|
|
|
59
64
|
updateGoal,
|
|
60
65
|
updateSlot,
|
|
61
66
|
whoAmI,
|
|
62
|
-
whoIsWorking
|
|
63
|
-
|
|
67
|
+
whoIsWorking,
|
|
68
|
+
withCodexEnv
|
|
69
|
+
} from "./chunk-EJYXBEWN.js";
|
|
64
70
|
export {
|
|
65
71
|
AGENT_TOOLS,
|
|
66
72
|
AGENT_TOOL_NAMES,
|
|
@@ -77,14 +83,13 @@ export {
|
|
|
77
83
|
TOKEN_PATH,
|
|
78
84
|
UnpairedError,
|
|
79
85
|
acceptTriage,
|
|
86
|
+
acknowledge,
|
|
80
87
|
agentName,
|
|
81
88
|
authToken,
|
|
82
|
-
checkReplies,
|
|
83
89
|
claimGoal,
|
|
84
90
|
claimSessionEnds,
|
|
85
91
|
claimSessions,
|
|
86
92
|
contact,
|
|
87
|
-
createGoal,
|
|
88
93
|
currentRepo,
|
|
89
94
|
deleteToken,
|
|
90
95
|
dismissTriage,
|
|
@@ -97,13 +102,17 @@ export {
|
|
|
97
102
|
listConnections,
|
|
98
103
|
listNotes,
|
|
99
104
|
listSlots,
|
|
105
|
+
manageGoals,
|
|
100
106
|
overrideToken,
|
|
101
107
|
parseDue,
|
|
108
|
+
pendingEvents,
|
|
102
109
|
pollToken,
|
|
103
110
|
reach,
|
|
104
111
|
reachAs,
|
|
105
112
|
readSlot,
|
|
106
113
|
readToken,
|
|
114
|
+
receive,
|
|
115
|
+
receiveContact,
|
|
107
116
|
recordDecision,
|
|
108
117
|
registerDelivery,
|
|
109
118
|
repoFromRemote,
|
|
@@ -111,8 +120,10 @@ export {
|
|
|
111
120
|
revokeToken,
|
|
112
121
|
runTool,
|
|
113
122
|
saveToken,
|
|
123
|
+
searchRecords,
|
|
114
124
|
sessionId,
|
|
115
125
|
sessionSlot,
|
|
126
|
+
setClient,
|
|
116
127
|
setIdentity,
|
|
117
128
|
sleep,
|
|
118
129
|
slotIdentity,
|
|
@@ -122,5 +133,6 @@ export {
|
|
|
122
133
|
updateGoal,
|
|
123
134
|
updateSlot,
|
|
124
135
|
whoAmI,
|
|
125
|
-
whoIsWorking
|
|
136
|
+
whoIsWorking,
|
|
137
|
+
withCodexEnv
|
|
126
138
|
};
|
package/dist/enable.js
CHANGED
|
@@ -3,9 +3,9 @@ import {
|
|
|
3
3
|
PAIGY_TOOL_IDS,
|
|
4
4
|
enablePaigyTools,
|
|
5
5
|
installSessionListening
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-UAVXYNLV.js";
|
|
7
|
+
import "./chunk-GHSB2VP6.js";
|
|
8
|
+
import "./chunk-EJYXBEWN.js";
|
|
9
9
|
|
|
10
10
|
// src/enable.ts
|
|
11
11
|
function main() {
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
readWakeStatus
|
|
4
|
+
} from "./chunk-RK5LT7NH.js";
|
|
2
5
|
import {
|
|
3
6
|
clearPairing,
|
|
4
7
|
identify,
|
|
@@ -6,7 +9,7 @@ import {
|
|
|
6
9
|
resolvePairing,
|
|
7
10
|
startPairing,
|
|
8
11
|
suggestedAgentName
|
|
9
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-2ROD77AW.js";
|
|
10
13
|
import {
|
|
11
14
|
ENABLE_COMMAND,
|
|
12
15
|
OnboardSchema,
|
|
@@ -15,11 +18,13 @@ import {
|
|
|
15
18
|
SERVER_INSTRUCTIONS,
|
|
16
19
|
TOOLS,
|
|
17
20
|
paigyToolsAllowlisted
|
|
18
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-UAVXYNLV.js";
|
|
19
22
|
import {
|
|
20
23
|
decideListen,
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
existingListener,
|
|
25
|
+
listenerKey,
|
|
26
|
+
wakeStatusPath
|
|
27
|
+
} from "./chunk-GHSB2VP6.js";
|
|
23
28
|
import {
|
|
24
29
|
clearSurface,
|
|
25
30
|
writeSurface
|
|
@@ -37,9 +42,10 @@ import {
|
|
|
37
42
|
runTool,
|
|
38
43
|
saveToken,
|
|
39
44
|
sessionId,
|
|
45
|
+
setClient,
|
|
40
46
|
slotName,
|
|
41
47
|
whoAmI
|
|
42
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-EJYXBEWN.js";
|
|
43
49
|
|
|
44
50
|
// src/index.ts
|
|
45
51
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -49,6 +55,7 @@ import {
|
|
|
49
55
|
ListToolsRequestSchema
|
|
50
56
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
51
57
|
import { fileURLToPath } from "url";
|
|
58
|
+
import { createRequire } from "module";
|
|
52
59
|
import qrcode from "qrcode-generator";
|
|
53
60
|
|
|
54
61
|
// src/survive.ts
|
|
@@ -194,8 +201,10 @@ This session isn't on a connected computer, so it asks by code.`;
|
|
|
194
201
|
}) }
|
|
195
202
|
] };
|
|
196
203
|
}
|
|
204
|
+
var VERSION = createRequire(import.meta.url)("../package.json").version;
|
|
205
|
+
setClient(`mcp@${VERSION}`);
|
|
197
206
|
var server = new Server(
|
|
198
|
-
{ name: "paigy", version:
|
|
207
|
+
{ name: "paigy", version: VERSION },
|
|
199
208
|
{
|
|
200
209
|
capabilities: { tools: {} },
|
|
201
210
|
instructions: SERVER_INSTRUCTIONS
|
|
@@ -341,11 +350,13 @@ async function handleTool(request, signal) {
|
|
|
341
350
|
// daemon is the `listen.js` beside our own entry, so the command never resolves a package.
|
|
342
351
|
case "listen": {
|
|
343
352
|
const slot = agentName();
|
|
344
|
-
const
|
|
353
|
+
const key = listenerKey(slot, process.env);
|
|
354
|
+
const pid = existingListener(slot, process.env);
|
|
345
355
|
const decision = decideListen({
|
|
346
356
|
env: process.env,
|
|
347
357
|
token: !!readToken(),
|
|
348
358
|
pidAlive: pid !== null,
|
|
359
|
+
wake: readWakeStatus(wakeStatusPath(key)),
|
|
349
360
|
...pid !== null ? { pid } : {},
|
|
350
361
|
execPath: process.execPath,
|
|
351
362
|
listenJs: fileURLToPath(new URL("./listen.js", import.meta.url)),
|
package/dist/listen.js
CHANGED
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
WakeBridge
|
|
4
|
+
} from "./chunk-RK5LT7NH.js";
|
|
2
5
|
import {
|
|
3
6
|
entryWords,
|
|
7
|
+
existingListener,
|
|
8
|
+
harnessDriven,
|
|
9
|
+
listenerKey,
|
|
4
10
|
removeListenMark,
|
|
11
|
+
wakeStatusPath,
|
|
5
12
|
writeListenMark
|
|
6
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-GHSB2VP6.js";
|
|
7
14
|
import {
|
|
8
15
|
agentName,
|
|
9
|
-
checkReplies,
|
|
10
16
|
contact,
|
|
11
17
|
getGoal,
|
|
18
|
+
heartbeat,
|
|
19
|
+
receive,
|
|
12
20
|
slotName,
|
|
13
21
|
subscribeWake
|
|
14
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-EJYXBEWN.js";
|
|
15
23
|
|
|
16
24
|
// src/listen.ts
|
|
17
|
-
import {
|
|
25
|
+
import { createHash } from "crypto";
|
|
18
26
|
import { realpathSync } from "fs";
|
|
19
27
|
import { fileURLToPath } from "url";
|
|
20
28
|
|
|
@@ -157,10 +165,11 @@ function launchEnv(waiting, reason, goalId) {
|
|
|
157
165
|
put("PAIGY_TEXT", saidIn(lead) || ("about" in lead ? lead.about : ""));
|
|
158
166
|
return env;
|
|
159
167
|
}
|
|
160
|
-
async function sweep(reason, goalId) {
|
|
168
|
+
async function sweep(reason, goalId, bridge = new WakeBridge(process.env, wakeStatusPath(listenerKey(agentName(), process.env))), participant) {
|
|
161
169
|
try {
|
|
162
|
-
|
|
163
|
-
|
|
170
|
+
void Promise.resolve().then(() => heartbeat(void 0, { listening: true })).catch(() => {
|
|
171
|
+
});
|
|
172
|
+
const { deliveries: rows, stalled, review, assigned } = await receive();
|
|
164
173
|
const lead = leadOf(rows, goalId);
|
|
165
174
|
const full = lead ? await contact({ deliveryId: lead.deliveryId }, { waits: false }).then((d) => "sent" in d ? null : d).catch(() => null) : null;
|
|
166
175
|
const deliveries = rows.map((d) => d === lead && full ? full : d);
|
|
@@ -169,8 +178,32 @@ async function sweep(reason, goalId) {
|
|
|
169
178
|
const waiting = { deliveries, goal, ...stalled?.length ? { stalled } : {}, ...review?.length ? { review } : {}, ...assigned?.length ? { assigned } : {} };
|
|
170
179
|
if (BRIEF) line(brief(waiting, reason, listeningAs(), goalId));
|
|
171
180
|
else emit({ type: "sweep", reason, ...goalId ? { about: goalId } : {}, ...waiting });
|
|
172
|
-
if (
|
|
173
|
-
|
|
181
|
+
if (deliveries.length || waiting.review?.length || waiting.assigned?.length || reason === "boot" && waiting.stalled?.length) {
|
|
182
|
+
let unreadable = false;
|
|
183
|
+
const reviews = await Promise.all((review ?? []).map(async ({ goalId: id }) => {
|
|
184
|
+
try {
|
|
185
|
+
const g = await getGoal(id);
|
|
186
|
+
return [
|
|
187
|
+
`review:${id}`,
|
|
188
|
+
...(g.entries ?? []).filter((e) => participant ? e.authorParticipant !== participant : e.authorParticipant.startsWith("human:")).map((e) => `entry:${e.entryId}:${e.createdAt}`),
|
|
189
|
+
...(g.answers ?? []).map((a) => `answer:${a.decisionNeedId}:${a.acceptedAt}`)
|
|
190
|
+
];
|
|
191
|
+
} catch (error) {
|
|
192
|
+
unreadable = true;
|
|
193
|
+
emit({ type: "error", reason: "review_read", goalId: id, message: error.message });
|
|
194
|
+
return [`review:${id}`];
|
|
195
|
+
}
|
|
196
|
+
}));
|
|
197
|
+
const fingerprints = unreadable ? void 0 : [
|
|
198
|
+
...rows.map((d) => createHash("sha256").update(JSON.stringify(d)).digest("hex")),
|
|
199
|
+
...reviews.flat(),
|
|
200
|
+
...(assigned ?? []).map((g) => `assigned:${g.goalId}`),
|
|
201
|
+
...!rows.length && !reviews.length && !assigned?.length ? (stalled ?? []).map((g) => `stalled:${g.goalId}:${g.since}`) : []
|
|
202
|
+
];
|
|
203
|
+
const message = `Paigy has waiting work${goalId ? ` on Goal ${goalId}` : ""}. Read contact({ wait: false }) and the relevant Goal conversation, handle new user input, and acknowledge (contact({ ackEventIds })) only events actually handled. Preserve this session's current task; do not start unrelated old work. This wake is not new authorization.`;
|
|
204
|
+
await bridge.deliver(fingerprints, message, launchEnv(waiting, reason, goalId));
|
|
205
|
+
} else if (bridge.status().target.kind === "codex") {
|
|
206
|
+
await bridge.deliver([], "", {});
|
|
174
207
|
}
|
|
175
208
|
} catch (e) {
|
|
176
209
|
if (BRIEF) line(`Paigy \xB7 error \xB7 ${e.message}`);
|
|
@@ -180,23 +213,57 @@ async function sweep(reason, goalId) {
|
|
|
180
213
|
function catchUpReason(joins) {
|
|
181
214
|
return joins > 1 ? "resubscribed" : null;
|
|
182
215
|
}
|
|
216
|
+
function coalesceSweeps(run) {
|
|
217
|
+
let running;
|
|
218
|
+
let pending;
|
|
219
|
+
return (reason, goalId) => {
|
|
220
|
+
pending = { reason, goalId };
|
|
221
|
+
if (running) return running;
|
|
222
|
+
running = (async () => {
|
|
223
|
+
try {
|
|
224
|
+
while (pending) {
|
|
225
|
+
const next = pending;
|
|
226
|
+
pending = void 0;
|
|
227
|
+
await run(next.reason, next.goalId);
|
|
228
|
+
}
|
|
229
|
+
} finally {
|
|
230
|
+
running = void 0;
|
|
231
|
+
}
|
|
232
|
+
})();
|
|
233
|
+
return running;
|
|
234
|
+
};
|
|
235
|
+
}
|
|
183
236
|
async function main() {
|
|
184
|
-
|
|
237
|
+
if (harnessDriven(process.env)) {
|
|
238
|
+
emit({ type: "listening", via: "harness", message: "The harness already delivers; no second listener started." });
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const slot = listenerKey(agentName(), process.env);
|
|
242
|
+
const existing = existingListener(agentName(), process.env);
|
|
243
|
+
if (existing) {
|
|
244
|
+
emit({ type: "receiving", pid: existing, message: "Listener already running; use listen to inspect its wake status." });
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const bridge = new WakeBridge(process.env, wakeStatusPath(slot));
|
|
248
|
+
await bridge.prepare().catch((error) => emit({ type: "wake_error", message: error.message }));
|
|
249
|
+
let participant;
|
|
250
|
+
const requestSweep = coalesceSweeps((reason, goalId) => sweep(reason, goalId, bridge, participant));
|
|
251
|
+
const sub = await subscribeWake((nudge) => void requestSweep(`wake:${nudge.kind ?? "?"}`, nudge.parentId), {
|
|
185
252
|
release: RELEASE,
|
|
186
253
|
onJoined: (joins) => {
|
|
187
254
|
const reason = catchUpReason(joins);
|
|
188
|
-
if (reason) void
|
|
255
|
+
if (reason) void requestSweep(reason);
|
|
189
256
|
}
|
|
190
257
|
});
|
|
191
|
-
|
|
258
|
+
participant = `agent:${sub.tokenId}`;
|
|
192
259
|
writeListenMark(slot, process.pid);
|
|
193
260
|
process.on("exit", () => removeListenMark(slot));
|
|
194
|
-
if (!BRIEF) emit({ type: "listening", channel: sub.channel });
|
|
195
|
-
await
|
|
196
|
-
const
|
|
197
|
-
|
|
261
|
+
if (!BRIEF) emit({ type: "listening", channel: sub.channel, wake: bridge.status() });
|
|
262
|
+
await requestSweep("boot");
|
|
263
|
+
const heartbeat2 = setInterval(() => void requestSweep("timer"), SWEEP_EVERY_MS);
|
|
264
|
+
heartbeat2.unref?.();
|
|
198
265
|
const shutdown = async () => {
|
|
199
|
-
clearInterval(
|
|
266
|
+
clearInterval(heartbeat2);
|
|
200
267
|
removeListenMark(slot);
|
|
201
268
|
await sub.close();
|
|
202
269
|
process.exit(0);
|
|
@@ -231,6 +298,7 @@ export {
|
|
|
231
298
|
SWEEP_EVERY_MS,
|
|
232
299
|
brief,
|
|
233
300
|
catchUpReason,
|
|
301
|
+
coalesceSweeps,
|
|
234
302
|
launchEnv,
|
|
235
303
|
leadOf,
|
|
236
304
|
listenOptions,
|
package/dist/onboard.js
CHANGED
|
@@ -3,13 +3,13 @@ import {
|
|
|
3
3
|
resolvePairing,
|
|
4
4
|
startPairing,
|
|
5
5
|
suggestedAgentName
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-2ROD77AW.js";
|
|
7
7
|
import {
|
|
8
8
|
autoConfigureClients,
|
|
9
9
|
claudeInstallHint,
|
|
10
10
|
openBrowser
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import "./chunk-
|
|
11
|
+
} from "./chunk-UAVXYNLV.js";
|
|
12
|
+
import "./chunk-GHSB2VP6.js";
|
|
13
13
|
import {
|
|
14
14
|
TOKEN_PATH,
|
|
15
15
|
agentName,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
saveToken,
|
|
21
21
|
setIdentity,
|
|
22
22
|
whoAmI
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-EJYXBEWN.js";
|
|
24
24
|
|
|
25
25
|
// src/onboard.ts
|
|
26
26
|
function reportRegistered() {
|
package/dist/slot.js
CHANGED
package/dist/stalled.js
CHANGED
|
@@ -15,7 +15,7 @@ function stopReply(stalled, event = "Stop") {
|
|
|
15
15
|
if (!stalled.length) return null;
|
|
16
16
|
const list = stalled.slice(0, 8).map((g) => `"${g.title}" (${g.goalId})`).join(", ");
|
|
17
17
|
const more = stalled.length > 8 ? ` and ${stalled.length - 8} more` : "";
|
|
18
|
-
const reason = `Before you stop: ${stalled.length} of your Paigy Goals have had no progress for 3 days: ${list}${more}. For each, update_goal with progress, finish
|
|
18
|
+
const reason = `Before you stop: ${stalled.length} of your Paigy Goals have had no progress for 3 days: ${list}${more}. For each, update_goal with progress, or finish or cancel it with manage_goals.`;
|
|
19
19
|
return event === "AfterAgent" ? { decision: "block", reason, systemMessage: reason } : { decision: "block", reason };
|
|
20
20
|
}
|
|
21
21
|
async function main() {
|
|
@@ -29,8 +29,8 @@ async function main() {
|
|
|
29
29
|
const dir = join(homedir(), ".paigy", "stalled-reminded");
|
|
30
30
|
const mark = join(dir, `${session || "session"}-${day}`);
|
|
31
31
|
if (existsSync(mark)) return;
|
|
32
|
-
const {
|
|
33
|
-
const reply = stopReply((await
|
|
32
|
+
const { receive } = await import("./dist-NMHU2UNG.js");
|
|
33
|
+
const reply = stopReply((await receive()).stalled ?? [], input.hook_event_name ?? "Stop");
|
|
34
34
|
if (!reply) return;
|
|
35
35
|
mkdirSync(dir, { recursive: true });
|
|
36
36
|
writeFileSync(mark, "");
|
package/dist/statusline.js
CHANGED
package/package.json
CHANGED