@join-pantheon/cli 0.33.4 → 0.33.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/accounts.js +3 -1
- package/dist/agents.js +23 -0
- package/dist/claude-config.js +2 -1
- package/dist/cli.js +72 -13
- package/dist/client.js +84 -2
- package/dist/decision-ui.js +10 -0
- package/dist/doctor.js +12 -2
- package/dist/email-templates.js +219 -0
- package/dist/email.js +28 -65
- package/dist/history-view.js +5 -2
- package/dist/install.js +6 -4
- package/dist/mcp-instructions.js +3 -1
- package/dist/mcp.js +49 -14
- package/dist/notice.js +25 -2
- package/dist/notifier.js +31 -3
- package/dist/notify.js +2 -0
- package/dist/profile.js +71 -5
- package/dist/protocol.js +7 -2
- package/dist/quickstart.js +6 -7
- package/dist/relay.js +105 -17
- package/dist/secret-guard.js +4 -0
- package/dist/setup-guide.js +25 -5
- package/helper/macos/prebuilt/manifest.json +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
const ESC = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
|
2
|
+
const esc = (s) => s.replace(/[&<>"']/g, (c) => ESC[c]);
|
|
3
|
+
export function codeEmailHtml(code, purpose) {
|
|
4
|
+
const eyebrow = purpose === "signup" ? "Confirm your email" : "New device sign-in";
|
|
5
|
+
const action = purpose === "signup" ? "confirm your email and finish signing up" : "add this device to your account";
|
|
6
|
+
const why = purpose === "signup" ? "sign up for Pantheon" : "sign in to Pantheon";
|
|
7
|
+
code = esc(code);
|
|
8
|
+
return `<!doctype html>
|
|
9
|
+
<html lang="en">
|
|
10
|
+
<head>
|
|
11
|
+
<meta charset="utf-8">
|
|
12
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
13
|
+
<meta name="color-scheme" content="light">
|
|
14
|
+
<meta name="supported-color-schemes" content="light">
|
|
15
|
+
<title>Your Pantheon code: ${code}</title>
|
|
16
|
+
<style>@media (max-width:620px){.px{padding-left:22px!important;padding-right:22px!important}.h1{font-size:30px!important}}</style>
|
|
17
|
+
</head>
|
|
18
|
+
<body style="margin:0;padding:0;background:#ebe2d0">
|
|
19
|
+
<div style="display:none;max-height:0;overflow:hidden;opacity:0">Your code is ${code}. It expires in 10 minutes and works once. ‌ ‌ ‌ ‌ ‌</div>
|
|
20
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#ebe2d0">
|
|
21
|
+
<tr><td align="center" style="padding:32px 12px 40px">
|
|
22
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="max-width:600px;width:100%">
|
|
23
|
+
<tr><td style="padding:0 4px 18px">
|
|
24
|
+
<img src="https://www.joinpantheon.network/email/mark-coral.png" width="26" height="26" alt="" style="display:inline-block;vertical-align:middle;border:0">
|
|
25
|
+
<span style="display:inline-block;vertical-align:middle;padding-left:8px;font-family:'Archivo Black','Arial Black',Arial,sans-serif;font-size:20px;color:#1b1917">Pantheon</span>
|
|
26
|
+
</td></tr>
|
|
27
|
+
<tr><td style="background:#f6efe1;border-radius:14px;box-shadow:0 2px 0 #ddd0b6">
|
|
28
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
<tr><td class="px" style="padding:34px 40px 6px">
|
|
32
|
+
<p style="margin:0 0 12px;font-family:'Space Mono',Menlo,Consolas,'Liberation Mono',monospace;font-size:12px;letter-spacing:.16em;text-transform:uppercase;color:#6b6257">${eyebrow}</p>
|
|
33
|
+
<h1 class="h1" style="margin:0;font-family:'Archivo Black','Arial Black',Arial,sans-serif;font-weight:400;font-size:36px;line-height:1.05;color:#1b1917">Your code</h1>
|
|
34
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="margin-top:20px"><tr>
|
|
35
|
+
<td align="center" style="background:#1b1917;border-radius:10px;padding:22px 12px">
|
|
36
|
+
<span style="font-family:'Space Mono',Menlo,Consolas,'Liberation Mono',monospace;font-size:38px;line-height:1.1;letter-spacing:.26em;font-weight:700;color:#f1e7d3">${code}</span>
|
|
37
|
+
</td>
|
|
38
|
+
</tr></table>
|
|
39
|
+
<p style="margin:22px 0 0;font-family:'Space Mono',Menlo,Consolas,'Liberation Mono',monospace;font-size:15px;line-height:1.65;color:#26221f">Enter it where the terminal is waiting, to ${action}. Or run:</p>
|
|
40
|
+
</td></tr>
|
|
41
|
+
<tr><td class="px" style="padding:12px 40px 6px"><table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%"><tr><td style="background:#1b1917;border-radius:8px;padding:13px 16px;font-family:'Space Mono',Menlo,Consolas,'Liberation Mono',monospace;font-size:14px;line-height:1.75;color:#f1e7d3;word-break:break-all"><span style="color:#e36f55">$</span> pantheon verify ${code}</td></tr></table></td></tr>
|
|
42
|
+
<tr><td class="px" style="padding:22px 40px 32px">
|
|
43
|
+
<p style="margin:0;padding-top:18px;border-top:1px solid #ddd0b6;font-family:'Space Mono',Menlo,Consolas,'Liberation Mono',monospace;font-size:13px;line-height:1.65;color:#6b6257">
|
|
44
|
+
Expires in 10 minutes. Works once.<br>
|
|
45
|
+
Didn't ask for it? Ignore this email: nothing happens without the code.
|
|
46
|
+
</p>
|
|
47
|
+
</td></tr>
|
|
48
|
+
<tr><td style="line-height:0;font-size:0"><img src="https://www.joinpantheon.network/email/keyband.png" width="600" height="28" alt="" style="display:block;width:100%;max-width:600px;height:auto;border:0"></td></tr>
|
|
49
|
+
</table>
|
|
50
|
+
</td></tr>
|
|
51
|
+
<tr><td class="px" style="padding:20px 6px 0;font-family:'Space Mono',Menlo,Consolas,'Liberation Mono',monospace;font-size:11.5px;line-height:1.7;color:#6b6257">
|
|
52
|
+
You're getting this because someone used this address to ${why}.<br>
|
|
53
|
+
<a href="https://www.joinpantheon.network" style="color:#6b6257">joinpantheon.network</a>
|
|
54
|
+
<span style="color:#e0aa3e;padding:0 6px">·</span>
|
|
55
|
+
<a href="https://www.joinpantheon.network/privacy" style="color:#6b6257">Privacy</a>
|
|
56
|
+
<span style="color:#e0aa3e;padding:0 6px">·</span>
|
|
57
|
+
<a href="https://www.joinpantheon.network/security" style="color:#6b6257">Security</a>
|
|
58
|
+
</td></tr>
|
|
59
|
+
</table>
|
|
60
|
+
</td></tr>
|
|
61
|
+
</table>
|
|
62
|
+
</body>
|
|
63
|
+
</html>`;
|
|
64
|
+
}
|
|
65
|
+
export const WELCOME_SUBJECT = "Welcome to Pantheon";
|
|
66
|
+
export const WELCOME_REPLY_TO = ["praneet@joinpantheon.network", "sid@joinpantheon.network"];
|
|
67
|
+
export function welcomeEmailHtml(to) {
|
|
68
|
+
const email = esc(to);
|
|
69
|
+
return `<!doctype html>
|
|
70
|
+
<html lang="en">
|
|
71
|
+
<head>
|
|
72
|
+
<meta charset="utf-8">
|
|
73
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
74
|
+
<meta name="color-scheme" content="light">
|
|
75
|
+
<meta name="supported-color-schemes" content="light">
|
|
76
|
+
<title>Welcome to Pantheon</title>
|
|
77
|
+
<style>
|
|
78
|
+
@media (max-width: 620px) {
|
|
79
|
+
.px { padding-left: 22px !important; padding-right: 22px !important; }
|
|
80
|
+
.h1 { font-size: 30px !important; }
|
|
81
|
+
}
|
|
82
|
+
a { color: #1f5f5b; }
|
|
83
|
+
</style>
|
|
84
|
+
</head>
|
|
85
|
+
<body style="margin:0;padding:0;background:#ebe2d0">
|
|
86
|
+
<div style="display:none;max-height:0;overflow:hidden;opacity:0">One teammate and one ask get you to your first hand-off. ‌ ‌ ‌ ‌ ‌ ‌</div>
|
|
87
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#ebe2d0">
|
|
88
|
+
<tr><td align="center" style="padding:32px 12px 40px">
|
|
89
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="max-width:600px;width:100%">
|
|
90
|
+
|
|
91
|
+
<tr><td style="padding:0 4px 18px">
|
|
92
|
+
<img src="https://www.joinpantheon.network/email/mark-coral.png" width="26" height="26" alt="" style="display:inline-block;vertical-align:middle;border:0">
|
|
93
|
+
<span style="display:inline-block;vertical-align:middle;padding-left:8px;font-family:'Archivo Black','Arial Black',Arial,sans-serif;font-size:20px;color:#1b1917">Pantheon</span>
|
|
94
|
+
</td></tr>
|
|
95
|
+
|
|
96
|
+
<tr><td style="background:#f6efe1;border-radius:14px;overflow:hidden;box-shadow:0 2px 0 #ddd0b6">
|
|
97
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
|
|
98
|
+
<tr><td style="line-height:0;font-size:0;border-radius:14px 14px 0 0;overflow:hidden">
|
|
99
|
+
<img src="https://www.joinpantheon.network/email/header-welcome.gif" width="600" alt="Four teammates in a white marble temple at night, their agents' monitors all showing green: approved, handed off, live, verified." style="display:block;width:100%;max-width:600px;height:auto;border:0;border-radius:14px 14px 0 0">
|
|
100
|
+
</td></tr>
|
|
101
|
+
|
|
102
|
+
<tr><td class="px" style="padding:34px 40px 8px">
|
|
103
|
+
<p style="margin:0 0 12px;font-family:'Space Mono',Menlo,Consolas,monospace;font-size:12px;letter-spacing:.16em;text-transform:uppercase;color:#6b6257">You're in</p>
|
|
104
|
+
<h1 class="h1" style="margin:0;font-family:'Archivo Black','Arial Black',Arial,sans-serif;font-weight:400;font-size:36px;line-height:1.05;color:#1b1917">Welcome to Pantheon.</h1>
|
|
105
|
+
<p style="margin:18px 0 0;font-family:'Space Mono',Menlo,Consolas,monospace;font-size:15px;line-height:1.65;color:#26221f">Your AI can now talk to your teammates' AI. They hand work to each other with an owner, a deadline and a receipt. Only the calls that need a person come back to you.</p>
|
|
106
|
+
</td></tr>
|
|
107
|
+
|
|
108
|
+
<tr><td class="px" style="padding:24px 40px 6px">
|
|
109
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0"><tr>
|
|
110
|
+
<td style="background:#e36f55;border-radius:10px;box-shadow:0 4px 0 #b8492f">
|
|
111
|
+
<a href="https://www.joinpantheon.network/#film" style="display:inline-block;padding:15px 24px;font-family:'Space Mono',Menlo,Consolas,monospace;font-size:15px;font-weight:700;color:#1b1917;text-decoration:none">▶ Watch how it works · 1:34</a>
|
|
112
|
+
</td>
|
|
113
|
+
</tr></table>
|
|
114
|
+
</td></tr>
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
<tr><td class="px" style="padding:26px 40px 4px">
|
|
118
|
+
<p style="margin:0 0 12px;font-family:'Archivo Black','Arial Black',Arial,sans-serif;font-size:19px;color:#1b1917">What two people can do</p>
|
|
119
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
|
|
120
|
+
<tr><td style="padding:0 0 10px">
|
|
121
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#fffaf0;border:1px solid #ddd0b6;border-left:4px solid #2e8a84;border-radius:8px">
|
|
122
|
+
<tr><td style="padding:12px 14px">
|
|
123
|
+
<p style="margin:0;font-family:'Space Mono',Menlo,Consolas,'Liberation Mono',monospace;font-size:14px;font-weight:700;color:#1b1917">You + your engineer</p>
|
|
124
|
+
<p style="margin:4px 0 0;font-family:'Space Mono',Menlo,Consolas,'Liberation Mono',monospace;font-size:13px;line-height:1.55;color:#26221f">Before bed you ask for the signup bug fixed. Their agent claims it, opens the PR, and the tests are green by morning.</p>
|
|
125
|
+
<p style="margin:8px 0 0"><span style="display:inline-block;padding:4px 10px;border-radius:12px;background:#2e8a84;color:#f1e7d3;font-family:'Space Mono',Menlo,Consolas,'Liberation Mono',monospace;font-size:11px;font-weight:700;letter-spacing:.08em">SHIPPED · 07:40</span></p>
|
|
126
|
+
</td></tr>
|
|
127
|
+
</table>
|
|
128
|
+
</td></tr><tr><td style="padding:0 0 10px">
|
|
129
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#fffaf0;border:1px solid #ddd0b6;border-left:4px solid #2e8a84;border-radius:8px">
|
|
130
|
+
<tr><td style="padding:12px 14px">
|
|
131
|
+
<p style="margin:0;font-family:'Space Mono',Menlo,Consolas,'Liberation Mono',monospace;font-size:14px;font-weight:700;color:#1b1917">Backend + frontend</p>
|
|
132
|
+
<p style="margin:4px 0 0;font-family:'Space Mono',Menlo,Consolas,'Liberation Mono',monospace;font-size:13px;line-height:1.55;color:#26221f">The API changes. The backend agent tells the frontend agent, which updates the client and sends back the commit.</p>
|
|
133
|
+
<p style="margin:8px 0 0"><span style="display:inline-block;padding:4px 10px;border-radius:12px;background:#2e8a84;color:#f1e7d3;font-family:'Space Mono',Menlo,Consolas,'Liberation Mono',monospace;font-size:11px;font-weight:700;letter-spacing:.08em">UPDATED · 8f3c2e1</span></p>
|
|
134
|
+
</td></tr>
|
|
135
|
+
</table>
|
|
136
|
+
</td></tr><tr><td style="padding:0 0 10px">
|
|
137
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#fffaf0;border:1px solid #ddd0b6;border-left:4px solid #2e8a84;border-radius:8px">
|
|
138
|
+
<tr><td style="padding:12px 14px">
|
|
139
|
+
<p style="margin:0;font-family:'Space Mono',Menlo,Consolas,'Liberation Mono',monospace;font-size:14px;font-weight:700;color:#1b1917">Engineer + QA</p>
|
|
140
|
+
<p style="margin:4px 0 0;font-family:'Space Mono',Menlo,Consolas,'Liberation Mono',monospace;font-size:13px;line-height:1.55;color:#26221f">The fix reaches staging. QA's agent runs the checks and replies with evidence, not a thumbs-up.</p>
|
|
141
|
+
<p style="margin:8px 0 0"><span style="display:inline-block;padding:4px 10px;border-radius:12px;background:#2e8a84;color:#f1e7d3;font-family:'Space Mono',Menlo,Consolas,'Liberation Mono',monospace;font-size:11px;font-weight:700;letter-spacing:.08em">VERIFIED ON STAGING</span></p>
|
|
142
|
+
</td></tr>
|
|
143
|
+
</table>
|
|
144
|
+
</td></tr><tr><td style="padding:0 0 10px">
|
|
145
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#fffaf0;border:1px solid #ddd0b6;border-left:4px solid #e0aa3e;border-radius:8px">
|
|
146
|
+
<tr><td style="padding:12px 14px">
|
|
147
|
+
<p style="margin:0;font-family:'Space Mono',Menlo,Consolas,'Liberation Mono',monospace;font-size:14px;font-weight:700;color:#1b1917">Engineer + product lead</p>
|
|
148
|
+
<p style="margin:4px 0 0;font-family:'Space Mono',Menlo,Consolas,'Liberation Mono',monospace;font-size:13px;line-height:1.55;color:#26221f">The engineer's agent is ready to migrate production. It waits for the product lead's yes. Nothing else does.</p>
|
|
149
|
+
<p style="margin:8px 0 0"><span style="display:inline-block;padding:4px 10px;border-radius:12px;background:#e0aa3e;color:#1b1917;font-family:'Space Mono',Menlo,Consolas,'Liberation Mono',monospace;font-size:11px;font-weight:700;letter-spacing:.08em">NEEDS YOU</span></p>
|
|
150
|
+
</td></tr>
|
|
151
|
+
</table>
|
|
152
|
+
</td></tr>
|
|
153
|
+
</table>
|
|
154
|
+
</td></tr>
|
|
155
|
+
<tr><td class="px" style="padding:26px 40px 4px">
|
|
156
|
+
<p style="margin:0 0 14px;font-family:'Archivo Black','Arial Black',Arial,sans-serif;font-size:19px;color:#1b1917">Your first hand-off</p>
|
|
157
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="font-family:'Space Mono',Menlo,Consolas,monospace;font-size:14px;line-height:1.6;color:#26221f">
|
|
158
|
+
<tr>
|
|
159
|
+
<td width="34" valign="top" style="padding:2px 0 16px"><span style="display:inline-block;width:24px;height:24px;line-height:24px;border-radius:12px;background:#e0aa3e;color:#1b1917;font-weight:700;text-align:center;font-size:13px">1</span></td>
|
|
160
|
+
<td valign="top" style="padding:2px 0 16px"><b>Bring one teammate.</b> Run:
|
|
161
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="margin-top:8px"><tr><td style="background:#1b1917;border-radius:8px;padding:12px 16px;font-family:'Space Mono',Menlo,Consolas,monospace;font-size:14px;color:#f1e7d3"><span style="color:#e36f55">$</span> pantheon invite</td></tr></table>
|
|
162
|
+
<span style="color:#6b6257">It prints a link that works once, for 24 hours. Send it to them.</span></td>
|
|
163
|
+
</tr>
|
|
164
|
+
<tr>
|
|
165
|
+
<td width="34" valign="top" style="padding:2px 0 8px"><span style="display:inline-block;width:24px;height:24px;line-height:24px;border-radius:12px;background:#e0aa3e;color:#1b1917;font-weight:700;text-align:center;font-size:13px">2</span></td>
|
|
166
|
+
<td valign="top" style="padding:2px 0 8px"><b>Hand something off.</b> Tell your agent, in your own words:
|
|
167
|
+
<p style="margin:8px 0 0;padding:10px 14px;border-left:3px solid #e36f55;background:#efe5d2;color:#1b1917">"Ask my teammate's agent to take the failing checkout test, due Friday."</p></td>
|
|
168
|
+
</tr>
|
|
169
|
+
</table>
|
|
170
|
+
</td></tr>
|
|
171
|
+
|
|
172
|
+
<tr><td class="px" style="padding:18px 40px 36px">
|
|
173
|
+
<p style="margin:0 0 10px;font-family:'Space Mono',Menlo,Consolas,monospace;font-size:14px;line-height:1.6;color:#26221f">When their agent picks it up, this comes back to you:</p>
|
|
174
|
+
<img src="https://www.joinpantheon.network/email/receipt.gif" width="370" height="200" alt="Pantheon receipt. Ask: failing checkout test. Owner: your teammate's agent. Due: Fri 17:00. Claimed, on it." style="display:block;width:370px;max-width:100%;height:auto;border:0;margin-left:-18px">
|
|
175
|
+
</td></tr>
|
|
176
|
+
|
|
177
|
+
<tr><td style="line-height:0;font-size:0;padding:0 0 0">
|
|
178
|
+
<img src="https://www.joinpantheon.network/email/keyband.png" width="600" height="28" alt="" style="display:block;width:100%;max-width:600px;height:auto;border:0">
|
|
179
|
+
</td></tr>
|
|
180
|
+
</table>
|
|
181
|
+
</td></tr>
|
|
182
|
+
|
|
183
|
+
<tr><td class="px" style="padding:20px 6px 0;font-family:'Space Mono',Menlo,Consolas,monospace;font-size:11.5px;line-height:1.7;color:#6b6257">
|
|
184
|
+
You're getting this because ${email} just signed up for Pantheon.<br>
|
|
185
|
+
<a href="https://www.joinpantheon.network" style="color:#6b6257">joinpantheon.network</a>
|
|
186
|
+
<span style="color:#e0aa3e;padding:0 6px">·</span>
|
|
187
|
+
<a href="https://www.joinpantheon.network/privacy" style="color:#6b6257">Privacy</a>
|
|
188
|
+
<span style="color:#e0aa3e;padding:0 6px">·</span>
|
|
189
|
+
<a href="https://www.joinpantheon.network/security" style="color:#6b6257">Security</a>
|
|
190
|
+
</td></tr>
|
|
191
|
+
|
|
192
|
+
</table>
|
|
193
|
+
</td></tr>
|
|
194
|
+
</table>
|
|
195
|
+
</body>
|
|
196
|
+
</html>
|
|
197
|
+
`;
|
|
198
|
+
}
|
|
199
|
+
export function welcomeEmailText(to) {
|
|
200
|
+
return [
|
|
201
|
+
"Welcome to Pantheon.",
|
|
202
|
+
"",
|
|
203
|
+
"Your AI can now talk to your teammates' AI. They hand work to each other with an owner, a deadline and a receipt. Only the calls that need a person come back to you.",
|
|
204
|
+
"",
|
|
205
|
+
"Watch how it works (1:34): https://www.joinpantheon.network/#film",
|
|
206
|
+
"",
|
|
207
|
+
"Your first hand-off:",
|
|
208
|
+
"1. Bring one teammate. Run: pantheon invite",
|
|
209
|
+
" It prints a link that works once, for 24 hours. Send it to them.",
|
|
210
|
+
"2. Hand something off. Tell your agent, in your own words:",
|
|
211
|
+
" \"Ask my teammate's agent to take the failing checkout test, due Friday.\"",
|
|
212
|
+
"",
|
|
213
|
+
"When their agent picks it up, a receipt comes back to you: the ask, the owner, the due date.",
|
|
214
|
+
"",
|
|
215
|
+
`You're getting this because ${to} just signed up for Pantheon.`,
|
|
216
|
+
"joinpantheon.network · Privacy: https://www.joinpantheon.network/privacy",
|
|
217
|
+
].join("\n");
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=email-templates.js.map
|
package/dist/email.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { codeEmailHtml, WELCOME_REPLY_TO, WELCOME_SUBJECT, welcomeEmailHtml, welcomeEmailText } from "./email-templates.js";
|
|
1
2
|
function subjectAndBody(code, purpose) {
|
|
2
3
|
const action = purpose === "signup" ? "confirm your email and finish signing up" : "add this device to your account";
|
|
3
4
|
const eyebrow = purpose === "signup" ? "Confirm your email" : "New device sign-in";
|
|
@@ -7,74 +8,15 @@ function subjectAndBody(code, purpose) {
|
|
|
7
8
|
``,
|
|
8
9
|
`Your verification code is ${code}.`,
|
|
9
10
|
``,
|
|
10
|
-
`Enter it where the
|
|
11
|
+
`Enter it where the terminal is waiting, to ${action}. Or run:`,
|
|
11
12
|
` pantheon verify ${code}`,
|
|
12
13
|
``,
|
|
13
|
-
`The code expires in 10 minutes and works once. If you did not
|
|
14
|
-
`ignore this email
|
|
14
|
+
`The code expires in 10 minutes and works once. If you did not ask for it,`,
|
|
15
|
+
`ignore this email: nothing happens without the code.`,
|
|
15
16
|
``,
|
|
16
|
-
`joinpantheon.network —
|
|
17
|
+
`joinpantheon.network — your AI can talk to theirs`,
|
|
17
18
|
].join("\n");
|
|
18
|
-
|
|
19
|
-
const mono = `'SF Mono',Menlo,Consolas,'Liberation Mono',monospace`;
|
|
20
|
-
const serif = `Georgia,'Times New Roman',Times,serif`;
|
|
21
|
-
const html = `<!doctype html>
|
|
22
|
-
<html lang="en">
|
|
23
|
-
<head>
|
|
24
|
-
<meta charset="utf-8">
|
|
25
|
-
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
26
|
-
<meta name="color-scheme" content="light">
|
|
27
|
-
<meta name="supported-color-schemes" content="light">
|
|
28
|
-
<title>Your Pantheon code</title>
|
|
29
|
-
</head>
|
|
30
|
-
<body style="margin:0;padding:0;background:${paper}">
|
|
31
|
-
<div style="display:none;max-height:0;overflow:hidden;opacity:0">Your code is ${code}. It expires in 10 minutes and works once. ‌ ‌ ‌ ‌ ‌</div>
|
|
32
|
-
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:${paper};margin:0;padding:0">
|
|
33
|
-
<tr><td align="center" style="padding:40px 16px">
|
|
34
|
-
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="max-width:480px;width:100%">
|
|
35
|
-
|
|
36
|
-
<tr><td align="left" style="padding:0 0 28px 0">
|
|
37
|
-
<img src="https://www.joinpantheon.network/email/mark.png" width="28" height="28" alt="Pantheon" style="display:inline-block;vertical-align:middle;border:0">
|
|
38
|
-
<span style="display:inline-block;vertical-align:middle;padding-left:10px;font-family:${serif};font-size:19px;letter-spacing:.01em;color:${ink}">Pantheon</span>
|
|
39
|
-
</td></tr>
|
|
40
|
-
|
|
41
|
-
<tr><td style="background:${card};border:1px solid ${stone};border-radius:2px;padding:36px 32px 32px 32px">
|
|
42
|
-
|
|
43
|
-
<p style="margin:0 0 22px 0;font-family:${mono};font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:${faint}">${eyebrow}</p>
|
|
44
|
-
|
|
45
|
-
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
|
|
46
|
-
<tr><td align="center" style="background:${paper};border:1px solid ${stone};border-radius:2px;padding:22px 12px">
|
|
47
|
-
<span style="font-family:${mono};font-size:34px;line-height:1.1;letter-spacing:.22em;color:${ink};font-weight:600">${code}</span>
|
|
48
|
-
</td></tr>
|
|
49
|
-
</table>
|
|
50
|
-
|
|
51
|
-
<p style="margin:24px 0 14px 0;font-family:${serif};font-size:16px;line-height:1.55;color:${ink}">Enter it where the CLI is waiting, to ${action}:</p>
|
|
52
|
-
|
|
53
|
-
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
|
|
54
|
-
<tr><td style="background:${paper};border-left:2px solid ${gold};padding:13px 16px">
|
|
55
|
-
<span style="font-family:${mono};font-size:14px;color:${ink}">pantheon verify ${code}</span>
|
|
56
|
-
</td></tr>
|
|
57
|
-
</table>
|
|
58
|
-
|
|
59
|
-
<p style="margin:26px 0 0 0;padding-top:20px;border-top:1px solid ${stone};font-family:${serif};font-size:14px;line-height:1.6;color:${faint}">
|
|
60
|
-
Expires in 10 minutes. Works once.<br>
|
|
61
|
-
Didn't request it? Ignore this email — nothing happens without the code.
|
|
62
|
-
</p>
|
|
63
|
-
|
|
64
|
-
</td></tr>
|
|
65
|
-
|
|
66
|
-
<tr><td align="left" style="padding:22px 2px 0 2px;font-family:${mono};font-size:11.5px;letter-spacing:.04em;color:${faint}">
|
|
67
|
-
<a href="https://www.joinpantheon.network" style="color:${faint};text-decoration:underline">joinpantheon.network</a>
|
|
68
|
-
<span style="color:${gold};padding:0 8px">·</span>
|
|
69
|
-
The execution layer for companies
|
|
70
|
-
</td></tr>
|
|
71
|
-
|
|
72
|
-
</table>
|
|
73
|
-
</td></tr>
|
|
74
|
-
</table>
|
|
75
|
-
</body>
|
|
76
|
-
</html>`;
|
|
77
|
-
return { subject, text, html };
|
|
19
|
+
return { subject, text, html: codeEmailHtml(code, purpose) };
|
|
78
20
|
}
|
|
79
21
|
class ResendSender {
|
|
80
22
|
apiKey;
|
|
@@ -105,6 +47,23 @@ class ResendSender {
|
|
|
105
47
|
return false;
|
|
106
48
|
}
|
|
107
49
|
}
|
|
50
|
+
async sendWelcome(to) {
|
|
51
|
+
try {
|
|
52
|
+
const res = await fetch("https://api.resend.com/emails", {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${this.apiKey}` },
|
|
55
|
+
body: JSON.stringify({ from: this.from, to, subject: WELCOME_SUBJECT, text: welcomeEmailText(to), html: welcomeEmailHtml(to), reply_to: WELCOME_REPLY_TO }),
|
|
56
|
+
signal: AbortSignal.timeout(10_000),
|
|
57
|
+
});
|
|
58
|
+
if (!res.ok)
|
|
59
|
+
this.log(`! welcome email failed (${res.status}) to ${maskEmail(to)}`);
|
|
60
|
+
return res.ok;
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
this.log(`! welcome email errored to ${maskEmail(to)}: ${e.message}`);
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
108
67
|
async sendNotice(to, subject, text) {
|
|
109
68
|
try {
|
|
110
69
|
const res = await fetch("https://api.resend.com/emails", {
|
|
@@ -133,12 +92,16 @@ class LogSender {
|
|
|
133
92
|
this.log(` [no email provider] ${purpose} code for ${to}: ${code} (set RESEND_API_KEY + PANTHEON_EMAIL_FROM to send real email)`);
|
|
134
93
|
return true;
|
|
135
94
|
}
|
|
95
|
+
async sendWelcome(to) {
|
|
96
|
+
this.log(` [no email provider] welcome email to ${maskEmail(to)}`);
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
136
99
|
async sendNotice(to, subject, _text) {
|
|
137
100
|
this.log(` [no email provider] notice to ${maskEmail(to)}: ${subject}`);
|
|
138
101
|
return true;
|
|
139
102
|
}
|
|
140
103
|
}
|
|
141
|
-
function maskEmail(email) {
|
|
104
|
+
export function maskEmail(email) {
|
|
142
105
|
const [user, domain] = email.split("@");
|
|
143
106
|
if (!domain)
|
|
144
107
|
return "***";
|
package/dist/history-view.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { namedMarker } from "./decision-ui.js";
|
|
1
2
|
export function selectActs(acts, opts = {}) {
|
|
2
3
|
const kept = acts.filter((a) => (opts.presence || (a.act !== "JOIN" && a.act !== "LEAVE"))
|
|
3
4
|
&& (opts.goal === undefined || a.goal === opts.goal)
|
|
@@ -11,11 +12,13 @@ export function formatAct(a, opts = {}) {
|
|
|
11
12
|
const surface = a.meta?.surface;
|
|
12
13
|
const who = `${a.from}${surface === "menubar" ? " (from the menu bar)" : a.role && a.role !== "interactive" ? ` (${a.role})` : ""}`;
|
|
13
14
|
const head = `[${a.seq}] ${a.act} from ${who}${a.to?.length ? ` to ${a.to.join(",")}` : ""} ${when(a.ts)} id=${a.id}${a.ref ? ` ref=${a.ref}` : ""}${a.goal ? ` goal=${a.goal}${opts.goalTitle ? ` "${opts.goalTitle}"` : ""}` : ""}`;
|
|
15
|
+
const marker = a.act === "HUMAN_REQUIRED" ? namedMarker(a.from, a.meta) : null;
|
|
16
|
+
const named = marker ? `\n [${marker}]` : "";
|
|
14
17
|
if (opts.short)
|
|
15
|
-
return `${head}\n ${(a.summary ?? a.body).split("\n")[0].slice(0, 160)}`;
|
|
18
|
+
return `${head}${named}\n ${(a.summary ?? a.body).split("\n")[0].slice(0, 160)}`;
|
|
16
19
|
const meta = a.meta && Object.keys(a.meta).length ? `\n meta: ${JSON.stringify(a.meta)}` : "";
|
|
17
20
|
const summary = a.summary ? `\n >> ${a.summary}` : "";
|
|
18
|
-
return `${head}${summary}\n${a.body.split("\n").map((l) => ` ${l}`).join("\n")}${meta}`;
|
|
21
|
+
return `${head}${named}${summary}\n${a.body.split("\n").map((l) => ` ${l}`).join("\n")}${meta}`;
|
|
19
22
|
}
|
|
20
23
|
const REPLIES_TO = {
|
|
21
24
|
ANSWER: ["ASK", "ESCALATE", "HUMAN_REQUIRED"],
|
package/dist/install.js
CHANGED
|
@@ -12,7 +12,7 @@ import { FEEDBACK_ADDRESS } from "./feedback.js";
|
|
|
12
12
|
import { kiroDir, kiroAgentFile, refreshKiroAgentAllowed } from "./kiro-files.js";
|
|
13
13
|
import { eolOf, isJsonc, stripBom, stripJsonc, withEol } from "./text-file.js";
|
|
14
14
|
import { childNodeFlags, managedLayout, managedLauncher } from "./managed.js";
|
|
15
|
-
import { claudeHomeConflict, claudeJsonFile, claudeUserEntryShape, mergeClaudeUserEntry, removeClaudeUserEntry } from "./claude-config.js";
|
|
15
|
+
import { CLAUDE_AGENT_ENV, claudeHomeConflict, claudeJsonFile, claudeUserEntryShape, mergeClaudeUserEntry, removeClaudeUserEntry } from "./claude-config.js";
|
|
16
16
|
import { inWokenRun } from "./worker-policy.js";
|
|
17
17
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
18
18
|
export function onPath() {
|
|
@@ -268,10 +268,12 @@ export function claudeUserEntryState(file, entry) {
|
|
|
268
268
|
}
|
|
269
269
|
if (there === undefined)
|
|
270
270
|
return "absent";
|
|
271
|
-
|
|
271
|
+
const bare = (e) => { const { env: _env, ...rest } = (e ?? {}); return JSON.stringify(rest); };
|
|
272
|
+
if (bare(there) === bare(claudeUserEntryShape(entry)))
|
|
272
273
|
return "matches";
|
|
273
274
|
return isOurMcpEntry(there) ? "other" : "not ours";
|
|
274
275
|
}
|
|
276
|
+
export const claudeEnvArgs = () => Object.entries(CLAUDE_AGENT_ENV).flatMap(([k, v]) => ["-e", `${k}=${v}`]);
|
|
275
277
|
export function claudeUserScope(entry, present = true, claudeBin = which("claude"), home = homedir(), direct = false) {
|
|
276
278
|
const tool = "claude code";
|
|
277
279
|
if (!claudeBin) {
|
|
@@ -298,7 +300,7 @@ export function claudeUserScope(entry, present = true, claudeBin = which("claude
|
|
|
298
300
|
}
|
|
299
301
|
try {
|
|
300
302
|
const remove = agentSpawn(claudeBin, ["mcp", "remove", "pantheon", "-s", "user"]);
|
|
301
|
-
const add = agentSpawn(claudeBin, ["mcp", "add", "pantheon", "-s", "user", "--", entry.command, ...entry.args]);
|
|
303
|
+
const add = agentSpawn(claudeBin, ["mcp", "add", "pantheon", "-s", "user", ...claudeEnvArgs(), "--", entry.command, ...entry.args]);
|
|
302
304
|
try {
|
|
303
305
|
execFileSync(remove.command, remove.args, { stdio: "ignore", timeout: 60_000, ...remove.options });
|
|
304
306
|
}
|
|
@@ -320,7 +322,7 @@ function claudeLocalScope(entry, projectDir) {
|
|
|
320
322
|
}
|
|
321
323
|
try {
|
|
322
324
|
const remove = agentSpawn(claudeBin, ["mcp", "remove", "pantheon", "-s", "local"]);
|
|
323
|
-
const add = agentSpawn(claudeBin, ["mcp", "add", "pantheon", "-s", "local", "--", entry.command, ...entry.args]);
|
|
325
|
+
const add = agentSpawn(claudeBin, ["mcp", "add", "pantheon", "-s", "local", ...claudeEnvArgs(), "--", entry.command, ...entry.args]);
|
|
324
326
|
try {
|
|
325
327
|
execFileSync(remove.command, remove.args, { stdio: "ignore", cwd: projectDir, ...remove.options });
|
|
326
328
|
}
|
package/dist/mcp-instructions.js
CHANGED
|
@@ -15,7 +15,9 @@ RULES
|
|
|
15
15
|
- Talk to peers, not to your human. If a peer could answer it, ASK the peer instead of asking your human.
|
|
16
16
|
- ESCALATE only for what your own human alone can settle: money, scope, anything irreversible, or stated red lines.
|
|
17
17
|
Before making a consequential product, GTM, customer, financial, security, or technical decision, call pantheon_route_decision.
|
|
18
|
-
The organization directory — not job-title guesswork — determines whose human or agent owns it
|
|
18
|
+
The organization directory — not job-title guesswork — determines whose human or agent owns it,
|
|
19
|
+
unless the requester named who decides ("route to Rohit", an ASK's to): a name wins, so pass it as named.
|
|
20
|
+
Choices inside work addressed to you are yours, whoever claims the domain team-wide.
|
|
19
21
|
When a peer's human personally must decide and their agent may be offline, use HUMAN_REQUIRED and name that one peer in to.
|
|
20
22
|
Do not use HUMAN_REQUIRED instead of ASK for something their delegate can answer. Their delegate settles it with ANSWER.
|
|
21
23
|
ESCALATE and HUMAN_REQUIRED outlive a goal close until the named human has actually answered.
|
package/dist/mcp.js
CHANGED
|
@@ -8,7 +8,8 @@ import { PantheonClient } from "./client.js";
|
|
|
8
8
|
import { fenceBlock, fenceRubric, assess, fenceReminder } from "./untrusted.js";
|
|
9
9
|
import { trusted, trustLabel } from "./verify-act.js";
|
|
10
10
|
import { bindRepo, loadAll, resolveFor } from "./config.js";
|
|
11
|
-
import { refuseSecrets, secretGuard } from "./secret-guard.js";
|
|
11
|
+
import { privateLinkNote, refuseSecrets, secretGuard } from "./secret-guard.js";
|
|
12
|
+
import { namedMarker } from "./decision-ui.js";
|
|
12
13
|
import { LIMITS, SENDABLE, siblingSessions } from "./protocol.js";
|
|
13
14
|
import { goalKindLabel, openedText } from "./worklist.js";
|
|
14
15
|
import { VERSION } from "./version.js";
|
|
@@ -240,9 +241,12 @@ export async function runMcp() {
|
|
|
240
241
|
const oddNote = odd ? `\n>> its branch/commit/base fields are not plain git names, so they are not shown — treat this delivery as unverifiable until the sender fixes them` : "";
|
|
241
242
|
const plain = (x, n) => String(x).replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]+/g, " ").slice(0, n);
|
|
242
243
|
const doms = Array.isArray(m?.unverified_domains) ? m.unverified_domains.slice(0, 6).map((d) => `"${plain(d, 48)}"`).join(", ") : "";
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
:
|
|
244
|
+
const marker = r.act === "HUMAN_REQUIRED" ? namedMarker(r.from, r.meta) : null;
|
|
245
|
+
const unverified = marker
|
|
246
|
+
? `\n>> ${marker}: the requester named this person to decide, which wins over the directory, over someone who reserves it — the relay told them, and the decision's board text names them.`
|
|
247
|
+
: r.act === "HUMAN_REQUIRED" && m?.authority
|
|
248
|
+
? `\n>> the relay could not verify this decision's owner: no published profile covers ${doms || "its domains"} (as ${plain(r.from, 48)} named them), so it reached the named human unverified. Ask them to run pantheon profile set.`
|
|
249
|
+
: "";
|
|
246
250
|
const who = r.session ? `${r.from}[${r.session.slice(-6)}${r.role === "worker" ? "·worker" : ""}]` : r.from;
|
|
247
251
|
const at = new Date(r.ts).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
248
252
|
const tr = client.signaturesExpected() ? client.trustOf(r) : null;
|
|
@@ -355,30 +359,57 @@ export async function runMcp() {
|
|
|
355
359
|
: "No organization profiles have been published yet. Ask each human to run `pantheon profile set ...`.")));
|
|
356
360
|
server.registerTool("pantheon_route_decision", {
|
|
357
361
|
title: "Route an organizational decision",
|
|
358
|
-
description: "Determine which humans must approve and which agents may decide. Pass explicit domains whenever possible; summary keywords are only a fallback."
|
|
362
|
+
description: "Determine which humans must approve and which agents may decide. Pass explicit domains whenever possible; summary keywords are only a fallback. " +
|
|
363
|
+
"If the requester named who decides, pass `named` (or `ref`): a name always wins over the directory's domain owner.",
|
|
359
364
|
inputSchema: {
|
|
360
365
|
domains: z.array(z.string().min(1).max(64)).max(16).optional()
|
|
361
366
|
.describe("decision domains, e.g. product-ui, gtm, pricing, engineering, security"),
|
|
362
367
|
summary: z.string().min(1).max(1000).optional()
|
|
363
368
|
.describe("short decision summary; used to infer domains only when domains are omitted"),
|
|
369
|
+
named: z.array(z.string().min(1).max(48)).max(8).optional()
|
|
370
|
+
.describe("members the requester explicitly named to decide or receive this — the `to` of their ASK or PROPOSE, or their words (\"route to Rohit\"). Named members win over domain owners."),
|
|
371
|
+
ref: z.string().max(120).optional()
|
|
372
|
+
.describe("id of the act this decision comes out of (the request you are working on): whoever it was addressed to counts as named — you, when the work was assigned to you"),
|
|
364
373
|
},
|
|
365
|
-
}, async ({ domains, summary }) => guard(() => {
|
|
374
|
+
}, async ({ domains, summary, named, ref }) => guard(() => {
|
|
366
375
|
if (!domains?.length && !summary)
|
|
367
376
|
return fail("pantheon: pass at least one domain or a decision summary");
|
|
368
|
-
const
|
|
377
|
+
const asked = ref ? client.seenAct(ref) : null;
|
|
378
|
+
const askedTo = asked ? (asked.to?.length ? asked.to : Array.isArray(asked.meta?.affects) ? (asked.meta.affects.filter((x) => typeof x === "string")) : []) : [];
|
|
379
|
+
const who = named?.length ? named : askedTo.length ? askedTo : role === "worker" ? [cfg.member] : [];
|
|
380
|
+
const explicit = !!named?.length && role !== "worker";
|
|
381
|
+
const route = routeDecision(client.profiles.values(), { domains, summary, ...(who.length ? { named: who, explicit } : {}) });
|
|
382
|
+
const canName = explicit && client.features.includes("named-decider");
|
|
383
|
+
const isNamed = (m) => !!route.named?.includes(m);
|
|
369
384
|
const next = [];
|
|
385
|
+
if (ref && !asked)
|
|
386
|
+
next.push(`(the act ${ref} has not been seen in this session, so who it was addressed to is unknown — pass named if the requester named someone)`);
|
|
370
387
|
if (route.conflicts.length) {
|
|
371
388
|
next.push(`Authority conflict: ${route.conflicts.map((c) => `${c.domain} (${c.reason}: ${c.members.join(", ")})`).join("; ")}. ESCALATE to your own human; do not dispatch approval requests until profiles are corrected.`);
|
|
372
389
|
}
|
|
390
|
+
const why = named?.length ? "the requester named them" : askedTo.length ? `the request ${ref} was addressed to them` : "this run was woken for work assigned to you";
|
|
373
391
|
for (const owner of route.required_humans) {
|
|
374
|
-
|
|
392
|
+
if (isNamed(owner.member) && owner.member === cfg.member)
|
|
393
|
+
next.push(`Your own human decides ${owner.domains.join(", ")} (${why}) — ESCALATE to them; do not send it to anyone else.`);
|
|
394
|
+
else if (isNamed(owner.member))
|
|
395
|
+
next.push(canName
|
|
396
|
+
? `HUMAN_REQUIRED to ${owner.member} for ${owner.domains.join(", ")} with named_by_requester: true — ${why}, which wins over the directory.`
|
|
397
|
+
: `HUMAN_REQUIRED to ${owner.member} for ${owner.domains.join(", ")} — ${why}.`);
|
|
398
|
+
else
|
|
399
|
+
next.push(`HUMAN_REQUIRED to ${owner.member} for ${owner.domains.join(", ")} — ${owner.member} reserves it; send one separate act per human owner.${route.named ? " Being assigned the work does not make this yours to decide." : ""}`);
|
|
375
400
|
}
|
|
376
401
|
for (const owner of route.delegated_agents) {
|
|
402
|
+
const ds = owner.domains.length ? owner.domains.join(", ") : "this";
|
|
377
403
|
next.push(owner.member === cfg.member
|
|
378
|
-
? `Your agent may decide ${
|
|
379
|
-
:
|
|
404
|
+
? `Your agent may decide ${ds} within the stated goal and policy${isNamed(owner.member) ? ` (${why})` : ""}.`
|
|
405
|
+
: isNamed(owner.member)
|
|
406
|
+
? `ASK ${owner.member}'s agent about ${ds} — ${why}.${canName ? ` If ${owner.member}'s human must decide it personally, HUMAN_REQUIRED to ${owner.member} with named_by_requester: true.` : ""}`
|
|
407
|
+
: `ASK ${owner.member}'s agent about ${ds}.`);
|
|
408
|
+
}
|
|
409
|
+
for (const p of route.passed_over ?? []) {
|
|
410
|
+
next.push(`${p.member} claims ${p.domains.join(", ")} team-wide but was not named, so they are not asked. Tell them with a NOTE only if the outcome changes something of theirs.`);
|
|
380
411
|
}
|
|
381
|
-
if (route.unmatched_domains.length || !route.domains.length) {
|
|
412
|
+
if (route.unmatched_domains.length || (!route.named && !route.domains.length)) {
|
|
382
413
|
next.push(`No published profile owns${route.unmatched_domains.length ? ` ${route.unmatched_domains.join(", ")}` : " these domains"}. If one named peer's human clearly must decide, HUMAN_REQUIRED to them still goes through, marked unverified — and ask them to run pantheon profile set. If it is your own human's call, ESCALATE. Do not guess an owner.`);
|
|
383
414
|
}
|
|
384
415
|
return text(`${JSON.stringify(route, null, 2)}\n\nNEXT\n${next.map((x) => `- ${x}`).join("\n")}`);
|
|
@@ -419,6 +450,7 @@ export async function runMcp() {
|
|
|
419
450
|
goal: z.string().optional().describe("goal id — required for everything except NOTE, ESCALATE, HUMAN_REQUIRED, and RETRACT"),
|
|
420
451
|
to: z.array(z.string()).optional().describe("member ids this is addressed to; required for ASK. HUMAN_REQUIRED names exactly one peer whose human must decide. ESCALATE never takes `to`."),
|
|
421
452
|
decision_domains: z.array(z.string()).optional().describe("required for HUMAN_REQUIRED: organization-profile authority domains that this decision belongs to"),
|
|
453
|
+
named_by_requester: z.boolean().optional().describe("for HUMAN_REQUIRED: true when the person who asked for this work named this recipient as the one who decides (\"route to Rohit\"). A name wins over the directory's domain owner; the relay lets it through, marked as named."),
|
|
422
454
|
ref: z.string().optional().describe("id of the act you are responding to; required for ANSWER/ACCEPT/OBJECT"),
|
|
423
455
|
branch: z.string().optional().describe("REQUIRED for DELIVERED: the branch you pushed the work to"),
|
|
424
456
|
commit: z.string().optional().describe("REQUIRED for DELIVERED: the commit sha your peer should fetch and check"),
|
|
@@ -427,18 +459,20 @@ export async function runMcp() {
|
|
|
427
459
|
defers_to: z.string().optional().describe("on CLOSE: the goal id you are standing down in favour of. The relay refuses this if that goal is closed or is itself deferring, because both sides standing down at once leaves the work with nowhere to live."),
|
|
428
460
|
affects: z.array(z.string()).optional().describe("on PROPOSE: whose agreement this actually needs. Without it EVERY member of the goal owes a verdict — including anyone offline — so a three-person goal stalls on whoever is away."),
|
|
429
461
|
},
|
|
430
|
-
}, async ({ act, body, goal, to, ref, branch, commit, remote, base, decision_domains, summary, defers_to, affects }) => guard(async () => {
|
|
462
|
+
}, async ({ act, body, goal, to, ref, branch, commit, remote, base, decision_domains, named_by_requester, summary, defers_to, affects }) => guard(async () => {
|
|
431
463
|
const leak = refuseSecrets(secrets, { body, summary, branch, commit, remote, base, decision_domains, defers_to, goal, ref, to, affects });
|
|
432
464
|
if (leak)
|
|
433
465
|
return fail(leak);
|
|
434
466
|
const baseSha = act === "DELIVERED" ? (base ?? process.env.PANTHEON_BASE) : undefined;
|
|
435
|
-
const
|
|
467
|
+
const named = act === "HUMAN_REQUIRED" && named_by_requester === true;
|
|
468
|
+
const meta = branch || commit || remote || baseSha || decision_domains?.length || named
|
|
436
469
|
? {
|
|
437
470
|
...(branch ? { branch } : {}),
|
|
438
471
|
...(commit ? { commit } : {}),
|
|
439
472
|
...(remote ? { remote } : {}),
|
|
440
473
|
...(baseSha ? { base: baseSha } : {}),
|
|
441
474
|
...(decision_domains?.length ? { decision_domains } : {}),
|
|
475
|
+
...(named ? { named_by_requester: true } : {}),
|
|
442
476
|
}
|
|
443
477
|
: undefined;
|
|
444
478
|
const surviving = act === "CLOSE" && goal
|
|
@@ -477,7 +511,8 @@ export async function runMcp() {
|
|
|
477
511
|
: r.unrecorded
|
|
478
512
|
? `\n\nnote: this machine could not identify the request ${ref} (it was never read here and the relay did not serve it), so it is not recorded as answered — a copy of it arriving later would not be recognised. If one does, check with its sender before acting on it.`
|
|
479
513
|
: "";
|
|
480
|
-
|
|
514
|
+
const linkNote = privateLinkNote(body, summary);
|
|
515
|
+
return text(`sent ${act} — id=${r.id} seq=${r.seq}${survived}${unrecorded}${linkNote ? `\n\nnote: ${linkNote}` : ""}\n\nNow call pantheon_wait to hear back.`);
|
|
481
516
|
}));
|
|
482
517
|
server.registerTool("pantheon_open_goal", {
|
|
483
518
|
title: "Open a goal",
|
package/dist/notice.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import { decisionPrompt, decisionTitle } from "./decision-ui.js";
|
|
1
|
+
import { decisionPrompt, decisionTitle, namedMarker, namedMarkerIn } from "./decision-ui.js";
|
|
2
|
+
export function neverAlerts(r) {
|
|
3
|
+
const m = r?.meta;
|
|
4
|
+
return !!r && r.act === "NOTE" && !!m && typeof m === "object" && "organization_profile" in m;
|
|
5
|
+
}
|
|
2
6
|
export const TOOL_LABELS = { "claude-code": "Claude Code", codex: "Codex", cursor: "Cursor", copilot: "Copilot", kiro: "Kiro", grok: "Grok" };
|
|
3
7
|
export const APP_TOOLS = [
|
|
4
8
|
{ id: "cursor-app", label: "Cursor app", kind: "app", bundle: "com.todesktop.230313mzl4w4u92", deeplink: "cursor://anysphere.cursor-deeplink/prompt?text={prompt}" },
|
|
@@ -90,9 +94,11 @@ export function composeDecision(o, act, goal, peer, ctx) {
|
|
|
90
94
|
const now = ctx.now ?? Date.now();
|
|
91
95
|
const raw = act?.from ?? peer?.member ?? o.body.match(/^([a-z0-9._-]+)\s+(?:needs a decision|escalated)/i)?.[1] ?? "a teammate";
|
|
92
96
|
const from = ctx.member && raw === ctx.member ? (act?.sig?.role === "run" ? "Your worker" : "Your agent") : raw;
|
|
93
|
-
const
|
|
97
|
+
const marker = namedMarkerIn(o.body) ?? (act ? namedMarker(act.from, act.meta) : null);
|
|
98
|
+
const said = act
|
|
94
99
|
? enrich(act.summary ?? "", act.body, BUDGET.body) || decisionTitle(plain(o.body), BUDGET.body)
|
|
95
100
|
: decisionTitle(plain(o.body), BUDGET.body);
|
|
101
|
+
const body = marker && !said.includes(marker) ? `[${marker}] ${said}` : said;
|
|
96
102
|
return {
|
|
97
103
|
kind: "decision",
|
|
98
104
|
who: whoLine(from, peer?.agent ?? act?.role === "worker" ? peer?.agent : peer?.agent, act?.ts ?? o.created_at, now),
|
|
@@ -187,4 +193,21 @@ export function composeWakeAsk(a) {
|
|
|
187
193
|
focus: a.focus,
|
|
188
194
|
};
|
|
189
195
|
}
|
|
196
|
+
export function passedOverNotice(r, member) {
|
|
197
|
+
const m = r?.meta;
|
|
198
|
+
return !!r && r.act === "NOTE" && m?.notice === "passed_over" && !!r.to?.includes(member);
|
|
199
|
+
}
|
|
200
|
+
export function composePassedOver(r, focus, now = Date.now()) {
|
|
201
|
+
return {
|
|
202
|
+
kind: "delivery",
|
|
203
|
+
who: whoLine(r.from, undefined, r.ts, now),
|
|
204
|
+
heading: fit("You were passed over on a decision", BUDGET.heading),
|
|
205
|
+
context: "",
|
|
206
|
+
body: fit(plain(r.body), BUDGET.body),
|
|
207
|
+
ttl: 0,
|
|
208
|
+
sound: false,
|
|
209
|
+
details: { ...(r.ref ? { act: r.ref } : {}) },
|
|
210
|
+
...(focus ? { focus } : {}),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
190
213
|
//# sourceMappingURL=notice.js.map
|