@rine-network/mastra 0.3.0 → 0.6.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.
@@ -0,0 +1,143 @@
1
+ /**
2
+ * The plural group renderers: the caller's own groups, a directory search, and
3
+ * one group's roster. `format.ts`'s `renderGroup` renders exactly ONE group and
4
+ * is what `rine_group_inspect` returns; these render pages, one numbered row
5
+ * each, in the shape `renderInbox`/`renderDiscover` already use.
6
+ *
7
+ * A sibling of `format.ts` rather than part of it, for the same reason
8
+ * `format-groups.ts` is one — `format.ts` holds its ~200-LOC budget.
9
+ *
10
+ * Ciphertext cannot leak into the model's context through these renderers: a
11
+ * group, a directory entry and a roster row carry no message payload at all.
12
+ *
13
+ * The types come from `@rine-network/sdk` directly: the SDK exports them
14
+ * alongside their schemas, so there is nothing here to re-derive.
15
+ */
16
+ import { type AgentHandleMap, type GroupMember, type GroupRead, type GroupSummary } from "@rine-network/sdk";
17
+ /**
18
+ * What a group with no conversation renders instead of an id.
19
+ *
20
+ * A group's thread is created lazily on its first post, so a null
21
+ * `conversation_id` is the fact "nothing has been said in this group yet" — not
22
+ * an error, not a refusal, and not a missing field. The clause is never omitted
23
+ * on null: a model that sees it on some rows and not others reads the absence
24
+ * as a rendering fault rather than as a fact about the group.
25
+ *
26
+ * `format.ts`'s `renderGroup` imports it from here so the two renderers cannot
27
+ * tell a model two different things about the same group. The same sentence is
28
+ * printed by the CLI, crewai, langchain, hermes and eve; it is restated per
29
+ * package because there is no runtime module all of them import.
30
+ */
31
+ export declare const NO_CONVERSATION = "(none \u2014 nothing has been said in this group yet)";
32
+ /**
33
+ * What a group renders in place of an agent list when this org holds no seat.
34
+ *
35
+ * The group list is ORG-scoped — it reports every group any agent of this org
36
+ * is in — so a row on it was never proof that the agent this host is acting as
37
+ * may post there. The empty list is that proof's absence stated plainly: no
38
+ * agent of this org is seated, and a send into the group would be refused.
39
+ *
40
+ * Never omitted, for the reason `NO_CONVERSATION` gives above: a clause a model
41
+ * sees on some rows and not others reads as a rendering fault rather than as a
42
+ * fact about the group. Restated per package because there is no runtime module
43
+ * all seven rendering surfaces import — though this package and its twin do
44
+ * share one (`@rine-network/sdk`), so the day either reworded its row would be
45
+ * the day the sentence belongs there instead.
46
+ *
47
+ * Pinned meanwhile: `tests/test_client-server-contract_group0.py` compares the
48
+ * four declared copies against `rine.group_words.NO_OWN_AGENTS`, which the
49
+ * three Python surfaces import, so a reword in one package fails there rather
50
+ * than shipping two answers to one question.
51
+ */
52
+ export declare const NO_OWN_AGENTS = "(none \u2014 no agent of your org is seated in this group)";
53
+ /**
54
+ * The seated-agents clause, from the one place both renderers read it.
55
+ *
56
+ * `format.ts`'s `renderGroup` calls this too, so the row and the single-group
57
+ * view cannot tell a model two different things about the same group — the same
58
+ * reason `NO_CONVERSATION` is imported there rather than retyped.
59
+ *
60
+ * Each id renders as its HANDLE. The three cases, and why a name is never one
61
+ * of them, are stated once on `AgentHandleMap` in the SDK
62
+ * (`rine-ts-sdk/src/resources/agent-handles.ts`); this renderer is one of the
63
+ * four that follow them. In short: a known id prints its handle, an unknown one
64
+ * prints itself, an empty list prints {@link NO_OWN_AGENTS}. A model handed a
65
+ * UUID cannot tell which of its agents it names — that is the whole reason
66
+ * handle resolution exists here — but a UUID is still addressable, so an
67
+ * unresolved id is degraded, not lost.
68
+ *
69
+ * `handles` is omitted whenever the org's agent list could not be read; that is
70
+ * case 2 for every id, and it is why nothing here throws.
71
+ *
72
+ * `undefined` is accepted on purpose. `GroupReadSchema` defaults the field to
73
+ * `[]`, so a parsed row always has one — but a row that never went through that
74
+ * parse (a hand-built object, a fake) has nothing, and a renderer whose job is
75
+ * to state an absence must not throw on one.
76
+ */
77
+ export declare function renderOwnAgents(ids: readonly string[] | undefined, handles?: AgentHandleMap): string;
78
+ /**
79
+ * How this package names a group's regime in one field, for a LIST ROW.
80
+ *
81
+ * One definition for the row, and for the row only. It is NOT what the create
82
+ * confirmation head asks: what a freshly created group runs is decided from the
83
+ * create RESULT, by `createdGroupMls` in `@rine-network/core`, because the row
84
+ * alone cannot tell a founded group from an unfounded one. `renderGroup`'s
85
+ * `[OK]` line re-derives its first clause from the predicates directly and has
86
+ * never called this either. So a reword here reaches this row and nothing else,
87
+ * and a reword in either of those two does not reach this row.
88
+ *
89
+ * The init window is reported as MLS because sends into it already are MLS, and
90
+ * a field that said "sender-key" there would state the opposite of what is
91
+ * happening. The window's own distinction is `rine_group_inspect`'s to make.
92
+ *
93
+ * A group created to run MLS whose ratchet tree was never founded really does
94
+ * carry sender-key traffic, so it names sender keys — and says the rest of the
95
+ * answer, because that row is otherwise field-for-field an open group's and
96
+ * nothing else on the page can tell the two apart. The parenthesis carries NO
97
+ * comma: the row below is comma-separated, which is the same reason its last
98
+ * clause opens with `; `. The whole sentence lives in `rine_group_inspect`; a
99
+ * list row is not the place for it.
100
+ */
101
+ export declare function groupModeLabel(g: GroupRead): string;
102
+ /**
103
+ * A numbered list of the groups this org's agents are seated in, or the
104
+ * empty-state line.
105
+ *
106
+ * The list is org-scoped, and each row's `your agents` clause names which of
107
+ * this org's agents are seated in that group — an empty clause means none of
108
+ * them is, and a send into that group would be refused. The clause names them
109
+ * by handle because that is the spelling `rine_whoami` prints, so the two
110
+ * outputs can be read against each other — a UUID matched nothing this surface
111
+ * had ever shown the model. What neither tool says is which agent this host is
112
+ * acting AS: `WhoAmI` carries no such field and `renderWhoAmI` marks no row, so
113
+ * in a multi-agent org that still has to come from the host's own config (the
114
+ * `agent` this client was built with, which may itself have been given as a
115
+ * UUID).
116
+ */
117
+ export declare function renderGroups(items: readonly GroupRead[], handles?: AgentHandleMap): string;
118
+ /**
119
+ * A numbered list of publicly listed groups, or the empty-state line.
120
+ *
121
+ * The header says what the list IS. `GET /directory/groups` is an
122
+ * unauthenticated public-visibility scan across every org and carries no
123
+ * identity at all, so nothing on a row can say whether this agent holds a seat
124
+ * in that group — while `renderGroups` above prints near-identical rows that
125
+ * end with exactly that. Two numbered lists told apart by two words in a header
126
+ * is not enough, so this one says the difference outright.
127
+ *
128
+ * It names no verb: the header states what the caller is looking at, and which
129
+ * tool to reach for next is the tool description's sentence, not this one's.
130
+ */
131
+ export declare function renderGroupSummaries(items: readonly GroupSummary[]): string;
132
+ /**
133
+ * A numbered roster, or the empty-state line.
134
+ *
135
+ * The roster is members only. An invitation and a nomination hold a seat
136
+ * against the group's ceiling without appearing here — `rine_group_requests`
137
+ * is what reports those — so a roster shorter than the ceiling suggests is not
138
+ * a gap in this list.
139
+ *
140
+ * Every member is listed, from every org. Nothing here filters on whose agent
141
+ * a row is; `(yours)` marks this org's rows and takes none away.
142
+ */
143
+ export declare function renderRoster(items: readonly GroupMember[]): string;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The admission strings this package composes itself: the outstanding list
3
+ * `rine_group_requests` renders, the line `rine_group_vote` returns, the refusal
4
+ * a vote from outside the electorate reads as, and what `rine_group_reclaim`
5
+ * did to the ratchet tree.
6
+ *
7
+ * They sit beside `format.ts`'s group renderers rather than in the tool module,
8
+ * in a sibling of their own for the same reason `schemas-groups.ts` is one —
9
+ * `format.ts` holds its ~200-LOC budget. Their wording is fixed across every
10
+ * surface that ships these verbs, so a vote reads the same here, from the CLI,
11
+ * and from the Python connectors.
12
+ *
13
+ * Every other group verb returns a sentence composed once, further in: a removal
14
+ * and a leave return `result.message` from the SDK, a sync returns
15
+ * `describeMlsRecovery`, and the reclamation half below returns
16
+ * `describeMlsReclamation`. The rest have no such sentence to return.
17
+ */
18
+ import { type MlsAdmissionResult } from "@rine-network/sdk";
19
+ import type { JoinRequestRead, VoteResponse } from "./types.js";
20
+ /**
21
+ * The outstanding admissions of one group under one filter, oldest first.
22
+ *
23
+ * Sorted here rather than trusted from the wire: the ordering is what every
24
+ * surface promises a voter, and the Python connectors sort too — so leaving it
25
+ * to the server would make the same list read differently by stack.
26
+ *
27
+ * `groupRef` is the reference the caller gave, not a UUID it never named.
28
+ */
29
+ export declare function renderRequests(groupRef: string, outstanding: string, rows: readonly JoinRequestRead[]): string;
30
+ /**
31
+ * The outcome of one vote.
32
+ *
33
+ * The leaf-and-Welcome sentence is conditioned on `mls_seated`, not on the
34
+ * server's `approved`. The seat the SDK attempts inside `vote()` is
35
+ * best-effort: a group running Sender Keys has no ratchet tree to mint a leaf
36
+ * in, and an add commit that fails is logged and swallowed so the vote can
37
+ * still be reported. On the status alone this claimed a Welcome for every vote
38
+ * that minted none — which is exactly the failure the seat exists to prevent,
39
+ * reported as a success.
40
+ *
41
+ * `invited` is the fourth answer a carried vote gives, and it is named rather
42
+ * than left to the bare status line. A member may nominate an agent into a
43
+ * `majority` or `unanimity` group, and the electorate can carry that nomination
44
+ * before the nominee has asked for anything; the request then resolves to a
45
+ * spendable invitation instead of a seat, so the agent that was never asked
46
+ * still decides for itself. Reporting it as "approved" would claim a membership
47
+ * that does not exist, and falling through to the bare line would leave a model
48
+ * reading a status word no other outcome uses.
49
+ */
50
+ export declare function renderVote(groupRef: string, result: VoteResponse): string;
51
+ /**
52
+ * A vote the electorate rule refused, as something the model can act on —
53
+ * or `undefined` for any other failure, which the shared `formatError` owns.
54
+ *
55
+ * Branching on the server's error CODE, never on its sentence: three 403s on
56
+ * this route mean different things, and the detail is written for an operator
57
+ * and is rewritten whenever the wording improves. `no vote was recorded` is
58
+ * stated first because that is the fact an agent would otherwise have to infer
59
+ * from a status code, and a retry of the same call cannot change any of them.
60
+ *
61
+ * There is deliberately no `BarUnreachable` arm. `bar_unreachable` and
62
+ * `electorate_empty` are one refusal and two facts about the row: the route
63
+ * raises `ElectorateEmptyError` for both, so an arm keyed on the other token
64
+ * could never run, and the `ElectorateEmpty` sentence is true of both cases.
65
+ * The two are told apart where they differ — on the row, by
66
+ * {@link renderRequests}.
67
+ */
68
+ export declare function renderVoteRefusal(groupRef: string, err: unknown): string | undefined;
69
+ /**
70
+ * What one reclamation pass seated and what it retired.
71
+ *
72
+ * The pass seats first and reclaims second, so the report carries both: a leaf
73
+ * belonging to an agent this run has just seated is not an orphan, and a
74
+ * reclamation reported without the seating that preceded it would read as
75
+ * though the tree had been trimmed against a roster nobody caught up.
76
+ *
77
+ * The reclamation half is `describeMlsReclamation` rather than a sentence of
78
+ * this package's own — one implementation for the four TypeScript surfaces that
79
+ * report a reclamation, sentence for sentence identical to the Python twin.
80
+ */
81
+ export declare function renderReclamation(groupRef: string, result: MlsAdmissionResult): string;
package/dist/format.d.ts CHANGED
@@ -1,16 +1,16 @@
1
1
  /**
2
2
  * Pure render functions: turn SDK return values into the human-readable strings
3
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 —
4
+ * — same snake_case wire field names —
5
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).
6
+ * because the TS SDK fully reads/posts MLS + PQ traffic.
7
7
  *
8
- * Invariant I-2: these read ONLY `plaintext` / `decrypt_error` / verification
8
+ * These read ONLY `plaintext` / `decrypt_error` / verification
9
9
  * fields. They NEVER read `encrypted_payload` or any envelope/ciphertext field,
10
10
  * so ciphertext can never reach the LLM context through a rendered string.
11
11
  */
12
- import type { ThreadEntry } from "@rine-network/sdk";
13
- import type { AgentProfile, AgentSummary, DecryptedMessage, GroupRead } from "./types.js";
12
+ import type { AgentHandleMap, SenderKeyExplainer, ThreadEntry, WhoAmI } from "@rine-network/sdk";
13
+ import type { AgentProfile, AgentSummary, DecryptedMessage, GroupRead, JoinRequestRead } from "./types.js";
14
14
  /**
15
15
  * Render a both-sided transcript (oldest→newest) for `rine_thread`. Each turn is a
16
16
  * role-tagged line; `[unavailable]` text passes through unchanged.
@@ -27,27 +27,65 @@ export declare function verifiedNote(msg: DecryptedMessage): string;
27
27
  * - anything else → compact JSON (last resort)
28
28
  */
29
29
  export declare function unwrapText(plaintext: unknown): string;
30
- /** Body of a message: decrypt error if unreadable, else the unwrapped plaintext. */
31
- export declare function renderMessageBody(msg: DecryptedMessage): string;
30
+ /**
31
+ * Body of a message: decrypt error if unreadable, else the unwrapped plaintext.
32
+ *
33
+ * `explain` is the per-render tiering: pass it on a MULTI render (an inbox,
34
+ * one call per `renderInbox()`) so the no-sender-key cause explains itself in
35
+ * full on the first unreadable row and marks every subsequent one. Omit it on
36
+ * a SINGLE render (`rine_read`, a `rine_send_and_wait` reply) so the one row
37
+ * keeps the full sentence it already carries — there is nothing to tier when
38
+ * the caller asked for exactly one message. Every other decrypt-error cause is
39
+ * untouched either way; classification is the typed `decrypt_error_code`, never
40
+ * the words in `decrypt_error`.
41
+ */
42
+ export declare function renderMessageBody(msg: DecryptedMessage, explain?: SenderKeyExplainer): string;
32
43
  /** A single message rendered across multiple labeled lines (for `rine_read`). */
33
44
  export declare function renderSingleMessage(msg: DecryptedMessage): string;
34
- /** A numbered inbox list, or the empty-state line. */
45
+ /**
46
+ * A numbered inbox list, or the empty-state line.
47
+ *
48
+ * MULTI render: one explainer per `renderInbox()` call, so the
49
+ * no-sender-key cause explains itself once for this render and marks every
50
+ * later row with the same cause — never module-level, never per-row.
51
+ */
35
52
  export declare function renderInbox(items: readonly DecryptedMessage[]): string;
36
53
  /** A numbered discovery list, or the empty-state line. */
37
54
  export declare function renderDiscover(items: readonly AgentSummary[]): string;
38
55
  /** A full agent profile (for `rine_inspect`). */
39
56
  export declare function renderProfile(p: AgentProfile): string;
40
57
  /**
41
- * Self-diagnose a group's E2EE mode. Uses `mls_group_id !== null` (the source of
42
- * truth the public `EncryptionVersion` enum is MISSING `mls-v1`/`hpke-hybrid-v1`
43
- * despite the SDK decrypting them), OR the explicit `mls_enabled`/`mls_pending`
44
- * flags. With MLS support present this is now a CAPABILITY flag, not a failure flag.
58
+ * This agent's own identity, for `rine_whoami`: the org it belongs to, the
59
+ * trust tier that org holds, and every handle it can act as.
60
+ *
61
+ * Revoked agents are dropped. The route returns them, and naming one here
62
+ * would offer the model a handle nothing can be sent from or to.
45
63
  */
46
- export declare function groupIsMls(g: GroupRead): boolean;
64
+ export declare function renderWhoAmI(me: WhoAmI): string;
47
65
  /**
48
- * A group rendered for `rine_group_inspect`. THE INVERSION: both the MLS and
49
- * sender-key branches are `[OK]` — an MLS group is readable/postable here, not a
50
- * warned-about ceiling (the Python integration printed `[WARN] MLS — cannot
51
- * read/post`).
66
+ * A group rendered for `rine_group_inspect`. Every branch renders `[OK]`: an
67
+ * MLS group is fully readable and postable here.
68
+ *
69
+ * Four states, not two. The second is the MLS init window: the group has no
70
+ * `mls_group_id` to render yet, while the server already accepts nothing but
71
+ * MLS there, so sends from here are MLS sends. Rendering that window as either
72
+ * of the other states says the opposite of what is happening.
73
+ *
74
+ * The third is a group created to run MLS whose ratchet tree was never founded.
75
+ * It reads to every other question exactly like the fourth — an ordinary
76
+ * sender-key group — so it used to fall into it, and the agent was never told
77
+ * the group had not got the encryption it was created for. It stays `[OK]`:
78
+ * every claim that marker makes is true here, the group carries traffic, and
79
+ * only the regime is other than intended. The sentence is the SDK's, and this
80
+ * surface asks for the no-verb spelling because `rine_group_reclaim` refuses on
81
+ * mastra — naming a verb that will not act is worse than naming none.
82
+ *
83
+ * `your agents` closes the gap the `[OK]` lines leave open: they describe how
84
+ * the GROUP is run, not whether this org may act in it. A group can be perfectly
85
+ * readable in the abstract and hold no seat of this org's at all. It names them
86
+ * by handle when `handles` resolves them — the rule is stated once on
87
+ * `AgentHandleMap` in the SDK, and `renderOwnAgents` is where it is applied.
52
88
  */
53
- export declare function renderGroup(g: GroupRead): string;
89
+ export declare function renderGroup(g: GroupRead, handles?: AgentHandleMap): string;
90
+ /** A numbered list of the caller's pending group invites, or the empty-state line. */
91
+ export declare function renderInvites(items: readonly JoinRequestRead[]): string;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The 5 admission + membership-exit group tools: `rine_group_requests`,
3
+ * `rine_group_vote`, `rine_group_leave`, `rine_group_sync`,
4
+ * `rine_group_reclaim`.
5
+ *
6
+ * Split from `groups.ts` (which holds the six create/invite/inspect/join tools)
7
+ * so both files stay inside the ~200-LOC budget. Same idiom: one exported
8
+ * factory per tool, the SDK does the work, the tool renders.
9
+ *
10
+ * `rine_group_leave` is a distinct tool rather than a mode of
11
+ * `rine_group_remove`, which keeps its own "name your own agent to leave"
12
+ * behaviour: a leave destroys THIS host's key material, a removal acts on a
13
+ * third party, and one wrong argument should not turn one into the other.
14
+ */
15
+ import { type RineToolOpts } from "./tool.js";
16
+ /** `rine_group_requests` — list a group's outstanding admissions. */
17
+ export declare function createRineGroupRequestsTool(opts?: RineToolOpts): import("@mastra/core/tools").Tool<any, any, any, any, import("@mastra/core/tools").ToolExecutionContext<any, any, unknown>, "rine_group_requests", unknown>;
18
+ /** `rine_group_vote` — approve or deny one pending join request. */
19
+ export declare function createRineGroupVoteTool(opts?: RineToolOpts): import("@mastra/core/tools").Tool<any, any, any, any, import("@mastra/core/tools").ToolExecutionContext<any, any, unknown>, "rine_group_vote", unknown>;
20
+ /** `rine_group_leave` — leave a group this agent is a member of. */
21
+ export declare function createRineGroupLeaveTool(opts?: RineToolOpts): import("@mastra/core/tools").Tool<any, any, any, any, import("@mastra/core/tools").ToolExecutionContext<any, any, unknown>, "rine_group_leave", unknown>;
22
+ /** `rine_group_sync` — catch this host's MLS state up with the group. */
23
+ export declare function createRineGroupSyncTool(opts?: RineToolOpts): import("@mastra/core/tools").Tool<any, any, any, any, import("@mastra/core/tools").ToolExecutionContext<any, any, unknown>, "rine_group_sync", unknown>;
24
+ /** `rine_group_reclaim` — seat the unseated, then retire the orphaned leaves. */
25
+ export declare function createRineGroupReclaimTool(opts?: RineToolOpts): import("@mastra/core/tools").Tool<any, any, any, any, import("@mastra/core/tools").ToolExecutionContext<any, any, unknown>, "rine_group_reclaim", unknown>;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * What an admission did, rendered for a model.
3
+ *
4
+ * A roster on `rine_group_create`, a batch on `rine_group_invite` and a single
5
+ * `agentToInvite` are the same operation at three entry points, so they answer
6
+ * in the same vocabulary. The two plural ones report one outcome per requested
7
+ * agent, in the order they were named: a batch never fails whole — naming 32
8
+ * agents and having one of them revoked costs that one agent its seat, not the
9
+ * other 31 theirs — which is only useful if the caller is told which one.
10
+ *
11
+ * Two reports, deliberately not merged. The server's says whether an
12
+ * **invitation** was minted; the MLS one says whether a ratchet-tree **leaf**
13
+ * was. An agent can be `invited` in the first and `no_key_package` in the
14
+ * second: it may join, and it will not be able to read a word until a leaf
15
+ * exists for it. Collapsing them into one number is how that goes unreported.
16
+ *
17
+ * On a `majority` or `unanimity` group the server's answer is `nominated`
18
+ * rather than `invited`: the invite files a join request the electorate
19
+ * decides, and no leaf is minted for it. So a nomination has no MLS report at
20
+ * all — the vote seats the member, and seating is what grants the key.
21
+ *
22
+ * The shapes and the reason→sentence maps are `@rine-network/core`'s, so this
23
+ * module is the rendering and nothing else. A skip used to print the raw wire
24
+ * token, which read as a refusal on exactly the groups it is not one for: an
25
+ * open group mints no invitation because it needs none, and `not_applicable`
26
+ * said nothing about that to a model deciding what to do next.
27
+ */
28
+ import { type GroupAdmissionReport, type MlsAdmissionResult } from "@rine-network/core";
29
+ /** The refusal when a caller names both invite targets, or neither. */
30
+ export declare const INVITE_TARGET_REQUIRED = "Name exactly one of agentToInvite (one invitation) or agentsToInvite (a batch).";
31
+ /**
32
+ * What one `agentToInvite` produced, told from the row's own status.
33
+ *
34
+ * The singular route answers one row rather than a per-agent report, so the
35
+ * status word is all a caller has to go on — and `Invited X (status pending)`
36
+ * both claims something that did not happen and hands a model a word no other
37
+ * outcome of this tool uses. `invited` and `pending` are therefore named, and
38
+ * any other status still prints itself rather than being read as one of them.
39
+ *
40
+ * `invited` covers two shapes and the sentence names both. A closed group mints
41
+ * a real invitation row that occupies a ratchet-tree seat until it is accepted
42
+ * or expires; an open group answers `invited` having minted nothing at all,
43
+ * because nobody needs an invitation to join one — and it runs sender keys, so
44
+ * there is no ratchet tree for a seat to sit in.
45
+ */
46
+ export declare function renderSingularInvite(agentRef: string, groupRef: string, status: string): string;
47
+ /** Per-agent invitation outcomes, one line each, in the order they were named. */
48
+ export declare function renderAdmission(report: GroupAdmissionReport): string;
49
+ /**
50
+ * What the MLS admission seated, and who is still without a leaf.
51
+ *
52
+ * `already_in_group` is filtered out of the unseated list: an agent that
53
+ * already holds a leaf is what a resumed admission looks like when it has
54
+ * nothing left to do, not a failure to report.
55
+ */
56
+ export declare function renderMlsAdmission(mls: MlsAdmissionResult): string;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The group domain's three READ tools: `rine_groups` (the groups this agent's
3
+ * ORG belongs to), `rine_discover_groups` (the public directory) and
4
+ * `rine_group_roster` (who is in one group).
5
+ *
6
+ * A third group module beside `groups.ts` (the six founding/admission tools)
7
+ * and `groups-admin.ts` (the five admission and exit verbs), so all three hold
8
+ * the ~200-LOC budget.
9
+ *
10
+ * None of them mutates anything, so none carries a `destructiveHint`.
11
+ * `rine_discover_groups` reads the public directory and needs no membership at
12
+ * all — it answers for `public`-visibility groups across every org, and returns
13
+ * no roster for any of them.
14
+ */
15
+ import { type RineToolOpts } from "./tool.js";
16
+ /** `rine_groups` — the groups this agent's ORG belongs to. */
17
+ export declare function createRineGroupsTool(opts?: RineToolOpts): import("@mastra/core/tools").Tool<any, any, any, any, import("@mastra/core/tools").ToolExecutionContext<any, any, unknown>, "rine_groups", unknown>;
18
+ /** `rine_discover_groups` — search the public group directory. */
19
+ export declare function createRineDiscoverGroupsTool(opts?: RineToolOpts): import("@mastra/core/tools").Tool<any, any, any, any, import("@mastra/core/tools").ToolExecutionContext<any, any, unknown>, "rine_discover_groups", unknown>;
20
+ /** `rine_group_roster` — who is in one group, and since when. */
21
+ export declare function createRineGroupRosterTool(opts?: RineToolOpts): import("@mastra/core/tools").Tool<any, any, any, any, import("@mastra/core/tools").ToolExecutionContext<any, any, unknown>, "rine_group_roster", unknown>;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * AGENT handle → UUID resolution for the group tools, and the two group
3
+ * resolutions that are the SDK's rather than this package's.
4
+ *
5
+ * 🔴 **Group references are not resolved here any more.** This module
6
+ * used to answer them from `groups.list()` and a local three-branch match, a
7
+ * fourth copy of a ladder the SDK now owns: `client.groups.resolveRef` reads a
8
+ * UUID from the group's own indexed row, a handle from the server's own lookup,
9
+ * and a bare name from the org's list — and answers with the group's whole
10
+ * record, so a verb handed the record reads no group of its own. Every group
11
+ * tool in this package calls that seam directly; nothing re-derives a row at a
12
+ * call site, and nothing here restates the ladder.
13
+ *
14
+ * What is left is the resolution the SDK does NOT do for these tools:
15
+ * - an agent → `resolveToUuid` (WebFinger) → its UUID (or pass a UUID
16
+ * through). WebFinger — not the public directory — so unlisted agents
17
+ * resolve too. This is the same resolution the SDK's own 1:1 send path uses.
18
+ * - a group for `rine_group_join`, which is a DIFFERENT ladder and is
19
+ * documented as such on {@link resolveJoinGroupUuid}.
20
+ *
21
+ * Split out of `groups.ts` so that file holds its six tool definitions and
22
+ * nothing else; the aggregator (`tools.ts`) never imports this module, so the
23
+ * shipped-surface gate's `id: "rine_*"` scan is unaffected by the move.
24
+ */
25
+ import type { AsyncRineClient } from "@rine-network/sdk";
26
+ /**
27
+ * A tool's answer, led by the item-18 completion notice when there is one.
28
+ *
29
+ * 🔴 `resolveToUuid` completes a short handle before it reaches WebFinger —
30
+ * `kofi@acme` is looked up as `kofi@acme.rine.network` — and answers with the
31
+ * resolved UUID, never the handle it resolved. The model named one agent and
32
+ * the call may have invited, removed or seated another, with nothing in the
33
+ * answer saying so.
34
+ *
35
+ * 🔴 A LINE, not a key, and that is this surface's shape rather than a choice:
36
+ * every tool here declares `strResult`, so there is no object to hang a field
37
+ * on. `@rine-network/mcp` returns objects and rides the same sentence as a
38
+ * `handle_completion` key; the CLI writes it to stderr. The BYTES are
39
+ * {@link handleCompletionNotes}' in `@rine-network/core` and are retyped by
40
+ * none of the three.
41
+ *
42
+ * It leads rather than trails because a model reads the head of a tool result
43
+ * and acts on it — a caveat about which agent was actually touched is worth
44
+ * nothing underneath the report of touching them.
45
+ */
46
+ export declare function withHandleCompletionLine(answer: string, spellings: readonly string[]): string;
47
+ /** Resolve an agent handle/UUID to its `AgentUuid` (UUIDs pass through). */
48
+ export declare function resolveAgentUuid(apiUrl: string, target: string): Promise<import("@rine-network/sdk").AgentUuid>;
49
+ /**
50
+ * Resolve a batch of agent handles/UUIDs, **in the order they were named**.
51
+ *
52
+ * Order is load-bearing: the server answers one admission entry per requested
53
+ * id in request order, so a report read against a re-ordered list names the
54
+ * wrong agents. Duplicates are dropped case-insensitively rather than sent
55
+ * twice, because the second copy of an id comes back `already_invited` and
56
+ * reads as a failure the caller did not cause.
57
+ */
58
+ export declare function resolveAgentUuids(apiUrl: string, targets: string[]): Promise<import("@rine-network/sdk").AgentUuid[]>;
59
+ /**
60
+ * Resolve a group handle/UUID to its `GroupUuid` for `rine_group_join`.
61
+ *
62
+ * 🔴 The SDK's own join ladder, not a copy of one, and NOT the ladder the other
63
+ * twelve group tools take. `client.groups.resolveRef` is scoped at every rung
64
+ * to a group the ORG already owns or is seated in — which for a join is the one
65
+ * set the target cannot be in — and this package
66
+ * used to answer instead from `groups.listInvites()` alone, so the commonest
67
+ * join there is, a handle read straight off `rine_discover_groups`, was refused
68
+ * here with the server never having been asked. `resolveRefForJoin` walks the
69
+ * org's seats, then this agent's invitations, then the public directory,
70
+ * matching the handle exactly at every rung. What this package could always do
71
+ * it still does: a BARE name resolves against this agent's own pending
72
+ * invitations — and against nothing else, because that list is the only set
73
+ * bounded to groups that have already asked for this agent, and a join cannot
74
+ * be taken back (`rine-core/src/resolve-group-join.ts` `resolveGroupUuidForJoin`).
75
+ *
76
+ * The refusals it throws lead with `Group not found`, which is the phrase
77
+ * `formatError` (`./errors.ts`) keys on to append this surface's own discover
78
+ * verbs — the old `No pending invite` wording matched none of its three
79
+ * spellings, so a refused join carried no remedy at all.
80
+ */
81
+ export declare function resolveJoinGroupUuid(client: AsyncRineClient, target: string): Promise<import("@rine-network/sdk").GroupUuid>;
package/dist/groups.d.ts CHANGED
@@ -1,25 +1,39 @@
1
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).
2
+ * 6 of the group domain's 13 tools: `rine_group_create`, `rine_group_invite`,
3
+ * `rine_group_remove`, `rine_group_inspect` (the INVERTED, MLS-capable
4
+ * section), `rine_group_join`, `rine_group_invites`. The four admission and
5
+ * exit verbs — `rine_group_requests`, `rine_group_vote`, `rine_group_leave`,
6
+ * `rine_group_sync` — live in `groups-admin.ts`, so both files hold the
7
+ * ~200-LOC budget.
5
8
  *
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
+ * `group_create` exposes `enableMls` (default true): the SDK best-effort-inits
10
+ * an MLS group and the renderer reports `[OK] MLS`, not a `[WARN]` ceiling. It
11
+ * exposes no `visibility` default at all — see `schemas-groups.ts`.
9
12
  *
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.
13
+ * Admission is plural on both write verbs. `members` on create and
14
+ * `agentsToInvite` on invite each take a batch and report one outcome per
15
+ * agent, because a batch drops what it cannot admit rather than refusing
16
+ * everyone, and on an MLS group it mints every new leaf in one commit instead
17
+ * of one commit each.
18
+ *
19
+ * Handle→UUID resolution lives in `groups-resolve.ts` and the admission
20
+ * rendering in `groups-admission.ts`, so this file is those six tool
21
+ * definitions and their bodies.
22
+ *
23
+ * `rine_group_join`/`rine_group_invites` close the accept-side gap: an agent
24
+ * invited into a group previously had no tool to join it or see its pending
25
+ * invites (only create/invite/remove/inspect existed).
16
26
  */
17
27
  import { type RineToolOpts } from "./tool.js";
18
28
  /** `rine_group_create` — create an MLS-by-default coordination group. */
19
29
  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). */
30
+ /** `rine_group_invite` — invite one agent or a batch (handle→UUID pre-resolved). */
21
31
  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). */
32
+ /** `rine_group_remove` — evict a member, or leave (handle→UUID pre-resolved). */
23
33
  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
34
  /** `rine_group_inspect` — report a group's E2EE mode + policy (`[OK] MLS` self-diagnosis). */
25
35
  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>;
36
+ /** `rine_group_join` — accept a pending invite / request to join (handle→UUID pre-resolved). */
37
+ export declare function createRineGroupJoinTool(opts?: RineToolOpts): import("@mastra/core/tools").Tool<any, any, any, any, import("@mastra/core/tools").ToolExecutionContext<any, any, unknown>, "rine_group_join", unknown>;
38
+ /** `rine_group_invites` — list the caller's own pending group invites. */
39
+ export declare function createRineGroupInvitesTool(opts?: RineToolOpts): import("@mastra/core/tools").Tool<any, any, any, any, import("@mastra/core/tools").ToolExecutionContext<any, any, unknown>, "rine_group_invites", unknown>;
package/dist/inbound.d.ts CHANGED
@@ -6,22 +6,30 @@
6
6
  * 1. resolve the message's `(handle, conversation_id)` → stored `runId` via the
7
7
  * {@link ThreadMapStore} (no mapping ⇒ skip, do NOT ack — lossless);
8
8
  * 2. **rehydrate the snapshot via `getWorkflowRunById(runId)` BEFORE resume**
9
- * (the load-bearing P0-10 recipe; defends mastra #5521 where
9
+ * (the load-bearing recipe; defends mastra #5521 where
10
10
  * `createRun({runId})` would otherwise read an empty in-process run-map);
11
11
  * a non-suspended / unknown run ⇒ skip, do NOT ack;
12
12
  * 3. build `resumeData` from `plaintext` / `verified` / `from` ONLY
13
- * (I-2: JSON-serializable, never ciphertext or an SDK client) — a
13
+ * (JSON-serializable, never ciphertext or an SDK client) — a
14
14
  * decrypt-failed message ⇒ skip, do NOT ack;
15
15
  * 4. `createRun({runId}).resume({ step, resumeData })` to continue the run.
16
16
  *
17
- * **Lossless-by-default (langchain D16):** every skip path leaves the message in
17
+ * **Lossless-by-default:** every skip path leaves the message in
18
18
  * the rine inbox (no `markDelivered`), so a later poll can retry after the run is
19
19
  * mapped / key recovery succeeds. The resumer NEVER acks.
20
+ *
21
+ * TRUST NOTE: `verified === true` on an inbound message means the envelope
22
+ * signature checked out AND the signing key it names belongs to the agent the
23
+ * server authenticated as the sender, so the `from` handed to the resumed step is
24
+ * the signer of that content. The signature covers the payload only — not this
25
+ * recipient, and no nonce — so an agent re-sending content it signed earlier
26
+ * produces a copy that verifies as its own. A workflow that must act on a payload
27
+ * once keys its own idempotency on `messageId`.
20
28
  */
21
29
  import type { ThreadMapStore } from "./threadmap.js";
22
30
  import type { DecryptedMessage } from "./types.js";
23
31
  /**
24
- * The minimal Mastra-workflow surface the resumer drives (the P0-10 recipe). A
32
+ * The minimal Mastra-workflow surface the resumer drives (the recipe). A
25
33
  * real `mastra.getWorkflow(id)` satisfies this structurally — we type only the
26
34
  * two methods used so the package does not hard-couple to a workflow generic and
27
35
  * tests can pass a fake. `step` is the suspended step (a `createStep` object or
@@ -50,13 +58,13 @@ export interface ResumableRun {
50
58
  }): Promise<unknown>;
51
59
  }
52
60
  /**
53
- * The JSON-serializable payload handed to the suspended step on resume. I-2:
61
+ * The JSON-serializable payload handed to the suspended step on resume:
54
62
  * plaintext + signature facts ONLY — no ciphertext, no envelope, no SDK client.
55
63
  */
56
64
  export interface ResumeData {
57
65
  /** Unwrapped decrypted body (or a decrypt-error marker), never ciphertext. */
58
66
  plaintext: string;
59
- /** Whether the inbound message's signature verified. */
67
+ /** Whether the signature verified as the server-authenticated sender's. */
60
68
  verified: boolean;
61
69
  /** Sender handle (or agent UUID fallback), for the resumed step's context. */
62
70
  from: string;
@@ -65,7 +73,7 @@ export interface ResumeData {
65
73
  }
66
74
  /** Options for {@link RineThreadResumer}. */
67
75
  export interface RineThreadResumerOptions {
68
- /** A pre-built workflow with snapshot storage already bound (P0-10). */
76
+ /** A pre-built workflow with snapshot storage already bound. */
69
77
  workflow: ResumableWorkflow;
70
78
  /** The durable `(handle, conversation) → runId` map. */
71
79
  threadMap: ThreadMapStore;