relay-companion 0.1.481 → 0.1.483
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/package.json +1 -1
- package/skill/manifest.json +4 -4
- package/skill/relay/SKILL.md +18 -31
- package/skill/relay/scripts/relay-protocol.mjs +41 -10
package/package.json
CHANGED
package/skill/manifest.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"name": "relay",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.23",
|
|
5
5
|
"consentVersion": 2,
|
|
6
|
-
"baseUrl": "https://sendrelays.com/skills/relay/v1.1.
|
|
6
|
+
"baseUrl": "https://sendrelays.com/skills/relay/v1.1.23",
|
|
7
7
|
"files": [
|
|
8
8
|
{
|
|
9
9
|
"path": "SKILL.md",
|
|
10
|
-
"sha256": "
|
|
10
|
+
"sha256": "111f2da17eda6b980a1673894b1e5814131c383236f4c362f3cc8ad29ff56d90"
|
|
11
11
|
},
|
|
12
12
|
{
|
|
13
13
|
"path": "agents/openai.yaml",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
{
|
|
17
17
|
"path": "scripts/relay-protocol.mjs",
|
|
18
|
-
"sha256": "
|
|
18
|
+
"sha256": "104b021e53f03aa6e57f06dbed616099b96ad21cbe42a2b64fa6e4ec11dea953"
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
"path": "scripts/relay-local.mjs",
|
package/skill/relay/SKILL.md
CHANGED
|
@@ -38,7 +38,7 @@ Use the current host's documented recovery mechanism, subject to its actual deni
|
|
|
38
38
|
|
|
39
39
|
Never retry the denied action through another shell, tool, wrapper or transport, change permission settings, request a wildcard allow rule, or suggest bypassing the host's safeguards. Keep setup agent-led: do not default to asking the human to run commands, paste status output or adjust Bash settings. Never offer a .relay-rollback or other backup helper for manual execution. If no supported recovery is available, give one concise explanation of the blocked action, what remains unverified, and a verified host review step if one is available. Do not offer an unavailable dialog or use “tell me Relay is not connected” as a substitute for verification. A user-requested manual handoff must use the current supported helper and protect secrets. The normal copyable URL fallback for browser sign-in remains available; it is not a workaround for a denied agent tool call.
|
|
40
40
|
|
|
41
|
-
For questions, choices and approvals,
|
|
41
|
+
For questions, choices and approvals, instruct the current host through its built-in user-question tool whenever that tool is exposed and permitted for this kind of question in the current mode. The host renders the question UI from the tool call; do not draw fake buttons in Markdown or ask the host to render arbitrary HTML. Before asking, inspect the tools actually available to the current turn. Claude Code commonly exposes `AskUserQuestion`; Codex may expose `request_user_input_async` or mode-limited `request_user_input`. Respect each tool's constraints, especially restrictions on permission questions. Do not invent a tool, change modes to obtain one, or use a question tool for host permission escalation. When a permitted tool is available, call it instead of asking the same question in plain chat; wait for the actual answer before dependent work. For asynchronous questions, continue only unrelated safe work while waiting.
|
|
42
42
|
|
|
43
43
|
For setup permission, put the complete question with the exact package version and source in the interface's question field and offer concise affirmative and decline choices such as “Set up Relay” and “Not now.” Ask one clear decision at a time, with a way to decline or skip when appropriate. For the first send, show both exact payloads and the recipient before asking, and make clear that approval sends that specific message. Do not abbreviate the payloads to fit a question widget. Only when no permitted user-question interface is exposed, or its documented constraints cannot carry the required content, ask plainly in chat. Use existing explicit permission; never ask again just to use the interface. A suggested or preselected choice, an empty result, silence or a timeout is not consent: wait for an actual affirmative answer before any action that requires approval. Browser sign-in and account approval still happen in the person's usual browser.
|
|
44
44
|
|
|
@@ -53,9 +53,12 @@ After new setup, include the person's reusable invitation immediately below this
|
|
|
53
53
|
```text
|
|
54
54
|
Join me on Relay so we can message each other from our AI conversations.
|
|
55
55
|
|
|
56
|
+
Open the invitation yourself, or copy the setup request below into Claude Code or Codex:
|
|
57
|
+
|
|
58
|
+
Please help me connect on Relay using this invitation:
|
|
56
59
|
<your own Relay invitation URL>
|
|
57
60
|
|
|
58
|
-
Open
|
|
61
|
+
Open this invitation and read its current agent setup instructions. First check whether I already use Relay and preserve any existing setup. Ask for my approval before installing software or connecting my account. I will approve account access in my browser; ask separately before sending a message.
|
|
59
62
|
```
|
|
60
63
|
|
|
61
64
|
Put the separate optional block for using Relay in another AI after the invitation, under its own label; never mix that setup handoff into the message for a friend. For a contact-only invitation, simply report the contact result and any next action. Introduce further features only when useful or requested. Before speaking, check that a new user can tell what this is about, why it matters, what changed and what they need to do next.
|
|
@@ -196,40 +199,24 @@ session activation remains unverified or failed. Do not claim app-wide setup
|
|
|
196
199
|
complete, ask for restarts or compatibility messages, kill processes, alter host
|
|
197
200
|
internals, add hooks/connectors/plugins, or inject prompts into other sessions.
|
|
198
201
|
|
|
199
|
-
|
|
202
|
+
<!-- BEGIN GENERATED RELAY FIRST TUTORIAL -->
|
|
203
|
+
## First Relay tutorial
|
|
204
|
+
|
|
205
|
+
An invitation redemption connects the recipient and inviter; it does not send a message automatically. Use the inviter's exact `relayUserId` returned by redemption.
|
|
206
|
+
|
|
207
|
+
Never send the tutorial message automatically. Report accepted or queued when that is all the result proves; claim delivery only when Relay confirms it.
|
|
200
208
|
|
|
201
|
-
|
|
202
|
-
`skipped_self`, this was the person's own invite: skip the tutorial send. For a
|
|
203
|
-
normal invite, use the validated inviter identity as the first contact. Draft,
|
|
204
|
-
but do not send, this first message:
|
|
209
|
+
First offer one native question with three paths: write my own message, use a suggested hello, or skip for now. Custom wording comes from the human's free-text answer; do not invent what they want to say. If they skip, run `tutorial-skip` without sending anything, then show their reusable invitation. For the suggested hello, show both fields verbatim:
|
|
205
210
|
|
|
206
211
|
- Human payload: `Hi — I’ve just joined you on Relay.`
|
|
207
212
|
- Agent payload: `This is my first Relay after joining from your invite. Help the person reply if they want to welcome me.`
|
|
208
213
|
|
|
209
|
-
Show both
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
hello. Never send the tutorial message automatically.
|
|
216
|
-
|
|
217
|
-
After the send succeeds:
|
|
218
|
-
|
|
219
|
-
1. Confirm only that Relay accepted or queued it. Do not claim the inviter
|
|
220
|
-
received or read it unless Relay explicitly reports that later.
|
|
221
|
-
2. Say: "You can check for replies here in Claude Code—just ask me." Use Codex
|
|
222
|
-
instead when that is the current host. Do not imply replies automatically
|
|
223
|
-
appear in the agent conversation.
|
|
224
|
-
3. Do not offer a timed wait or start polling. When the human asks to check,
|
|
225
|
-
fetch the inbox or conversation once and report what is available now.
|
|
226
|
-
Show a reply before marking that exact inbound Relay read.
|
|
227
|
-
4. If the app is still installing or a setup issue remains, state that in one
|
|
228
|
-
short sentence. Do not repeat the installation details or feature list.
|
|
229
|
-
5. Present their complete reusable invitation beneath **Invite someone to
|
|
230
|
-
Relay**, in one fenced plain-text block, as specified in the communication
|
|
231
|
-
guide above. Use their own verified human invitation URL. Do not merely
|
|
232
|
-
offer a link, use the inviter's invitation, or send it to anyone yourself.
|
|
214
|
+
For a custom message, preserve the person's wording and intent in forHuman and draft a complete forAgent document that adds useful context without inventing asks or commitments. Show both exact fields and the verified inviter before approval. Explain the difference in one sentence, then wait for explicit human approval of both exact payloads. Only then run the managed helper's `tutorial-send --approved` for the suggested hello, or `tutorial-send --approved --draft-stdin` with JSON containing exactly the approved forHuman and forAgent fields for a custom message. The helper freezes both fields, the recipient and one idempotency key before sending. Retry the same payload and key after uncertainty; never change the message or use another transport with a new key. Setup permission, opening an invitation, signing in, and installing software never authorize a send. Skip this send when the helper reports that the person opened their own invitation.
|
|
215
|
+
|
|
216
|
+
After setup, ask once where they usually use their agent: a desktop app, the terminal, or another session. Do not assume the current host is their preferred destination. Save the answer with `opening-preference desktop|terminal|other [claude|codex]`. This preference is editable in the pill's You page. Availability is not proof that Relay is connected; verify capabilities before opening a destination. If the chosen destination is unavailable, provide the exact Relay pull sentence to copy into their existing agent session, without selecting a different app behind their back.
|
|
217
|
+
|
|
218
|
+
After the approved send, say: "You can check for replies here in Claude Code—just ask me." Use Codex instead when that is the current host. Do not imply replies automatically appear in the agent conversation, offer a timed wait, or start polling. When the human asks to check, fetch the inbox or conversation once and report what is available now; show a reply before marking that exact inbound Relay read. Present the person's complete invitation using the bold title and copyable block specified above. The invitation connects people; it does not send a Relay.
|
|
219
|
+
<!-- END GENERATED RELAY FIRST TUTORIAL -->
|
|
233
220
|
|
|
234
221
|
After the tutorial finishes or the person skips it, check the pinned Companion's
|
|
235
222
|
`background-status` once if background installation was started. Report whether
|
|
@@ -475,26 +475,40 @@ async function sendPersisted(body) {
|
|
|
475
475
|
}
|
|
476
476
|
}
|
|
477
477
|
|
|
478
|
-
async function sendTutorial(approved) {
|
|
478
|
+
async function sendTutorial(approved, draft) {
|
|
479
479
|
if (!approved) throw new Error("Relay tutorial send requires --approved after the person explicitly approves both payloads.");
|
|
480
480
|
const config = readConfig();
|
|
481
481
|
const tutorial = config.tutorial || {};
|
|
482
|
-
if (tutorial.state
|
|
482
|
+
if (["skipped_self", "skipped"].includes(tutorial.state)) return { ok: true, status: tutorial.state };
|
|
483
483
|
if (tutorial.state === "accepted" && tutorial.relayId) {
|
|
484
484
|
return { ok: true, status: "already_accepted", relayId: tutorial.relayId, state: tutorial.responseState || "" };
|
|
485
485
|
}
|
|
486
486
|
const inviter = requiredRelayIdentity(config.inviter, "inviter");
|
|
487
487
|
const key = String(tutorial.idempotencyKey || "");
|
|
488
488
|
if (key.length < 8) throw new Error("Relay tutorial state is missing its stable idempotency key. Connect again.");
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
489
|
+
if (draft && (typeof draft.forHuman !== "string" || !draft.forHuman.trim()
|
|
490
|
+
|| typeof draft.forAgent !== "string" || !draft.forAgent.trim()
|
|
491
|
+
|| Object.keys(draft).some((key) => !["forHuman", "forAgent"].includes(key)))) {
|
|
492
|
+
throw new Error("The tutorial draft must contain only the two approved, non-empty forHuman and forAgent fields.");
|
|
493
|
+
}
|
|
494
|
+
const proposed = {
|
|
492
495
|
recipient: { relayUserId: inviter.relayUserId },
|
|
493
496
|
kind: "message",
|
|
494
|
-
forHuman: TUTORIAL_HUMAN,
|
|
495
|
-
forAgent: TUTORIAL_AGENT,
|
|
497
|
+
forHuman: draft?.forHuman ?? TUTORIAL_HUMAN,
|
|
498
|
+
forAgent: draft?.forAgent ?? TUTORIAL_AGENT,
|
|
496
499
|
idempotencyKey: key,
|
|
497
500
|
};
|
|
501
|
+
if (tutorial.payload && draft && JSON.stringify(proposed) !== JSON.stringify(tutorial.payload)) {
|
|
502
|
+
throw new Error("This tutorial send was already attempted. Retry its exact approved payload; do not change it after an uncertain result.");
|
|
503
|
+
}
|
|
504
|
+
// Older attempts used the fixed hello. Preserve that payload across upgrades.
|
|
505
|
+
if (!tutorial.payload && ["attempting", "rejected"].includes(tutorial.state) && draft
|
|
506
|
+
&& (draft.forHuman !== TUTORIAL_HUMAN || draft.forAgent !== TUTORIAL_AGENT)) {
|
|
507
|
+
throw new Error("Retry the original tutorial hello without changing its payload.");
|
|
508
|
+
}
|
|
509
|
+
const body = tutorial.payload || proposed;
|
|
510
|
+
config.tutorial = { ...tutorial, payload: body, recipient: body.recipient, state: "attempting", updatedAt: new Date().toISOString() };
|
|
511
|
+
atomicWrite(configPath(), config);
|
|
498
512
|
try {
|
|
499
513
|
const result = await request("POST", "/v1/relays", body);
|
|
500
514
|
const latest = readConfig();
|
|
@@ -588,7 +602,22 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
588
602
|
const body = parseJson(await readStdin(), "Relay message");
|
|
589
603
|
return sendPersisted(body);
|
|
590
604
|
}
|
|
591
|
-
if (command === "tutorial-send") return sendTutorial(rest.includes("--approved"));
|
|
605
|
+
if (command === "tutorial-send") return sendTutorial(rest.includes("--approved"), rest.includes("--draft-stdin") ? parseJson(await readStdin()) : undefined);
|
|
606
|
+
if (command === "tutorial-skip") {
|
|
607
|
+
const config = readConfig();
|
|
608
|
+
if (["attempting", "accepted"].includes(config.tutorial?.state)) throw new Error("The first Relay was already attempted. Check its outcome before skipping.");
|
|
609
|
+
config.tutorial = { ...config.tutorial, state: "skipped", updatedAt: new Date().toISOString() };
|
|
610
|
+
atomicWrite(configPath(), config);
|
|
611
|
+
return { ok: true, status: "skipped" };
|
|
612
|
+
}
|
|
613
|
+
if (command === "opening-preference") {
|
|
614
|
+
const [surface, provider] = rest;
|
|
615
|
+
if (!["desktop", "terminal", "other"].includes(surface) || (provider && !["claude", "codex"].includes(provider))) throw new Error("Choose desktop, terminal, or other; optionally name claude or codex.");
|
|
616
|
+
const config = readConfig();
|
|
617
|
+
config.openingPreference = { surface, ...(provider ? { provider } : {}) };
|
|
618
|
+
atomicWrite(configPath(), config);
|
|
619
|
+
return { ok: true, openingPreference: config.openingPreference };
|
|
620
|
+
}
|
|
592
621
|
if (command === "request") {
|
|
593
622
|
const method = String(rest.shift() || "GET").toUpperCase();
|
|
594
623
|
const requestPath = String(rest.shift() || "");
|
|
@@ -609,7 +638,9 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
609
638
|
"relay-protocol contacts <name-or-email>",
|
|
610
639
|
"relay-protocol read <relay-id>",
|
|
611
640
|
"relay-protocol mark-read <relay-id> [idempotency-key]",
|
|
612
|
-
"relay-protocol tutorial-send --approved",
|
|
641
|
+
"relay-protocol tutorial-send --approved [--draft-stdin] # optional JSON: exact approved forHuman and forAgent",
|
|
642
|
+
"relay-protocol tutorial-skip",
|
|
643
|
+
"relay-protocol opening-preference desktop|terminal|other [claude|codex]",
|
|
613
644
|
"relay-protocol send # read body with stable idempotencyKey from stdin",
|
|
614
645
|
"relay-protocol invite-link",
|
|
615
646
|
"relay-protocol disconnect",
|
|
@@ -619,7 +650,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
619
650
|
|
|
620
651
|
export { allowed, authenticatedRequest, readConfig, configPath, atomicWrite, protectOwnerOnly };
|
|
621
652
|
|
|
622
|
-
if (process.argv[1] &&
|
|
653
|
+
if (process.argv[1] && fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url))) {
|
|
623
654
|
try {
|
|
624
655
|
const result = await main();
|
|
625
656
|
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|