@rine-network/eve 0.2.0 → 0.5.0

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.
Files changed (49) hide show
  1. package/README.md +12 -6
  2. package/dist/_zod.d.ts +1 -1
  3. package/dist/{channel-b0iIAZTh.js → channel-B81UiHyr.js} +29 -26
  4. package/dist/channel-core.d.ts +14 -11
  5. package/dist/channel-outbound.d.ts +3 -3
  6. package/dist/channel.d.ts +5 -5
  7. package/dist/channel.js +1 -1
  8. package/dist/{client-X_-9CpQT.js → client-CXJATA-m.js} +24 -4
  9. package/dist/client.d.ts +20 -2
  10. package/dist/errors.d.ts +1 -1
  11. package/dist/format-groups-list.d.ts +140 -0
  12. package/dist/format-groups.d.ts +81 -0
  13. package/dist/format.d.ts +57 -13
  14. package/dist/inbound.d.ts +3 -3
  15. package/dist/index.d.ts +1 -1
  16. package/dist/index.js +7 -7
  17. package/dist/onboard.d.ts +1 -1
  18. package/dist/onboard.js +3 -3
  19. package/dist/{registry-6sWyhOyF.js → registry-DsU13KY3.js} +57 -2
  20. package/dist/relay.d.ts +1 -1
  21. package/dist/relay.js +3 -3
  22. package/dist/scaffold-DQ2CA1kD.js +239 -0
  23. package/dist/scaffold.js +1 -1
  24. package/dist/schemas-groups-list.d.ts +29 -0
  25. package/dist/schemas-groups.d.ts +95 -9
  26. package/dist/schemas-payments.d.ts +2 -2
  27. package/dist/schemas.d.ts +27 -7
  28. package/dist/skill-content.d.ts +8 -2
  29. package/dist/tool-DeOeMNlK.js +618 -0
  30. package/dist/tool.d.ts +8 -8
  31. package/dist/tools/discovery.d.ts +8 -2
  32. package/dist/tools/groups-admin.d.ts +25 -0
  33. package/dist/tools/groups-admission.d.ts +56 -0
  34. package/dist/tools/groups-list.d.ts +21 -0
  35. package/dist/tools/groups-resolve.d.ts +80 -0
  36. package/dist/tools/groups.d.ts +22 -9
  37. package/dist/tools/index.d.ts +6 -4
  38. package/dist/tools/index.js +3 -3
  39. package/dist/tools/messaging.d.ts +14 -12
  40. package/dist/tools-CQDZG-qN.js +1149 -0
  41. package/dist/transcript.d.ts +2 -2
  42. package/dist/types.d.ts +4 -1
  43. package/dist/webhook.d.ts +9 -2
  44. package/dist/webhook.js +34 -14
  45. package/dist/x402.d.ts +3 -3
  46. package/package.json +3 -3
  47. package/dist/scaffold-0luQyBRK.js +0 -146
  48. package/dist/tool-BC49DldZ.js +0 -259
  49. package/dist/tools-BcVm_Onx.js +0 -535
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Token-budgeted, role-tagged transcript rendering for the Eve channel's
3
- * push-injection (REQ-CTX-01). The Eve session is STATELESS per inbound message,
3
+ * push-injection. The Eve session is STATELESS per inbound message,
4
4
  * so cross-turn memory comes from injecting the recent thread into the session
5
5
  * `context[]` on each inbound. This module renders that transcript within a
6
6
  * char-approximated token budget, truncating the OLDEST turns first.
7
7
  *
8
- * R4 holds: it reads only `ThreadEntry.text` (already decrypted + rendered by the
8
+ * It reads only `ThreadEntry.text` (already decrypted + rendered by the
9
9
  * SDK), never any ciphertext field.
10
10
  */
11
11
  import type { ThreadEntry } from "@rine-network/sdk";
package/dist/types.d.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  * schema changes, these follow). These are type-only re-derivations; runtime tool
8
8
  * schemas are authored with the host `zod` in `schemas.ts`.
9
9
  */
10
- import type { AgentProfileSchema, AgentSummarySchema, DecryptedMessageSchema, GroupReadSchema, InviteResultSchema, MessageReadSchema } from "@rine-network/sdk";
10
+ import type { AgentProfileSchema, AgentSummarySchema, DecryptedMessageSchema, GroupReadSchema, InviteResultSchema, JoinRequestReadSchema, JoinResultSchema, MessageReadSchema, VoteResponseSchema } from "@rine-network/sdk";
11
11
  /** Project a Zod schema's output type without importing the SDK's `z` instance. */
12
12
  type Infer<S> = S extends {
13
13
  _output: infer O;
@@ -16,6 +16,9 @@ export type DecryptedMessage = Infer<typeof DecryptedMessageSchema>;
16
16
  export type MessageRead = Infer<typeof MessageReadSchema>;
17
17
  export type GroupRead = Infer<typeof GroupReadSchema>;
18
18
  export type InviteResult = Infer<typeof InviteResultSchema>;
19
+ export type JoinResult = Infer<typeof JoinResultSchema>;
20
+ export type JoinRequestRead = Infer<typeof JoinRequestReadSchema>;
19
21
  export type AgentSummary = Infer<typeof AgentSummarySchema>;
20
22
  export type AgentProfile = Infer<typeof AgentProfileSchema>;
23
+ export type VoteResponse = Infer<typeof VoteResponseSchema>;
21
24
  export {};
package/dist/webhook.d.ts CHANGED
@@ -5,13 +5,14 @@
5
5
  * by webhook (no daemon, serverless-native). This registers a rine webhook that
6
6
  * POSTs new-message notifications to the channel route, and returns the HMAC
7
7
  * `secret` the channel verifies with (`RINE_WEBHOOK_SECRET`). `--delete` tears it
8
- * down. R1: the client is built lazily inside the call.
8
+ * down. The client is built lazily inside the call.
9
9
  */
10
10
  /** Options shared by register/delete. */
11
11
  export interface WebhookOptions {
12
12
  /** Public base URL of the deployed Eve agent (e.g. `https://x.vercel.app`). */
13
13
  baseUrl: string;
14
- /** Acting agent handle/UUID; defaults to `process.env.RINE_AGENT`. */
14
+ /** Acting agent name, handle or UUID; falls back to `RINE_AGENT`, then to
15
+ * the org's only agent. */
15
16
  agent?: string;
16
17
  apiUrl?: string;
17
18
  configDir?: string;
@@ -27,6 +28,12 @@ export interface RegisteredWebhook {
27
28
  /**
28
29
  * Register a rine webhook pointing at the deployed agent's inbound route. Returns
29
30
  * the webhook id + the HMAC secret (shown once) for `RINE_WEBHOOK_SECRET`.
31
+ *
32
+ * The whole acting-agent ladder runs here, because this is the one Eve
33
+ * entry point that must name a *specific* agent — the webhook is created against
34
+ * an agent id, not an org. So a bare name works, a single-agent org needs no
35
+ * `--agent` at all, and the refusal names the org's agents rather than asserting
36
+ * the caller forgot an env var they may well have set to the wrong thing.
30
37
  */
31
38
  export declare function registerRineWebhook(opts: WebhookOptions): Promise<RegisteredWebhook>;
32
39
  /** Delete a previously-registered webhook by id (best-effort teardown). */
package/dist/webhook.js CHANGED
@@ -1,6 +1,6 @@
1
- import { t as getRineClient } from "./client-X_-9CpQT.js";
1
+ import { n as getRineClient, t as actingAgent } from "./client-CXJATA-m.js";
2
2
  import { asAgentUuid, asWebhookUuid } from "@rine-network/sdk";
3
- import { UUID_RE, resolveApiUrl, resolveToUuid } from "@rine-network/core";
3
+ import { ACTOR_SURFACES, UUID_RE, normalizeActor, resolveAgent } from "@rine-network/core";
4
4
  //#region src/webhook.ts
5
5
  /**
6
6
  * Prod inbound wiring — `npx @rine-network/eve webhook --url <publicBase>`.
@@ -9,33 +9,53 @@ import { UUID_RE, resolveApiUrl, resolveToUuid } from "@rine-network/core";
9
9
  * by webhook (no daemon, serverless-native). This registers a rine webhook that
10
10
  * POSTs new-message notifications to the channel route, and returns the HMAC
11
11
  * `secret` the channel verifies with (`RINE_WEBHOOK_SECRET`). `--delete` tears it
12
- * down. R1: the client is built lazily inside the call.
12
+ * down. The client is built lazily inside the call.
13
13
  */
14
14
  const DEFAULT_INBOUND_PATH = "/rine/v1/inbound";
15
+ /**
16
+ * How Eve spells the acting agent in a refusal.
17
+ *
18
+ * `ACTOR_SURFACES.ts` with the environment rung switched back on: the SDK
19
+ * itself reads no environment, but Eve is a *surface* and
20
+ * `actingAgent()` climbs to `RINE_AGENT` on its behalf — the scaffolder even
21
+ * writes the variable into every generated `.env.example`. A refusal that
22
+ * omitted it would hide the lever this deployment is configured with.
23
+ */
24
+ const EVE_SURFACE = {
25
+ ...ACTOR_SURFACES.ts,
26
+ readsEnv: true
27
+ };
15
28
  function inboundUrl(opts) {
16
29
  const path = opts.path ?? process.env.RINE_INBOUND_PATH ?? DEFAULT_INBOUND_PATH;
17
30
  return `${opts.baseUrl.replace(/\/$/, "")}${path}`;
18
31
  }
19
- async function resolveAgentUuid(apiUrl, agent) {
20
- if (UUID_RE.test(agent)) return asAgentUuid(agent);
21
- return asAgentUuid(await resolveToUuid(apiUrl, agent));
22
- }
23
32
  /**
24
33
  * Register a rine webhook pointing at the deployed agent's inbound route. Returns
25
34
  * the webhook id + the HMAC secret (shown once) for `RINE_WEBHOOK_SECRET`.
35
+ *
36
+ * The whole acting-agent ladder runs here, because this is the one Eve
37
+ * entry point that must name a *specific* agent — the webhook is created against
38
+ * an agent id, not an org. So a bare name works, a single-agent org needs no
39
+ * `--agent` at all, and the refusal names the org's agents rather than asserting
40
+ * the caller forgot an env var they may well have set to the wrong thing.
26
41
  */
27
42
  async function registerRineWebhook(opts) {
28
- const agent = opts.agent ?? process.env.RINE_AGENT;
29
- if (!agent) throw new Error("no acting agent — set RINE_AGENT or pass --agent <handle>");
30
- const apiUrl = opts.apiUrl ?? resolveApiUrl();
31
43
  const client = getRineClient({
32
- agent,
33
44
  apiUrl: opts.apiUrl,
34
45
  configDir: opts.configDir
35
46
  });
36
- const agentUuid = await resolveAgentUuid(apiUrl, agent);
47
+ const orgAgents = async () => (await client.identity.listAgents()).map((a) => ({
48
+ ...a,
49
+ verification_words: a.verification_words ?? void 0,
50
+ warnings: a.warnings ?? void 0,
51
+ poll_url: a.poll_url ?? void 0
52
+ }));
53
+ const named = actingAgent(opts);
54
+ const resolved = named !== void 0 && UUID_RE.test(named) ? { agentId: named } : await resolveAgent(await orgAgents(), named, void 0, EVE_SURFACE, normalizeActor(opts.agent) === void 0 ? "environment" : "argument");
55
+ if (resolved.warning !== void 0) console.warn(`rine: ${resolved.warning}`);
56
+ const agentUuid = asAgentUuid(resolved.agentId);
37
57
  const url = inboundUrl(opts);
38
- const created = await client.webhooks.create(agentUuid, url);
58
+ const created = await client.withAgent(agentUuid).webhooks.create(agentUuid, url);
39
59
  return {
40
60
  id: created.id,
41
61
  secret: created.secret,
@@ -45,7 +65,7 @@ async function registerRineWebhook(opts) {
45
65
  /** Delete a previously-registered webhook by id (best-effort teardown). */
46
66
  async function deleteRineWebhook(id, opts = {}) {
47
67
  await getRineClient({
48
- agent: opts.agent ?? process.env.RINE_AGENT,
68
+ agent: actingAgent(opts),
49
69
  apiUrl: opts.apiUrl,
50
70
  configDir: opts.configDir
51
71
  }).webhooks.delete(asWebhookUuid(id));
package/dist/x402.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * x402 payment-aware inbound re-surfacing for the rine channel (D1 + D2).
2
+ * x402 payment-aware inbound re-surfacing for the rine channel.
3
3
  *
4
4
  * The three x402 payment frames are first-class message types carrying a verbatim
5
5
  * x402 V2 object, NOT a chat turn. v1.0 dropped them wholesale (a stateless gateway
@@ -12,7 +12,7 @@
12
12
  * - `x402_payment` → the signed authorization + guidance to `rine_fulfill`.
13
13
  * - `x402_receipt` → the settlement outcome, informational (no reply).
14
14
  *
15
- * D2 auto-pay (opt-in, DEFAULT OFF): when enabled, a `payment_required` at/below the
15
+ * Auto-pay (opt-in, DEFAULT OFF): when enabled, a `payment_required` at/below the
16
16
  * policy's `autoPayThreshold` is paid with NO LLM turn — still bounded by the policy
17
17
  * caps, deny-by-default, journal and reserve-lock (all in rine-core). Any decline
18
18
  * falls back to surfacing the quote to the model.
@@ -31,7 +31,7 @@ export declare function renderX402Body(msg: DecryptedMessage): string;
31
31
  /** The routing-guidance line: what the model should do, and the do-not-reply rule. */
32
32
  export declare function x402GuidanceLine(msg: DecryptedMessage): string;
33
33
  /**
34
- * Handle a verified inbound x402 frame (D1). `payment_required` may auto-pay (D2)
34
+ * Handle a verified inbound x402 frame. `payment_required` may auto-pay
35
35
  * when enabled and at/below threshold; otherwise, and for `payment` / `receipt`,
36
36
  * the frame is surfaced to the model as a payment-aware turn.
37
37
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rine-network/eve",
3
- "version": "0.2.0",
3
+ "version": "0.5.0",
4
4
  "description": "Native Vercel Eve connector for the rine network \u2014 a custom channel that makes an Eve agent reachable over E2E-encrypted (HPKE 1:1, MLS groups RFC 9420, PQ-hybrid) agent-to-agent messaging, plus file-discovered rine tools, a skill, and an init/onboard/relay CLI.",
5
5
  "author": "mmmbs <mmmbs@proton.me>",
6
6
  "license": "EUPL-1.2",
@@ -48,8 +48,8 @@
48
48
  "prepublishOnly": "node scripts/check-no-file-deps.mjs"
49
49
  },
50
50
  "dependencies": {
51
- "@rine-network/core": "^0.11.0",
52
- "@rine-network/sdk": "^0.8.0",
51
+ "@rine-network/core": "^0.14.0",
52
+ "@rine-network/sdk": "^0.11.0",
53
53
  "zod": "^3.25.0",
54
54
  "zod-to-json-schema": "^3.24.1"
55
55
  },
@@ -1,146 +0,0 @@
1
- import { t as RINE_TOOL_META } from "./registry-6sWyhOyF.js";
2
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
- import { dirname, join } from "node:path";
4
- //#region src/skill-content.ts
5
- /**
6
- * The rine skill text — plain constants with NO `eve` import, so the scaffolder
7
- * and CLI can write `agent/skills/rine/SKILL.md` without resolving the `eve` peer
8
- * dependency. `skill.ts` wraps {@link RINE_SKILL_BODY} in `defineSkill` for the
9
- * TypeScript-skill path.
10
- */
11
- const RINE_SKILL_DESCRIPTION = "Use when communicating with other AI agents over the rine network — sending or replying to agent-to-agent messages, discovering agents, or coordinating in groups.";
12
- /** The skill body (no frontmatter) — used by `rineSkill()`'s `markdown`. */
13
- const RINE_SKILL_BODY = `# rine — agent-to-agent messaging
14
-
15
- rine is an end-to-end-encrypted messaging network for AI agents. Each agent has a
16
- handle like \`name@org\`. You are reachable on rine: other agents message you and
17
- your replies are encrypted and delivered back to them automatically.
18
-
19
- ## When messages arrive
20
-
21
- Inbound rine messages are delivered to you as a normal turn. The context line tells
22
- you who sent it and whether their signature verified. **Just answer** — your final
23
- reply is encrypted and sent back to the sender in the same conversation. You do not
24
- need to call a tool to reply to an inbound rine message.
25
-
26
- Call \`rine_reply\` only when you want to reply to a *specific* earlier message by
27
- its id; call \`rine_send\` to start a *new* conversation with another agent.
28
-
29
- ## Reaching other agents
30
-
31
- - \`rine_discover\` — search the public directory by text/category/language to find
32
- an agent's handle. Do this before messaging an agent you don't already know.
33
- - \`rine_inspect\` — read an agent's full public profile (verification, oversight).
34
- - \`rine_send\` — send a 1:1 message (\`name@org\` / UUID) or a group message
35
- (\`#group@org\`). End-to-end encrypted; a real, irreversible network action.
36
- - \`rine_send_and_wait\` — 1:1 only: send and block up to N seconds for a reply.
37
- - \`rine_check_inbox\` / \`rine_read\` — pull or read mail by id (rarely needed, since
38
- inbound is pushed to you as a turn).
39
- - \`rine_thread\` — fetch the full decrypted transcript of a conversation by its id
40
- (both sides, oldest→newest). Use to recover earlier context on demand.
41
-
42
- ## Groups
43
-
44
- Groups are end-to-end encrypted (MLS / RFC 9420 by default, with forward secrecy).
45
- \`rine_group_create\`, \`rine_group_invite\`, \`rine_group_remove\`,
46
- \`rine_group_inspect\`. Sending to a \`#group@org\` handle posts to the whole group.
47
-
48
- ## Trust
49
-
50
- Every inbound message is HPKE-decrypted and its sender signature is verified before
51
- it reaches you; unverified mail is dropped by default. Treat the sender handle in the
52
- context line as authenticated. Do not put secrets in messages you would not want the
53
- recipient agent to read.
54
- `;
55
- /** The full SKILL.md file contents (frontmatter + body) the scaffolder writes. */
56
- const RINE_SKILL_FILE = `---
57
- description: ${RINE_SKILL_DESCRIPTION}
58
- ---
59
-
60
- ${RINE_SKILL_BODY}`;
61
- //#endregion
62
- //#region src/scaffold.ts
63
- /**
64
- * `init` scaffolding — writes the thin `agent/` files that wire rine into an Eve
65
- * project: the one-line channel re-export, one re-export file per selected tool
66
- * (Eve discovers tools by filename, so the slug IS the tool name), the skill
67
- * markdown, and a `.env.example` block. Pure content builders are exported for
68
- * tests; {@link scaffoldRine} does the filesystem writes.
69
- */
70
- const ENV_MARKER = "# rine — @rine-network/eve";
71
- const DEFAULT_INBOUND_PATH = "/rine/v1/inbound";
72
- /** The default-export channel file (`agent/channels/rine.ts`). */
73
- function channelFileContent() {
74
- return `import { rineChannel } from "@rine-network/eve/channel";\n\n// Inbound rine messages start/resume a session here; replies flow back over rine.\n// Identity comes from env: RINE_CONFIG_DIR, RINE_AGENT, RINE_WEBHOOK_SECRET.\nexport default rineChannel();\n`;
75
- }
76
- /** A single tool's re-export file (`agent/tools/<name>.ts`). */
77
- function toolFileContent(spec) {
78
- return `import { ${spec.factoryName} } from "@rine-network/eve/tools";\n\nexport default ${spec.factoryName}();\n`;
79
- }
80
- /** The `.env.example` block (with the inbound path applied). */
81
- function envExampleBlock(path) {
82
- return `${ENV_MARKER}
83
- RINE_CONFIG_DIR=
84
- RINE_AGENT=
85
- # RINE_API_URL=https://rine.network
86
- RINE_WEBHOOK_SECRET=
87
- # RINE_WEBHOOK_ID=
88
- # RINE_INBOUND_PATH=${path}
89
- # x402 payments (optional). RINE_X402_AUTO_PAY=1 auto-pays quotes at/below your
90
- # policy's autoPayThreshold with no LLM turn (default OFF). RINE_FACILITATOR is the
91
- # rine_fulfill facilitator — a preset name (cdp/payai/x402-rs) or a base URL.
92
- # RINE_X402_AUTO_PAY=
93
- # RINE_FACILITATOR=
94
- `;
95
- }
96
- /**
97
- * Resolve a `--tools` selection to the ordered tool specs. Accepts `"all"`,
98
- * `"none"`, or a comma-separated list of tool names (`rine_send`) and/or domains
99
- * (`messaging`/`discovery`/`groups`). Unknown tokens throw.
100
- */
101
- function resolveToolSelection(value) {
102
- const v = (value ?? "all").trim();
103
- if (v === "none") return [];
104
- if (v === "all") return [...RINE_TOOL_META];
105
- const tokens = v.split(",").map((t) => t.trim()).filter(Boolean);
106
- const out = [];
107
- for (const spec of RINE_TOOL_META) if (tokens.includes(spec.name) || tokens.includes(spec.domain)) out.push(spec);
108
- const matchedDomains = new Set(RINE_TOOL_META.map((s) => s.domain));
109
- const matchedNames = new Set(RINE_TOOL_META.map((s) => s.name));
110
- for (const tok of tokens) if (!matchedDomains.has(tok) && !matchedNames.has(tok)) throw new Error(`unknown tool/domain '${tok}' (valid: all, none, messaging, discovery, groups, or a rine_* tool name)`);
111
- return out;
112
- }
113
- /** Write `content` to `file`, honoring `force`; record into the result. */
114
- function writeFile(file, content, force, res) {
115
- if (existsSync(file) && !force) {
116
- res.skipped.push(file);
117
- return;
118
- }
119
- mkdirSync(dirname(file), { recursive: true });
120
- writeFileSync(file, content, "utf-8");
121
- res.written.push(file);
122
- }
123
- /** Scaffold the rine channel, tools, skill, and `.env.example` block. */
124
- function scaffoldRine(opts = {}) {
125
- const cwd = opts.cwd ?? process.cwd();
126
- const agentDir = join(cwd, opts.dir ?? "agent");
127
- const path = opts.path ?? DEFAULT_INBOUND_PATH;
128
- const res = {
129
- written: [],
130
- skipped: []
131
- };
132
- const force = opts.force ?? false;
133
- if (opts.channel ?? true) writeFile(join(agentDir, "channels", "rine.ts"), channelFileContent(), force, res);
134
- for (const spec of resolveToolSelection(opts.tools)) writeFile(join(agentDir, "tools", `${spec.name}.ts`), toolFileContent(spec), force, res);
135
- writeFile(join(agentDir, "skills", "rine", "SKILL.md"), RINE_SKILL_FILE, force, res);
136
- const envFile = join(cwd, ".env.example");
137
- const block = envExampleBlock(path);
138
- const existing = existsSync(envFile) ? readFileSync(envFile, "utf-8") : "";
139
- if (!existing.includes(ENV_MARKER)) {
140
- writeFileSync(envFile, existing.length > 0 ? `${existing.trimEnd()}\n\n${block}` : block, "utf-8");
141
- res.written.push(envFile);
142
- } else res.skipped.push(envFile);
143
- return res;
144
- }
145
- //#endregion
146
- export { toolFileContent as a, RINE_SKILL_FILE as c, scaffoldRine as i, envExampleBlock as n, RINE_SKILL_BODY as o, resolveToolSelection as r, RINE_SKILL_DESCRIPTION as s, channelFileContent as t };
@@ -1,259 +0,0 @@
1
- import { t as getRineClient } from "./client-X_-9CpQT.js";
2
- import { APIConnectionError, AuthenticationError, AuthorizationError, ConfigError, CryptoError, NotFoundError, RateLimitError, RineApiError, RineTimeoutError, SchemaValidationError, ValidationError } from "@rine-network/sdk";
3
- import { resolveApiUrl } from "@rine-network/core";
4
- import { always, never, once } from "eve/tools/approval";
5
- import { ZodError } from "zod";
6
- //#region src/format.ts
7
- /** One thread turn as a role-tagged line: `[sent] you: …` / `[received] alice@org: …`. */
8
- function renderThreadLine(e) {
9
- const who = e.direction === "sent" ? "you" : e.senderHandle ?? "unknown";
10
- return `[${e.direction}] ${who}: ${e.text}`;
11
- }
12
- /**
13
- * Render a both-sided transcript (oldest→newest) for `rine_thread`. Each turn is
14
- * a role-tagged line; `[unavailable]` text passes through unchanged.
15
- */
16
- function renderThread(entries) {
17
- if (entries.length === 0) return "No messages in this conversation.";
18
- return entries.map(renderThreadLine).join("\n");
19
- }
20
- /** Honest signature note — never claims "verified" for an unverifiable message. */
21
- function verifiedNote(msg) {
22
- return msg.verified ? "signature verified" : `signature ${msg.verification_status}`;
23
- }
24
- /**
25
- * THE PLAINTEXT-IS-JSON FOOTGUN. Outbound sends wrap `{ text: body }`, and the
26
- * SDK auto-`JSON.parse`s inbound `application/json` plaintext into a structured
27
- * value. Unwrap defensively so the model sees prose, never raw JSON:
28
- * - a string → returned as-is
29
- * - `{ text: "…" }` → the inner text
30
- * - anything else → compact JSON (last resort)
31
- */
32
- function unwrapText(plaintext) {
33
- if (typeof plaintext === "string") return plaintext;
34
- if (plaintext === null || plaintext === void 0) return "";
35
- if (typeof plaintext === "object") {
36
- const text = plaintext.text;
37
- if (typeof text === "string") return text;
38
- }
39
- try {
40
- return JSON.stringify(plaintext);
41
- } catch {
42
- return String(plaintext);
43
- }
44
- }
45
- /** Body of a message: decrypt error if unreadable, else the unwrapped plaintext. */
46
- function renderMessageBody(msg) {
47
- if (msg.decrypt_error) return `[unreadable] ${msg.decrypt_error}`;
48
- return unwrapText(msg.plaintext);
49
- }
50
- /** Sender label: prefer the human handle, fall back to the agent UUID. */
51
- function senderLabel(msg) {
52
- return msg.sender_handle ?? msg.from_agent_id ?? "unknown sender";
53
- }
54
- /** A single message rendered across multiple labeled lines (for `rine_read`). */
55
- function renderSingleMessage(msg) {
56
- const lines = [
57
- `Message ${msg.id}`,
58
- `from: ${senderLabel(msg)}`,
59
- `type: ${msg.type}`
60
- ];
61
- if (msg.group_handle ?? msg.group_id) lines.push(`group: ${msg.group_handle ?? msg.group_id}`);
62
- lines.push(`body: ${renderMessageBody(msg)}`);
63
- lines.push(`(${verifiedNote(msg)})`);
64
- return lines.join("\n");
65
- }
66
- /** One inbox row: compact single line keyed by id + sender + body preview. */
67
- function renderInboxRow(msg) {
68
- const from = senderLabel(msg);
69
- const where = msg.group_handle ? ` in ${msg.group_handle}` : "";
70
- return `${msg.id} from ${from}${where}: ${renderMessageBody(msg)} (${verifiedNote(msg)})`;
71
- }
72
- /** A numbered inbox list, or the empty-state line. */
73
- function renderInbox(items) {
74
- if (items.length === 0) return "No new messages.";
75
- return items.map((msg, i) => `${i + 1}. ${renderInboxRow(msg)}`).join("\n");
76
- }
77
- /** One discovery row. */
78
- function renderAgentSummary(a) {
79
- const verified = a.verified ? " [verified]" : "";
80
- const desc = a.description ? ` — ${a.description}` : "";
81
- const cat = a.category ? ` (${a.category})` : "";
82
- return `${a.handle}${verified}${cat}${desc}`;
83
- }
84
- /** A numbered discovery list, or the empty-state line. */
85
- function renderDiscover(items) {
86
- if (items.length === 0) return "No agents matched.";
87
- return items.map((a, i) => `${i + 1}. ${renderAgentSummary(a)}`).join("\n");
88
- }
89
- /** A full agent profile (for `rine_inspect`). */
90
- function renderProfile(p) {
91
- const lines = [
92
- `${p.name} (${p.handle})`,
93
- `id: ${p.id}`,
94
- `verified: ${p.verified ? "yes" : "no"}`,
95
- `human oversight: ${p.human_oversight ? "yes" : "no"}`
96
- ];
97
- if (p.category) lines.push(`category: ${p.category}`);
98
- if (p.description) lines.push(`description: ${p.description}`);
99
- return lines.join("\n");
100
- }
101
- /**
102
- * Self-diagnose a group's E2EE mode. Uses `mls_group_id !== null`, OR the
103
- * explicit `mls_enabled`/`mls_pending` flags. With MLS support present this is a
104
- * CAPABILITY flag, not a failure flag (R9).
105
- */
106
- function groupIsMls(g) {
107
- return Boolean(g.mls_enabled || g.mls_group_id !== null || g.mls_pending);
108
- }
109
- /**
110
- * A group rendered for `rine_group_inspect`. Both the MLS and sender-key branches
111
- * are `[OK]` — an MLS group is readable/postable from here (R9).
112
- */
113
- function renderGroup(g) {
114
- const lines = [`Group ${g.handle} (id ${g.id})`];
115
- if (groupIsMls(g)) {
116
- lines.push("[OK] MLS group — end-to-end encrypted (RFC 9420), readable/postable from here.");
117
- if (g.mls_group_id) lines.push(`mls_group_id: ${g.mls_group_id}`);
118
- } else lines.push("[OK] sender-key group — readable/postable from here.");
119
- lines.push(`enrollment: ${g.enrollment_policy}`);
120
- lines.push(`visibility: ${g.visibility}`);
121
- return lines.join("\n");
122
- }
123
- //#endregion
124
- //#region src/errors.ts
125
- /**
126
- * `formatError(err)` — invariant R2: turn any thrown SDK error into a readable
127
- * string for the LLM, never a stack trace. Tools wrap their one `await client.*`
128
- * call in `try/catch → formatError` and RESOLVE (never reject) for mapped errors.
129
- *
130
- * Ordering matters: the TS SDK error classes form a hierarchy rooted at
131
- * `RineApiError`, so this maps MOST-SPECIFIC FIRST and lets `RineApiError` be the
132
- * catch-all for API errors.
133
- */
134
- /** The synchronous message thrown by `sendAndWait` for a `#` group handle. */
135
- const GROUP_ON_WAIT_PREFIX = "sendAndWait() does not support group handles";
136
- /**
137
- * True when `err` is the plain `Error` `sendAndWait` throws for a group handle.
138
- * The `send_and_wait` tool checks this BEFORE `formatError` and returns the
139
- * "1:1 only" guidance; it is not a typed SDK error class.
140
- */
141
- function isGroupUnsupportedOnWait(err) {
142
- return err instanceof Error && err.message.startsWith(GROUP_ON_WAIT_PREFIX);
143
- }
144
- /** The fixed reply for the group-on-`sendAndWait` case. */
145
- const GROUP_ON_WAIT_MESSAGE = "rine_send_and_wait is 1:1 only; use rine_send for groups.";
146
- function formatError(err) {
147
- if (err instanceof ZodError) {
148
- const first = err.issues[0];
149
- return `Invalid input: ${first?.path.length ? first.path.join(".") : "input"} — ${first?.message ?? "validation failed"}.`;
150
- }
151
- if (err instanceof AuthenticationError) return "rine auth failed — onboard with `npx @rine-network/eve onboard` or set RINE_CONFIG_DIR to your credentials directory.";
152
- if (err instanceof AuthorizationError) return `Not authorized: ${err.detail}.`;
153
- if (err instanceof NotFoundError) return `Not found: ${err.detail}. Try rine_discover to find the right handle.`;
154
- if (err instanceof RateLimitError) return `Rate-limited; retry after ${err.retryAfter ?? "a few"}s.`;
155
- if (err instanceof ValidationError) return `Invalid input: ${err.detail}.`;
156
- if (err instanceof SchemaValidationError) return `Invalid input: ${err.message}.`;
157
- if (err instanceof RineTimeoutError) return "Request timed out; try again or raise the timeout.";
158
- if (err instanceof ConfigError) return String(err.message);
159
- if (err instanceof CryptoError) return `Encryption error: ${err.message} (check your agent keys are on disk).`;
160
- if (err instanceof APIConnectionError) return `Could not reach rine: ${err.message}.`;
161
- if (err instanceof RineApiError) return `${err.status}: ${err.detail}`;
162
- return String(messageOf(err));
163
- }
164
- function messageOf(err) {
165
- if (err instanceof Error) return err.message;
166
- return String(err ?? "unknown error");
167
- }
168
- //#endregion
169
- //#region src/tool.ts
170
- /**
171
- * Shared `defineTool` plumbing every rine tool reuses (no duplicated logic):
172
- * env-based identity resolution (R3) and the try/catch → `formatError` wrapper
173
- * (R2 errors→strings, never a reject).
174
- *
175
- * Unlike the Mastra integration (which threads identity through a `RequestContext`),
176
- * Eve tools run in the app runtime with full `process.env` access, so identity is
177
- * resolved straight from the environment via `getRineClient` — no per-call context
178
- * plumbing, no credentials in the model-visible input schema.
179
- *
180
- * A tool's per-tool body is just `(client, input, apiUrl) => string`; this helper
181
- * supplies the surrounding contract. The concrete `defineTool({...})` (with the
182
- * model-facing description + schemas) lives in each domain module; this file only
183
- * builds the `execute` closure and the shared option/body types.
184
- */
185
- /**
186
- * Resolve {@link RineToolOpts.needsApproval} to an Eve `needsApproval` callback,
187
- * or `undefined` (no gate) when unset. Only the mutating tool factories apply it.
188
- */
189
- function approvalGate(opts) {
190
- switch (opts.needsApproval) {
191
- case "always": return always();
192
- case "once": return once();
193
- case "never": return never();
194
- default: return;
195
- }
196
- }
197
- /** The acting agent for a tool: factory override → `RINE_AGENT` env → none. */
198
- function resolveAgent(opts) {
199
- return opts.agent ?? process.env.RINE_AGENT ?? void 0;
200
- }
201
- /** Resolve the effective `AsyncRineClient` for one `execute` call (lazy, R1). */
202
- function resolveClient(opts) {
203
- if (opts.client) {
204
- const agent = resolveAgent(opts);
205
- return agent ? opts.client.withAgent(agent) : opts.client;
206
- }
207
- return getRineClient({
208
- configDir: opts.configDir,
209
- apiUrl: opts.apiUrl,
210
- agent: resolveAgent(opts)
211
- });
212
- }
213
- /**
214
- * The same resolved `apiUrl` the client is built against (factory override →
215
- * `RINE_API_URL` env → `resolveApiUrl()`). Exposed so handle→UUID pre-resolution
216
- * hits the SAME server WebFinger the client uses.
217
- */
218
- function resolveApiUrlFor(opts) {
219
- return opts.apiUrl ?? resolveApiUrl();
220
- }
221
- /** Brand a model-supplied recipient string for the SDK send surface. */
222
- function asRecipient(to) {
223
- return to;
224
- }
225
- /**
226
- * Belt-and-suspenders ciphertext redactor for `read` / `check_inbox` (R4).
227
- * The renderers + `outputSchema: z.string()` already guarantee the `execute`
228
- * return is redacted text; this coerces ANY value to plain text before it can
229
- * reach the model, in Eve's `ToolModelOutput` shape.
230
- */
231
- function redactToText(output) {
232
- return {
233
- type: "text",
234
- value: typeof output === "string" ? output : ""
235
- };
236
- }
237
- /**
238
- * Build an Eve-tool `execute(input, ctx)` from a zod schema + body, applying:
239
- * 1. zod `.parse(raw)` of the model input (defaults + coercion + validation),
240
- * 2. the lazy env-resolved client (R1/R3),
241
- * 3. the try/catch→formatError contract (R2 — resolves a string, never rejects).
242
- *
243
- * Eve hands `execute` the raw model input as `Record<string, unknown>`; we own the
244
- * parse because Eve receives a JSON Schema (not the zod schema) and may not apply
245
- * zod defaults. The Eve `ToolContext` is intentionally ignored — rine identity is
246
- * environment-resolved (R3), not session-scoped.
247
- */
248
- function makeExecute(schema, opts, body) {
249
- return async (raw) => {
250
- try {
251
- const input = schema.parse(raw);
252
- return await body(resolveClient(opts), input, resolveApiUrlFor(opts));
253
- } catch (err) {
254
- return formatError(err);
255
- }
256
- };
257
- }
258
- //#endregion
259
- export { senderLabel as _, GROUP_ON_WAIT_MESSAGE as a, groupIsMls as c, renderInbox as d, renderMessageBody as f, renderThreadLine as g, renderThread as h, redactToText as i, renderDiscover as l, renderSingleMessage as m, asRecipient as n, formatError as o, renderProfile as p, makeExecute as r, isGroupUnsupportedOnWait as s, approvalGate as t, renderGroup as u, verifiedNote as v };