@openmarket/rooms-client 0.1.0 → 0.2.1
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/dist/agent/armed-mode.d.ts +22 -5
- package/dist/agent/armed-mode.js +42 -6
- package/dist/client.d.ts +26 -0
- package/dist/client.js +41 -0
- package/dist/shared/rooms-protocol.d.ts +87 -0
- package/dist/shared/rooms-protocol.js +6 -0
- package/dist/social-client.d.ts +8 -0
- package/dist/social-client.js +5 -0
- package/dist/social-types.d.ts +8 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/package.json +1 -1
- package/src/agent/armed-mode.ts +45 -4
- package/src/client.ts +63 -0
- package/src/shared/rooms-protocol.ts +96 -0
- package/src/social-client.ts +21 -0
- package/src/social-types.ts +5 -0
- package/src/version.ts +2 -2
|
@@ -89,18 +89,35 @@ export interface ArmedRoomState {
|
|
|
89
89
|
}
|
|
90
90
|
/** A fresh armed state for a newly armed room. */
|
|
91
91
|
export declare function makeArmedRoomState(roomId: string, lastSeq?: number): ArmedRoomState;
|
|
92
|
+
/**
|
|
93
|
+
* A fresh armed state that CARRIES the cost/rate counters from a prior armed
|
|
94
|
+
* session of the same room. Disarm/rearm must not zero the cooldown, hourly
|
|
95
|
+
* window, or session budget: otherwise toggling /disarm + /arm bypasses every
|
|
96
|
+
* cost guard. Error and chain counters DO reset (rearming is an explicit
|
|
97
|
+
* operator action; a fresh process still starts with nothing retained).
|
|
98
|
+
*/
|
|
99
|
+
export declare function rearmedRoomState(roomId: string, retired: ArmedRoomState | null, lastSeq?: number): ArmedRoomState;
|
|
100
|
+
/** Seqs of the user's own posts in a window of entries; the reply-trigger
|
|
101
|
+
* check tests `inReplyTo` against this set. Pure. */
|
|
102
|
+
export declare function ownSeqsOf(entries: ReadonlyArray<RoomLedgerEntry>, ownUserId: string): Set<number>;
|
|
92
103
|
/**
|
|
93
104
|
* Whether a single incoming entry is a qualifying TRIGGER: a NEW human POST in
|
|
94
|
-
* the room that @mentions the user's own handle
|
|
95
|
-
* the user's own
|
|
105
|
+
* the room that @mentions the user's own handle, OR (when `ownSeqs` is given)
|
|
106
|
+
* directly replies to one of the user's own messages. Reply semantics match a
|
|
107
|
+
* mention (replying pings the author), so armed mode treats both as addressed
|
|
108
|
+
* to the user. Agent posts, system entries, the user's own posts, and
|
|
109
|
+
* non-mentions never trigger. Pure.
|
|
96
110
|
*/
|
|
97
|
-
export declare function isTriggeringEntry(entry: RoomLedgerEntry, ownHandle: string, ownUserId: string): boolean;
|
|
111
|
+
export declare function isTriggeringEntry(entry: RoomLedgerEntry, ownHandle: string, ownUserId: string, ownSeqs?: ReadonlySet<number>): boolean;
|
|
98
112
|
/**
|
|
99
113
|
* The newest qualifying trigger among entries strictly newer than
|
|
100
114
|
* `afterSeq`, or null if none. Used by the loop to decide whether the latest
|
|
101
|
-
* batch of room messages contains a reason to act.
|
|
115
|
+
* batch of room messages contains a reason to act. Reply triggers resolve
|
|
116
|
+
* against the user's own seqs within `entries` unless a wider `ownSeqs`
|
|
117
|
+
* window is supplied (the live single-entry path passes the room cache).
|
|
118
|
+
* Pure.
|
|
102
119
|
*/
|
|
103
|
-
export declare function findTrigger(entries: ReadonlyArray<RoomLedgerEntry>, ownHandle: string, ownUserId: string, afterSeq: number): RoomLedgerEntry | null;
|
|
120
|
+
export declare function findTrigger(entries: ReadonlyArray<RoomLedgerEntry>, ownHandle: string, ownUserId: string, afterSeq: number, ownSeqs?: ReadonlySet<number>): RoomLedgerEntry | null;
|
|
104
121
|
/** Autonomous posts inside the rolling hourly window as of `now`. */
|
|
105
122
|
export declare function postsInWindow(state: ArmedRoomState, now?: number): number;
|
|
106
123
|
/** Remaining cooldown (ms) before the next autonomous post is allowed; 0 when
|
package/dist/agent/armed-mode.js
CHANGED
|
@@ -83,12 +83,41 @@ export function makeArmedRoomState(roomId, lastSeq = 0) {
|
|
|
83
83
|
lastEvaluatedSeq: lastSeq,
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* A fresh armed state that CARRIES the cost/rate counters from a prior armed
|
|
88
|
+
* session of the same room. Disarm/rearm must not zero the cooldown, hourly
|
|
89
|
+
* window, or session budget: otherwise toggling /disarm + /arm bypasses every
|
|
90
|
+
* cost guard. Error and chain counters DO reset (rearming is an explicit
|
|
91
|
+
* operator action; a fresh process still starts with nothing retained).
|
|
92
|
+
*/
|
|
93
|
+
export function rearmedRoomState(roomId, retired, lastSeq = 0) {
|
|
94
|
+
const state = makeArmedRoomState(roomId, lastSeq);
|
|
95
|
+
if (!retired || retired.roomId !== roomId)
|
|
96
|
+
return state;
|
|
97
|
+
state.postTimes = [...retired.postTimes];
|
|
98
|
+
state.postsThisSession = retired.postsThisSession;
|
|
99
|
+
state.tokensThisSession = retired.tokensThisSession;
|
|
100
|
+
return state;
|
|
101
|
+
}
|
|
102
|
+
/** Seqs of the user's own posts in a window of entries; the reply-trigger
|
|
103
|
+
* check tests `inReplyTo` against this set. Pure. */
|
|
104
|
+
export function ownSeqsOf(entries, ownUserId) {
|
|
105
|
+
const seqs = new Set();
|
|
106
|
+
for (const entry of entries) {
|
|
107
|
+
if (entry.userId === ownUserId)
|
|
108
|
+
seqs.add(entry.seq);
|
|
109
|
+
}
|
|
110
|
+
return seqs;
|
|
111
|
+
}
|
|
86
112
|
/**
|
|
87
113
|
* Whether a single incoming entry is a qualifying TRIGGER: a NEW human POST in
|
|
88
|
-
* the room that @mentions the user's own handle
|
|
89
|
-
* the user's own
|
|
114
|
+
* the room that @mentions the user's own handle, OR (when `ownSeqs` is given)
|
|
115
|
+
* directly replies to one of the user's own messages. Reply semantics match a
|
|
116
|
+
* mention (replying pings the author), so armed mode treats both as addressed
|
|
117
|
+
* to the user. Agent posts, system entries, the user's own posts, and
|
|
118
|
+
* non-mentions never trigger. Pure.
|
|
90
119
|
*/
|
|
91
|
-
export function isTriggeringEntry(entry, ownHandle, ownUserId) {
|
|
120
|
+
export function isTriggeringEntry(entry, ownHandle, ownUserId, ownSeqs) {
|
|
92
121
|
if (entry.type !== RoomLedgerEntryType.POST)
|
|
93
122
|
return false;
|
|
94
123
|
if (entry.isAgent)
|
|
@@ -102,6 +131,10 @@ export function isTriggeringEntry(entry, ownHandle, ownUserId) {
|
|
|
102
131
|
return false;
|
|
103
132
|
if (me.length > 0 && fromHandle === me)
|
|
104
133
|
return false;
|
|
134
|
+
// A direct reply to one of our messages is addressed to us. The set only
|
|
135
|
+
// spans the cached window, so replies to long-scrolled-out posts stay inert.
|
|
136
|
+
if (entry.inReplyTo !== undefined && ownSeqs?.has(entry.inReplyTo))
|
|
137
|
+
return true;
|
|
105
138
|
// Must EXPLICITLY @mention the user's own handle. A bare-substring match (the
|
|
106
139
|
// old mentionsAgent) fired on ordinary words for short handles like "may" /
|
|
107
140
|
// "btc"; armed posting has no human review, so it triggers ONLY on an `@handle`
|
|
@@ -111,14 +144,17 @@ export function isTriggeringEntry(entry, ownHandle, ownUserId) {
|
|
|
111
144
|
/**
|
|
112
145
|
* The newest qualifying trigger among entries strictly newer than
|
|
113
146
|
* `afterSeq`, or null if none. Used by the loop to decide whether the latest
|
|
114
|
-
* batch of room messages contains a reason to act.
|
|
147
|
+
* batch of room messages contains a reason to act. Reply triggers resolve
|
|
148
|
+
* against the user's own seqs within `entries` unless a wider `ownSeqs`
|
|
149
|
+
* window is supplied (the live single-entry path passes the room cache).
|
|
150
|
+
* Pure.
|
|
115
151
|
*/
|
|
116
|
-
export function findTrigger(entries, ownHandle, ownUserId, afterSeq) {
|
|
152
|
+
export function findTrigger(entries, ownHandle, ownUserId, afterSeq, ownSeqs = ownSeqsOf(entries, ownUserId)) {
|
|
117
153
|
let found = null;
|
|
118
154
|
for (const entry of entries) {
|
|
119
155
|
if (entry.seq <= afterSeq)
|
|
120
156
|
continue;
|
|
121
|
-
if (isTriggeringEntry(entry, ownHandle, ownUserId))
|
|
157
|
+
if (isTriggeringEntry(entry, ownHandle, ownUserId, ownSeqs))
|
|
122
158
|
found = entry;
|
|
123
159
|
}
|
|
124
160
|
return found;
|
package/dist/client.d.ts
CHANGED
|
@@ -208,6 +208,10 @@ export interface RoomDmSendRequest {
|
|
|
208
208
|
conversationId?: string;
|
|
209
209
|
content: string;
|
|
210
210
|
clientMsgId?: string;
|
|
211
|
+
/** A private DM-attachment key (dm-attachments/…) or a legacy public image
|
|
212
|
+
* URL. Stored on the message's imageUrl; the client signs keys on render.
|
|
213
|
+
* Additive — omitted for text-only sends. */
|
|
214
|
+
imageUrl?: string;
|
|
211
215
|
}
|
|
212
216
|
export interface RoomThreadRequest {
|
|
213
217
|
room: string;
|
|
@@ -251,6 +255,28 @@ export declare function requestRoomFriends(client: RoomWsClient): Promise<RoomFr
|
|
|
251
255
|
export declare function openRoomDmOnClient(client: RoomWsClient, request: RoomDmOpenRequest, timeoutMs?: number): Promise<RoomDmOpenedPayload>;
|
|
252
256
|
export declare function requestRoomDmHistoryOnClient(client: RoomWsClient, request: RoomDmHistoryRequest, timeoutMs?: number): Promise<RoomDmHistoryResultPayload>;
|
|
253
257
|
export declare function sendRoomDmOnClient(client: RoomWsClient, request: RoomDmSendRequest, timeoutMs?: number): Promise<RoomDmMessagePayload>;
|
|
258
|
+
/** Edit one of my DM messages. Fire-and-forget over the WS; the relay fans out
|
|
259
|
+
* DM_MESSAGE_EDITED to both participants. */
|
|
260
|
+
export declare function editRoomDmOnClient(client: RoomWsClient, request: {
|
|
261
|
+
conversationId?: string;
|
|
262
|
+
username?: string;
|
|
263
|
+
messageId: string;
|
|
264
|
+
content: string;
|
|
265
|
+
}): void;
|
|
266
|
+
/** Soft-delete one of my DM messages. The relay fans out DM_MESSAGE_DELETED. */
|
|
267
|
+
export declare function deleteRoomDmOnClient(client: RoomWsClient, request: {
|
|
268
|
+
conversationId?: string;
|
|
269
|
+
username?: string;
|
|
270
|
+
messageId: string;
|
|
271
|
+
}): void;
|
|
272
|
+
/** Toggle a reaction on a DM message. The relay fans out DM_REACTION_UPDATED. */
|
|
273
|
+
export declare function reactRoomDmOnClient(client: RoomWsClient, request: {
|
|
274
|
+
conversationId?: string;
|
|
275
|
+
username?: string;
|
|
276
|
+
messageId: string;
|
|
277
|
+
emoji: string;
|
|
278
|
+
op: "add" | "remove";
|
|
279
|
+
}): void;
|
|
254
280
|
export declare function peekRoom(config: RoomClientConfig, options: RoomPeekOptions): Promise<RoomBackfillPayload>;
|
|
255
281
|
export declare function sayRoom(config: RoomClientConfig, options: RoomSayOptions): Promise<RoomSayResult>;
|
|
256
282
|
export declare function joinRoom(config: RoomClientConfig, options: RoomJoinOptions): Promise<RoomJoinedSession>;
|
package/dist/client.js
CHANGED
|
@@ -465,6 +465,7 @@ export async function sendRoomDmOnClient(client, request, timeoutMs) {
|
|
|
465
465
|
clientMsgId,
|
|
466
466
|
...(request.username ? { username: request.username } : {}),
|
|
467
467
|
...(request.conversationId ? { conversationId: request.conversationId } : {}),
|
|
468
|
+
...(request.imageUrl ? { imageUrl: request.imageUrl } : {}),
|
|
468
469
|
},
|
|
469
470
|
timestamp: Date.now(),
|
|
470
471
|
});
|
|
@@ -473,6 +474,46 @@ export async function sendRoomDmOnClient(client, request, timeoutMs) {
|
|
|
473
474
|
msg.payload.message.clientMsgId === clientMsgId, timeoutMs, requestId);
|
|
474
475
|
return sent.payload;
|
|
475
476
|
}
|
|
477
|
+
/** Edit one of my DM messages. Fire-and-forget over the WS; the relay fans out
|
|
478
|
+
* DM_MESSAGE_EDITED to both participants. */
|
|
479
|
+
export function editRoomDmOnClient(client, request) {
|
|
480
|
+
client.send({
|
|
481
|
+
type: RoomClientMessageType.DM_EDIT,
|
|
482
|
+
payload: {
|
|
483
|
+
messageId: request.messageId,
|
|
484
|
+
content: request.content,
|
|
485
|
+
...(request.conversationId ? { conversationId: request.conversationId } : {}),
|
|
486
|
+
...(request.username ? { username: request.username } : {}),
|
|
487
|
+
},
|
|
488
|
+
timestamp: Date.now(),
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
/** Soft-delete one of my DM messages. The relay fans out DM_MESSAGE_DELETED. */
|
|
492
|
+
export function deleteRoomDmOnClient(client, request) {
|
|
493
|
+
client.send({
|
|
494
|
+
type: RoomClientMessageType.DM_DELETE,
|
|
495
|
+
payload: {
|
|
496
|
+
messageId: request.messageId,
|
|
497
|
+
...(request.conversationId ? { conversationId: request.conversationId } : {}),
|
|
498
|
+
...(request.username ? { username: request.username } : {}),
|
|
499
|
+
},
|
|
500
|
+
timestamp: Date.now(),
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
/** Toggle a reaction on a DM message. The relay fans out DM_REACTION_UPDATED. */
|
|
504
|
+
export function reactRoomDmOnClient(client, request) {
|
|
505
|
+
client.send({
|
|
506
|
+
type: RoomClientMessageType.DM_REACT,
|
|
507
|
+
payload: {
|
|
508
|
+
messageId: request.messageId,
|
|
509
|
+
emoji: request.emoji,
|
|
510
|
+
op: request.op,
|
|
511
|
+
...(request.conversationId ? { conversationId: request.conversationId } : {}),
|
|
512
|
+
...(request.username ? { username: request.username } : {}),
|
|
513
|
+
},
|
|
514
|
+
timestamp: Date.now(),
|
|
515
|
+
});
|
|
516
|
+
}
|
|
476
517
|
export async function peekRoom(config, options) {
|
|
477
518
|
if (!config.webSocketCtor) {
|
|
478
519
|
try {
|
|
@@ -38,6 +38,9 @@ export declare const RoomClientMessageType: {
|
|
|
38
38
|
readonly DM_HISTORY: "DM_HISTORY";
|
|
39
39
|
readonly DM_SEND: "DM_SEND";
|
|
40
40
|
readonly DM_READ: "DM_READ";
|
|
41
|
+
readonly DM_EDIT: "DM_EDIT";
|
|
42
|
+
readonly DM_DELETE: "DM_DELETE";
|
|
43
|
+
readonly DM_REACT: "DM_REACT";
|
|
41
44
|
readonly TOPIC_CREATE: "TOPIC_CREATE";
|
|
42
45
|
readonly TOPIC_RENAME: "TOPIC_RENAME";
|
|
43
46
|
readonly TOPIC_MOVE: "TOPIC_MOVE";
|
|
@@ -87,6 +90,9 @@ export declare const RoomServerMessageType: {
|
|
|
87
90
|
readonly DM_HISTORY: "DM_HISTORY";
|
|
88
91
|
readonly DM_MESSAGE: "DM_MESSAGE";
|
|
89
92
|
readonly DM_INBOX_UPDATED: "DM_INBOX_UPDATED";
|
|
93
|
+
readonly DM_MESSAGE_EDITED: "DM_MESSAGE_EDITED";
|
|
94
|
+
readonly DM_MESSAGE_DELETED: "DM_MESSAGE_DELETED";
|
|
95
|
+
readonly DM_REACTION_UPDATED: "DM_REACTION_UPDATED";
|
|
90
96
|
readonly TOPIC: "TOPIC";
|
|
91
97
|
readonly TOPICS: "TOPICS";
|
|
92
98
|
readonly TOPIC_UPDATED: "TOPIC_UPDATED";
|
|
@@ -438,6 +444,11 @@ export interface RoomDmMessage {
|
|
|
438
444
|
/** Sealed DM: content is '' and the opaque envelope rides here. */
|
|
439
445
|
secret?: RoomLedgerSecretPayload | null | undefined;
|
|
440
446
|
metadata?: Record<string, unknown> | undefined;
|
|
447
|
+
/** Legacy public chat-image URL (renders as-is, no signing). */
|
|
448
|
+
imageUrl?: string | null | undefined;
|
|
449
|
+
/** Private, signed, scanned DM attachments (dm-attachments/ keyspace).
|
|
450
|
+
* Additive: old messages carry none and render unchanged. */
|
|
451
|
+
attachments?: RoomAttachment[] | undefined;
|
|
441
452
|
}
|
|
442
453
|
export interface RoomDmOpenPayload {
|
|
443
454
|
requestId?: string;
|
|
@@ -459,6 +470,58 @@ export interface RoomDmSendPayload {
|
|
|
459
470
|
conversationId?: string;
|
|
460
471
|
content: string;
|
|
461
472
|
clientMsgId?: string;
|
|
473
|
+
/** DM-attachment key or legacy public image URL; stored on the message's
|
|
474
|
+
* imageUrl. Additive — text-only sends omit it. */
|
|
475
|
+
imageUrl?: string;
|
|
476
|
+
}
|
|
477
|
+
/** Client → server: edit one of my DM messages (sealed messages are rejected). */
|
|
478
|
+
export interface RoomDmEditPayload {
|
|
479
|
+
requestId?: string;
|
|
480
|
+
conversationId?: string;
|
|
481
|
+
username?: string;
|
|
482
|
+
messageId: string;
|
|
483
|
+
content: string;
|
|
484
|
+
}
|
|
485
|
+
/** Client → server: soft-delete one of my DM messages (tombstone in place). */
|
|
486
|
+
export interface RoomDmDeletePayload {
|
|
487
|
+
requestId?: string;
|
|
488
|
+
conversationId?: string;
|
|
489
|
+
username?: string;
|
|
490
|
+
messageId: string;
|
|
491
|
+
}
|
|
492
|
+
/** Server → both participants: a DM message was edited. */
|
|
493
|
+
export interface RoomDmMessageEditedPayload {
|
|
494
|
+
conversationId: string;
|
|
495
|
+
messageId: string;
|
|
496
|
+
content: string;
|
|
497
|
+
editedAt: string | number;
|
|
498
|
+
participant: RoomDmParticipant;
|
|
499
|
+
}
|
|
500
|
+
/** Server → both participants: a DM message was soft-deleted (tombstone). */
|
|
501
|
+
export interface RoomDmMessageDeletedPayload {
|
|
502
|
+
conversationId: string;
|
|
503
|
+
messageId: string;
|
|
504
|
+
deletedAt: string | number;
|
|
505
|
+
participant: RoomDmParticipant;
|
|
506
|
+
}
|
|
507
|
+
/** Client → server: toggle a reaction on a DM message. */
|
|
508
|
+
export interface RoomDmReactPayload {
|
|
509
|
+
requestId?: string;
|
|
510
|
+
conversationId?: string;
|
|
511
|
+
username?: string;
|
|
512
|
+
messageId: string;
|
|
513
|
+
emoji: string;
|
|
514
|
+
op: "add" | "remove";
|
|
515
|
+
}
|
|
516
|
+
/** Server → both participants: a DM message's aggregated reactions changed. */
|
|
517
|
+
export interface RoomDmReactionUpdatedPayload {
|
|
518
|
+
conversationId: string;
|
|
519
|
+
messageId: string;
|
|
520
|
+
reactions: Array<{
|
|
521
|
+
emoji: string;
|
|
522
|
+
users: string[];
|
|
523
|
+
}>;
|
|
524
|
+
participant: RoomDmParticipant;
|
|
462
525
|
}
|
|
463
526
|
export interface RoomDmReadPayload {
|
|
464
527
|
requestId?: string;
|
|
@@ -975,6 +1038,18 @@ export type RoomClientMessage = {
|
|
|
975
1038
|
type: typeof RoomClientMessageType.DM_READ;
|
|
976
1039
|
payload: RoomDmReadPayload;
|
|
977
1040
|
timestamp?: number;
|
|
1041
|
+
} | {
|
|
1042
|
+
type: typeof RoomClientMessageType.DM_EDIT;
|
|
1043
|
+
payload: RoomDmEditPayload;
|
|
1044
|
+
timestamp?: number;
|
|
1045
|
+
} | {
|
|
1046
|
+
type: typeof RoomClientMessageType.DM_DELETE;
|
|
1047
|
+
payload: RoomDmDeletePayload;
|
|
1048
|
+
timestamp?: number;
|
|
1049
|
+
} | {
|
|
1050
|
+
type: typeof RoomClientMessageType.DM_REACT;
|
|
1051
|
+
payload: RoomDmReactPayload;
|
|
1052
|
+
timestamp?: number;
|
|
978
1053
|
} | {
|
|
979
1054
|
type: typeof RoomClientMessageType.TOPIC_CREATE;
|
|
980
1055
|
payload: RoomTopicCreatePayload;
|
|
@@ -1165,6 +1240,18 @@ export type RoomServerMessage = {
|
|
|
1165
1240
|
type: typeof RoomServerMessageType.DM_INBOX_UPDATED;
|
|
1166
1241
|
payload: RoomDmInboxUpdatedPayload;
|
|
1167
1242
|
timestamp: number;
|
|
1243
|
+
} | {
|
|
1244
|
+
type: typeof RoomServerMessageType.DM_MESSAGE_EDITED;
|
|
1245
|
+
payload: RoomDmMessageEditedPayload;
|
|
1246
|
+
timestamp: number;
|
|
1247
|
+
} | {
|
|
1248
|
+
type: typeof RoomServerMessageType.DM_MESSAGE_DELETED;
|
|
1249
|
+
payload: RoomDmMessageDeletedPayload;
|
|
1250
|
+
timestamp: number;
|
|
1251
|
+
} | {
|
|
1252
|
+
type: typeof RoomServerMessageType.DM_REACTION_UPDATED;
|
|
1253
|
+
payload: RoomDmReactionUpdatedPayload;
|
|
1254
|
+
timestamp: number;
|
|
1168
1255
|
} | {
|
|
1169
1256
|
type: typeof RoomServerMessageType.TOPIC;
|
|
1170
1257
|
payload: RoomTopicResultPayload;
|
|
@@ -38,6 +38,9 @@ export const RoomClientMessageType = {
|
|
|
38
38
|
DM_HISTORY: "DM_HISTORY",
|
|
39
39
|
DM_SEND: "DM_SEND",
|
|
40
40
|
DM_READ: "DM_READ",
|
|
41
|
+
DM_EDIT: "DM_EDIT",
|
|
42
|
+
DM_DELETE: "DM_DELETE",
|
|
43
|
+
DM_REACT: "DM_REACT",
|
|
41
44
|
TOPIC_CREATE: "TOPIC_CREATE",
|
|
42
45
|
TOPIC_RENAME: "TOPIC_RENAME",
|
|
43
46
|
TOPIC_MOVE: "TOPIC_MOVE",
|
|
@@ -86,6 +89,9 @@ export const RoomServerMessageType = {
|
|
|
86
89
|
DM_HISTORY: "DM_HISTORY",
|
|
87
90
|
DM_MESSAGE: "DM_MESSAGE",
|
|
88
91
|
DM_INBOX_UPDATED: "DM_INBOX_UPDATED",
|
|
92
|
+
DM_MESSAGE_EDITED: "DM_MESSAGE_EDITED",
|
|
93
|
+
DM_MESSAGE_DELETED: "DM_MESSAGE_DELETED",
|
|
94
|
+
DM_REACTION_UPDATED: "DM_REACTION_UPDATED",
|
|
89
95
|
TOPIC: "TOPIC",
|
|
90
96
|
TOPICS: "TOPICS",
|
|
91
97
|
TOPIC_UPDATED: "TOPIC_UPDATED",
|
package/dist/social-client.d.ts
CHANGED
|
@@ -175,6 +175,12 @@ export interface RoomSpaceMember {
|
|
|
175
175
|
/** Named identity roles (RoomSpaceRole.roleId). */
|
|
176
176
|
roleIds?: string[];
|
|
177
177
|
joinedAt?: string;
|
|
178
|
+
/** The member's handle/username, resolved server-side from their room
|
|
179
|
+
* memberships. Absent when the store couldn't resolve a real name (never
|
|
180
|
+
* the raw userId). */
|
|
181
|
+
handle?: string;
|
|
182
|
+
/** Per-space display name; null or absent means the account username. */
|
|
183
|
+
nickname?: string | null;
|
|
178
184
|
}
|
|
179
185
|
export declare function getUserPrefs(config: RoomSocialConfig): Promise<Record<string, unknown>>;
|
|
180
186
|
/** Whole-blob replace (16KB cap server-side; last writer wins). */
|
|
@@ -321,6 +327,8 @@ export declare function removeSpaceMember(config: RoomSocialConfig, spaceId: str
|
|
|
321
327
|
removed?: boolean;
|
|
322
328
|
}>;
|
|
323
329
|
export declare function changeSpaceMemberRole(config: RoomSocialConfig, spaceId: string, targetUserId: string, role: "admin" | "member"): Promise<RoomSpaceMember>;
|
|
330
|
+
/** Set (string) or clear (null) a member's per-space nickname; the store validates and enforces uniqueness. */
|
|
331
|
+
export declare function setSpaceMemberNickname(config: RoomSocialConfig, spaceId: string, targetUserId: string, nickname: string | null): Promise<RoomSpaceMember>;
|
|
324
332
|
export declare function createRoomSpace(config: RoomSocialConfig, input: {
|
|
325
333
|
name: string;
|
|
326
334
|
access?: "open" | "invite";
|
package/dist/social-client.js
CHANGED
|
@@ -501,6 +501,11 @@ export async function changeSpaceMemberRole(config, spaceId, targetUserId, role)
|
|
|
501
501
|
const result = await socialRequest(config, `/spaces/${encodeURIComponent(spaceId)}/members/${encodeURIComponent(targetUserId)}/role`, { method: "PATCH", body: { role } });
|
|
502
502
|
return result.member;
|
|
503
503
|
}
|
|
504
|
+
/** Set (string) or clear (null) a member's per-space nickname; the store validates and enforces uniqueness. */
|
|
505
|
+
export async function setSpaceMemberNickname(config, spaceId, targetUserId, nickname) {
|
|
506
|
+
const result = await socialRequest(config, `/spaces/${encodeURIComponent(spaceId)}/members/${encodeURIComponent(targetUserId)}/nickname`, { method: "PATCH", body: { nickname } });
|
|
507
|
+
return result.member;
|
|
508
|
+
}
|
|
504
509
|
export async function createRoomSpace(config, input) {
|
|
505
510
|
const result = await socialRequest(config, "/spaces", {
|
|
506
511
|
method: "POST",
|
package/dist/social-types.d.ts
CHANGED
|
@@ -112,6 +112,14 @@ export interface RoomDmMessage {
|
|
|
112
112
|
/** Sealed DM: content is '' and the opaque envelope rides here. */
|
|
113
113
|
secret?: RoomLedgerSecretPayload | null;
|
|
114
114
|
metadata?: Record<string, unknown>;
|
|
115
|
+
/** DM image: a private dm-attachments/ key (signed on render) or a legacy
|
|
116
|
+
* public URL. Additive — old messages carry none. */
|
|
117
|
+
imageUrl?: string | null;
|
|
118
|
+
/** Aggregated reactions (per-emoji userId lists). Additive. */
|
|
119
|
+
reactions?: Array<{
|
|
120
|
+
emoji: string;
|
|
121
|
+
users: string[];
|
|
122
|
+
}>;
|
|
115
123
|
}
|
|
116
124
|
export interface RoomDmHistory {
|
|
117
125
|
conversationId: string;
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.5.
|
|
2
|
-
export declare const RUNNER_VERSION = "0.5.
|
|
1
|
+
export declare const VERSION = "0.5.1";
|
|
2
|
+
export declare const RUNNER_VERSION = "0.5.1";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = "0.5.
|
|
2
|
-
export const RUNNER_VERSION = "0.5.
|
|
1
|
+
export const VERSION = "0.5.1";
|
|
2
|
+
export const RUNNER_VERSION = "0.5.1";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmarket/rooms-client",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
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",
|
package/src/agent/armed-mode.ts
CHANGED
|
@@ -126,15 +126,49 @@ export function makeArmedRoomState(roomId: string, lastSeq = 0): ArmedRoomState
|
|
|
126
126
|
};
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
/**
|
|
130
|
+
* A fresh armed state that CARRIES the cost/rate counters from a prior armed
|
|
131
|
+
* session of the same room. Disarm/rearm must not zero the cooldown, hourly
|
|
132
|
+
* window, or session budget: otherwise toggling /disarm + /arm bypasses every
|
|
133
|
+
* cost guard. Error and chain counters DO reset (rearming is an explicit
|
|
134
|
+
* operator action; a fresh process still starts with nothing retained).
|
|
135
|
+
*/
|
|
136
|
+
export function rearmedRoomState(
|
|
137
|
+
roomId: string,
|
|
138
|
+
retired: ArmedRoomState | null,
|
|
139
|
+
lastSeq = 0,
|
|
140
|
+
): ArmedRoomState {
|
|
141
|
+
const state = makeArmedRoomState(roomId, lastSeq);
|
|
142
|
+
if (!retired || retired.roomId !== roomId) return state;
|
|
143
|
+
state.postTimes = [...retired.postTimes];
|
|
144
|
+
state.postsThisSession = retired.postsThisSession;
|
|
145
|
+
state.tokensThisSession = retired.tokensThisSession;
|
|
146
|
+
return state;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Seqs of the user's own posts in a window of entries; the reply-trigger
|
|
150
|
+
* check tests `inReplyTo` against this set. Pure. */
|
|
151
|
+
export function ownSeqsOf(entries: ReadonlyArray<RoomLedgerEntry>, ownUserId: string): Set<number> {
|
|
152
|
+
const seqs = new Set<number>();
|
|
153
|
+
for (const entry of entries) {
|
|
154
|
+
if (entry.userId === ownUserId) seqs.add(entry.seq);
|
|
155
|
+
}
|
|
156
|
+
return seqs;
|
|
157
|
+
}
|
|
158
|
+
|
|
129
159
|
/**
|
|
130
160
|
* Whether a single incoming entry is a qualifying TRIGGER: a NEW human POST in
|
|
131
|
-
* the room that @mentions the user's own handle
|
|
132
|
-
* the user's own
|
|
161
|
+
* the room that @mentions the user's own handle, OR (when `ownSeqs` is given)
|
|
162
|
+
* directly replies to one of the user's own messages. Reply semantics match a
|
|
163
|
+
* mention (replying pings the author), so armed mode treats both as addressed
|
|
164
|
+
* to the user. Agent posts, system entries, the user's own posts, and
|
|
165
|
+
* non-mentions never trigger. Pure.
|
|
133
166
|
*/
|
|
134
167
|
export function isTriggeringEntry(
|
|
135
168
|
entry: RoomLedgerEntry,
|
|
136
169
|
ownHandle: string,
|
|
137
170
|
ownUserId: string,
|
|
171
|
+
ownSeqs?: ReadonlySet<number>,
|
|
138
172
|
): boolean {
|
|
139
173
|
if (entry.type !== RoomLedgerEntryType.POST) return false;
|
|
140
174
|
if (entry.isAgent) return false;
|
|
@@ -144,6 +178,9 @@ export function isTriggeringEntry(
|
|
|
144
178
|
const me = normalizeHandle(ownHandle).toLowerCase();
|
|
145
179
|
if (entry.userId === ownUserId) return false;
|
|
146
180
|
if (me.length > 0 && fromHandle === me) return false;
|
|
181
|
+
// A direct reply to one of our messages is addressed to us. The set only
|
|
182
|
+
// spans the cached window, so replies to long-scrolled-out posts stay inert.
|
|
183
|
+
if (entry.inReplyTo !== undefined && ownSeqs?.has(entry.inReplyTo)) return true;
|
|
147
184
|
// Must EXPLICITLY @mention the user's own handle. A bare-substring match (the
|
|
148
185
|
// old mentionsAgent) fired on ordinary words for short handles like "may" /
|
|
149
186
|
// "btc"; armed posting has no human review, so it triggers ONLY on an `@handle`
|
|
@@ -154,18 +191,22 @@ export function isTriggeringEntry(
|
|
|
154
191
|
/**
|
|
155
192
|
* The newest qualifying trigger among entries strictly newer than
|
|
156
193
|
* `afterSeq`, or null if none. Used by the loop to decide whether the latest
|
|
157
|
-
* batch of room messages contains a reason to act.
|
|
194
|
+
* batch of room messages contains a reason to act. Reply triggers resolve
|
|
195
|
+
* against the user's own seqs within `entries` unless a wider `ownSeqs`
|
|
196
|
+
* window is supplied (the live single-entry path passes the room cache).
|
|
197
|
+
* Pure.
|
|
158
198
|
*/
|
|
159
199
|
export function findTrigger(
|
|
160
200
|
entries: ReadonlyArray<RoomLedgerEntry>,
|
|
161
201
|
ownHandle: string,
|
|
162
202
|
ownUserId: string,
|
|
163
203
|
afterSeq: number,
|
|
204
|
+
ownSeqs: ReadonlySet<number> = ownSeqsOf(entries, ownUserId),
|
|
164
205
|
): RoomLedgerEntry | null {
|
|
165
206
|
let found: RoomLedgerEntry | null = null;
|
|
166
207
|
for (const entry of entries) {
|
|
167
208
|
if (entry.seq <= afterSeq) continue;
|
|
168
|
-
if (isTriggeringEntry(entry, ownHandle, ownUserId)) found = entry;
|
|
209
|
+
if (isTriggeringEntry(entry, ownHandle, ownUserId, ownSeqs)) found = entry;
|
|
169
210
|
}
|
|
170
211
|
return found;
|
|
171
212
|
}
|
package/src/client.ts
CHANGED
|
@@ -284,6 +284,10 @@ export interface RoomDmSendRequest {
|
|
|
284
284
|
conversationId?: string;
|
|
285
285
|
content: string;
|
|
286
286
|
clientMsgId?: string;
|
|
287
|
+
/** A private DM-attachment key (dm-attachments/…) or a legacy public image
|
|
288
|
+
* URL. Stored on the message's imageUrl; the client signs keys on render.
|
|
289
|
+
* Additive — omitted for text-only sends. */
|
|
290
|
+
imageUrl?: string;
|
|
287
291
|
}
|
|
288
292
|
|
|
289
293
|
export interface RoomThreadRequest {
|
|
@@ -931,6 +935,7 @@ export async function sendRoomDmOnClient(
|
|
|
931
935
|
clientMsgId,
|
|
932
936
|
...(request.username ? { username: request.username } : {}),
|
|
933
937
|
...(request.conversationId ? { conversationId: request.conversationId } : {}),
|
|
938
|
+
...(request.imageUrl ? { imageUrl: request.imageUrl } : {}),
|
|
934
939
|
},
|
|
935
940
|
timestamp: Date.now(),
|
|
936
941
|
});
|
|
@@ -946,6 +951,64 @@ export async function sendRoomDmOnClient(
|
|
|
946
951
|
return sent.payload;
|
|
947
952
|
}
|
|
948
953
|
|
|
954
|
+
/** Edit one of my DM messages. Fire-and-forget over the WS; the relay fans out
|
|
955
|
+
* DM_MESSAGE_EDITED to both participants. */
|
|
956
|
+
export function editRoomDmOnClient(
|
|
957
|
+
client: RoomWsClient,
|
|
958
|
+
request: { conversationId?: string; username?: string; messageId: string; content: string },
|
|
959
|
+
): void {
|
|
960
|
+
client.send({
|
|
961
|
+
type: RoomClientMessageType.DM_EDIT,
|
|
962
|
+
payload: {
|
|
963
|
+
messageId: request.messageId,
|
|
964
|
+
content: request.content,
|
|
965
|
+
...(request.conversationId ? { conversationId: request.conversationId } : {}),
|
|
966
|
+
...(request.username ? { username: request.username } : {}),
|
|
967
|
+
},
|
|
968
|
+
timestamp: Date.now(),
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/** Soft-delete one of my DM messages. The relay fans out DM_MESSAGE_DELETED. */
|
|
973
|
+
export function deleteRoomDmOnClient(
|
|
974
|
+
client: RoomWsClient,
|
|
975
|
+
request: { conversationId?: string; username?: string; messageId: string },
|
|
976
|
+
): void {
|
|
977
|
+
client.send({
|
|
978
|
+
type: RoomClientMessageType.DM_DELETE,
|
|
979
|
+
payload: {
|
|
980
|
+
messageId: request.messageId,
|
|
981
|
+
...(request.conversationId ? { conversationId: request.conversationId } : {}),
|
|
982
|
+
...(request.username ? { username: request.username } : {}),
|
|
983
|
+
},
|
|
984
|
+
timestamp: Date.now(),
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/** Toggle a reaction on a DM message. The relay fans out DM_REACTION_UPDATED. */
|
|
989
|
+
export function reactRoomDmOnClient(
|
|
990
|
+
client: RoomWsClient,
|
|
991
|
+
request: {
|
|
992
|
+
conversationId?: string;
|
|
993
|
+
username?: string;
|
|
994
|
+
messageId: string;
|
|
995
|
+
emoji: string;
|
|
996
|
+
op: "add" | "remove";
|
|
997
|
+
},
|
|
998
|
+
): void {
|
|
999
|
+
client.send({
|
|
1000
|
+
type: RoomClientMessageType.DM_REACT,
|
|
1001
|
+
payload: {
|
|
1002
|
+
messageId: request.messageId,
|
|
1003
|
+
emoji: request.emoji,
|
|
1004
|
+
op: request.op,
|
|
1005
|
+
...(request.conversationId ? { conversationId: request.conversationId } : {}),
|
|
1006
|
+
...(request.username ? { username: request.username } : {}),
|
|
1007
|
+
},
|
|
1008
|
+
timestamp: Date.now(),
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
|
|
949
1012
|
export async function peekRoom(
|
|
950
1013
|
config: RoomClientConfig,
|
|
951
1014
|
options: RoomPeekOptions,
|
|
@@ -38,6 +38,9 @@ export const RoomClientMessageType = {
|
|
|
38
38
|
DM_HISTORY: "DM_HISTORY",
|
|
39
39
|
DM_SEND: "DM_SEND",
|
|
40
40
|
DM_READ: "DM_READ",
|
|
41
|
+
DM_EDIT: "DM_EDIT",
|
|
42
|
+
DM_DELETE: "DM_DELETE",
|
|
43
|
+
DM_REACT: "DM_REACT",
|
|
41
44
|
TOPIC_CREATE: "TOPIC_CREATE",
|
|
42
45
|
TOPIC_RENAME: "TOPIC_RENAME",
|
|
43
46
|
TOPIC_MOVE: "TOPIC_MOVE",
|
|
@@ -90,6 +93,9 @@ export const RoomServerMessageType = {
|
|
|
90
93
|
DM_HISTORY: "DM_HISTORY",
|
|
91
94
|
DM_MESSAGE: "DM_MESSAGE",
|
|
92
95
|
DM_INBOX_UPDATED: "DM_INBOX_UPDATED",
|
|
96
|
+
DM_MESSAGE_EDITED: "DM_MESSAGE_EDITED",
|
|
97
|
+
DM_MESSAGE_DELETED: "DM_MESSAGE_DELETED",
|
|
98
|
+
DM_REACTION_UPDATED: "DM_REACTION_UPDATED",
|
|
93
99
|
TOPIC: "TOPIC",
|
|
94
100
|
TOPICS: "TOPICS",
|
|
95
101
|
TOPIC_UPDATED: "TOPIC_UPDATED",
|
|
@@ -527,6 +533,11 @@ export interface RoomDmMessage {
|
|
|
527
533
|
/** Sealed DM: content is '' and the opaque envelope rides here. */
|
|
528
534
|
secret?: RoomLedgerSecretPayload | null | undefined;
|
|
529
535
|
metadata?: Record<string, unknown> | undefined;
|
|
536
|
+
/** Legacy public chat-image URL (renders as-is, no signing). */
|
|
537
|
+
imageUrl?: string | null | undefined;
|
|
538
|
+
/** Private, signed, scanned DM attachments (dm-attachments/ keyspace).
|
|
539
|
+
* Additive: old messages carry none and render unchanged. */
|
|
540
|
+
attachments?: RoomAttachment[] | undefined;
|
|
530
541
|
}
|
|
531
542
|
|
|
532
543
|
export interface RoomDmOpenPayload {
|
|
@@ -551,6 +562,61 @@ export interface RoomDmSendPayload {
|
|
|
551
562
|
conversationId?: string;
|
|
552
563
|
content: string;
|
|
553
564
|
clientMsgId?: string;
|
|
565
|
+
/** DM-attachment key or legacy public image URL; stored on the message's
|
|
566
|
+
* imageUrl. Additive — text-only sends omit it. */
|
|
567
|
+
imageUrl?: string;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/** Client → server: edit one of my DM messages (sealed messages are rejected). */
|
|
571
|
+
export interface RoomDmEditPayload {
|
|
572
|
+
requestId?: string;
|
|
573
|
+
conversationId?: string;
|
|
574
|
+
username?: string;
|
|
575
|
+
messageId: string;
|
|
576
|
+
content: string;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/** Client → server: soft-delete one of my DM messages (tombstone in place). */
|
|
580
|
+
export interface RoomDmDeletePayload {
|
|
581
|
+
requestId?: string;
|
|
582
|
+
conversationId?: string;
|
|
583
|
+
username?: string;
|
|
584
|
+
messageId: string;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/** Server → both participants: a DM message was edited. */
|
|
588
|
+
export interface RoomDmMessageEditedPayload {
|
|
589
|
+
conversationId: string;
|
|
590
|
+
messageId: string;
|
|
591
|
+
content: string;
|
|
592
|
+
editedAt: string | number;
|
|
593
|
+
participant: RoomDmParticipant;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/** Server → both participants: a DM message was soft-deleted (tombstone). */
|
|
597
|
+
export interface RoomDmMessageDeletedPayload {
|
|
598
|
+
conversationId: string;
|
|
599
|
+
messageId: string;
|
|
600
|
+
deletedAt: string | number;
|
|
601
|
+
participant: RoomDmParticipant;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/** Client → server: toggle a reaction on a DM message. */
|
|
605
|
+
export interface RoomDmReactPayload {
|
|
606
|
+
requestId?: string;
|
|
607
|
+
conversationId?: string;
|
|
608
|
+
username?: string;
|
|
609
|
+
messageId: string;
|
|
610
|
+
emoji: string;
|
|
611
|
+
op: "add" | "remove";
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/** Server → both participants: a DM message's aggregated reactions changed. */
|
|
615
|
+
export interface RoomDmReactionUpdatedPayload {
|
|
616
|
+
conversationId: string;
|
|
617
|
+
messageId: string;
|
|
618
|
+
reactions: Array<{ emoji: string; users: string[] }>;
|
|
619
|
+
participant: RoomDmParticipant;
|
|
554
620
|
}
|
|
555
621
|
|
|
556
622
|
export interface RoomDmReadPayload {
|
|
@@ -1085,6 +1151,21 @@ export type RoomClientMessage =
|
|
|
1085
1151
|
payload: RoomDmReadPayload;
|
|
1086
1152
|
timestamp?: number;
|
|
1087
1153
|
}
|
|
1154
|
+
| {
|
|
1155
|
+
type: typeof RoomClientMessageType.DM_EDIT;
|
|
1156
|
+
payload: RoomDmEditPayload;
|
|
1157
|
+
timestamp?: number;
|
|
1158
|
+
}
|
|
1159
|
+
| {
|
|
1160
|
+
type: typeof RoomClientMessageType.DM_DELETE;
|
|
1161
|
+
payload: RoomDmDeletePayload;
|
|
1162
|
+
timestamp?: number;
|
|
1163
|
+
}
|
|
1164
|
+
| {
|
|
1165
|
+
type: typeof RoomClientMessageType.DM_REACT;
|
|
1166
|
+
payload: RoomDmReactPayload;
|
|
1167
|
+
timestamp?: number;
|
|
1168
|
+
}
|
|
1088
1169
|
| {
|
|
1089
1170
|
type: typeof RoomClientMessageType.TOPIC_CREATE;
|
|
1090
1171
|
payload: RoomTopicCreatePayload;
|
|
@@ -1317,6 +1398,21 @@ export type RoomServerMessage =
|
|
|
1317
1398
|
payload: RoomDmInboxUpdatedPayload;
|
|
1318
1399
|
timestamp: number;
|
|
1319
1400
|
}
|
|
1401
|
+
| {
|
|
1402
|
+
type: typeof RoomServerMessageType.DM_MESSAGE_EDITED;
|
|
1403
|
+
payload: RoomDmMessageEditedPayload;
|
|
1404
|
+
timestamp: number;
|
|
1405
|
+
}
|
|
1406
|
+
| {
|
|
1407
|
+
type: typeof RoomServerMessageType.DM_MESSAGE_DELETED;
|
|
1408
|
+
payload: RoomDmMessageDeletedPayload;
|
|
1409
|
+
timestamp: number;
|
|
1410
|
+
}
|
|
1411
|
+
| {
|
|
1412
|
+
type: typeof RoomServerMessageType.DM_REACTION_UPDATED;
|
|
1413
|
+
payload: RoomDmReactionUpdatedPayload;
|
|
1414
|
+
timestamp: number;
|
|
1415
|
+
}
|
|
1320
1416
|
| {
|
|
1321
1417
|
type: typeof RoomServerMessageType.TOPIC;
|
|
1322
1418
|
payload: RoomTopicResultPayload;
|
package/src/social-client.ts
CHANGED
|
@@ -658,6 +658,12 @@ export interface RoomSpaceMember {
|
|
|
658
658
|
/** Named identity roles (RoomSpaceRole.roleId). */
|
|
659
659
|
roleIds?: string[];
|
|
660
660
|
joinedAt?: string;
|
|
661
|
+
/** The member's handle/username, resolved server-side from their room
|
|
662
|
+
* memberships. Absent when the store couldn't resolve a real name (never
|
|
663
|
+
* the raw userId). */
|
|
664
|
+
handle?: string;
|
|
665
|
+
/** Per-space display name; null or absent means the account username. */
|
|
666
|
+
nickname?: string | null;
|
|
661
667
|
}
|
|
662
668
|
|
|
663
669
|
export async function getUserPrefs(config: RoomSocialConfig): Promise<Record<string, unknown>> {
|
|
@@ -1048,6 +1054,21 @@ export async function changeSpaceMemberRole(
|
|
|
1048
1054
|
return result.member;
|
|
1049
1055
|
}
|
|
1050
1056
|
|
|
1057
|
+
/** Set (string) or clear (null) a member's per-space nickname; the store validates and enforces uniqueness. */
|
|
1058
|
+
export async function setSpaceMemberNickname(
|
|
1059
|
+
config: RoomSocialConfig,
|
|
1060
|
+
spaceId: string,
|
|
1061
|
+
targetUserId: string,
|
|
1062
|
+
nickname: string | null,
|
|
1063
|
+
): Promise<RoomSpaceMember> {
|
|
1064
|
+
const result = await socialRequest<{ member: RoomSpaceMember }>(
|
|
1065
|
+
config,
|
|
1066
|
+
`/spaces/${encodeURIComponent(spaceId)}/members/${encodeURIComponent(targetUserId)}/nickname`,
|
|
1067
|
+
{ method: "PATCH", body: { nickname } },
|
|
1068
|
+
);
|
|
1069
|
+
return result.member;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1051
1072
|
export async function createRoomSpace(
|
|
1052
1073
|
config: RoomSocialConfig,
|
|
1053
1074
|
input: { name: string; access?: "open" | "invite" },
|
package/src/social-types.ts
CHANGED
|
@@ -126,6 +126,11 @@ export interface RoomDmMessage {
|
|
|
126
126
|
/** Sealed DM: content is '' and the opaque envelope rides here. */
|
|
127
127
|
secret?: RoomLedgerSecretPayload | null;
|
|
128
128
|
metadata?: Record<string, unknown>;
|
|
129
|
+
/** DM image: a private dm-attachments/ key (signed on render) or a legacy
|
|
130
|
+
* public URL. Additive — old messages carry none. */
|
|
131
|
+
imageUrl?: string | null;
|
|
132
|
+
/** Aggregated reactions (per-emoji userId lists). Additive. */
|
|
133
|
+
reactions?: Array<{ emoji: string; users: string[] }>;
|
|
129
134
|
}
|
|
130
135
|
|
|
131
136
|
export interface RoomDmHistory {
|
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = "0.5.
|
|
2
|
-
export const RUNNER_VERSION = "0.5.
|
|
1
|
+
export const VERSION = "0.5.1";
|
|
2
|
+
export const RUNNER_VERSION = "0.5.1";
|