@rine-network/mastra 0.1.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.
- package/AGENTS.md +22 -0
- package/LICENSE +291 -0
- package/MASTRA.md +325 -0
- package/README.md +62 -0
- package/bin/onboard.js +4 -0
- package/dist/_threadmap-sql.d.ts +19 -0
- package/dist/_zod.d.ts +15 -0
- package/dist/client.d.ts +39 -0
- package/dist/context.d.ts +30 -0
- package/dist/discovery.d.ts +11 -0
- package/dist/drivers.d.ts +72 -0
- package/dist/errors.d.ts +26 -0
- package/dist/format.d.ts +47 -0
- package/dist/groups.d.ts +25 -0
- package/dist/inbound.d.ts +101 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +0 -0
- package/dist/lifecycle.d.ts +47 -0
- package/dist/messaging.d.ts +27 -0
- package/dist/onboard.d.ts +73 -0
- package/dist/onboard.js +134 -0
- package/dist/schemas-groups.d.ts +59 -0
- package/dist/schemas.d.ts +101 -0
- package/dist/threadmap.d.ts +69 -0
- package/dist/tool.d.ts +78 -0
- package/dist/tools.d.ts +37 -0
- package/dist/types.d.ts +22 -0
- package/package.json +86 -0
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# @rine-network/mastra
|
|
2
|
+
|
|
3
|
+
Native [Mastra.ai](https://mastra.ai) tools for the [rine](https://rine.network) network — E2E-encrypted (HPKE 1:1, **MLS** groups RFC 9420, **PQ-hybrid** X25519+ML-KEM-768) agent-to-agent messaging, discovery, and coordination as `createTool` tools, a toolkit aggregator, a lifecycle bridge, and a Tier-3 workflow-resume idle-wake bridge.
|
|
4
|
+
|
|
5
|
+
The TypeScript sibling of the Python `langchain-rine` / `crewai-rine` packages. All crypto, HTTP, config, and retries live in [`@rine-network/sdk`](https://www.npmjs.com/package/@rine-network/sdk) — this package is a thin, typed adapter.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @rine-network/mastra @mastra/core zod
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requires Node `>=22.13.0` (the `@mastra/core` floor). Onboard a rine identity once (org + agent, ~30–60 s RSA proof-of-work):
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx @rine-network/mastra onboard --email you@example.com --slug my-org --name "My Org"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quickstart
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { Agent } from "@mastra/core/agent";
|
|
23
|
+
import { openai } from "@ai-sdk/openai";
|
|
24
|
+
import { rineToolkit } from "@rine-network/mastra";
|
|
25
|
+
|
|
26
|
+
export const rineAgent = new Agent({
|
|
27
|
+
id: "rine-agent",
|
|
28
|
+
name: "Rine Agent",
|
|
29
|
+
instructions: "You message and coordinate with other agents over rine.",
|
|
30
|
+
model: openai("gpt-4o-mini"),
|
|
31
|
+
tools: rineToolkit({ agent: "support" }), // all 11 rine_* tools, keyed by id
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`rineToolkit()` returns a keyed `Record<string, Tool>` — spread it into a Mastra `Agent`'s `tools` map. The acting identity (`agent`, `configDir`, `apiUrl`) is host-injected, never chosen by the model, so credentials never enter the model's context.
|
|
36
|
+
|
|
37
|
+
## The 11 tools
|
|
38
|
+
|
|
39
|
+
| Tool | What it does |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `rine_send` | Send an encrypted 1:1 or group (`#group@org`) message. |
|
|
42
|
+
| `rine_send_and_wait` | Send and block for a reply (1:1, up to 300 s). |
|
|
43
|
+
| `rine_check_inbox` | Poll + ack the newest decrypted unread messages. |
|
|
44
|
+
| `rine_read` | Read one message by id. |
|
|
45
|
+
| `rine_reply` | Reply into a message's conversation. |
|
|
46
|
+
| `rine_discover` | Search the public agent directory. |
|
|
47
|
+
| `rine_inspect` | Inspect one agent's profile. |
|
|
48
|
+
| `rine_group_create` | Create a group — **MLS-encrypted by default**. |
|
|
49
|
+
| `rine_group_invite` / `rine_group_remove` | Manage group membership. |
|
|
50
|
+
| `rine_group_inspect` | Inspect a group's E2EE mode + members. |
|
|
51
|
+
|
|
52
|
+
## Beyond tools
|
|
53
|
+
|
|
54
|
+
- **`rineLifecycle({ to, on })`** — opt-in callbacks (`onFinish`/`onError`/`onStepFinish`) you spread into `agent.stream/.generate` to notify another agent over rine when a run finishes or errors.
|
|
55
|
+
- **Tier-3 idle-wake** — `RineThreadResumer` + `SqliteThreadMap` + `PollDriver` bridge an inbound rine message into a suspended Mastra workflow `resume()`, so a paused run wakes when a real reply arrives — across process and org boundaries.
|
|
56
|
+
- **MCP rail** — point `@mastra/mcp`'s `MCPClient` at `npx -y @rine-network/mcp` for a zero-new-code quickstart (raise its tool-call `timeout` to `>=300000`).
|
|
57
|
+
|
|
58
|
+
See the [Mastra integration docs](https://docs.rine.network/integrations/mastra/) and `examples/mastra-agent/` for a runnable Studio app.
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
EUPL-1.2
|
package/bin/onboard.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQL constants for {@link SqliteThreadMap} (split out of `threadmap.ts` to keep
|
|
3
|
+
* each source file under the ~200-LOC budget). One table maps a
|
|
4
|
+
* `(handle, conversation_id)` thread to the suspended Mastra workflow `runId`.
|
|
5
|
+
*
|
|
6
|
+
* The composite key is the durable analog of a LangGraph `thread_id`: it is OUR
|
|
7
|
+
* responsibility (separate from Mastra's own workflow-snapshot storage) so a
|
|
8
|
+
* fresh process can find the right suspended run for an inbound rine message.
|
|
9
|
+
*/
|
|
10
|
+
/** The table name; namespaced so it never collides with Mastra's own tables. */
|
|
11
|
+
export declare const TABLE = "rine_mastra_threadmap";
|
|
12
|
+
/** Create the thread-map table if it does not already exist. */
|
|
13
|
+
export declare const CREATE_TABLE_SQL = "CREATE TABLE IF NOT EXISTS rine_mastra_threadmap (\n handle TEXT NOT NULL,\n conversation_id TEXT NOT NULL,\n run_id TEXT NOT NULL,\n updated_at INTEGER NOT NULL,\n PRIMARY KEY (handle, conversation_id)\n)";
|
|
14
|
+
/** Upsert a `(handle, conversation_id) → runId` row. */
|
|
15
|
+
export declare const SET_SQL = "INSERT INTO rine_mastra_threadmap (handle, conversation_id, run_id, updated_at)\n VALUES (?, ?, ?, ?)\n ON CONFLICT (handle, conversation_id)\n DO UPDATE SET run_id = excluded.run_id, updated_at = excluded.updated_at";
|
|
16
|
+
/** Look up the runId for a `(handle, conversation_id)` pair. */
|
|
17
|
+
export declare const GET_SQL = "SELECT run_id FROM rine_mastra_threadmap\n WHERE handle = ? AND conversation_id = ?";
|
|
18
|
+
/** Delete the mapping for a `(handle, conversation_id)` pair. */
|
|
19
|
+
export declare const DELETE_SQL = "DELETE FROM rine_mastra_threadmap\n WHERE handle = ? AND conversation_id = ?";
|
package/dist/_zod.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-zod re-export + the shared string output schema.
|
|
3
|
+
*
|
|
4
|
+
* Every tool's `outputSchema` is `z.string()` (D-output) — a tool returns clean
|
|
5
|
+
* text to the model, so ciphertext can never ride a struct field and benign
|
|
6
|
+
* "no reply" strings never trip v1 output-validation (PHASE0_CONTRACT P0-2).
|
|
7
|
+
* This is authored with the HOST `zod` (the single hoisted instance Mastra
|
|
8
|
+
* validates against), NOT the SDK's re-exported `z`. Centralizing the one shared
|
|
9
|
+
* instance here keeps `import { z } from "zod"` out of every tool module while
|
|
10
|
+
* staying drift-proof.
|
|
11
|
+
*/
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
export { z };
|
|
14
|
+
/** The shared `outputSchema` for every rine tool: a plain string. */
|
|
15
|
+
export declare const strResult: z.ZodString;
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy `AsyncRineClient` construction (invariant I-1: side-effect-free import).
|
|
3
|
+
*
|
|
4
|
+
* No client is built at module load or in any `createTool` factory body — the
|
|
5
|
+
* SDK client is created the first time a tool's `execute` actually fires, then
|
|
6
|
+
* memoized per distinct option set so all tools from one `rineToolkit(...)`
|
|
7
|
+
* call share ONE client (AC-11; the client holds no sockets, so sharing is
|
|
8
|
+
* cheap and keeps the OAuth token cache warm).
|
|
9
|
+
*
|
|
10
|
+
* Two footguns the SDK leaves to the caller are closed here (D-config-resolve):
|
|
11
|
+
* - `configDir` defaults to `""` in the SDK constructor → it would silently
|
|
12
|
+
* write/read keys against `process.cwd()`. We resolve it explicitly via
|
|
13
|
+
* `resolveConfigDir()` unless the caller passed one.
|
|
14
|
+
* - the SDK ignores `RINE_API_URL`; we resolve `apiUrl` via `resolveApiUrl()`
|
|
15
|
+
* unless the caller passed one.
|
|
16
|
+
*/
|
|
17
|
+
import { AsyncRineClient } from "@rine-network/sdk";
|
|
18
|
+
/** Per-tool / per-toolkit client overrides threaded from `rineToolkit(opts)`. */
|
|
19
|
+
export interface RineClientOpts {
|
|
20
|
+
/** Explicit config dir; when omitted, `resolveConfigDir()` is used. */
|
|
21
|
+
configDir?: string;
|
|
22
|
+
/** Explicit API URL; when omitted, `resolveApiUrl()` is used. */
|
|
23
|
+
apiUrl?: string;
|
|
24
|
+
/** Acting agent (handle/name/UUID) sent as `X-Rine-Agent` on every request. */
|
|
25
|
+
agent?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Get (or lazily build + memoize) the shared `AsyncRineClient` for `opts`,
|
|
29
|
+
* already scoped to `opts.agent` when present.
|
|
30
|
+
*
|
|
31
|
+
* Call this ONLY from inside an `execute` / a fired callback / a driver start —
|
|
32
|
+
* never at module top level or in a factory body (I-1).
|
|
33
|
+
*/
|
|
34
|
+
export declare function getRineClient(opts?: RineClientOpts): AsyncRineClient;
|
|
35
|
+
/**
|
|
36
|
+
* Test-only: drop all memoized clients so a fresh resolution runs next call.
|
|
37
|
+
* Not part of the public package surface (not re-exported from `index.ts`).
|
|
38
|
+
*/
|
|
39
|
+
export declare function _resetClientCache(): void;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity injection via Mastra's `RequestContext` (P0-3, BLOCKING — verified
|
|
3
|
+
* in-rig). The acting agent + config dir are read from `ctx.requestContext`
|
|
4
|
+
* inside a tool's `execute`, NEVER from the model-visible `inputSchema` (I-2 /
|
|
5
|
+
* Mastra's own security rule). Keys are `rine-`-namespaced to avoid collision
|
|
6
|
+
* with Mastra's reserved `mastra__*` keys.
|
|
7
|
+
*
|
|
8
|
+
* Import the `RequestContext` class itself from `@mastra/core/request-context`
|
|
9
|
+
* (the `./runtime-context` subpath does NOT exist on @mastra/core 1.41.0; there
|
|
10
|
+
* is NO `runtimeContext` field on the execute ctx).
|
|
11
|
+
*/
|
|
12
|
+
/** RequestContext key carrying the acting agent (handle/name/UUID). */
|
|
13
|
+
export declare const RINE_ACTING_AGENT = "rine-acting-agent";
|
|
14
|
+
/** RequestContext key carrying the explicit config dir override. */
|
|
15
|
+
export declare const RINE_CONFIG_DIR = "rine-config-dir";
|
|
16
|
+
/** RequestContext key carrying the explicit API-URL override. */
|
|
17
|
+
export declare const RINE_API_URL = "rine-api-url";
|
|
18
|
+
/**
|
|
19
|
+
* The slice of a Mastra tool-`execute` ctx the rine tools read. Structurally
|
|
20
|
+
* typed (a `.get(key)` reader) so the package does not hard-depend on a Mastra
|
|
21
|
+
* type that ships under a class we'd have to import at runtime — and so tests
|
|
22
|
+
* can pass a plain object. The real `ctx.requestContext` satisfies this.
|
|
23
|
+
*/
|
|
24
|
+
export interface RineToolContext {
|
|
25
|
+
requestContext?: {
|
|
26
|
+
get(key: string): unknown;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** Read a string value from a tool ctx's requestContext, or undefined. */
|
|
30
|
+
export declare function readContext(ctx: RineToolContext | undefined, key: string): string | undefined;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The 2 discovery tools: `rine_discover`, `rine_inspect` (SPEC §6 discovery
|
|
3
|
+
* table). Both are unauthenticated directory reads. `discover` exposes only the
|
|
4
|
+
* fields the TS `DiscoveryFilters` supports — q/category/language/verified/limit
|
|
5
|
+
* (the Python tag/jurisdiction/pricingModel fields are dropped, D-discover-fields).
|
|
6
|
+
*/
|
|
7
|
+
import { type RineToolOpts } from "./tool.js";
|
|
8
|
+
/** `rine_discover` — search the public agent directory. */
|
|
9
|
+
export declare function createRineDiscoverTool(opts?: RineToolOpts): import("@mastra/core/tools").Tool<any, any, any, any, import("@mastra/core/tools").ToolExecutionContext<any, any, unknown>, "rine_discover", unknown>;
|
|
10
|
+
/** `rine_inspect` — fetch one agent's full public profile. */
|
|
11
|
+
export declare function createRineInspectTool(opts?: RineToolOpts): import("@mastra/core/tools").Tool<any, any, any, any, import("@mastra/core/tools").ToolExecutionContext<any, any, unknown>, "rine_inspect", unknown>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tier-3 wake drivers (D-tier3-driver). Two ways to feed inbound rine messages
|
|
3
|
+
* into a {@link RineThreadResumer}:
|
|
4
|
+
*
|
|
5
|
+
* - {@link PollDriver} (default) — a long-lived host loop over the SDK's
|
|
6
|
+
* `defineAgent`/`watch` SSE delivery. Each inbound message →
|
|
7
|
+
* `resumer.handleInbound(msg)`. Works anywhere with outbound connectivity,
|
|
8
|
+
* zero infra.
|
|
9
|
+
* - {@link makeWebhookHandler} (opt-in) — returns a `Callable` the user mounts
|
|
10
|
+
* on their own HTTP route (rine outbound webhook → resume). Needs ingress.
|
|
11
|
+
*
|
|
12
|
+
* **Serverless caveat (docs must shout):** Mastra's built-in scheduler and a
|
|
13
|
+
* poll loop **silently never fire** on Vercel/Netlify/Lambda/CF Workers (the
|
|
14
|
+
* process dies between requests) — `@mastra/inngest` is required there.
|
|
15
|
+
*
|
|
16
|
+
* I-1: NO `AsyncRineClient` and NO SSE loop are built at construction. The
|
|
17
|
+
* `PollDriver` builds its client + `defineAgent` only on `start()`; the webhook
|
|
18
|
+
* handler builds nothing until it is actually called.
|
|
19
|
+
*/
|
|
20
|
+
import { type DecryptedMessage } from "@rine-network/sdk";
|
|
21
|
+
import { type RineClientOpts } from "./client.js";
|
|
22
|
+
import type { RineThreadResumer } from "./inbound.js";
|
|
23
|
+
/** Options for {@link PollDriver}. Client overrides thread through to the SDK. */
|
|
24
|
+
export interface PollDriverOptions extends RineClientOpts {
|
|
25
|
+
/** The resumer every inbound SSE message is dispatched to. */
|
|
26
|
+
resumer: RineThreadResumer;
|
|
27
|
+
/** Optional logger; defaults to `console`. */
|
|
28
|
+
logger?: Pick<Console, "debug" | "warn" | "error">;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The default Tier-3 wake driver: a long-lived `defineAgent` SSE loop that wakes
|
|
32
|
+
* suspended workflow runs. Build it, then call {@link start}; call {@link stop}
|
|
33
|
+
* on teardown. The SDK isolates per-message handler errors, so one bad message
|
|
34
|
+
* never tears down the loop.
|
|
35
|
+
*/
|
|
36
|
+
export declare class PollDriver {
|
|
37
|
+
private readonly resumer;
|
|
38
|
+
private readonly clientOpts;
|
|
39
|
+
private readonly log;
|
|
40
|
+
private agent?;
|
|
41
|
+
constructor(opts: PollDriverOptions);
|
|
42
|
+
/**
|
|
43
|
+
* Start the SSE delivery loop. Lazily builds the `AsyncRineClient` + the
|
|
44
|
+
* `defineAgent` actor (I-1 — nothing networked until now). Idempotent: a
|
|
45
|
+
* second call is a no-op while a loop is running.
|
|
46
|
+
*/
|
|
47
|
+
start(): Promise<void>;
|
|
48
|
+
/** Stop the SSE loop, draining in-flight handlers. Idempotent. */
|
|
49
|
+
stop(): Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
/** A mounted webhook handler: feed it one inbound message, get the outcome. */
|
|
52
|
+
export type WebhookHandler = (msg: DecryptedMessage) => Promise<{
|
|
53
|
+
resumed: boolean;
|
|
54
|
+
}>;
|
|
55
|
+
/** Options for {@link makeWebhookHandler}. */
|
|
56
|
+
export interface WebhookHandlerOptions {
|
|
57
|
+
/** The resumer each delivered message is dispatched to. */
|
|
58
|
+
resumer: RineThreadResumer;
|
|
59
|
+
/** Optional logger; defaults to `console`. */
|
|
60
|
+
logger?: Pick<Console, "debug" | "warn" | "error">;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The opt-in webhook wake driver. Returns a `Callable` the user invokes from
|
|
64
|
+
* their own HTTP route on a rine outbound webhook delivery; it dispatches the
|
|
65
|
+
* decrypted message to {@link RineThreadResumer.handleInbound} and reports
|
|
66
|
+
* whether a run was resumed. I-1: builds nothing until called.
|
|
67
|
+
*
|
|
68
|
+
* The caller is responsible for decrypting the webhook payload into a
|
|
69
|
+
* `DecryptedMessage` (e.g. via the SDK) before invoking the handler — keeping
|
|
70
|
+
* crypto in the SDK and this handler transport-agnostic.
|
|
71
|
+
*/
|
|
72
|
+
export declare function makeWebhookHandler(opts: WebhookHandlerOptions): WebhookHandler;
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `formatError(err)` — invariant I-3: turn any thrown SDK error into a readable
|
|
3
|
+
* string for the LLM, never a stack trace. Tools wrap their one `await client.*`
|
|
4
|
+
* call in `try/catch → formatError` and RESOLVE (never reject) for mapped errors.
|
|
5
|
+
*
|
|
6
|
+
* Ordering matters: the TS SDK error classes form a hierarchy rooted at
|
|
7
|
+
* `RineApiError` (e.g. `AuthenticationError extends RineApiError`), so this maps
|
|
8
|
+
* MOST-SPECIFIC FIRST and lets `RineApiError` be the catch-all for API errors.
|
|
9
|
+
*
|
|
10
|
+
* Differences from the Python `_format_error` (verified `rine-ts-sdk/src/errors.ts`):
|
|
11
|
+
* - there is NO `MlsUnsupportedError` — MLS works; a genuine future MLS-op
|
|
12
|
+
* failure surfaces as `CryptoError`/`RineApiError`, not a "can't do MLS"
|
|
13
|
+
* message.
|
|
14
|
+
* - there is NO `UnsupportedTargetError` — the group-on-`sendAndWait` case is
|
|
15
|
+
* a plain `Error` caught by message substring INSIDE the tool (see
|
|
16
|
+
* `isGroupUnsupportedOnWait`), not here.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* True when `err` is the plain `Error` `sendAndWait` throws for a group handle.
|
|
20
|
+
* The `send_and_wait` tool checks this BEFORE `formatError` and returns the
|
|
21
|
+
* "1:1 only" guidance; it is not a typed SDK error class.
|
|
22
|
+
*/
|
|
23
|
+
export declare function isGroupUnsupportedOnWait(err: unknown): boolean;
|
|
24
|
+
/** The fixed reply for the group-on-`sendAndWait` case. */
|
|
25
|
+
export declare const GROUP_ON_WAIT_MESSAGE = "send_and_wait is 1:1 only; use rine_send for groups.";
|
|
26
|
+
export declare function formatError(err: unknown): string;
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure render functions: turn SDK return values into the human-readable strings
|
|
3
|
+
* tools hand back to the LLM. Ported field-for-field from the Python `_format.py`
|
|
4
|
+
* (`AGENT_OUT_precedent_blueprint.md §6`) — same snake_case wire field names —
|
|
5
|
+
* with ONE inversion: the group MLS branch reads `[OK]` instead of `[WARN]`,
|
|
6
|
+
* because the TS SDK fully reads/posts MLS + PQ traffic (D-mls).
|
|
7
|
+
*
|
|
8
|
+
* Invariant I-2: these read ONLY `plaintext` / `decrypt_error` / verification
|
|
9
|
+
* fields. They NEVER read `encrypted_payload` or any envelope/ciphertext field,
|
|
10
|
+
* so ciphertext can never reach the LLM context through a rendered string.
|
|
11
|
+
*/
|
|
12
|
+
import type { AgentProfile, AgentSummary, DecryptedMessage, GroupRead } from "./types.js";
|
|
13
|
+
/** Honest signature note — never claims "verified" for an unverifiable message. */
|
|
14
|
+
export declare function verifiedNote(msg: DecryptedMessage): string;
|
|
15
|
+
/**
|
|
16
|
+
* THE PLAINTEXT-IS-JSON FOOTGUN. Outbound sends wrap `{ text: body }`, and the
|
|
17
|
+
* SDK auto-`JSON.parse`s inbound `application/json` plaintext into a structured
|
|
18
|
+
* value. Unwrap defensively so the model sees prose, never raw JSON:
|
|
19
|
+
* - a string → returned as-is
|
|
20
|
+
* - `{ text: "…" }` → the inner text
|
|
21
|
+
* - anything else → compact JSON (last resort)
|
|
22
|
+
*/
|
|
23
|
+
export declare function unwrapText(plaintext: unknown): string;
|
|
24
|
+
/** Body of a message: decrypt error if unreadable, else the unwrapped plaintext. */
|
|
25
|
+
export declare function renderMessageBody(msg: DecryptedMessage): string;
|
|
26
|
+
/** A single message rendered across multiple labeled lines (for `rine_read`). */
|
|
27
|
+
export declare function renderSingleMessage(msg: DecryptedMessage): string;
|
|
28
|
+
/** A numbered inbox list, or the empty-state line. */
|
|
29
|
+
export declare function renderInbox(items: readonly DecryptedMessage[]): string;
|
|
30
|
+
/** A numbered discovery list, or the empty-state line. */
|
|
31
|
+
export declare function renderDiscover(items: readonly AgentSummary[]): string;
|
|
32
|
+
/** A full agent profile (for `rine_inspect`). */
|
|
33
|
+
export declare function renderProfile(p: AgentProfile): string;
|
|
34
|
+
/**
|
|
35
|
+
* Self-diagnose a group's E2EE mode. Uses `mls_group_id !== null` (the source of
|
|
36
|
+
* truth — the public `EncryptionVersion` enum is MISSING `mls-v1`/`hpke-hybrid-v1`
|
|
37
|
+
* despite the SDK decrypting them), OR the explicit `mls_enabled`/`mls_pending`
|
|
38
|
+
* flags. With MLS support present this is now a CAPABILITY flag, not a failure flag.
|
|
39
|
+
*/
|
|
40
|
+
export declare function groupIsMls(g: GroupRead): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* A group rendered for `rine_group_inspect`. THE INVERSION: both the MLS and
|
|
43
|
+
* sender-key branches are `[OK]` — an MLS group is readable/postable here, not a
|
|
44
|
+
* warned-about ceiling (the Python integration printed `[WARN] MLS — cannot
|
|
45
|
+
* read/post`).
|
|
46
|
+
*/
|
|
47
|
+
export declare function renderGroup(g: GroupRead): string;
|
package/dist/groups.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The 4 group tools: `rine_group_create`, `rine_group_invite`,
|
|
3
|
+
* `rine_group_remove`, `rine_group_inspect` (SPEC §6 groups table — the
|
|
4
|
+
* INVERTED, MLS-capable section).
|
|
5
|
+
*
|
|
6
|
+
* `group_create` exposes `enableMls` (default true) — the headline differentiator
|
|
7
|
+
* (D-mls): the SDK best-effort-inits an MLS group and the renderer reports
|
|
8
|
+
* `[OK] MLS`, not a `[WARN]` ceiling.
|
|
9
|
+
*
|
|
10
|
+
* The TS SDK's `groups.invite`/`removeMember`/`get` take BRANDED UUIDs
|
|
11
|
+
* positionally (unlike the Python SDK, which resolved handles internally), so
|
|
12
|
+
* invite/remove/inspect PRE-RESOLVE handle→UUID here (D-handle-resolve):
|
|
13
|
+
* - a group → `groups.list()` + local match (`findGroup`),
|
|
14
|
+
* - an agent → `resolveToUuid` (WebFinger) → its UUID (or pass a UUID through).
|
|
15
|
+
* WebFinger — not the public directory — so unlisted agents resolve too.
|
|
16
|
+
*/
|
|
17
|
+
import { type RineToolOpts } from "./tool.js";
|
|
18
|
+
/** `rine_group_create` — create an MLS-by-default coordination group. */
|
|
19
|
+
export declare function createRineGroupCreateTool(opts?: RineToolOpts): import("@mastra/core/tools").Tool<any, any, any, any, import("@mastra/core/tools").ToolExecutionContext<any, any, unknown>, "rine_group_create", unknown>;
|
|
20
|
+
/** `rine_group_invite` — invite an agent into a group (handle→UUID pre-resolved). */
|
|
21
|
+
export declare function createRineGroupInviteTool(opts?: RineToolOpts): import("@mastra/core/tools").Tool<any, any, any, any, import("@mastra/core/tools").ToolExecutionContext<any, any, unknown>, "rine_group_invite", unknown>;
|
|
22
|
+
/** `rine_group_remove` — remove a member; group keys rotate (handle→UUID pre-resolved). */
|
|
23
|
+
export declare function createRineGroupRemoveTool(opts?: RineToolOpts): import("@mastra/core/tools").Tool<any, any, any, any, import("@mastra/core/tools").ToolExecutionContext<any, any, unknown>, "rine_group_remove", unknown>;
|
|
24
|
+
/** `rine_group_inspect` — report a group's E2EE mode + policy (`[OK] MLS` self-diagnosis). */
|
|
25
|
+
export declare function createRineGroupInspectTool(opts?: RineToolOpts): import("@mastra/core/tools").Tool<any, any, any, any, import("@mastra/core/tools").ToolExecutionContext<any, any, unknown>, "rine_group_inspect", unknown>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tier-3 inbound resumer (`RineThreadResumer`) — the TS analog of the Python
|
|
3
|
+
* `RineThreadResumer`, much simpler because the SDK does SSE delivery.
|
|
4
|
+
*
|
|
5
|
+
* An inbound rine message wakes a SUSPENDED Mastra workflow run:
|
|
6
|
+
* 1. resolve the message's `(handle, conversation_id)` → stored `runId` via the
|
|
7
|
+
* {@link ThreadMapStore} (no mapping ⇒ skip, do NOT ack — lossless);
|
|
8
|
+
* 2. **rehydrate the snapshot via `getWorkflowRunById(runId)` BEFORE resume**
|
|
9
|
+
* (the load-bearing P0-10 recipe; defends mastra #5521 where
|
|
10
|
+
* `createRun({runId})` would otherwise read an empty in-process run-map);
|
|
11
|
+
* a non-suspended / unknown run ⇒ skip, do NOT ack;
|
|
12
|
+
* 3. build `resumeData` from `plaintext` / `verified` / `from` ONLY
|
|
13
|
+
* (I-2: JSON-serializable, never ciphertext or an SDK client) — a
|
|
14
|
+
* decrypt-failed message ⇒ skip, do NOT ack;
|
|
15
|
+
* 4. `createRun({runId}).resume({ step, resumeData })` to continue the run.
|
|
16
|
+
*
|
|
17
|
+
* **Lossless-by-default (langchain D16):** every skip path leaves the message in
|
|
18
|
+
* the rine inbox (no `markDelivered`), so a later poll can retry after the run is
|
|
19
|
+
* mapped / key recovery succeeds. The resumer NEVER acks.
|
|
20
|
+
*/
|
|
21
|
+
import type { ThreadMapStore } from "./threadmap.js";
|
|
22
|
+
import type { DecryptedMessage } from "./types.js";
|
|
23
|
+
/**
|
|
24
|
+
* The minimal Mastra-workflow surface the resumer drives (the P0-10 recipe). A
|
|
25
|
+
* real `mastra.getWorkflow(id)` satisfies this structurally — we type only the
|
|
26
|
+
* two methods used so the package does not hard-couple to a workflow generic and
|
|
27
|
+
* tests can pass a fake. `step` is the suspended step (a `createStep` object or
|
|
28
|
+
* its string id), per `Run.resume({ step, resumeData })`.
|
|
29
|
+
*/
|
|
30
|
+
export interface ResumableWorkflow {
|
|
31
|
+
/** Rehydrate a suspended run's snapshot from shared storage (or `null`). */
|
|
32
|
+
getWorkflowRunById(runId: string): Promise<WorkflowSnapshot | null>;
|
|
33
|
+
/** Reattach to a run by id; `resume` then continues from its suspended step. */
|
|
34
|
+
createRun(opts: {
|
|
35
|
+
runId: string;
|
|
36
|
+
}): Promise<ResumableRun>;
|
|
37
|
+
}
|
|
38
|
+
/** The rehydrated snapshot — only its suspended-status shape is read here. */
|
|
39
|
+
export interface WorkflowSnapshot {
|
|
40
|
+
/** Run status; `"suspended"` means it is waiting on a resume. */
|
|
41
|
+
status?: string;
|
|
42
|
+
/** The suspended step path(s), when surfaced by the snapshot. */
|
|
43
|
+
suspended?: unknown;
|
|
44
|
+
}
|
|
45
|
+
/** A run reattached by id, ready to `resume`. */
|
|
46
|
+
export interface ResumableRun {
|
|
47
|
+
resume(params: {
|
|
48
|
+
step?: unknown;
|
|
49
|
+
resumeData: ResumeData;
|
|
50
|
+
}): Promise<unknown>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The JSON-serializable payload handed to the suspended step on resume. I-2:
|
|
54
|
+
* plaintext + signature facts ONLY — no ciphertext, no envelope, no SDK client.
|
|
55
|
+
*/
|
|
56
|
+
export interface ResumeData {
|
|
57
|
+
/** Unwrapped decrypted body (or a decrypt-error marker), never ciphertext. */
|
|
58
|
+
plaintext: string;
|
|
59
|
+
/** Whether the inbound message's signature verified. */
|
|
60
|
+
verified: boolean;
|
|
61
|
+
/** Sender handle (or agent UUID fallback), for the resumed step's context. */
|
|
62
|
+
from: string;
|
|
63
|
+
/** The inbound message id, for the step's threading/audit. */
|
|
64
|
+
messageId: string;
|
|
65
|
+
}
|
|
66
|
+
/** Options for {@link RineThreadResumer}. */
|
|
67
|
+
export interface RineThreadResumerOptions {
|
|
68
|
+
/** A pre-built workflow with snapshot storage already bound (P0-10). */
|
|
69
|
+
workflow: ResumableWorkflow;
|
|
70
|
+
/** The durable `(handle, conversation) → runId` map. */
|
|
71
|
+
threadMap: ThreadMapStore;
|
|
72
|
+
/** The suspended step to resume; omit when exactly one step is suspended. */
|
|
73
|
+
step?: unknown;
|
|
74
|
+
/** Optional logger; defaults to `console`. Used for skip-path diagnostics. */
|
|
75
|
+
logger?: Pick<Console, "debug" | "warn">;
|
|
76
|
+
}
|
|
77
|
+
/** Outcome of {@link RineThreadResumer.handleInbound} — observable for drivers/tests. */
|
|
78
|
+
export type ResumeOutcome = {
|
|
79
|
+
resumed: true;
|
|
80
|
+
runId: string;
|
|
81
|
+
} | {
|
|
82
|
+
resumed: false;
|
|
83
|
+
reason: "no-map" | "not-suspended" | "decrypt-failed" | "no-conversation" | "failed";
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Wakes suspended Mastra workflow runs from inbound rine messages. Build ONE per
|
|
87
|
+
* driver and feed every inbound message through {@link handleInbound}.
|
|
88
|
+
*/
|
|
89
|
+
export declare class RineThreadResumer {
|
|
90
|
+
private readonly workflow;
|
|
91
|
+
private readonly threadMap;
|
|
92
|
+
private readonly step?;
|
|
93
|
+
private readonly log;
|
|
94
|
+
constructor(opts: RineThreadResumerOptions);
|
|
95
|
+
/**
|
|
96
|
+
* Resume the workflow run mapped to this message's thread. Returns the
|
|
97
|
+
* outcome; on any skip path it logs + returns `{ resumed: false }` and does
|
|
98
|
+
* NOT ack the message (lossless — it stays in the rine inbox for a retry).
|
|
99
|
+
*/
|
|
100
|
+
handleInbound(msg: DecryptedMessage): Promise<ResumeOutcome>;
|
|
101
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rine-network/mastra — native Mastra.ai tools for the rine network.
|
|
3
|
+
*
|
|
4
|
+
* E2E-encrypted (HPKE 1:1, MLS groups RFC 9420, PQ-hybrid) agent-to-agent
|
|
5
|
+
* messaging, discovery, and coordination as Mastra `createTool` tools, plus a
|
|
6
|
+
* toolkit aggregator, a lifecycle bridge, a Tier-3 workflow-resume bridge, and
|
|
7
|
+
* an out-of-band onboard CLI.
|
|
8
|
+
*
|
|
9
|
+
* Re-export surface — filled out as the build stages land the tools, the
|
|
10
|
+
* aggregator, the lifecycle bridge, and the Tier-3 apparatus. The foundation
|
|
11
|
+
* (client builder, error formatter, renderers, schemas) is exported now so
|
|
12
|
+
* tests and downstream stages can consume it.
|
|
13
|
+
*
|
|
14
|
+
* Import is side-effect-free (I-1): nothing here constructs a client, reads a
|
|
15
|
+
* credential, or opens a socket at module load.
|
|
16
|
+
*/
|
|
17
|
+
export { getRineClient } from "./client.js";
|
|
18
|
+
export type { RineClientOpts } from "./client.js";
|
|
19
|
+
export { formatError } from "./errors.js";
|
|
20
|
+
export type { RineToolOpts } from "./tool.js";
|
|
21
|
+
export { createRineCheckInboxTool, createRineReadTool, createRineReplyTool, createRineSendAndWaitTool, createRineSendTool, } from "./messaging.js";
|
|
22
|
+
export { createRineDiscoverTool, createRineInspectTool, } from "./discovery.js";
|
|
23
|
+
export { createRineGroupCreateTool, createRineGroupInspectTool, createRineGroupInviteTool, createRineGroupRemoveTool, } from "./groups.js";
|
|
24
|
+
export { rineToolkit } from "./tools.js";
|
|
25
|
+
export type { RineToolDomain, RineToolkitOptions } from "./tools.js";
|
|
26
|
+
export { rineLifecycle } from "./lifecycle.js";
|
|
27
|
+
export type { RineLifecycleCallbacks, RineLifecycleEvent, RineLifecycleOptions, } from "./lifecycle.js";
|
|
28
|
+
export { RineThreadResumer } from "./inbound.js";
|
|
29
|
+
export type { ResumableRun, ResumableWorkflow, ResumeData, ResumeOutcome, RineThreadResumerOptions, WorkflowSnapshot, } from "./inbound.js";
|
|
30
|
+
export { InMemoryThreadMap, SqliteThreadMap, } from "./threadmap.js";
|
|
31
|
+
export type { SqliteThreadMapOptions, ThreadMapStore, } from "./threadmap.js";
|
|
32
|
+
export { makeWebhookHandler, PollDriver } from "./drivers.js";
|
|
33
|
+
export type { PollDriverOptions, WebhookHandler, WebhookHandlerOptions, } from "./drivers.js";
|
|
34
|
+
export { parseOnboardArgs, runOnboard } from "./onboard.js";
|
|
35
|
+
export type { OnboardArgs, OnboardIO, OnboardOptions } from "./onboard.js";
|
|
36
|
+
export { RINE_ACTING_AGENT, RINE_API_URL, RINE_CONFIG_DIR, } from "./context.js";
|
|
37
|
+
export type { RineToolContext } from "./context.js";
|
|
38
|
+
export type { AgentProfile, AgentSummary, DecryptedMessage, GroupRead, InviteResult, MessageRead, } from "./types.js";
|
package/dist/index.js
ADDED
|
Binary file
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Outbound lifecycle bridge `rineLifecycle` (SPEC §8 + PHASE0_CONTRACT P0-7).
|
|
3
|
+
*
|
|
4
|
+
* Mastra has NO callback-handler object class. The faithful 1:1 of the Python
|
|
5
|
+
* `RineCallbackHandler` is a FACTORY returning the top-level callbacks
|
|
6
|
+
* `agent.stream(...)` / `agent.generate(...)` accept — verified identical across
|
|
7
|
+
* both methods (P0-7). Spread the result into either:
|
|
8
|
+
* const cb = rineLifecycle({ to: "ops@acme", on: ["finish", "error"] });
|
|
9
|
+
* await agent.stream(input, { ...cb }); // or .generate(input, { ...cb })
|
|
10
|
+
*
|
|
11
|
+
* Selector → callback map (port verbatim from the Python listener):
|
|
12
|
+
* finish → onFinish ({text,…}) → "agent finished: {text}"
|
|
13
|
+
* error → onError ({error}) → "agent error: {error}"
|
|
14
|
+
* step → onStepFinish ({toolCalls,…}) → "step: {toolName}"
|
|
15
|
+
*
|
|
16
|
+
* Invariants:
|
|
17
|
+
* - I-1: opt-in by instantiation; the `AsyncRineClient` is built lazily on the
|
|
18
|
+
* FIRST fired callback (never at module load or in this factory body).
|
|
19
|
+
* - I-2: only the model-visible `text` / tool name / error message is summarized
|
|
20
|
+
* into the notify body — never ciphertext (these callbacks never see an
|
|
21
|
+
* envelope).
|
|
22
|
+
* - Best-effort: each fired callback attempts EXACTLY ONE `client.send` (AC-13);
|
|
23
|
+
* a send failure is swallowed + debug-logged and never thrown out of the
|
|
24
|
+
* callback (AC-14) — a notify failure must not crash the agent run.
|
|
25
|
+
*/
|
|
26
|
+
import { type RineClientOpts } from "./client.js";
|
|
27
|
+
/** The lifecycle selectors a caller can opt into. */
|
|
28
|
+
export type RineLifecycleEvent = "finish" | "error" | "step";
|
|
29
|
+
/** Options for {@link rineLifecycle}. */
|
|
30
|
+
export interface RineLifecycleOptions extends RineClientOpts {
|
|
31
|
+
/** Recipient handle/UUID every notify is sent to (`name@org`, `#group@org`). */
|
|
32
|
+
to: string;
|
|
33
|
+
/** Which lifecycle events fire a notify. Defaults to `["finish", "error"]`. */
|
|
34
|
+
on?: readonly RineLifecycleEvent[];
|
|
35
|
+
}
|
|
36
|
+
/** The callback bag spread into `agent.stream(...)` / `agent.generate(...)`. */
|
|
37
|
+
export interface RineLifecycleCallbacks {
|
|
38
|
+
onFinish?: (event: unknown) => void;
|
|
39
|
+
onError?: (event: unknown) => void;
|
|
40
|
+
onStepFinish?: (event: unknown) => void;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Build the opt-in outbound lifecycle bridge. Returns ONLY the callbacks for the
|
|
44
|
+
* selected events (so `{ ...cb }` never installs a no-op for an unselected one).
|
|
45
|
+
* The client is resolved lazily on the first fired callback (I-1).
|
|
46
|
+
*/
|
|
47
|
+
export declare function rineLifecycle(opts: RineLifecycleOptions): RineLifecycleCallbacks;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The 5 messaging tools: `rine_send`, `rine_send_and_wait`, `rine_check_inbox`,
|
|
3
|
+
* `rine_read`, `rine_reply` (SPEC §6 messaging table). Each is a thin
|
|
4
|
+
* `createTool` adapter: one `AsyncRineClient` call rendered to a string, wrapped
|
|
5
|
+
* by `makeExecute` (lazy client + formatError). Identity comes from
|
|
6
|
+
* `ctx.requestContext`, never the model-visible `inputSchema`.
|
|
7
|
+
*
|
|
8
|
+
* I-2 holds throughout: renderers read only plaintext/decrypt_error/verification
|
|
9
|
+
* (`format.ts`), never the ciphertext envelope. `rine_check_inbox` is the
|
|
10
|
+
* Track-0 poll+ack receive primitive (Tier-1).
|
|
11
|
+
*/
|
|
12
|
+
import { type RineToolOpts } from "./tool.js";
|
|
13
|
+
/** `rine_send` — send a 1:1 or `#`-group message (the SDK auto-routes groups). */
|
|
14
|
+
export declare function createRineSendTool(opts?: RineToolOpts): import("@mastra/core/tools").Tool<any, any, any, any, import("@mastra/core/tools").ToolExecutionContext<any, any, unknown>, "rine_send", unknown>;
|
|
15
|
+
/** `rine_send_and_wait` — 1:1 send that blocks for a reply (Tier-2, ms timeout). */
|
|
16
|
+
export declare function createRineSendAndWaitTool(opts?: RineToolOpts): import("@mastra/core/tools").Tool<any, any, any, any, import("@mastra/core/tools").ToolExecutionContext<any, any, unknown>, "rine_send_and_wait", unknown>;
|
|
17
|
+
/**
|
|
18
|
+
* `rine_check_inbox` — poll the newest new messages, decrypt them, then
|
|
19
|
+
* best-effort `markDelivered` the decryptable ids so a later check returns only
|
|
20
|
+
* newer mail (Track-0 poll+ack, Tier-1). On ack failure: warn + note the
|
|
21
|
+
* messages "may reappear" but still return the reads (AC-5).
|
|
22
|
+
*/
|
|
23
|
+
export declare function createRineCheckInboxTool(opts?: RineToolOpts): import("@mastra/core/tools").Tool<any, any, any, any, import("@mastra/core/tools").ToolExecutionContext<any, any, unknown>, "rine_check_inbox", unknown>;
|
|
24
|
+
/** `rine_read` — fetch + decrypt one message by id. */
|
|
25
|
+
export declare function createRineReadTool(opts?: RineToolOpts): import("@mastra/core/tools").Tool<any, any, any, any, import("@mastra/core/tools").ToolExecutionContext<any, any, unknown>, "rine_read", unknown>;
|
|
26
|
+
/** `rine_reply` — reply to a message, threading into the same conversation. */
|
|
27
|
+
export declare function createRineReplyTool(opts?: RineToolOpts): import("@mastra/core/tools").Tool<any, any, any, any, import("@mastra/core/tools").ToolExecutionContext<any, any, unknown>, "rine_reply", unknown>;
|