@openmarket/rooms-client 0.10.0 → 0.11.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/README.md +7 -1
- package/dist/agent/armed-mode.d.ts +5 -4
- package/dist/agent/armed-mode.js +8 -19
- package/dist/shared/om-permissions.d.ts +17 -0
- package/dist/shared/om-permissions.js +73 -0
- package/dist/shared/rooms-protocol.d.ts +17 -0
- package/dist/shared/rooms-protocol.js +1 -0
- package/dist/social-client.d.ts +47 -1
- package/dist/social-client.js +63 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/package.json +6 -7
- package/src/agent/armed-mode.ts +7 -20
- package/src/shared/om-permissions.ts +93 -0
- package/src/shared/rooms-protocol.ts +19 -0
- package/src/social-client.ts +143 -2
- package/src/version.ts +2 -2
- package/dist/agent/clients/common.d.ts +0 -82
- package/dist/agent/clients/common.js +0 -56
- package/src/agent/clients/common.ts +0 -107
package/README.md
CHANGED
|
@@ -6,6 +6,12 @@ The OM Rooms protocol client: wire types and frame schemas, the WebSocket client
|
|
|
6
6
|
|
|
7
7
|
Everything in this package runs unmodified in a browser: no `node:` or `bun:` imports, no filesystem or OS access, static imports only. Environment reads go through `envString()` in `endpoints.ts`, which returns `undefined` where `process` does not exist. The repo's boundary gate enforces this on every CI run; if you need daemon-side facilities (SQLite, vault, HTTP server), they live in the `om` CLI, not here.
|
|
8
8
|
|
|
9
|
+
Provider adapters and agent runtime contracts are also CLI-owned. In particular,
|
|
10
|
+
LLM messages, streamed turn events, and usage accounting live under
|
|
11
|
+
`packages/cli/src/agent/`; this package must not import a provider SDK or expose
|
|
12
|
+
those contracts as public subpaths. Only browser-safe rooms protocol and view-model
|
|
13
|
+
types belong here, and the CLI may depend on them in that direction.
|
|
14
|
+
|
|
9
15
|
## Usage
|
|
10
16
|
|
|
11
17
|
There is no root export; import the surface you need by subpath:
|
|
@@ -16,7 +22,7 @@ import { RoomClientMessageType, SUPPORTED_ROOM_PROTOCOL_VERSION } from "@openmar
|
|
|
16
22
|
import type { RoomSpaceSummary } from "@openmarket/rooms-client/social-types";
|
|
17
23
|
```
|
|
18
24
|
|
|
19
|
-
Subpaths: `client`, `ws-client`, `social-client`, `social-types`, `library-publisher`, `doc-reconcile`, `topic-uri`, `doc-uri`, `endpoints`, `jwt`, `names`, `suggestion-attribution`, `merge3`, `types`, `version`, `agent/{armed-mode, lane-draft, lane-prompts, library-context, library-model, room-context, sidebar-model, topic-inbox, topic-view-batcher}`, `
|
|
25
|
+
Subpaths: `client`, `ws-client`, `social-client`, `social-types`, `library-publisher`, `doc-reconcile`, `topic-uri`, `doc-uri`, `endpoints`, `jwt`, `names`, `suggestion-attribution`, `merge3`, `types`, `version`, `agent/{armed-mode, lane-draft, lane-prompts, library-context, library-model, room-context, sidebar-model, topic-inbox, topic-view-batcher}`, `shared/{rooms-protocol, mentions, emoji-data}`.
|
|
20
26
|
|
|
21
27
|
Bun consumers resolve raw TypeScript via the `bun` export condition; Node and bundlers get compiled JS plus `.d.ts` from `dist/`.
|
|
22
28
|
|
|
@@ -181,10 +181,11 @@ export declare function canPostAutonomously(input: {
|
|
|
181
181
|
* successful `post()`. Mutates `state`.
|
|
182
182
|
*/
|
|
183
183
|
export declare function recordAutonomousPost(state: ArmedRoomState, now?: number): void;
|
|
184
|
-
/** Record token spend from an armed turn against the session cost guard.
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
|
|
184
|
+
/** Record token spend from an armed turn against the session cost guard. The
|
|
185
|
+
* caller owns provider usage shapes; armed mode needs only the canonical
|
|
186
|
+
* total. A missing or invalid value is a no-op because the post counter is
|
|
187
|
+
* the fallback guard. */
|
|
188
|
+
export declare function recordArmedTurnUsage(state: ArmedRoomState, totalTokens: number | undefined): void;
|
|
188
189
|
/** A human (non-agent) POST that is NOT our own resets the consecutive-chain
|
|
189
190
|
* counter, freeing a chain-limit pause. Pure. */
|
|
190
191
|
export declare function isHumanResetEntry(entry: RoomLedgerEntry, ownHandle: string, ownUserId: string): boolean;
|
package/dist/agent/armed-mode.js
CHANGED
|
@@ -299,25 +299,14 @@ export function recordAutonomousPost(state, now = Date.now()) {
|
|
|
299
299
|
state.consecutiveAutoPosts += 1;
|
|
300
300
|
state.postsThisSession += 1;
|
|
301
301
|
}
|
|
302
|
-
/** Record token spend from an armed turn against the session cost guard.
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
if (
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
function readTotalTokens(usage) {
|
|
311
|
-
if (!usage)
|
|
312
|
-
return 0;
|
|
313
|
-
const direct = usage.totalTokens;
|
|
314
|
-
if (typeof direct === "number" && Number.isFinite(direct) && direct > 0)
|
|
315
|
-
return direct;
|
|
316
|
-
// Fall back to summing input + output if a client reports those instead.
|
|
317
|
-
const input = typeof usage.input === "number" ? usage.input : 0;
|
|
318
|
-
const output = typeof usage.output === "number" ? usage.output : 0;
|
|
319
|
-
const sum = input + output;
|
|
320
|
-
return Number.isFinite(sum) && sum > 0 ? sum : 0;
|
|
302
|
+
/** Record token spend from an armed turn against the session cost guard. The
|
|
303
|
+
* caller owns provider usage shapes; armed mode needs only the canonical
|
|
304
|
+
* total. A missing or invalid value is a no-op because the post counter is
|
|
305
|
+
* the fallback guard. */
|
|
306
|
+
export function recordArmedTurnUsage(state, totalTokens) {
|
|
307
|
+
if (typeof totalTokens !== "number" || !Number.isFinite(totalTokens) || totalTokens <= 0)
|
|
308
|
+
return;
|
|
309
|
+
state.tokensThisSession += totalTokens;
|
|
321
310
|
}
|
|
322
311
|
/** A human (non-agent) POST that is NOT our own resets the consecutive-chain
|
|
323
312
|
* counter, freeing a chain-limit pause. Pure. */
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { RoomRole } from "./rooms-protocol.js";
|
|
2
|
+
/** Channel-scope actions, byte-stable in store order. */
|
|
3
|
+
export declare const OM_ROOM_PERMISSION_ACTIONS: readonly ["post", "edit-own", "delete-any", "pin", "invite", "remove-member", "set-read-only", "manage-roles", "mute", "kick", "ban", "unban", "view-audit", "manage-room", "mass-mention"];
|
|
4
|
+
export type OmRoomPermissionAction = (typeof OM_ROOM_PERMISSION_ACTIONS)[number];
|
|
5
|
+
/** Space-scope actions, byte-stable in store order. */
|
|
6
|
+
export declare const OM_SPACE_PERMISSION_ACTIONS: readonly ["create-channel", "manage-server", "manage-server-roles", "create-invite", "manage-invites", "manage-members", "manage-nicknames", "change-nickname", "administrator"];
|
|
7
|
+
export type OmSpacePermissionAction = (typeof OM_SPACE_PERMISSION_ACTIONS)[number];
|
|
8
|
+
/** Any grantable action. The two vocabularies are disjoint; a role's
|
|
9
|
+
* grants[] may hold values from both. */
|
|
10
|
+
export type OmPermissionAction = OmRoomPermissionAction | OmSpacePermissionAction;
|
|
11
|
+
/** The space ladder (RoomSpaceMember.role). Channel tiers are RoomRole. */
|
|
12
|
+
export type OmSpaceLadderRole = "owner" | "admin" | "librarian" | "member";
|
|
13
|
+
/** Channel ladder base: what each channel tier natively holds. */
|
|
14
|
+
export declare const OM_ROOM_PERMISSION_MATRIX: Record<RoomRole, ReadonlySet<OmRoomPermissionAction>>;
|
|
15
|
+
/** Space ladder base: preserves the pre-grants behavior exactly (managers
|
|
16
|
+
* hold everything, everyone may change their own nickname). */
|
|
17
|
+
export declare const OM_SPACE_PERMISSION_MATRIX: Record<OmSpaceLadderRole, ReadonlySet<OmSpacePermissionAction>>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// The permission vocabulary shared by the store, relay, and clients: the
|
|
2
|
+
// channel-scope actions (per-room moderation) and the space-scope actions
|
|
3
|
+
// (server management), plus the fixed ladder base matrices. Parity-pinned to
|
|
4
|
+
// the store's permission services; the store stays the authority (every verb
|
|
5
|
+
// re-checks server-side), this mirror only decides what clients offer.
|
|
6
|
+
// Effective permissions = ladder base UNION the grants of every held named
|
|
7
|
+
// role (one role's grants[] may mix both vocabularies). Pure and browser-safe.
|
|
8
|
+
/** Channel-scope actions, byte-stable in store order. */
|
|
9
|
+
export const OM_ROOM_PERMISSION_ACTIONS = [
|
|
10
|
+
"post",
|
|
11
|
+
"edit-own",
|
|
12
|
+
"delete-any",
|
|
13
|
+
"pin",
|
|
14
|
+
"invite",
|
|
15
|
+
"remove-member",
|
|
16
|
+
"set-read-only",
|
|
17
|
+
"manage-roles",
|
|
18
|
+
"mute",
|
|
19
|
+
"kick",
|
|
20
|
+
"ban",
|
|
21
|
+
"unban",
|
|
22
|
+
"view-audit",
|
|
23
|
+
"manage-room",
|
|
24
|
+
"mass-mention",
|
|
25
|
+
];
|
|
26
|
+
/** Space-scope actions, byte-stable in store order. */
|
|
27
|
+
export const OM_SPACE_PERMISSION_ACTIONS = [
|
|
28
|
+
// create channels in this server
|
|
29
|
+
"create-channel",
|
|
30
|
+
// rename server, icon
|
|
31
|
+
"manage-server",
|
|
32
|
+
// create/edit/delete/assign named roles within reach
|
|
33
|
+
"manage-server-roles",
|
|
34
|
+
// mint a server invite
|
|
35
|
+
"create-invite",
|
|
36
|
+
// list/revoke invites, set/rotate/clear join code
|
|
37
|
+
"manage-invites",
|
|
38
|
+
// add and remove server members
|
|
39
|
+
"manage-members",
|
|
40
|
+
// change other members' nicknames
|
|
41
|
+
"manage-nicknames",
|
|
42
|
+
// change own nickname
|
|
43
|
+
"change-nickname",
|
|
44
|
+
// every action in both scopes, admin rank envelope; never the owner-only
|
|
45
|
+
// verbs (archive/purge space, ownership transfers)
|
|
46
|
+
"administrator",
|
|
47
|
+
];
|
|
48
|
+
/** Channel ladder base: what each channel tier natively holds. */
|
|
49
|
+
export const OM_ROOM_PERMISSION_MATRIX = {
|
|
50
|
+
owner: new Set(OM_ROOM_PERMISSION_ACTIONS),
|
|
51
|
+
admin: new Set(OM_ROOM_PERMISSION_ACTIONS),
|
|
52
|
+
mod: new Set([
|
|
53
|
+
"post",
|
|
54
|
+
"edit-own",
|
|
55
|
+
"delete-any",
|
|
56
|
+
"pin",
|
|
57
|
+
"invite",
|
|
58
|
+
"remove-member",
|
|
59
|
+
"mute",
|
|
60
|
+
"kick",
|
|
61
|
+
"view-audit",
|
|
62
|
+
"mass-mention",
|
|
63
|
+
]),
|
|
64
|
+
member: new Set(["post", "edit-own"]),
|
|
65
|
+
};
|
|
66
|
+
/** Space ladder base: preserves the pre-grants behavior exactly (managers
|
|
67
|
+
* hold everything, everyone may change their own nickname). */
|
|
68
|
+
export const OM_SPACE_PERMISSION_MATRIX = {
|
|
69
|
+
owner: new Set(OM_SPACE_PERMISSION_ACTIONS),
|
|
70
|
+
admin: new Set(OM_SPACE_PERMISSION_ACTIONS),
|
|
71
|
+
librarian: new Set(["change-nickname"]),
|
|
72
|
+
member: new Set(["change-nickname"]),
|
|
73
|
+
};
|
|
@@ -106,6 +106,7 @@ export declare const RoomServerMessageType: {
|
|
|
106
106
|
readonly TOPIC_UPDATED: "TOPIC_UPDATED";
|
|
107
107
|
readonly DOC_CHANGED: "DOC_CHANGED";
|
|
108
108
|
readonly FRIEND_UPDATED: "FRIEND_UPDATED";
|
|
109
|
+
readonly SPACE_ROLES_UPDATED: "SPACE_ROLES_UPDATED";
|
|
109
110
|
};
|
|
110
111
|
export declare function addBounded<T>(set: Set<T>, value: T, max: number): void;
|
|
111
112
|
export type RoomServerMessageType = (typeof RoomServerMessageType)[keyof typeof RoomServerMessageType];
|
|
@@ -175,6 +176,9 @@ export interface RoomMeta {
|
|
|
175
176
|
viewerRole?: RoomRole | null | undefined;
|
|
176
177
|
/** Head event sequence; diff against the read cursor for unread badges. */
|
|
177
178
|
headSeq?: number | undefined;
|
|
179
|
+
/** Epoch milliseconds of the newest visible message or sealed message.
|
|
180
|
+
* Unlike room metadata timestamps, this changes only with chat content. */
|
|
181
|
+
lastMessageAt?: number | undefined;
|
|
178
182
|
/** Topics dial for this channel. Absent reads as "allowed" since the
|
|
179
183
|
* 2026-07-12 dial-default flip (post-flip relays always advertise the
|
|
180
184
|
* stored value, "off" included); explicit "off" means topics are inert. */
|
|
@@ -842,6 +846,9 @@ export interface RoomSpace {
|
|
|
842
846
|
/** Archived docs twin: keeps the Archive row reachable when only docs
|
|
843
847
|
* are archived. */
|
|
844
848
|
archivedDocCount?: number | undefined;
|
|
849
|
+
/** Space permission model revision; delegate-facing UI gates on >= 3.
|
|
850
|
+
* Absent on stores that predate space permissions. */
|
|
851
|
+
permissionsVersion?: number | undefined;
|
|
845
852
|
}
|
|
846
853
|
export interface RoomSpacesPayload {
|
|
847
854
|
requestId?: string;
|
|
@@ -895,6 +902,12 @@ export interface SpaceUpdatedPayload {
|
|
|
895
902
|
/** Rides WITH archived on hard deletion (see RoomUpdatedPayload). */
|
|
896
903
|
deleted?: boolean | undefined;
|
|
897
904
|
}
|
|
905
|
+
/** Space roles changed (role CRUD, member role assignment, @everyone
|
|
906
|
+
* grants): a poke, not a diff. Clients invalidate their space-roles cache
|
|
907
|
+
* and refetch lazily. */
|
|
908
|
+
export interface SpaceRolesUpdatedPayload {
|
|
909
|
+
spaceId: string;
|
|
910
|
+
}
|
|
898
911
|
export interface RoomSpaceJoinedPayload {
|
|
899
912
|
requestId?: string;
|
|
900
913
|
space: RoomSpace;
|
|
@@ -1428,6 +1441,10 @@ export type RoomServerMessage = {
|
|
|
1428
1441
|
type: typeof RoomServerMessageType.SPACE_UPDATED;
|
|
1429
1442
|
payload: SpaceUpdatedPayload;
|
|
1430
1443
|
timestamp?: number;
|
|
1444
|
+
} | {
|
|
1445
|
+
type: typeof RoomServerMessageType.SPACE_ROLES_UPDATED;
|
|
1446
|
+
payload: SpaceRolesUpdatedPayload;
|
|
1447
|
+
timestamp?: number;
|
|
1431
1448
|
} | {
|
|
1432
1449
|
type: typeof RoomServerMessageType.SPACE_INVITE;
|
|
1433
1450
|
payload: SpaceInvitePayload;
|
|
@@ -105,6 +105,7 @@ export const RoomServerMessageType = {
|
|
|
105
105
|
TOPIC_UPDATED: "TOPIC_UPDATED",
|
|
106
106
|
DOC_CHANGED: "DOC_CHANGED",
|
|
107
107
|
FRIEND_UPDATED: "FRIEND_UPDATED",
|
|
108
|
+
SPACE_ROLES_UPDATED: "SPACE_ROLES_UPDATED",
|
|
108
109
|
};
|
|
109
110
|
export function addBounded(set, value, max) {
|
|
110
111
|
set.add(value);
|
package/dist/social-client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RoomTopicAttention } from "./shared/rooms-protocol.js";
|
|
1
|
+
import type { RoomRole, RoomTopicAttention } from "./shared/rooms-protocol.js";
|
|
2
2
|
import type { RoomBookmarkSaved, RoomBookmarksResult, RoomDmConversation, RoomDmHistory, RoomDmSendResult, RoomDmUnreadConversation, RoomDoc, RoomDocAttribution, RoomDocChanges, RoomDocConflictHead, RoomDocEditingLease, RoomDocProvenance, RoomDocRevision, RoomDocRunRevertResult, RoomDocWriteResult, RoomFollow, RoomFollowResult, RoomFriend, RoomFriendRequests, RoomNotificationInboxResult, RoomPollVotersResult, RoomPublicProfile, RoomRepliesReadResult, RoomRepliesResult, RoomRestEvent, RoomSocialConfig, RoomSpaceRestorePlan, RoomSpaceRestoreReceipt, RoomSuggestion, RoomSuggestionRequest, RoomUserBlock, RoomUserBlocksResult } from "./social-types.js";
|
|
3
3
|
export type { RoomAttachment, RoomBookmark, RoomBookmarkSaved, RoomBookmarksResult, RoomDmConversation, RoomDmHistory, RoomDmMessage, RoomDmParticipant, RoomDmSendResult, RoomDmUnreadConversation, RoomDocAttribution, RoomDocEditingLease, RoomDocMergeRegion, RoomDocRevisionSource, RoomDocRunRevertResult, RoomFollow, RoomFollowResult, RoomFriend, RoomFriendRequest, RoomFriendRequests, RoomNotificationInboxItem, RoomNotificationInboxResult, RoomNotificationKind, RoomNotificationMessageData, RoomNotifyQuietHours, RoomPollOptionVoters, RoomPollVotersResult, RoomPublicProfile, RoomRepliesReadResult, RoomRepliesResult, RoomReplyInboxItem, RoomRestEvent, RoomSocialConfig, RoomSpaceNotificationOverride, RoomSpaceRestoreAction, RoomSpaceRestoreActionKind, RoomSpaceRestorePlan, RoomSpaceRestoreReceipt, RoomSpaceRestoreSummary, RoomSuggestion, RoomSuggestionIntent, RoomSuggestionRequest, RoomSuggestionSource, RoomsNotificationSettings, RoomUserBlock, RoomUserBlocksResult, } from "./social-types.js";
|
|
4
4
|
/** Largest per-file size any tier accepts (plus tier). The server enforces
|
|
@@ -178,6 +178,9 @@ export interface RoomSpaceSummary {
|
|
|
178
178
|
/** Archived docs in this server: the row must stay reachable when only
|
|
179
179
|
* docs are archived, since they have no other retrieval surface. */
|
|
180
180
|
archivedDocCount?: number | undefined;
|
|
181
|
+
/** Space permission model revision; delegate-facing UI gates on >= 3.
|
|
182
|
+
* Absent on stores that predate space permissions. */
|
|
183
|
+
permissionsVersion?: number | undefined;
|
|
181
184
|
}
|
|
182
185
|
export declare function listRoomSpacesViaChat(config: RoomSocialConfig): Promise<RoomSpaceSummary[]>;
|
|
183
186
|
export interface RoomSpaceMember {
|
|
@@ -204,6 +207,13 @@ export interface RoomSpaceRole {
|
|
|
204
207
|
color: string | null;
|
|
205
208
|
hoist: boolean;
|
|
206
209
|
position: number;
|
|
210
|
+
/** Permission actions this role grants, mixing the channel and space
|
|
211
|
+
* vocabularies (see shared/om-permissions). Absent on stores that
|
|
212
|
+
* predate roles-v2 grants. */
|
|
213
|
+
grants?: string[];
|
|
214
|
+
/** Discord's "Allow anyone to @mention this role". Absent on stores
|
|
215
|
+
* that predate it. */
|
|
216
|
+
mentionable?: boolean;
|
|
207
217
|
}
|
|
208
218
|
export declare function listSpaceRoles(config: RoomSocialConfig, spaceId: string): Promise<RoomSpaceRole[]>;
|
|
209
219
|
export declare function createSpaceRole(config: RoomSocialConfig, spaceId: string, input: {
|
|
@@ -215,7 +225,12 @@ export declare function updateSpaceRole(config: RoomSocialConfig, spaceId: strin
|
|
|
215
225
|
name?: string;
|
|
216
226
|
color?: string | null;
|
|
217
227
|
hoist?: boolean;
|
|
228
|
+
/** Discord's "Allow anyone to @mention this role". Only send it to
|
|
229
|
+
* stores that emit the field (strict schemas 400 on unknown keys). */
|
|
230
|
+
mentionable?: boolean;
|
|
218
231
|
position?: number;
|
|
232
|
+
/** Full replace of the role's grants. */
|
|
233
|
+
grants?: string[];
|
|
219
234
|
}): Promise<RoomSpaceRole>;
|
|
220
235
|
export declare function deleteSpaceRole(config: RoomSocialConfig, spaceId: string, roleId: string): Promise<void>;
|
|
221
236
|
/** A channel webhook: the Discord-compatible ingest capability. The
|
|
@@ -347,6 +362,35 @@ export declare function updateRoomChannel(config: RoomSocialConfig, room: string
|
|
|
347
362
|
export declare function resolveRoomChannel(config: RoomSocialConfig, room: string): Promise<Record<string, unknown> & {
|
|
348
363
|
topicsPolicy?: "off" | "allowed" | "required";
|
|
349
364
|
}>;
|
|
365
|
+
/** Set or clear a space channel's role guest list. Null (or empty) clears:
|
|
366
|
+
* the channel opens back up to every space member. */
|
|
367
|
+
export declare function setRoomAllowedRoles(config: RoomSocialConfig, room: string, allowedRoleIds: string[] | null,
|
|
368
|
+
/** Per-member access rules (union with roles). ABSENT = leave the stored
|
|
369
|
+
* list untouched (and let a legacy conversion seed from current members);
|
|
370
|
+
* null or [] = clear; array = set. */
|
|
371
|
+
allowedUserIds?: string[] | null, reason?: string): Promise<Record<string, unknown>>;
|
|
372
|
+
export interface RoomAccessMeta {
|
|
373
|
+
/** Active role guest list; null = open to every space member. */
|
|
374
|
+
allowedRoleIds: string[] | null;
|
|
375
|
+
/** Per-member access rules (union with the roles); null = none. */
|
|
376
|
+
allowedUserIds: string[] | null;
|
|
377
|
+
/** Tiers allowed to post; the store default is all four. */
|
|
378
|
+
postingRoles: RoomRole[];
|
|
379
|
+
access: string | null;
|
|
380
|
+
spaceId: string | null;
|
|
381
|
+
kind: string | null;
|
|
382
|
+
/** The caller's resolved channel role (owner fallback = creator). */
|
|
383
|
+
viewerRole: RoomRole | null;
|
|
384
|
+
}
|
|
385
|
+
/** Authoritative access meta for the settings surface: the room's guest
|
|
386
|
+
* list, posting tiers and the caller's resolved role. Rides
|
|
387
|
+
* resolveRoomChannel (the relay's route ordering), which is also where the
|
|
388
|
+
* store places viewerRole (existence-hiding 404 preserved as a throw). */
|
|
389
|
+
export declare function resolveRoomAccess(config: RoomSocialConfig, room: string): Promise<RoomAccessMeta>;
|
|
390
|
+
/** Owner-only. The old owner becomes a channel admin; audited server-side. */
|
|
391
|
+
export declare function transferRoomOwnership(config: RoomSocialConfig, room: string, targetUserId: string, reason?: string): Promise<void>;
|
|
392
|
+
/** Lift a channel ban (no WS frame exists for unban; REST is the path). */
|
|
393
|
+
export declare function unbanRoomMember(config: RoomSocialConfig, room: string, targetUserId: string, reason?: string): Promise<void>;
|
|
350
394
|
/** Archive a channel (owner/admin): drops from listings, refuses posts. */
|
|
351
395
|
export declare function archiveRoomChannel(config: RoomSocialConfig, room: string): Promise<{
|
|
352
396
|
roomId?: string;
|
|
@@ -358,6 +402,8 @@ export declare function updateRoomSpace(config: RoomSocialConfig, spaceId: strin
|
|
|
358
402
|
access?: "open" | "invite";
|
|
359
403
|
iconUrl?: string | null;
|
|
360
404
|
}): Promise<RoomSpaceSummary>;
|
|
405
|
+
/** Owner-only. The old owner becomes a space admin; audited server-side. */
|
|
406
|
+
export declare function transferSpaceOwnership(config: RoomSocialConfig, spaceId: string, targetUserId: string, reason?: string): Promise<void>;
|
|
361
407
|
/** Archive a server (owner only). */
|
|
362
408
|
export declare function archiveRoomSpace(config: RoomSocialConfig, spaceId: string): Promise<{
|
|
363
409
|
archived?: boolean;
|
package/dist/social-client.js
CHANGED
|
@@ -552,6 +552,62 @@ export async function resolveRoomChannel(config, room) {
|
|
|
552
552
|
const result = await socialRequest(config, `/rooms/${encodeURIComponent(cleanRoomId(room))}/resolve`, { method: "GET" });
|
|
553
553
|
return result.room;
|
|
554
554
|
}
|
|
555
|
+
/** Set or clear a space channel's role guest list. Null (or empty) clears:
|
|
556
|
+
* the channel opens back up to every space member. */
|
|
557
|
+
export async function setRoomAllowedRoles(config, room, allowedRoleIds,
|
|
558
|
+
/** Per-member access rules (union with roles). ABSENT = leave the stored
|
|
559
|
+
* list untouched (and let a legacy conversion seed from current members);
|
|
560
|
+
* null or [] = clear; array = set. */
|
|
561
|
+
allowedUserIds, reason) {
|
|
562
|
+
const result = await socialRequest(config, `/rooms/${encodeURIComponent(cleanRoomId(room))}/allowed-roles`, {
|
|
563
|
+
method: "PATCH",
|
|
564
|
+
body: {
|
|
565
|
+
allowedRoleIds,
|
|
566
|
+
...(allowedUserIds !== undefined ? { allowedUserIds } : {}),
|
|
567
|
+
...(reason ? { reason } : {}),
|
|
568
|
+
},
|
|
569
|
+
});
|
|
570
|
+
return result.room ?? {};
|
|
571
|
+
}
|
|
572
|
+
/** Authoritative access meta for the settings surface: the room's guest
|
|
573
|
+
* list, posting tiers and the caller's resolved role. Rides
|
|
574
|
+
* resolveRoomChannel (the relay's route ordering), which is also where the
|
|
575
|
+
* store places viewerRole (existence-hiding 404 preserved as a throw). */
|
|
576
|
+
export async function resolveRoomAccess(config, room) {
|
|
577
|
+
const meta = (await resolveRoomChannel(config, room)) ?? {};
|
|
578
|
+
const allowed = Array.isArray(meta.allowedRoleIds)
|
|
579
|
+
? meta.allowedRoleIds.filter((value) => typeof value === "string")
|
|
580
|
+
: null;
|
|
581
|
+
const allowedUsers = Array.isArray(meta.allowedUserIds)
|
|
582
|
+
? meta.allowedUserIds.filter((value) => typeof value === "string")
|
|
583
|
+
: null;
|
|
584
|
+
const posting = Array.isArray(meta.postingRoles)
|
|
585
|
+
? meta.postingRoles.filter((value) => value === "owner" || value === "admin" || value === "mod" || value === "member")
|
|
586
|
+
: [];
|
|
587
|
+
const viewer = typeof meta.viewerRole === "string" ? meta.viewerRole : null;
|
|
588
|
+
return {
|
|
589
|
+
allowedRoleIds: allowed && allowed.length > 0 ? allowed : null,
|
|
590
|
+
allowedUserIds: allowedUsers && allowedUsers.length > 0 ? allowedUsers : null,
|
|
591
|
+
postingRoles: posting.length > 0 ? posting : ["owner", "admin", "mod", "member"],
|
|
592
|
+
access: typeof meta.access === "string" ? meta.access : null,
|
|
593
|
+
spaceId: typeof meta.spaceId === "string" ? meta.spaceId : null,
|
|
594
|
+
kind: typeof meta.kind === "string" ? meta.kind : null,
|
|
595
|
+
viewerRole: viewer === "owner" || viewer === "admin" || viewer === "mod" || viewer === "member"
|
|
596
|
+
? viewer
|
|
597
|
+
: null,
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
/** Owner-only. The old owner becomes a channel admin; audited server-side. */
|
|
601
|
+
export async function transferRoomOwnership(config, room, targetUserId, reason) {
|
|
602
|
+
await socialRequest(config, `/rooms/${encodeURIComponent(cleanRoomId(room))}/ownership/transfer`, { method: "POST", body: { targetUserId, ...(reason ? { reason } : {}) } });
|
|
603
|
+
}
|
|
604
|
+
/** Lift a channel ban (no WS frame exists for unban; REST is the path). */
|
|
605
|
+
export async function unbanRoomMember(config, room, targetUserId, reason) {
|
|
606
|
+
await socialRequest(config, `/rooms/${encodeURIComponent(cleanRoomId(room))}/mod/unban`, {
|
|
607
|
+
method: "POST",
|
|
608
|
+
body: { targetUserId, ...(reason ? { reason } : {}) },
|
|
609
|
+
});
|
|
610
|
+
}
|
|
555
611
|
/** Archive a channel (owner/admin): drops from listings, refuses posts. */
|
|
556
612
|
export async function archiveRoomChannel(config, room) {
|
|
557
613
|
return socialRequest(config, `/rooms/${encodeURIComponent(cleanRoomId(room))}`, {
|
|
@@ -563,6 +619,13 @@ export async function updateRoomSpace(config, spaceId, input) {
|
|
|
563
619
|
const result = await socialRequest(config, `/spaces/${encodeURIComponent(spaceId)}`, { method: "PATCH", body: input });
|
|
564
620
|
return result.space;
|
|
565
621
|
}
|
|
622
|
+
/** Owner-only. The old owner becomes a space admin; audited server-side. */
|
|
623
|
+
export async function transferSpaceOwnership(config, spaceId, targetUserId, reason) {
|
|
624
|
+
await socialRequest(config, `/spaces/${encodeURIComponent(spaceId)}/ownership/transfer`, {
|
|
625
|
+
method: "POST",
|
|
626
|
+
body: { targetUserId, ...(reason ? { reason } : {}) },
|
|
627
|
+
});
|
|
628
|
+
}
|
|
566
629
|
/** Archive a server (owner only). */
|
|
567
630
|
export async function archiveRoomSpace(config, spaceId) {
|
|
568
631
|
return socialRequest(config, `/spaces/${encodeURIComponent(spaceId)}`, { method: "DELETE" });
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
2
|
-
export declare const RUNNER_VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.11.0";
|
|
2
|
+
export declare const RUNNER_VERSION = "0.11.0";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = "0.
|
|
2
|
-
export const RUNNER_VERSION = "0.
|
|
1
|
+
export const VERSION = "0.11.0";
|
|
2
|
+
export const RUNNER_VERSION = "0.11.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmarket/rooms-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "OM Rooms protocol client: wire types, WebSocket + REST clients, and chat view-models. Browser-safe (no node:/bun: imports).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -137,11 +137,6 @@
|
|
|
137
137
|
"types": "./dist/agent/topic-view-batcher.d.ts",
|
|
138
138
|
"default": "./dist/agent/topic-view-batcher.js"
|
|
139
139
|
},
|
|
140
|
-
"./agent/clients/common": {
|
|
141
|
-
"bun": "./src/agent/clients/common.ts",
|
|
142
|
-
"types": "./dist/agent/clients/common.d.ts",
|
|
143
|
-
"default": "./dist/agent/clients/common.js"
|
|
144
|
-
},
|
|
145
140
|
"./shared/rooms-protocol": {
|
|
146
141
|
"bun": "./src/shared/rooms-protocol.ts",
|
|
147
142
|
"types": "./dist/shared/rooms-protocol.d.ts",
|
|
@@ -157,6 +152,11 @@
|
|
|
157
152
|
"types": "./dist/shared/emoji-data.d.ts",
|
|
158
153
|
"default": "./dist/shared/emoji-data.js"
|
|
159
154
|
},
|
|
155
|
+
"./shared/om-permissions": {
|
|
156
|
+
"bun": "./src/shared/om-permissions.ts",
|
|
157
|
+
"types": "./dist/shared/om-permissions.d.ts",
|
|
158
|
+
"default": "./dist/shared/om-permissions.js"
|
|
159
|
+
},
|
|
160
160
|
"./version": {
|
|
161
161
|
"bun": "./src/version.ts",
|
|
162
162
|
"types": "./dist/version.d.ts",
|
|
@@ -171,7 +171,6 @@
|
|
|
171
171
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
172
172
|
},
|
|
173
173
|
"dependencies": {
|
|
174
|
-
"@earendil-works/pi-ai": "0.78.1",
|
|
175
174
|
"picocolors": "1.1.1"
|
|
176
175
|
},
|
|
177
176
|
"devDependencies": {
|
package/src/agent/armed-mode.ts
CHANGED
|
@@ -362,26 +362,13 @@ export function recordAutonomousPost(state: ArmedRoomState, now = Date.now()): v
|
|
|
362
362
|
state.postsThisSession += 1;
|
|
363
363
|
}
|
|
364
364
|
|
|
365
|
-
/** Record token spend from an armed turn against the session cost guard.
|
|
366
|
-
*
|
|
367
|
-
*
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
const total = readTotalTokens(usage);
|
|
373
|
-
if (total > 0) state.tokensThisSession += total;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
function readTotalTokens(usage: Record<string, unknown> | undefined): number {
|
|
377
|
-
if (!usage) return 0;
|
|
378
|
-
const direct = usage.totalTokens;
|
|
379
|
-
if (typeof direct === "number" && Number.isFinite(direct) && direct > 0) return direct;
|
|
380
|
-
// Fall back to summing input + output if a client reports those instead.
|
|
381
|
-
const input = typeof usage.input === "number" ? usage.input : 0;
|
|
382
|
-
const output = typeof usage.output === "number" ? usage.output : 0;
|
|
383
|
-
const sum = input + output;
|
|
384
|
-
return Number.isFinite(sum) && sum > 0 ? sum : 0;
|
|
365
|
+
/** Record token spend from an armed turn against the session cost guard. The
|
|
366
|
+
* caller owns provider usage shapes; armed mode needs only the canonical
|
|
367
|
+
* total. A missing or invalid value is a no-op because the post counter is
|
|
368
|
+
* the fallback guard. */
|
|
369
|
+
export function recordArmedTurnUsage(state: ArmedRoomState, totalTokens: number | undefined): void {
|
|
370
|
+
if (typeof totalTokens !== "number" || !Number.isFinite(totalTokens) || totalTokens <= 0) return;
|
|
371
|
+
state.tokensThisSession += totalTokens;
|
|
385
372
|
}
|
|
386
373
|
|
|
387
374
|
/** A human (non-agent) POST that is NOT our own resets the consecutive-chain
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// The permission vocabulary shared by the store, relay, and clients: the
|
|
2
|
+
// channel-scope actions (per-room moderation) and the space-scope actions
|
|
3
|
+
// (server management), plus the fixed ladder base matrices. Parity-pinned to
|
|
4
|
+
// the store's permission services; the store stays the authority (every verb
|
|
5
|
+
// re-checks server-side), this mirror only decides what clients offer.
|
|
6
|
+
// Effective permissions = ladder base UNION the grants of every held named
|
|
7
|
+
// role (one role's grants[] may mix both vocabularies). Pure and browser-safe.
|
|
8
|
+
|
|
9
|
+
import type { RoomRole } from "./rooms-protocol.js";
|
|
10
|
+
|
|
11
|
+
/** Channel-scope actions, byte-stable in store order. */
|
|
12
|
+
export const OM_ROOM_PERMISSION_ACTIONS = [
|
|
13
|
+
"post",
|
|
14
|
+
"edit-own",
|
|
15
|
+
"delete-any",
|
|
16
|
+
"pin",
|
|
17
|
+
"invite",
|
|
18
|
+
"remove-member",
|
|
19
|
+
"set-read-only",
|
|
20
|
+
"manage-roles",
|
|
21
|
+
"mute",
|
|
22
|
+
"kick",
|
|
23
|
+
"ban",
|
|
24
|
+
"unban",
|
|
25
|
+
"view-audit",
|
|
26
|
+
"manage-room",
|
|
27
|
+
"mass-mention",
|
|
28
|
+
] as const;
|
|
29
|
+
|
|
30
|
+
export type OmRoomPermissionAction = (typeof OM_ROOM_PERMISSION_ACTIONS)[number];
|
|
31
|
+
|
|
32
|
+
/** Space-scope actions, byte-stable in store order. */
|
|
33
|
+
export const OM_SPACE_PERMISSION_ACTIONS = [
|
|
34
|
+
// create channels in this server
|
|
35
|
+
"create-channel",
|
|
36
|
+
// rename server, icon
|
|
37
|
+
"manage-server",
|
|
38
|
+
// create/edit/delete/assign named roles within reach
|
|
39
|
+
"manage-server-roles",
|
|
40
|
+
// mint a server invite
|
|
41
|
+
"create-invite",
|
|
42
|
+
// list/revoke invites, set/rotate/clear join code
|
|
43
|
+
"manage-invites",
|
|
44
|
+
// add and remove server members
|
|
45
|
+
"manage-members",
|
|
46
|
+
// change other members' nicknames
|
|
47
|
+
"manage-nicknames",
|
|
48
|
+
// change own nickname
|
|
49
|
+
"change-nickname",
|
|
50
|
+
// every action in both scopes, admin rank envelope; never the owner-only
|
|
51
|
+
// verbs (archive/purge space, ownership transfers)
|
|
52
|
+
"administrator",
|
|
53
|
+
] as const;
|
|
54
|
+
|
|
55
|
+
export type OmSpacePermissionAction = (typeof OM_SPACE_PERMISSION_ACTIONS)[number];
|
|
56
|
+
|
|
57
|
+
/** Any grantable action. The two vocabularies are disjoint; a role's
|
|
58
|
+
* grants[] may hold values from both. */
|
|
59
|
+
export type OmPermissionAction = OmRoomPermissionAction | OmSpacePermissionAction;
|
|
60
|
+
|
|
61
|
+
/** The space ladder (RoomSpaceMember.role). Channel tiers are RoomRole. */
|
|
62
|
+
export type OmSpaceLadderRole = "owner" | "admin" | "librarian" | "member";
|
|
63
|
+
|
|
64
|
+
/** Channel ladder base: what each channel tier natively holds. */
|
|
65
|
+
export const OM_ROOM_PERMISSION_MATRIX: Record<RoomRole, ReadonlySet<OmRoomPermissionAction>> = {
|
|
66
|
+
owner: new Set<OmRoomPermissionAction>(OM_ROOM_PERMISSION_ACTIONS),
|
|
67
|
+
admin: new Set<OmRoomPermissionAction>(OM_ROOM_PERMISSION_ACTIONS),
|
|
68
|
+
mod: new Set<OmRoomPermissionAction>([
|
|
69
|
+
"post",
|
|
70
|
+
"edit-own",
|
|
71
|
+
"delete-any",
|
|
72
|
+
"pin",
|
|
73
|
+
"invite",
|
|
74
|
+
"remove-member",
|
|
75
|
+
"mute",
|
|
76
|
+
"kick",
|
|
77
|
+
"view-audit",
|
|
78
|
+
"mass-mention",
|
|
79
|
+
]),
|
|
80
|
+
member: new Set<OmRoomPermissionAction>(["post", "edit-own"]),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/** Space ladder base: preserves the pre-grants behavior exactly (managers
|
|
84
|
+
* hold everything, everyone may change their own nickname). */
|
|
85
|
+
export const OM_SPACE_PERMISSION_MATRIX: Record<
|
|
86
|
+
OmSpaceLadderRole,
|
|
87
|
+
ReadonlySet<OmSpacePermissionAction>
|
|
88
|
+
> = {
|
|
89
|
+
owner: new Set<OmSpacePermissionAction>(OM_SPACE_PERMISSION_ACTIONS),
|
|
90
|
+
admin: new Set<OmSpacePermissionAction>(OM_SPACE_PERMISSION_ACTIONS),
|
|
91
|
+
librarian: new Set<OmSpacePermissionAction>(["change-nickname"]),
|
|
92
|
+
member: new Set<OmSpacePermissionAction>(["change-nickname"]),
|
|
93
|
+
};
|
|
@@ -109,6 +109,7 @@ export const RoomServerMessageType = {
|
|
|
109
109
|
TOPIC_UPDATED: "TOPIC_UPDATED",
|
|
110
110
|
DOC_CHANGED: "DOC_CHANGED",
|
|
111
111
|
FRIEND_UPDATED: "FRIEND_UPDATED",
|
|
112
|
+
SPACE_ROLES_UPDATED: "SPACE_ROLES_UPDATED",
|
|
112
113
|
} as const;
|
|
113
114
|
|
|
114
115
|
export function addBounded<T>(set: Set<T>, value: T, max: number): void {
|
|
@@ -202,6 +203,9 @@ export interface RoomMeta {
|
|
|
202
203
|
viewerRole?: RoomRole | null | undefined;
|
|
203
204
|
/** Head event sequence; diff against the read cursor for unread badges. */
|
|
204
205
|
headSeq?: number | undefined;
|
|
206
|
+
/** Epoch milliseconds of the newest visible message or sealed message.
|
|
207
|
+
* Unlike room metadata timestamps, this changes only with chat content. */
|
|
208
|
+
lastMessageAt?: number | undefined;
|
|
205
209
|
/** Topics dial for this channel. Absent reads as "allowed" since the
|
|
206
210
|
* 2026-07-12 dial-default flip (post-flip relays always advertise the
|
|
207
211
|
* stored value, "off" included); explicit "off" means topics are inert. */
|
|
@@ -967,6 +971,9 @@ export interface RoomSpace {
|
|
|
967
971
|
/** Archived docs twin: keeps the Archive row reachable when only docs
|
|
968
972
|
* are archived. */
|
|
969
973
|
archivedDocCount?: number | undefined;
|
|
974
|
+
/** Space permission model revision; delegate-facing UI gates on >= 3.
|
|
975
|
+
* Absent on stores that predate space permissions. */
|
|
976
|
+
permissionsVersion?: number | undefined;
|
|
970
977
|
}
|
|
971
978
|
|
|
972
979
|
export interface RoomSpacesPayload {
|
|
@@ -1025,6 +1032,13 @@ export interface SpaceUpdatedPayload {
|
|
|
1025
1032
|
deleted?: boolean | undefined;
|
|
1026
1033
|
}
|
|
1027
1034
|
|
|
1035
|
+
/** Space roles changed (role CRUD, member role assignment, @everyone
|
|
1036
|
+
* grants): a poke, not a diff. Clients invalidate their space-roles cache
|
|
1037
|
+
* and refetch lazily. */
|
|
1038
|
+
export interface SpaceRolesUpdatedPayload {
|
|
1039
|
+
spaceId: string;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1028
1042
|
export interface RoomSpaceJoinedPayload {
|
|
1029
1043
|
requestId?: string;
|
|
1030
1044
|
space: RoomSpace;
|
|
@@ -1600,6 +1614,11 @@ export type RoomServerMessage =
|
|
|
1600
1614
|
payload: SpaceUpdatedPayload;
|
|
1601
1615
|
timestamp?: number;
|
|
1602
1616
|
}
|
|
1617
|
+
| {
|
|
1618
|
+
type: typeof RoomServerMessageType.SPACE_ROLES_UPDATED;
|
|
1619
|
+
payload: SpaceRolesUpdatedPayload;
|
|
1620
|
+
timestamp?: number;
|
|
1621
|
+
}
|
|
1603
1622
|
| {
|
|
1604
1623
|
type: typeof RoomServerMessageType.SPACE_INVITE;
|
|
1605
1624
|
payload: SpaceInvitePayload;
|
package/src/social-client.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolveRoomsWsUrl } from "./client.js";
|
|
2
2
|
import { envString, ROOM_CHAT_API_URL } from "./endpoints.js";
|
|
3
|
-
import type { RoomTopicAttention } from "./shared/rooms-protocol.js";
|
|
3
|
+
import type { RoomRole, RoomTopicAttention } from "./shared/rooms-protocol.js";
|
|
4
4
|
import type {
|
|
5
5
|
RoomBookmarkSaved,
|
|
6
6
|
RoomBookmarksResult,
|
|
@@ -678,6 +678,9 @@ export interface RoomSpaceSummary {
|
|
|
678
678
|
/** Archived docs in this server: the row must stay reachable when only
|
|
679
679
|
* docs are archived, since they have no other retrieval surface. */
|
|
680
680
|
archivedDocCount?: number | undefined;
|
|
681
|
+
/** Space permission model revision; delegate-facing UI gates on >= 3.
|
|
682
|
+
* Absent on stores that predate space permissions. */
|
|
683
|
+
permissionsVersion?: number | undefined;
|
|
681
684
|
}
|
|
682
685
|
|
|
683
686
|
export async function listRoomSpacesViaChat(config: RoomSocialConfig): Promise<RoomSpaceSummary[]> {
|
|
@@ -720,6 +723,13 @@ export interface RoomSpaceRole {
|
|
|
720
723
|
color: string | null;
|
|
721
724
|
hoist: boolean;
|
|
722
725
|
position: number;
|
|
726
|
+
/** Permission actions this role grants, mixing the channel and space
|
|
727
|
+
* vocabularies (see shared/om-permissions). Absent on stores that
|
|
728
|
+
* predate roles-v2 grants. */
|
|
729
|
+
grants?: string[];
|
|
730
|
+
/** Discord's "Allow anyone to @mention this role". Absent on stores
|
|
731
|
+
* that predate it. */
|
|
732
|
+
mentionable?: boolean;
|
|
723
733
|
}
|
|
724
734
|
|
|
725
735
|
export async function listSpaceRoles(
|
|
@@ -751,7 +761,17 @@ export async function updateSpaceRole(
|
|
|
751
761
|
config: RoomSocialConfig,
|
|
752
762
|
spaceId: string,
|
|
753
763
|
roleId: string,
|
|
754
|
-
patch: {
|
|
764
|
+
patch: {
|
|
765
|
+
name?: string;
|
|
766
|
+
color?: string | null;
|
|
767
|
+
hoist?: boolean;
|
|
768
|
+
/** Discord's "Allow anyone to @mention this role". Only send it to
|
|
769
|
+
* stores that emit the field (strict schemas 400 on unknown keys). */
|
|
770
|
+
mentionable?: boolean;
|
|
771
|
+
position?: number;
|
|
772
|
+
/** Full replace of the role's grants. */
|
|
773
|
+
grants?: string[];
|
|
774
|
+
},
|
|
755
775
|
): Promise<RoomSpaceRole> {
|
|
756
776
|
const result = await socialRequest<{ role?: RoomSpaceRole }>(
|
|
757
777
|
config,
|
|
@@ -1154,6 +1174,114 @@ export async function resolveRoomChannel(
|
|
|
1154
1174
|
};
|
|
1155
1175
|
}
|
|
1156
1176
|
|
|
1177
|
+
/** Set or clear a space channel's role guest list. Null (or empty) clears:
|
|
1178
|
+
* the channel opens back up to every space member. */
|
|
1179
|
+
export async function setRoomAllowedRoles(
|
|
1180
|
+
config: RoomSocialConfig,
|
|
1181
|
+
room: string,
|
|
1182
|
+
allowedRoleIds: string[] | null,
|
|
1183
|
+
/** Per-member access rules (union with roles). ABSENT = leave the stored
|
|
1184
|
+
* list untouched (and let a legacy conversion seed from current members);
|
|
1185
|
+
* null or [] = clear; array = set. */
|
|
1186
|
+
allowedUserIds?: string[] | null,
|
|
1187
|
+
reason?: string,
|
|
1188
|
+
): Promise<Record<string, unknown>> {
|
|
1189
|
+
const result = await socialRequest<{ room?: Record<string, unknown> }>(
|
|
1190
|
+
config,
|
|
1191
|
+
`/rooms/${encodeURIComponent(cleanRoomId(room))}/allowed-roles`,
|
|
1192
|
+
{
|
|
1193
|
+
method: "PATCH",
|
|
1194
|
+
body: {
|
|
1195
|
+
allowedRoleIds,
|
|
1196
|
+
...(allowedUserIds !== undefined ? { allowedUserIds } : {}),
|
|
1197
|
+
...(reason ? { reason } : {}),
|
|
1198
|
+
},
|
|
1199
|
+
},
|
|
1200
|
+
);
|
|
1201
|
+
return result.room ?? {};
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
export interface RoomAccessMeta {
|
|
1205
|
+
/** Active role guest list; null = open to every space member. */
|
|
1206
|
+
allowedRoleIds: string[] | null;
|
|
1207
|
+
/** Per-member access rules (union with the roles); null = none. */
|
|
1208
|
+
allowedUserIds: string[] | null;
|
|
1209
|
+
/** Tiers allowed to post; the store default is all four. */
|
|
1210
|
+
postingRoles: RoomRole[];
|
|
1211
|
+
access: string | null;
|
|
1212
|
+
spaceId: string | null;
|
|
1213
|
+
kind: string | null;
|
|
1214
|
+
/** The caller's resolved channel role (owner fallback = creator). */
|
|
1215
|
+
viewerRole: RoomRole | null;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
/** Authoritative access meta for the settings surface: the room's guest
|
|
1219
|
+
* list, posting tiers and the caller's resolved role. Rides
|
|
1220
|
+
* resolveRoomChannel (the relay's route ordering), which is also where the
|
|
1221
|
+
* store places viewerRole (existence-hiding 404 preserved as a throw). */
|
|
1222
|
+
export async function resolveRoomAccess(
|
|
1223
|
+
config: RoomSocialConfig,
|
|
1224
|
+
room: string,
|
|
1225
|
+
): Promise<RoomAccessMeta> {
|
|
1226
|
+
const meta: Record<string, unknown> = (await resolveRoomChannel(config, room)) ?? {};
|
|
1227
|
+
const allowed = Array.isArray(meta.allowedRoleIds)
|
|
1228
|
+
? (meta.allowedRoleIds as unknown[]).filter(
|
|
1229
|
+
(value): value is string => typeof value === "string",
|
|
1230
|
+
)
|
|
1231
|
+
: null;
|
|
1232
|
+
const allowedUsers = Array.isArray(meta.allowedUserIds)
|
|
1233
|
+
? (meta.allowedUserIds as unknown[]).filter(
|
|
1234
|
+
(value): value is string => typeof value === "string",
|
|
1235
|
+
)
|
|
1236
|
+
: null;
|
|
1237
|
+
const posting = Array.isArray(meta.postingRoles)
|
|
1238
|
+
? (meta.postingRoles as unknown[]).filter(
|
|
1239
|
+
(value): value is RoomRole =>
|
|
1240
|
+
value === "owner" || value === "admin" || value === "mod" || value === "member",
|
|
1241
|
+
)
|
|
1242
|
+
: [];
|
|
1243
|
+
const viewer = typeof meta.viewerRole === "string" ? meta.viewerRole : null;
|
|
1244
|
+
return {
|
|
1245
|
+
allowedRoleIds: allowed && allowed.length > 0 ? allowed : null,
|
|
1246
|
+
allowedUserIds: allowedUsers && allowedUsers.length > 0 ? allowedUsers : null,
|
|
1247
|
+
postingRoles: posting.length > 0 ? posting : ["owner", "admin", "mod", "member"],
|
|
1248
|
+
access: typeof meta.access === "string" ? meta.access : null,
|
|
1249
|
+
spaceId: typeof meta.spaceId === "string" ? meta.spaceId : null,
|
|
1250
|
+
kind: typeof meta.kind === "string" ? meta.kind : null,
|
|
1251
|
+
viewerRole:
|
|
1252
|
+
viewer === "owner" || viewer === "admin" || viewer === "mod" || viewer === "member"
|
|
1253
|
+
? viewer
|
|
1254
|
+
: null,
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
/** Owner-only. The old owner becomes a channel admin; audited server-side. */
|
|
1259
|
+
export async function transferRoomOwnership(
|
|
1260
|
+
config: RoomSocialConfig,
|
|
1261
|
+
room: string,
|
|
1262
|
+
targetUserId: string,
|
|
1263
|
+
reason?: string,
|
|
1264
|
+
): Promise<void> {
|
|
1265
|
+
await socialRequest(
|
|
1266
|
+
config,
|
|
1267
|
+
`/rooms/${encodeURIComponent(cleanRoomId(room))}/ownership/transfer`,
|
|
1268
|
+
{ method: "POST", body: { targetUserId, ...(reason ? { reason } : {}) } },
|
|
1269
|
+
);
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
/** Lift a channel ban (no WS frame exists for unban; REST is the path). */
|
|
1273
|
+
export async function unbanRoomMember(
|
|
1274
|
+
config: RoomSocialConfig,
|
|
1275
|
+
room: string,
|
|
1276
|
+
targetUserId: string,
|
|
1277
|
+
reason?: string,
|
|
1278
|
+
): Promise<void> {
|
|
1279
|
+
await socialRequest(config, `/rooms/${encodeURIComponent(cleanRoomId(room))}/mod/unban`, {
|
|
1280
|
+
method: "POST",
|
|
1281
|
+
body: { targetUserId, ...(reason ? { reason } : {}) },
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1157
1285
|
/** Archive a channel (owner/admin): drops from listings, refuses posts. */
|
|
1158
1286
|
export async function archiveRoomChannel(
|
|
1159
1287
|
config: RoomSocialConfig,
|
|
@@ -1178,6 +1306,19 @@ export async function updateRoomSpace(
|
|
|
1178
1306
|
return result.space;
|
|
1179
1307
|
}
|
|
1180
1308
|
|
|
1309
|
+
/** Owner-only. The old owner becomes a space admin; audited server-side. */
|
|
1310
|
+
export async function transferSpaceOwnership(
|
|
1311
|
+
config: RoomSocialConfig,
|
|
1312
|
+
spaceId: string,
|
|
1313
|
+
targetUserId: string,
|
|
1314
|
+
reason?: string,
|
|
1315
|
+
): Promise<void> {
|
|
1316
|
+
await socialRequest(config, `/spaces/${encodeURIComponent(spaceId)}/ownership/transfer`, {
|
|
1317
|
+
method: "POST",
|
|
1318
|
+
body: { targetUserId, ...(reason ? { reason } : {}) },
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1181
1322
|
/** Archive a server (owner only). */
|
|
1182
1323
|
export async function archiveRoomSpace(
|
|
1183
1324
|
config: RoomSocialConfig,
|
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = "0.
|
|
2
|
-
export const RUNNER_VERSION = "0.
|
|
1
|
+
export const VERSION = "0.11.0";
|
|
2
|
+
export const RUNNER_VERSION = "0.11.0";
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { type KnownProvider } from "@earendil-works/pi-ai";
|
|
2
|
-
/** Every chat surface, as a runtime list. The `SurfaceId` union derives from
|
|
3
|
-
* this literal so per-surface policy checklists (packages/cli/test/
|
|
4
|
-
* harness-budgets.test.ts) can iterate the surfaces at runtime while a new
|
|
5
|
-
* entry still breaks type-exhaustive tables at compile time. Adding a
|
|
6
|
-
* surface here requires classifying it in EVERY policy set (see AGENTS.md,
|
|
7
|
-
* "Harness invariants"). */
|
|
8
|
-
export declare const SURFACE_IDS: readonly ["cli", "telegram", "discord", "slack", "webui"];
|
|
9
|
-
export type SurfaceId = (typeof SURFACE_IDS)[number];
|
|
10
|
-
export interface AgentToolCall {
|
|
11
|
-
id: string;
|
|
12
|
-
name: string;
|
|
13
|
-
args: unknown;
|
|
14
|
-
}
|
|
15
|
-
export type AgentMessage = {
|
|
16
|
-
role: "user";
|
|
17
|
-
content: string;
|
|
18
|
-
} | {
|
|
19
|
-
role: "assistant";
|
|
20
|
-
content: string;
|
|
21
|
-
toolCalls?: AgentToolCall[];
|
|
22
|
-
} | {
|
|
23
|
-
role: "tool";
|
|
24
|
-
content: string;
|
|
25
|
-
toolCallId: string;
|
|
26
|
-
name: string;
|
|
27
|
-
};
|
|
28
|
-
export interface ToolDispatchResult {
|
|
29
|
-
isError?: true;
|
|
30
|
-
content: string;
|
|
31
|
-
/** Full-fidelity render payload for in-process SURFACES (e.g. the TUI's
|
|
32
|
-
* backtest card). Present only on the streamed copy channels consume —
|
|
33
|
-
* the agent runtime strips it before the result reaches the model's
|
|
34
|
-
* messages or the conversation store, so it never costs context. */
|
|
35
|
-
artifact?: unknown;
|
|
36
|
-
}
|
|
37
|
-
export interface LlmTool {
|
|
38
|
-
name: string;
|
|
39
|
-
description: string;
|
|
40
|
-
inputSchema: Record<string, unknown>;
|
|
41
|
-
handler: (args: unknown) => Promise<ToolDispatchResult>;
|
|
42
|
-
}
|
|
43
|
-
/** OM-level provider for any OpenAI-compatible /v1/chat/completions endpoint
|
|
44
|
-
* (Ollama, vLLM, LM Studio, llama.cpp, custom proxies). Not a pi-ai
|
|
45
|
-
* KnownProvider: the model object is constructed at turn time from the
|
|
46
|
-
* credential's stored base URL instead of pi-ai's catalog. */
|
|
47
|
-
export declare const OPENAI_COMPATIBLE_PROVIDER: "openai-compatible";
|
|
48
|
-
export type LlmProvider = KnownProvider | typeof OPENAI_COMPATIBLE_PROVIDER;
|
|
49
|
-
export declare const PRIMARY_LLM_PROVIDERS: readonly LlmProvider[];
|
|
50
|
-
export declare function isLlmProvider(value: string): value is LlmProvider;
|
|
51
|
-
export type TurnEvent = {
|
|
52
|
-
kind: "assistant.text";
|
|
53
|
-
delta: string;
|
|
54
|
-
} | {
|
|
55
|
-
kind: "tool.call";
|
|
56
|
-
id: string;
|
|
57
|
-
name: string;
|
|
58
|
-
args: unknown;
|
|
59
|
-
} | {
|
|
60
|
-
kind: "tool.result";
|
|
61
|
-
id: string;
|
|
62
|
-
name: string;
|
|
63
|
-
result: ToolDispatchResult;
|
|
64
|
-
} | {
|
|
65
|
-
kind: "turn.done";
|
|
66
|
-
usage?: Record<string, unknown>;
|
|
67
|
-
} | {
|
|
68
|
-
kind: "turn.error";
|
|
69
|
-
code?: string;
|
|
70
|
-
message: string;
|
|
71
|
-
};
|
|
72
|
-
export interface RunLlmTurnInput {
|
|
73
|
-
apiKey: string;
|
|
74
|
-
system: string;
|
|
75
|
-
messages: AgentMessage[];
|
|
76
|
-
tools: LlmTool[];
|
|
77
|
-
model: string;
|
|
78
|
-
signal?: AbortSignal | undefined;
|
|
79
|
-
}
|
|
80
|
-
export type RunLlmTurn = (input: RunLlmTurnInput) => AsyncIterable<TurnEvent>;
|
|
81
|
-
export declare function stringifyToolContent(value: unknown): string;
|
|
82
|
-
export declare function parseToolArguments(raw: unknown): unknown;
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { getProviders } from "@earendil-works/pi-ai";
|
|
2
|
-
/** Every chat surface, as a runtime list. The `SurfaceId` union derives from
|
|
3
|
-
* this literal so per-surface policy checklists (packages/cli/test/
|
|
4
|
-
* harness-budgets.test.ts) can iterate the surfaces at runtime while a new
|
|
5
|
-
* entry still breaks type-exhaustive tables at compile time. Adding a
|
|
6
|
-
* surface here requires classifying it in EVERY policy set (see AGENTS.md,
|
|
7
|
-
* "Harness invariants"). */
|
|
8
|
-
export const SURFACE_IDS = ["cli", "telegram", "discord", "slack", "webui"];
|
|
9
|
-
/** OM-level provider for any OpenAI-compatible /v1/chat/completions endpoint
|
|
10
|
-
* (Ollama, vLLM, LM Studio, llama.cpp, custom proxies). Not a pi-ai
|
|
11
|
-
* KnownProvider: the model object is constructed at turn time from the
|
|
12
|
-
* credential's stored base URL instead of pi-ai's catalog. */
|
|
13
|
-
export const OPENAI_COMPATIBLE_PROVIDER = "openai-compatible";
|
|
14
|
-
// Provider-resolution precedence (authProviderCandidates): these are tried
|
|
15
|
-
// before any store.order / generic provider. Invariant: an active profile for
|
|
16
|
-
// an earlier provider here shadows a later-configured non-primary one (groq,
|
|
17
|
-
// openai-compatible, …). The headless/env path short-circuits this, but an
|
|
18
|
-
// interactive switch via `om init` to a non-primary provider only takes effect
|
|
19
|
-
// once the shadowing primary profile is removed. Changing that is a product
|
|
20
|
-
// decision (explicit active-provider selection), not a per-provider concern.
|
|
21
|
-
export const PRIMARY_LLM_PROVIDERS = [
|
|
22
|
-
"anthropic",
|
|
23
|
-
"openai-codex",
|
|
24
|
-
"openai",
|
|
25
|
-
"google",
|
|
26
|
-
"openrouter",
|
|
27
|
-
];
|
|
28
|
-
let knownProviderSet = null;
|
|
29
|
-
export function isLlmProvider(value) {
|
|
30
|
-
if (value === OPENAI_COMPATIBLE_PROVIDER)
|
|
31
|
-
return true;
|
|
32
|
-
if (knownProviderSet === null) {
|
|
33
|
-
knownProviderSet = new Set(getProviders());
|
|
34
|
-
}
|
|
35
|
-
return knownProviderSet.has(value);
|
|
36
|
-
}
|
|
37
|
-
export function stringifyToolContent(value) {
|
|
38
|
-
if (value === undefined)
|
|
39
|
-
return "";
|
|
40
|
-
if (typeof value === "string")
|
|
41
|
-
return value;
|
|
42
|
-
// Compact on purpose: this string is LLM input, and 2-space pretty-printing
|
|
43
|
-
// inflated every structured tool result by roughly a fifth in tokens for
|
|
44
|
-
// zero model benefit.
|
|
45
|
-
return JSON.stringify(value);
|
|
46
|
-
}
|
|
47
|
-
export function parseToolArguments(raw) {
|
|
48
|
-
if (raw === undefined || raw === null)
|
|
49
|
-
return {};
|
|
50
|
-
if (typeof raw !== "string")
|
|
51
|
-
return raw;
|
|
52
|
-
const trimmed = raw.trim();
|
|
53
|
-
if (!trimmed)
|
|
54
|
-
return {};
|
|
55
|
-
return JSON.parse(trimmed);
|
|
56
|
-
}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { getProviders, type KnownProvider } from "@earendil-works/pi-ai";
|
|
2
|
-
|
|
3
|
-
/** Every chat surface, as a runtime list. The `SurfaceId` union derives from
|
|
4
|
-
* this literal so per-surface policy checklists (packages/cli/test/
|
|
5
|
-
* harness-budgets.test.ts) can iterate the surfaces at runtime while a new
|
|
6
|
-
* entry still breaks type-exhaustive tables at compile time. Adding a
|
|
7
|
-
* surface here requires classifying it in EVERY policy set (see AGENTS.md,
|
|
8
|
-
* "Harness invariants"). */
|
|
9
|
-
export const SURFACE_IDS = ["cli", "telegram", "discord", "slack", "webui"] as const;
|
|
10
|
-
|
|
11
|
-
export type SurfaceId = (typeof SURFACE_IDS)[number];
|
|
12
|
-
|
|
13
|
-
export interface AgentToolCall {
|
|
14
|
-
id: string;
|
|
15
|
-
name: string;
|
|
16
|
-
args: unknown;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export type AgentMessage =
|
|
20
|
-
| { role: "user"; content: string }
|
|
21
|
-
| { role: "assistant"; content: string; toolCalls?: AgentToolCall[] }
|
|
22
|
-
| { role: "tool"; content: string; toolCallId: string; name: string };
|
|
23
|
-
|
|
24
|
-
export interface ToolDispatchResult {
|
|
25
|
-
isError?: true;
|
|
26
|
-
content: string;
|
|
27
|
-
/** Full-fidelity render payload for in-process SURFACES (e.g. the TUI's
|
|
28
|
-
* backtest card). Present only on the streamed copy channels consume —
|
|
29
|
-
* the agent runtime strips it before the result reaches the model's
|
|
30
|
-
* messages or the conversation store, so it never costs context. */
|
|
31
|
-
artifact?: unknown;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface LlmTool {
|
|
35
|
-
name: string;
|
|
36
|
-
description: string;
|
|
37
|
-
inputSchema: Record<string, unknown>;
|
|
38
|
-
handler: (args: unknown) => Promise<ToolDispatchResult>;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** OM-level provider for any OpenAI-compatible /v1/chat/completions endpoint
|
|
42
|
-
* (Ollama, vLLM, LM Studio, llama.cpp, custom proxies). Not a pi-ai
|
|
43
|
-
* KnownProvider: the model object is constructed at turn time from the
|
|
44
|
-
* credential's stored base URL instead of pi-ai's catalog. */
|
|
45
|
-
export const OPENAI_COMPATIBLE_PROVIDER = "openai-compatible" as const;
|
|
46
|
-
|
|
47
|
-
export type LlmProvider = KnownProvider | typeof OPENAI_COMPATIBLE_PROVIDER;
|
|
48
|
-
|
|
49
|
-
// Provider-resolution precedence (authProviderCandidates): these are tried
|
|
50
|
-
// before any store.order / generic provider. Invariant: an active profile for
|
|
51
|
-
// an earlier provider here shadows a later-configured non-primary one (groq,
|
|
52
|
-
// openai-compatible, …). The headless/env path short-circuits this, but an
|
|
53
|
-
// interactive switch via `om init` to a non-primary provider only takes effect
|
|
54
|
-
// once the shadowing primary profile is removed. Changing that is a product
|
|
55
|
-
// decision (explicit active-provider selection), not a per-provider concern.
|
|
56
|
-
export const PRIMARY_LLM_PROVIDERS: readonly LlmProvider[] = [
|
|
57
|
-
"anthropic",
|
|
58
|
-
"openai-codex",
|
|
59
|
-
"openai",
|
|
60
|
-
"google",
|
|
61
|
-
"openrouter",
|
|
62
|
-
];
|
|
63
|
-
|
|
64
|
-
let knownProviderSet: Set<string> | null = null;
|
|
65
|
-
|
|
66
|
-
export function isLlmProvider(value: string): value is LlmProvider {
|
|
67
|
-
if (value === OPENAI_COMPATIBLE_PROVIDER) return true;
|
|
68
|
-
if (knownProviderSet === null) {
|
|
69
|
-
knownProviderSet = new Set(getProviders());
|
|
70
|
-
}
|
|
71
|
-
return knownProviderSet.has(value);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export type TurnEvent =
|
|
75
|
-
| { kind: "assistant.text"; delta: string }
|
|
76
|
-
| { kind: "tool.call"; id: string; name: string; args: unknown }
|
|
77
|
-
| { kind: "tool.result"; id: string; name: string; result: ToolDispatchResult }
|
|
78
|
-
| { kind: "turn.done"; usage?: Record<string, unknown> }
|
|
79
|
-
| { kind: "turn.error"; code?: string; message: string };
|
|
80
|
-
|
|
81
|
-
export interface RunLlmTurnInput {
|
|
82
|
-
apiKey: string;
|
|
83
|
-
system: string;
|
|
84
|
-
messages: AgentMessage[];
|
|
85
|
-
tools: LlmTool[];
|
|
86
|
-
model: string;
|
|
87
|
-
signal?: AbortSignal | undefined;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export type RunLlmTurn = (input: RunLlmTurnInput) => AsyncIterable<TurnEvent>;
|
|
91
|
-
|
|
92
|
-
export function stringifyToolContent(value: unknown): string {
|
|
93
|
-
if (value === undefined) return "";
|
|
94
|
-
if (typeof value === "string") return value;
|
|
95
|
-
// Compact on purpose: this string is LLM input, and 2-space pretty-printing
|
|
96
|
-
// inflated every structured tool result by roughly a fifth in tokens for
|
|
97
|
-
// zero model benefit.
|
|
98
|
-
return JSON.stringify(value);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export function parseToolArguments(raw: unknown): unknown {
|
|
102
|
-
if (raw === undefined || raw === null) return {};
|
|
103
|
-
if (typeof raw !== "string") return raw;
|
|
104
|
-
const trimmed = raw.trim();
|
|
105
|
-
if (!trimmed) return {};
|
|
106
|
-
return JSON.parse(trimmed) as unknown;
|
|
107
|
-
}
|