@moltzap/client 2026.503.3 → 2026.504.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/dist/auth.d.ts +10 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +8 -5
- package/dist/auth.js.map +1 -1
- package/dist/channel-core.d.ts +1 -4
- package/dist/channel-core.d.ts.map +1 -1
- package/dist/channel-core.js +0 -9
- package/dist/channel-core.js.map +1 -1
- package/dist/channel-core.test.js +90 -71
- package/dist/channel-core.test.js.map +1 -1
- package/dist/cli/commands/agents.d.ts.map +1 -1
- package/dist/cli/commands/agents.js +5 -3
- package/dist/cli/commands/agents.js.map +1 -1
- package/dist/cli/commands/apps.d.ts +9 -27
- package/dist/cli/commands/apps.d.ts.map +1 -1
- package/dist/cli/commands/apps.js +36 -57
- package/dist/cli/commands/apps.js.map +1 -1
- package/dist/cli/commands/apps.test.js +63 -75
- package/dist/cli/commands/apps.test.js.map +1 -1
- package/dist/cli/commands/contacts.d.ts +1 -5
- package/dist/cli/commands/contacts.d.ts.map +1 -1
- package/dist/cli/commands/contacts.js +14 -27
- package/dist/cli/commands/contacts.js.map +1 -1
- package/dist/cli/commands/conversations-v2.test.js +34 -29
- package/dist/cli/commands/conversations-v2.test.js.map +1 -1
- package/dist/cli/commands/conversations.d.ts +7 -3
- package/dist/cli/commands/conversations.d.ts.map +1 -1
- package/dist/cli/commands/conversations.js +63 -48
- package/dist/cli/commands/conversations.js.map +1 -1
- package/dist/cli/commands/messages.d.ts +7 -3
- package/dist/cli/commands/messages.d.ts.map +1 -1
- package/dist/cli/commands/messages.js +13 -15
- package/dist/cli/commands/messages.js.map +1 -1
- package/dist/cli/commands/messages.test.js +30 -31
- package/dist/cli/commands/messages.test.js.map +1 -1
- package/dist/cli/commands/ping.d.ts +9 -0
- package/dist/cli/commands/ping.d.ts.map +1 -1
- package/dist/cli/commands/ping.js +17 -4
- package/dist/cli/commands/ping.js.map +1 -1
- package/dist/cli/commands/presence.d.ts.map +1 -1
- package/dist/cli/commands/presence.js +6 -4
- package/dist/cli/commands/presence.js.map +1 -1
- package/dist/cli/commands/register.d.ts.map +1 -1
- package/dist/cli/commands/register.js +2 -1
- package/dist/cli/commands/register.js.map +1 -1
- package/dist/cli/commands/send.d.ts.map +1 -1
- package/dist/cli/commands/send.js +22 -11
- package/dist/cli/commands/send.js.map +1 -1
- package/dist/cli/commands/send.test.js +12 -9
- package/dist/cli/commands/send.test.js.map +1 -1
- package/dist/cli/commands/status.js +2 -2
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/test-transport.d.ts +10 -0
- package/dist/cli/commands/test-transport.d.ts.map +1 -0
- package/dist/cli/commands/test-transport.js +29 -0
- package/dist/cli/commands/test-transport.js.map +1 -0
- package/dist/cli/commands/whoami.d.ts.map +1 -1
- package/dist/cli/commands/whoami.js +5 -1
- package/dist/cli/commands/whoami.js.map +1 -1
- package/dist/cli/config.d.ts +42 -6
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +91 -27
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/http-client.d.ts +11 -1
- package/dist/cli/http-client.d.ts.map +1 -1
- package/dist/cli/http-client.js +13 -4
- package/dist/cli/http-client.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/profile.d.ts +3 -4
- package/dist/cli/profile.d.ts.map +1 -1
- package/dist/cli/profile.js +42 -29
- package/dist/cli/profile.js.map +1 -1
- package/dist/cli/runtime.d.ts.map +1 -1
- package/dist/cli/runtime.js +9 -4
- package/dist/cli/runtime.js.map +1 -1
- package/dist/cli/socket-client.d.ts +29 -4
- package/dist/cli/socket-client.d.ts.map +1 -1
- package/dist/cli/socket-client.js +52 -21
- package/dist/cli/socket-client.js.map +1 -1
- package/dist/cli/transport.d.ts +6 -9
- package/dist/cli/transport.d.ts.map +1 -1
- package/dist/cli/transport.js +47 -37
- package/dist/cli/transport.js.map +1 -1
- package/dist/cli/transport.test.js +19 -16
- package/dist/cli/transport.test.js.map +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/__tests__/app-callback-partition-key.test.d.ts +2 -0
- package/dist/internal/__tests__/app-callback-partition-key.test.d.ts.map +1 -0
- package/dist/internal/__tests__/app-callback-partition-key.test.js +33 -0
- package/dist/internal/__tests__/app-callback-partition-key.test.js.map +1 -0
- package/dist/internal/__tests__/app-callback-partition-worker.test.d.ts +2 -0
- package/dist/internal/__tests__/app-callback-partition-worker.test.d.ts.map +1 -0
- package/dist/internal/__tests__/{s2c-partition-worker.test.js → app-callback-partition-worker.test.js} +6 -8
- package/dist/internal/__tests__/app-callback-partition-worker.test.js.map +1 -0
- package/dist/internal/__tests__/app-callback-partitioned-dispatcher-real-ws.test.d.ts +2 -0
- package/dist/internal/__tests__/app-callback-partitioned-dispatcher-real-ws.test.d.ts.map +1 -0
- package/dist/internal/__tests__/{s2c-partitioned-dispatcher-real-ws.test.js → app-callback-partitioned-dispatcher-real-ws.test.js} +69 -93
- package/dist/internal/__tests__/app-callback-partitioned-dispatcher-real-ws.test.js.map +1 -0
- package/dist/internal/__tests__/app-callback-partitioned-dispatcher.test.d.ts +2 -0
- package/dist/internal/__tests__/app-callback-partitioned-dispatcher.test.d.ts.map +1 -0
- package/dist/internal/__tests__/{s2c-partitioned-dispatcher.test.js → app-callback-partitioned-dispatcher.test.js} +36 -69
- package/dist/internal/__tests__/app-callback-partitioned-dispatcher.test.js.map +1 -0
- package/dist/internal/__tests__/app-callback-test-requests.d.ts +58 -0
- package/dist/internal/__tests__/app-callback-test-requests.d.ts.map +1 -0
- package/dist/internal/__tests__/app-callback-test-requests.js +124 -0
- package/dist/internal/__tests__/app-callback-test-requests.js.map +1 -0
- package/dist/internal/{s2c-dispatcher-errors.d.ts → app-callback-dispatcher-errors.d.ts} +5 -21
- package/dist/internal/app-callback-dispatcher-errors.d.ts.map +1 -0
- package/dist/internal/{s2c-dispatcher-errors.js → app-callback-dispatcher-errors.js} +3 -13
- package/dist/internal/app-callback-dispatcher-errors.js.map +1 -0
- package/dist/internal/app-callback-partition-key.d.ts +68 -0
- package/dist/internal/app-callback-partition-key.d.ts.map +1 -0
- package/dist/internal/app-callback-partition-key.js +90 -0
- package/dist/internal/app-callback-partition-key.js.map +1 -0
- package/dist/internal/{s2c-partition-worker.d.ts → app-callback-partition-worker.d.ts} +5 -5
- package/dist/internal/app-callback-partition-worker.d.ts.map +1 -0
- package/dist/internal/{s2c-partition-worker.js → app-callback-partition-worker.js} +5 -5
- package/dist/internal/app-callback-partition-worker.js.map +1 -0
- package/dist/internal/{s2c-partitioned-dispatcher.d.ts → app-callback-partitioned-dispatcher.d.ts} +9 -10
- package/dist/internal/app-callback-partitioned-dispatcher.d.ts.map +1 -0
- package/dist/internal/{s2c-partitioned-dispatcher.js → app-callback-partitioned-dispatcher.js} +13 -21
- package/dist/internal/app-callback-partitioned-dispatcher.js.map +1 -0
- package/dist/runtime/close-info.d.ts +15 -16
- package/dist/runtime/close-info.d.ts.map +1 -1
- package/dist/runtime/close-info.js +12 -8
- package/dist/runtime/close-info.js.map +1 -1
- package/dist/runtime/errors.d.ts +2 -28
- package/dist/runtime/errors.d.ts.map +1 -1
- package/dist/runtime/errors.js +0 -9
- package/dist/runtime/errors.js.map +1 -1
- package/dist/runtime/errors.test.js +4 -3
- package/dist/runtime/errors.test.js.map +1 -1
- package/dist/runtime/frame.d.ts +17 -18
- package/dist/runtime/frame.d.ts.map +1 -1
- package/dist/runtime/frame.js +86 -31
- package/dist/runtime/frame.js.map +1 -1
- package/dist/runtime/frame.test.js +15 -11
- package/dist/runtime/frame.test.js.map +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/local-service-commands.d.ts +7 -0
- package/dist/runtime/local-service-commands.d.ts.map +1 -0
- package/dist/runtime/local-service-commands.js +6 -0
- package/dist/runtime/local-service-commands.js.map +1 -0
- package/dist/runtime/runtime.test.js +3 -3
- package/dist/runtime/runtime.test.js.map +1 -1
- package/dist/runtime/subscribers.d.ts +37 -33
- package/dist/runtime/subscribers.d.ts.map +1 -1
- package/dist/runtime/subscribers.js +22 -21
- package/dist/runtime/subscribers.js.map +1 -1
- package/dist/runtime/subscribers.test.js +50 -31
- package/dist/runtime/subscribers.test.js.map +1 -1
- package/dist/service.d.ts +24 -39
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +190 -171
- package/dist/service.js.map +1 -1
- package/dist/service.test.js +117 -175
- package/dist/service.test.js.map +1 -1
- package/dist/test/index.d.ts +9 -1
- package/dist/test/index.d.ts.map +1 -1
- package/dist/test/index.js +14 -3
- package/dist/test/index.js.map +1 -1
- package/dist/test-utils/channel-service-fixture.d.ts +1 -2
- package/dist/test-utils/channel-service-fixture.d.ts.map +1 -1
- package/dist/test-utils/channel-service-fixture.js +28 -20
- package/dist/test-utils/channel-service-fixture.js.map +1 -1
- package/dist/test-utils/conformance-adapter.d.ts +2 -2
- package/dist/test-utils/conformance-adapter.d.ts.map +1 -1
- package/dist/test-utils/conformance-adapter.js +78 -54
- package/dist/test-utils/conformance-adapter.js.map +1 -1
- package/dist/test-utils/fake-service.d.ts +14 -39
- package/dist/test-utils/fake-service.d.ts.map +1 -1
- package/dist/test-utils/fake-service.js +33 -39
- package/dist/test-utils/fake-service.js.map +1 -1
- package/dist/test-utils/ids.d.ts +9 -0
- package/dist/test-utils/ids.d.ts.map +1 -0
- package/dist/test-utils/ids.js +52 -0
- package/dist/test-utils/ids.js.map +1 -0
- package/dist/test-utils/index.d.ts +10 -2
- package/dist/test-utils/index.d.ts.map +1 -1
- package/dist/test-utils/index.js +25 -9
- package/dist/test-utils/index.js.map +1 -1
- package/dist/ws-client.d.ts +59 -64
- package/dist/ws-client.d.ts.map +1 -1
- package/dist/ws-client.js +228 -175
- package/dist/ws-client.js.map +1 -1
- package/dist/ws-client.test.js +190 -176
- package/dist/ws-client.test.js.map +1 -1
- package/package.json +3 -3
- package/dist/cli/commands/permissions.d.ts +0 -66
- package/dist/cli/commands/permissions.d.ts.map +0 -1
- package/dist/cli/commands/permissions.js +0 -101
- package/dist/cli/commands/permissions.js.map +0 -1
- package/dist/cli/commands/permissions.test.d.ts +0 -2
- package/dist/cli/commands/permissions.test.d.ts.map +0 -1
- package/dist/cli/commands/permissions.test.js +0 -126
- package/dist/cli/commands/permissions.test.js.map +0 -1
- package/dist/internal/__tests__/s2c-partition-key.test.d.ts +0 -2
- package/dist/internal/__tests__/s2c-partition-key.test.d.ts.map +0 -1
- package/dist/internal/__tests__/s2c-partition-key.test.js +0 -230
- package/dist/internal/__tests__/s2c-partition-key.test.js.map +0 -1
- package/dist/internal/__tests__/s2c-partition-worker.test.d.ts +0 -2
- package/dist/internal/__tests__/s2c-partition-worker.test.d.ts.map +0 -1
- package/dist/internal/__tests__/s2c-partition-worker.test.js.map +0 -1
- package/dist/internal/__tests__/s2c-partitioned-dispatcher-real-ws.test.d.ts +0 -2
- package/dist/internal/__tests__/s2c-partitioned-dispatcher-real-ws.test.d.ts.map +0 -1
- package/dist/internal/__tests__/s2c-partitioned-dispatcher-real-ws.test.js.map +0 -1
- package/dist/internal/__tests__/s2c-partitioned-dispatcher.test.d.ts +0 -2
- package/dist/internal/__tests__/s2c-partitioned-dispatcher.test.d.ts.map +0 -1
- package/dist/internal/__tests__/s2c-partitioned-dispatcher.test.js.map +0 -1
- package/dist/internal/s2c-dispatcher-errors.d.ts.map +0 -1
- package/dist/internal/s2c-dispatcher-errors.js.map +0 -1
- package/dist/internal/s2c-partition-key.d.ts +0 -71
- package/dist/internal/s2c-partition-key.d.ts.map +0 -1
- package/dist/internal/s2c-partition-key.js +0 -140
- package/dist/internal/s2c-partition-key.js.map +0 -1
- package/dist/internal/s2c-partition-worker.d.ts.map +0 -1
- package/dist/internal/s2c-partition-worker.js.map +0 -1
- package/dist/internal/s2c-partitioned-dispatcher.d.ts.map +0 -1
- package/dist/internal/s2c-partitioned-dispatcher.js.map +0 -1
package/dist/service.js
CHANGED
|
@@ -2,14 +2,26 @@ import * as net from "node:net";
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import * as os from "node:os";
|
|
5
|
-
import { AgentsLookup, AgentsLookupByName, AppsAuthorizeDispatch, ConversationsCreate, ConversationsGet, ErrorCodes,
|
|
6
|
-
import { Effect, HashMap, Match, Option, Ref } from "effect";
|
|
5
|
+
import { AgentsLookup, AgentsLookupByName, agentId as toAgentId, AppsAuthorizeDispatch, bindNotificationHandler, ConversationsCreate, ConversationsGet, conversationId as toConversationId, defineEffectNotificationHandlers, defineNotificationGroup, MessageReceivedNotificationDefinition, ConversationCreatedNotificationDefinition, ConversationUpdatedNotificationDefinition, ConversationArchivedNotificationDefinition, ConversationUnarchivedNotificationDefinition, ErrorCodes, isDecodedNotificationInGroup, messageId as toMessageId, MessagesList, MessagesSend, NotConnectedError, rpcMethods, RpcServerError, RpcTimeoutError, } from "@moltzap/protocol";
|
|
6
|
+
import { Config, ConfigProvider, Data, Effect, HashMap, Match, Option, Ref, } from "effect";
|
|
7
7
|
import { MoltZapWsClient, } from "./ws-client.js";
|
|
8
|
-
import { AgentNotFoundError
|
|
8
|
+
import { AgentNotFoundError } from "./runtime/errors.js";
|
|
9
9
|
import { getOr, snapshot } from "./runtime/refs.js";
|
|
10
|
+
import { LocalServiceCommands } from "./runtime/local-service-commands.js";
|
|
10
11
|
const isPlainRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12
|
+
const CROSS_CONTEXT_TEXT_LIMIT = 120;
|
|
13
|
+
const DEFAULT_HISTORY_LIMIT = 10;
|
|
14
|
+
const DEFAULT_MAX_CONTEXT_CONVERSATIONS = 5;
|
|
15
|
+
const DEFAULT_MAX_MESSAGES_PER_CONVERSATION = 3;
|
|
16
|
+
const HISTORY_LOOKUP_CONCURRENCY = 2;
|
|
17
|
+
const MILLISECONDS_PER_MINUTE = 60_000;
|
|
18
|
+
const SOCKET_FILE_MODE = 0o600;
|
|
19
|
+
class ServiceInputError extends Data.TaggedError("ServiceInputError") {
|
|
20
|
+
}
|
|
21
|
+
const ClientEventLogDir = Config.option(Config.string("MOLTZAP_CLIENT_EVENT_LOG_DIR"));
|
|
22
|
+
const getClientEventLogDir = () => Option.getOrUndefined(Effect.runSync(ClientEventLogDir.pipe(Effect.withConfigProvider(ConfigProvider.fromEnv()))));
|
|
11
23
|
function appendClientEventTrace(record) {
|
|
12
|
-
const dir =
|
|
24
|
+
const dir = getClientEventLogDir();
|
|
13
25
|
if (!dir)
|
|
14
26
|
return;
|
|
15
27
|
try {
|
|
@@ -41,7 +53,7 @@ export function formatCrossConversationBlock(entries, opts) {
|
|
|
41
53
|
return null;
|
|
42
54
|
const lines = entries.map((e) => {
|
|
43
55
|
const safeSender = sanitizeForSystemReminder(e.senderName);
|
|
44
|
-
const safeText = sanitizeForSystemReminder(e.text.slice(0,
|
|
56
|
+
const safeText = sanitizeForSystemReminder(e.text.slice(0, CROSS_CONTEXT_TEXT_LIMIT));
|
|
45
57
|
return `@${safeSender} (${e.minutesAgo}m ago): (${e.count} new) "${safeText}"`;
|
|
46
58
|
});
|
|
47
59
|
return [
|
|
@@ -63,6 +75,14 @@ const renderPart = Match.type().pipe(Match.discriminatorsExhaustive("type")({
|
|
|
63
75
|
* display windows — `conversations get` shows at most a few hundred.
|
|
64
76
|
*/
|
|
65
77
|
const MAX_MESSAGES_PER_CONV = 1000;
|
|
78
|
+
const serviceNotificationDefinitions = [
|
|
79
|
+
MessageReceivedNotificationDefinition,
|
|
80
|
+
ConversationCreatedNotificationDefinition,
|
|
81
|
+
ConversationUpdatedNotificationDefinition,
|
|
82
|
+
ConversationArchivedNotificationDefinition,
|
|
83
|
+
ConversationUnarchivedNotificationDefinition,
|
|
84
|
+
];
|
|
85
|
+
const serviceNotificationGroup = defineNotificationGroup("clientService", serviceNotificationDefinitions);
|
|
66
86
|
/**
|
|
67
87
|
* Invoke every handler with `arg`, isolating throws so one bad handler
|
|
68
88
|
* doesn't abort the remaining fanout. Logs via the optional client logger.
|
|
@@ -73,7 +93,7 @@ function fanout(handlers, arg, logger) {
|
|
|
73
93
|
h(arg);
|
|
74
94
|
}
|
|
75
95
|
catch (err) {
|
|
76
|
-
logger?.error("
|
|
96
|
+
logger?.error("notification handler threw", err);
|
|
77
97
|
}
|
|
78
98
|
}
|
|
79
99
|
}
|
|
@@ -99,13 +119,15 @@ export class MoltZapService {
|
|
|
99
119
|
lastNotifiedRef = Effect.runSync(Ref.make(HashMap.empty()));
|
|
100
120
|
lastReadRef = Effect.runSync(Ref.make(HashMap.empty()));
|
|
101
121
|
archivedConversationIds = new Set();
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
122
|
+
handlers = {
|
|
123
|
+
message: [],
|
|
124
|
+
rawNotification: [],
|
|
125
|
+
disconnect: [],
|
|
126
|
+
reconnect: [],
|
|
127
|
+
conversationArchived: [],
|
|
128
|
+
conversationUnarchived: [],
|
|
129
|
+
};
|
|
130
|
+
notificationHandlers = this.createNotificationHandlers();
|
|
109
131
|
_ownAgentId;
|
|
110
132
|
constructor(opts) {
|
|
111
133
|
this.opts = opts;
|
|
@@ -126,24 +148,24 @@ export class MoltZapService {
|
|
|
126
148
|
// Spec #222 OQ-6: arg required. The body doesn't branch on
|
|
127
149
|
// close metadata today; signature kept explicit so a future
|
|
128
150
|
// disconnect-handler chain can plumb code/reason through.
|
|
129
|
-
onDisconnect: (
|
|
151
|
+
onDisconnect: (close) => {
|
|
152
|
+
void close;
|
|
130
153
|
this._connected = false;
|
|
131
|
-
fanout(this.
|
|
154
|
+
fanout(this.handlers.disconnect, undefined, this.opts.logger);
|
|
132
155
|
},
|
|
133
156
|
onReconnect: (helloOk) => {
|
|
134
157
|
this._connected = true;
|
|
135
|
-
|
|
136
|
-
this.
|
|
137
|
-
fanout(this.reconnectHandlers, hello, this.opts.logger);
|
|
158
|
+
this.populateFromHello(helloOk);
|
|
159
|
+
fanout(this.handlers.reconnect, helloOk, this.opts.logger);
|
|
138
160
|
},
|
|
139
161
|
});
|
|
140
|
-
// Spec #222 OQ-4
|
|
162
|
+
// Spec #222 OQ-4: per-notification callback is replaced by subscriptions.
|
|
141
163
|
// Replacement: register a `{}` filter subscription before
|
|
142
|
-
// `connect()` so every inbound
|
|
143
|
-
// `
|
|
164
|
+
// `connect()` so every inbound notification still fans out to
|
|
165
|
+
// `handleNotification`. Pre-connect registration is supported by the
|
|
144
166
|
// registry.
|
|
145
|
-
yield* this.client.subscribe({}, (
|
|
146
|
-
const helloOk =
|
|
167
|
+
yield* this.client.subscribe({}, (notification) => Effect.sync(() => this.handleNotification(notification)));
|
|
168
|
+
const helloOk = yield* this.client.connect();
|
|
147
169
|
this._connected = true;
|
|
148
170
|
this._ownAgentId = helloOk.agentId;
|
|
149
171
|
this.populateFromHello(helloOk);
|
|
@@ -232,7 +254,7 @@ export class MoltZapService {
|
|
|
232
254
|
// impersonates this agent to the server, so other local users on the
|
|
233
255
|
// host must not be able to connect.
|
|
234
256
|
try {
|
|
235
|
-
fs.chmodSync(sockPath,
|
|
257
|
+
fs.chmodSync(sockPath, SOCKET_FILE_MODE);
|
|
236
258
|
}
|
|
237
259
|
catch (err) {
|
|
238
260
|
this.opts.logger?.warn("chmod 0600 on socket failed", err);
|
|
@@ -319,60 +341,66 @@ export class MoltZapService {
|
|
|
319
341
|
handleSocketRequestEffect(method, params) {
|
|
320
342
|
return Effect.suspend(() => {
|
|
321
343
|
switch (method) {
|
|
322
|
-
case
|
|
344
|
+
case LocalServiceCommands.Ping:
|
|
323
345
|
return Effect.succeed({ ok: true, agentId: this.ownAgentId });
|
|
324
|
-
case
|
|
346
|
+
case LocalServiceCommands.Status:
|
|
325
347
|
return Effect.succeed({
|
|
326
348
|
agentId: this.ownAgentId,
|
|
327
349
|
connected: this._connected,
|
|
328
350
|
conversations: this.getConversations().length,
|
|
329
351
|
});
|
|
330
|
-
case
|
|
352
|
+
case LocalServiceCommands.History:
|
|
331
353
|
return this.handleHistoryRequest(params);
|
|
332
|
-
default:
|
|
333
|
-
|
|
334
|
-
|
|
354
|
+
default: {
|
|
355
|
+
const definition = rpcMethods.find((def) => def.name === method);
|
|
356
|
+
if (definition === undefined || !definition.validateParams(params)) {
|
|
357
|
+
return Effect.fail(new ServiceInputError({
|
|
358
|
+
message: `unknown or invalid RPC method: ${method}`,
|
|
359
|
+
}));
|
|
360
|
+
}
|
|
361
|
+
return this.sendRpc(definition, params);
|
|
362
|
+
}
|
|
335
363
|
}
|
|
336
364
|
});
|
|
337
365
|
}
|
|
338
366
|
handleHistoryRequest(params) {
|
|
339
367
|
return Effect.gen(this, function* () {
|
|
340
368
|
if (typeof params.conversationId !== "string" || !params.conversationId) {
|
|
341
|
-
return yield* Effect.fail(new
|
|
369
|
+
return yield* Effect.fail(new ServiceInputError({
|
|
370
|
+
message: "conversationId is required and must be a string",
|
|
371
|
+
}));
|
|
342
372
|
}
|
|
343
373
|
if (params.limit !== undefined && typeof params.limit !== "number") {
|
|
344
|
-
return yield* Effect.fail(new
|
|
374
|
+
return yield* Effect.fail(new ServiceInputError({ message: "limit must be a number" }));
|
|
345
375
|
}
|
|
346
376
|
const convId = params.conversationId;
|
|
347
|
-
const limit = params.limit ??
|
|
377
|
+
const limit = params.limit ?? DEFAULT_HISTORY_LIMIT;
|
|
348
378
|
const sessionKey = params.sessionKey;
|
|
349
|
-
const result =
|
|
350
|
-
conversationId: convId,
|
|
379
|
+
const result = yield* this.sendRpc(MessagesList, {
|
|
380
|
+
conversationId: toConversationId(convId),
|
|
351
381
|
limit,
|
|
352
|
-
})
|
|
382
|
+
});
|
|
353
383
|
// Resolve agent names (batch) + fetch conversation metadata (concurrent)
|
|
354
384
|
const knownNames = yield* Ref.get(this.agentNamesRef);
|
|
355
385
|
const unknownAgentIds = [
|
|
356
386
|
...new Set(result.messages.map((m) => m.senderId)),
|
|
357
387
|
].filter((id) => !HashMap.has(knownNames, id));
|
|
358
388
|
const lookupEff = unknownAgentIds.length > 0
|
|
359
|
-
? this.sendRpc(AgentsLookup
|
|
360
|
-
const agents = res.agents;
|
|
389
|
+
? this.sendRpc(AgentsLookup, { agentIds: unknownAgentIds }).pipe(Effect.tap((res) => {
|
|
361
390
|
return Ref.update(this.agentNamesRef, (names) => {
|
|
362
391
|
let next = names;
|
|
363
|
-
for (const a of agents) {
|
|
392
|
+
for (const a of res.agents) {
|
|
364
393
|
next = HashMap.set(next, a.id, a.name);
|
|
365
394
|
}
|
|
366
395
|
return next;
|
|
367
396
|
});
|
|
368
397
|
}), Effect.asVoid, Effect.catchAll(() => Effect.void))
|
|
369
398
|
: Effect.void;
|
|
370
|
-
const metaEff = this.sendRpc(ConversationsGet
|
|
371
|
-
conversationId: convId,
|
|
372
|
-
}).pipe(Effect.map((res) => res
|
|
373
|
-
.conversation), Effect.catchAll(() => Effect.succeed(undefined)));
|
|
399
|
+
const metaEff = this.sendRpc(ConversationsGet, {
|
|
400
|
+
conversationId: toConversationId(convId),
|
|
401
|
+
}).pipe(Effect.map((res) => res.conversation), Effect.catchAll(() => Effect.succeed(undefined)));
|
|
374
402
|
const [, convMeta] = yield* Effect.all([lookupEff, metaEff], {
|
|
375
|
-
concurrency:
|
|
403
|
+
concurrency: HISTORY_LOOKUP_CONCURRENCY,
|
|
376
404
|
});
|
|
377
405
|
// Determine what's "new" using lastRead (not lastNotified).
|
|
378
406
|
// lastNotified is advanced by getContext() (system-reminder).
|
|
@@ -446,7 +474,7 @@ export class MoltZapService {
|
|
|
446
474
|
const cached = Option.getOrUndefined(HashMap.get(snapshot(this.agentNamesRef), agentId));
|
|
447
475
|
if (cached !== undefined)
|
|
448
476
|
return cached;
|
|
449
|
-
return yield* this.sendRpc(AgentsLookup
|
|
477
|
+
return yield* this.sendRpc(AgentsLookup, {
|
|
450
478
|
agentIds: [agentId],
|
|
451
479
|
}).pipe(Effect.flatMap((result) => {
|
|
452
480
|
const agent = result.agents[0];
|
|
@@ -464,33 +492,43 @@ export class MoltZapService {
|
|
|
464
492
|
message: "Conversation is archived",
|
|
465
493
|
}));
|
|
466
494
|
}
|
|
467
|
-
return Effect.asVoid(this.sendRpc(MessagesSend
|
|
468
|
-
conversationId: convId,
|
|
495
|
+
return Effect.asVoid(this.sendRpc(MessagesSend, {
|
|
496
|
+
conversationId: toConversationId(convId),
|
|
469
497
|
parts: [{ type: "text", text }],
|
|
470
|
-
...(opts?.replyTo ? { replyToId: opts.replyTo } : {}),
|
|
498
|
+
...(opts?.replyTo ? { replyToId: toMessageId(opts.replyTo) } : {}),
|
|
471
499
|
...(opts?.dispatchLeaseId
|
|
472
500
|
? { dispatchLeaseId: opts.dispatchLeaseId }
|
|
473
501
|
: {}),
|
|
474
502
|
}));
|
|
475
503
|
}
|
|
476
504
|
authorizeDispatch(request) {
|
|
477
|
-
return this.sendRpc(AppsAuthorizeDispatch
|
|
478
|
-
conversationId: request.conversationId,
|
|
479
|
-
messageId: request.message.id,
|
|
480
|
-
senderAgentId: request.senderAgentId,
|
|
481
|
-
parts: request.message.parts,
|
|
505
|
+
return this.sendRpc(AppsAuthorizeDispatch, {
|
|
506
|
+
conversationId: toConversationId(request.conversationId),
|
|
507
|
+
messageId: toMessageId(request.message.id),
|
|
508
|
+
senderAgentId: toAgentId(request.senderAgentId),
|
|
509
|
+
parts: [...request.message.parts],
|
|
482
510
|
receivedAt: request.receivedAt,
|
|
483
511
|
clock: request.clock,
|
|
484
|
-
pending: request.pending
|
|
512
|
+
pending: request.pending.map((pending) => ({
|
|
513
|
+
...pending,
|
|
514
|
+
messageId: toMessageId(pending.messageId),
|
|
515
|
+
conversationId: toConversationId(pending.conversationId),
|
|
516
|
+
senderAgentId: toAgentId(pending.senderAgentId),
|
|
517
|
+
...(pending.parts !== undefined ? { parts: [...pending.parts] } : {}),
|
|
518
|
+
})),
|
|
485
519
|
attempt: request.attempt,
|
|
486
520
|
}, { timeoutMs: DISPATCH_AUTHORIZATION_RPC_TIMEOUT_MS }).pipe(Effect.map((result) => {
|
|
487
|
-
const admission = result
|
|
521
|
+
const { admission } = result;
|
|
488
522
|
switch (admission.decision) {
|
|
489
523
|
case "grant":
|
|
490
524
|
return {
|
|
491
525
|
_tag: "grant",
|
|
492
|
-
|
|
493
|
-
|
|
526
|
+
...(admission.leaseId !== undefined
|
|
527
|
+
? { leaseId: admission.leaseId }
|
|
528
|
+
: {}),
|
|
529
|
+
...(admission.leaseTimeoutMs !== undefined
|
|
530
|
+
? { leaseTimeoutMs: admission.leaseTimeoutMs }
|
|
531
|
+
: {}),
|
|
494
532
|
...(admission.dispatchMessageId
|
|
495
533
|
? { dispatchMessageId: admission.dispatchMessageId }
|
|
496
534
|
: {}),
|
|
@@ -498,12 +536,16 @@ export class MoltZapService {
|
|
|
498
536
|
case "deny":
|
|
499
537
|
return {
|
|
500
538
|
_tag: "deny",
|
|
501
|
-
|
|
539
|
+
...(admission.reason !== undefined
|
|
540
|
+
? { reason: admission.reason }
|
|
541
|
+
: {}),
|
|
502
542
|
};
|
|
503
543
|
case "hold":
|
|
504
544
|
return {
|
|
505
545
|
_tag: "hold",
|
|
506
|
-
|
|
546
|
+
...(admission.reason !== undefined
|
|
547
|
+
? { reason: admission.reason }
|
|
548
|
+
: {}),
|
|
507
549
|
};
|
|
508
550
|
}
|
|
509
551
|
}));
|
|
@@ -513,17 +555,17 @@ export class MoltZapService {
|
|
|
513
555
|
const cache = yield* Ref.get(this.agentConversationCacheRef);
|
|
514
556
|
let conversationId = Option.getOrUndefined(HashMap.get(cache, agentName));
|
|
515
557
|
if (!conversationId) {
|
|
516
|
-
const lookupResult =
|
|
558
|
+
const lookupResult = yield* this.sendRpc(AgentsLookupByName, {
|
|
517
559
|
names: [agentName],
|
|
518
|
-
})
|
|
560
|
+
});
|
|
519
561
|
const agent = lookupResult.agents[0];
|
|
520
562
|
if (!agent) {
|
|
521
563
|
return yield* Effect.fail(new AgentNotFoundError({ agentName }));
|
|
522
564
|
}
|
|
523
|
-
const createResult =
|
|
565
|
+
const createResult = yield* this.sendRpc(ConversationsCreate, {
|
|
524
566
|
type: "dm",
|
|
525
|
-
participants: [{ type: "agent", id: agent.id }],
|
|
526
|
-
})
|
|
567
|
+
participants: [{ type: "agent", id: toAgentId(agent.id) }],
|
|
568
|
+
});
|
|
527
569
|
conversationId = createResult.conversation.id;
|
|
528
570
|
const newId = conversationId;
|
|
529
571
|
yield* Ref.update(this.agentConversationCacheRef, (m) => HashMap.set(m, agentName, newId));
|
|
@@ -553,8 +595,8 @@ export class MoltZapService {
|
|
|
553
595
|
* committing can re-peek idempotently.
|
|
554
596
|
*/
|
|
555
597
|
peekContextEntries(currentConvId, opts) {
|
|
556
|
-
const maxConvs = opts?.maxConversations ??
|
|
557
|
-
const maxMsgsPerConv = opts?.maxMessagesPerConv ??
|
|
598
|
+
const maxConvs = opts?.maxConversations ?? DEFAULT_MAX_CONTEXT_CONVERSATIONS;
|
|
599
|
+
const maxMsgsPerConv = opts?.maxMessagesPerConv ?? DEFAULT_MAX_MESSAGES_PER_CONVERSATION;
|
|
558
600
|
const { messagesMap, conversationsMap, agentNamesMap, viewMarkers } = this.readCrossConvState(currentConvId);
|
|
559
601
|
// Collect all candidate conversations with their last-message timestamp,
|
|
560
602
|
// then sort by recency before applying `maxConvs`. HashMap iteration
|
|
@@ -577,7 +619,8 @@ export class MoltZapService {
|
|
|
577
619
|
const reportable = newMsgs.slice(-maxMsgsPerConv);
|
|
578
620
|
const last = reportable[reportable.length - 1];
|
|
579
621
|
const senderName = getOr(agentNamesMap, last.senderId, () => last.senderId);
|
|
580
|
-
const minutesAgo = Math.max(0, Math.round((Date.now() - new Date(last.createdAt).getTime()) /
|
|
622
|
+
const minutesAgo = Math.max(0, Math.round((Date.now() - new Date(last.createdAt).getTime()) /
|
|
623
|
+
MILLISECONDS_PER_MINUTE));
|
|
581
624
|
entries.push({
|
|
582
625
|
conversationId: convId,
|
|
583
626
|
conversationName: Option.getOrUndefined(HashMap.get(conversationsMap, convId))?.name,
|
|
@@ -656,57 +699,37 @@ export class MoltZapService {
|
|
|
656
699
|
return HashMap.set(outer, currentConvId, markers);
|
|
657
700
|
}));
|
|
658
701
|
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
this.messageHandlers.push(handler);
|
|
663
|
-
break;
|
|
664
|
-
case "rawEvent":
|
|
665
|
-
this.rawEventHandlers.push(handler);
|
|
666
|
-
break;
|
|
667
|
-
case "disconnect":
|
|
668
|
-
this.disconnectHandlers.push(handler);
|
|
669
|
-
break;
|
|
670
|
-
case "reconnect":
|
|
671
|
-
this.reconnectHandlers.push(handler);
|
|
672
|
-
break;
|
|
673
|
-
case "permissionRequired":
|
|
674
|
-
this.permissionRequiredHandlers.push(handler);
|
|
675
|
-
break;
|
|
676
|
-
case "conversationArchived":
|
|
677
|
-
this.conversationArchivedHandlers.push(handler);
|
|
678
|
-
break;
|
|
679
|
-
case "conversationUnarchived":
|
|
680
|
-
this.conversationUnarchivedHandlers.push(handler);
|
|
681
|
-
break;
|
|
682
|
-
}
|
|
702
|
+
// --- Notifications ---
|
|
703
|
+
on(notification, handler) {
|
|
704
|
+
this.handlers[notification].push(handler);
|
|
683
705
|
}
|
|
684
706
|
// --- RPC passthrough ---
|
|
685
|
-
sendRpc(
|
|
707
|
+
sendRpc(definition, params, opts) {
|
|
686
708
|
return Effect.suspend(() => {
|
|
687
709
|
if (!this.client) {
|
|
688
710
|
return Effect.fail(new NotConnectedError({ message: "Not connected" }));
|
|
689
711
|
}
|
|
690
|
-
return this.client.sendRpc(
|
|
712
|
+
return this.client.sendRpc(definition, params, opts);
|
|
691
713
|
});
|
|
692
714
|
}
|
|
693
|
-
grantPermission(params) {
|
|
694
|
-
return Effect.asVoid(this.sendRpc(PermissionsGrant.name, params));
|
|
695
|
-
}
|
|
696
715
|
// --- Internals ---
|
|
697
716
|
/**
|
|
698
717
|
* Fetch full conversation details (including participants) via RPC and merge
|
|
699
|
-
* into the local cache. Called on ConversationCreated
|
|
700
|
-
*
|
|
718
|
+
* into the local cache. Called on ConversationCreated notifications because
|
|
719
|
+
* the notification schema omits the participants list — see
|
|
720
|
+
* protocol/notifications.ts.
|
|
701
721
|
*/
|
|
702
722
|
refreshConversationParticipants(conversationId) {
|
|
703
|
-
return this.sendRpc(ConversationsGet
|
|
704
|
-
|
|
723
|
+
return this.sendRpc(ConversationsGet, {
|
|
724
|
+
conversationId: toConversationId(conversationId),
|
|
725
|
+
}).pipe(Effect.tap((res) => {
|
|
705
726
|
const meta = {
|
|
706
|
-
id:
|
|
707
|
-
type:
|
|
708
|
-
|
|
709
|
-
|
|
727
|
+
id: res.conversation.id,
|
|
728
|
+
type: res.conversation.type,
|
|
729
|
+
participants: res.participants.map((p) => `${p.participant.type}:${p.participant.id}`),
|
|
730
|
+
...(res.conversation.name !== undefined
|
|
731
|
+
? { name: res.conversation.name }
|
|
732
|
+
: {}),
|
|
710
733
|
};
|
|
711
734
|
if (this.archivedConversationIds.has(conversationId)) {
|
|
712
735
|
return Effect.void;
|
|
@@ -727,92 +750,88 @@ export class MoltZapService {
|
|
|
727
750
|
next = HashMap.set(next, conv.id, {
|
|
728
751
|
id: conv.id,
|
|
729
752
|
type: conv.type,
|
|
730
|
-
name: conv.name,
|
|
731
753
|
participants: (conv.participants ?? []).map((p) => `${p.type}:${p.id}`),
|
|
754
|
+
...(conv.name !== undefined ? { name: conv.name } : {}),
|
|
732
755
|
});
|
|
733
756
|
}
|
|
734
757
|
return next;
|
|
735
758
|
}));
|
|
736
759
|
}
|
|
737
|
-
|
|
738
|
-
const
|
|
739
|
-
|
|
740
|
-
|
|
760
|
+
handleNotification(notification) {
|
|
761
|
+
const params = isPlainRecord(notification.params)
|
|
762
|
+
? notification.params
|
|
763
|
+
: {};
|
|
764
|
+
const message = isPlainRecord(params["message"])
|
|
765
|
+
? params["message"]
|
|
741
766
|
: undefined;
|
|
742
|
-
const conversation = isPlainRecord(
|
|
743
|
-
?
|
|
767
|
+
const conversation = isPlainRecord(params["conversation"])
|
|
768
|
+
? params["conversation"]
|
|
744
769
|
: undefined;
|
|
745
|
-
const
|
|
746
|
-
?
|
|
770
|
+
const notificationConversationId = typeof params["conversationId"] === "string"
|
|
771
|
+
? params["conversationId"]
|
|
747
772
|
: undefined;
|
|
748
773
|
appendClientEventTrace({
|
|
749
774
|
ts: new Date().toISOString(),
|
|
750
775
|
agentId: this._ownAgentId ?? "unknown",
|
|
751
|
-
|
|
776
|
+
notification: notification.method,
|
|
752
777
|
messageId: message?.["id"],
|
|
753
778
|
messageConversationId: message?.["conversationId"],
|
|
754
779
|
messageSenderId: message?.["senderId"],
|
|
755
|
-
conversationId: conversation?.["id"] ??
|
|
780
|
+
conversationId: conversation?.["id"] ?? notificationConversationId,
|
|
756
781
|
conversationName: conversation?.["name"],
|
|
757
782
|
});
|
|
758
|
-
fanout(this.
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
// specific event.
|
|
762
|
-
switch (event.event) {
|
|
763
|
-
case EventNames.MessageReceived: {
|
|
764
|
-
const msg = event.data.message;
|
|
765
|
-
this.storeMessage(msg);
|
|
766
|
-
// Name resolution is driven lazily by channel-core's serialized
|
|
767
|
-
// consumer via resolveAgentName(), which populates agentNamesRef on
|
|
768
|
-
// first miss and hits the cache on every subsequent message.
|
|
769
|
-
if (msg.senderId !== this._ownAgentId) {
|
|
770
|
-
fanout(this.messageHandlers, msg, this.opts.logger);
|
|
771
|
-
}
|
|
772
|
-
break;
|
|
773
|
-
}
|
|
774
|
-
case EventNames.PermissionsRequired: {
|
|
775
|
-
fanout(this.permissionRequiredHandlers, event.data, this.opts.logger);
|
|
776
|
-
break;
|
|
777
|
-
}
|
|
778
|
-
case EventNames.ConversationCreated:
|
|
779
|
-
case EventNames.ConversationUpdated: {
|
|
780
|
-
const { conversation } = event.data;
|
|
781
|
-
this.archivedConversationIds.delete(conversation.id);
|
|
782
|
-
Effect.runSync(Ref.update(this.conversationsRef, (m) => {
|
|
783
|
-
const existing = Option.getOrUndefined(HashMap.get(m, conversation.id));
|
|
784
|
-
return HashMap.set(m, conversation.id, {
|
|
785
|
-
id: conversation.id,
|
|
786
|
-
type: conversation.type,
|
|
787
|
-
name: conversation.name,
|
|
788
|
-
participants: existing?.participants ?? [],
|
|
789
|
-
});
|
|
790
|
-
}));
|
|
791
|
-
// The ConversationCreated event schema doesn't carry participants
|
|
792
|
-
// (protocol/schema/events.ts: ConversationSchema is id/type/name/
|
|
793
|
-
// createdBy/timestamps only). Fetch full details asynchronously so
|
|
794
|
-
// downstream code that reads getConversation(id).participants sees
|
|
795
|
-
// a populated list within a round-trip of the event.
|
|
796
|
-
if (event.event === EventNames.ConversationCreated) {
|
|
797
|
-
// Fire-and-forget: refreshConversationParticipants never fails.
|
|
798
|
-
Effect.runFork(this.refreshConversationParticipants(conversation.id));
|
|
799
|
-
}
|
|
800
|
-
break;
|
|
801
|
-
}
|
|
802
|
-
case EventNames.ConversationArchived: {
|
|
803
|
-
const data = event.data;
|
|
804
|
-
this.markConversationArchived(data.conversationId);
|
|
805
|
-
fanout(this.conversationArchivedHandlers, data, this.opts.logger);
|
|
806
|
-
break;
|
|
807
|
-
}
|
|
808
|
-
case EventNames.ConversationUnarchived: {
|
|
809
|
-
const data = event.data;
|
|
810
|
-
this.archivedConversationIds.delete(data.conversationId);
|
|
811
|
-
fanout(this.conversationUnarchivedHandlers, data, this.opts.logger);
|
|
812
|
-
break;
|
|
813
|
-
}
|
|
783
|
+
fanout(this.handlers.rawNotification, notification, this.opts.logger);
|
|
784
|
+
if (isDecodedNotificationInGroup(serviceNotificationGroup, notification)) {
|
|
785
|
+
Effect.runSync(this.notificationHandlers.dispatch(notification));
|
|
814
786
|
}
|
|
815
787
|
}
|
|
788
|
+
createNotificationHandlers() {
|
|
789
|
+
return defineEffectNotificationHandlers(serviceNotificationGroup, [
|
|
790
|
+
bindNotificationHandler(MessageReceivedNotificationDefinition, (params) => Effect.sync(() => this.handleMessageReceivedNotification(params))),
|
|
791
|
+
bindNotificationHandler(ConversationCreatedNotificationDefinition, (params) => Effect.sync(() => this.handleConversationCreatedNotification(params))),
|
|
792
|
+
bindNotificationHandler(ConversationUpdatedNotificationDefinition, (params) => Effect.sync(() => this.handleConversationUpdatedNotification(params))),
|
|
793
|
+
bindNotificationHandler(ConversationArchivedNotificationDefinition, (params) => Effect.sync(() => {
|
|
794
|
+
this.markConversationArchived(params.conversationId);
|
|
795
|
+
fanout(this.handlers.conversationArchived, params, this.opts.logger);
|
|
796
|
+
})),
|
|
797
|
+
bindNotificationHandler(ConversationUnarchivedNotificationDefinition, (params) => Effect.sync(() => {
|
|
798
|
+
this.archivedConversationIds.delete(params.conversationId);
|
|
799
|
+
fanout(this.handlers.conversationUnarchived, params, this.opts.logger);
|
|
800
|
+
})),
|
|
801
|
+
]);
|
|
802
|
+
}
|
|
803
|
+
handleMessageReceivedNotification(notification) {
|
|
804
|
+
const msg = notification.message;
|
|
805
|
+
this.storeMessage(msg);
|
|
806
|
+
// Name resolution is driven lazily by channel-core's serialized consumer
|
|
807
|
+
// via resolveAgentName(), which populates agentNamesRef on first miss and
|
|
808
|
+
// hits the cache on every subsequent message.
|
|
809
|
+
if (msg.senderId !== this._ownAgentId) {
|
|
810
|
+
fanout(this.handlers.message, msg, this.opts.logger);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
handleConversationCreatedNotification(notification) {
|
|
814
|
+
this.upsertConversationNotification(notification);
|
|
815
|
+
Effect.runFork(this.refreshConversationParticipants(notification.conversation.id));
|
|
816
|
+
}
|
|
817
|
+
handleConversationUpdatedNotification(notification) {
|
|
818
|
+
this.upsertConversationNotification(notification);
|
|
819
|
+
}
|
|
820
|
+
upsertConversationNotification(notification) {
|
|
821
|
+
const { conversation } = notification;
|
|
822
|
+
this.archivedConversationIds.delete(conversation.id);
|
|
823
|
+
Effect.runSync(Ref.update(this.conversationsRef, (m) => {
|
|
824
|
+
const existing = Option.getOrUndefined(HashMap.get(m, conversation.id));
|
|
825
|
+
return HashMap.set(m, conversation.id, {
|
|
826
|
+
id: conversation.id,
|
|
827
|
+
type: conversation.type,
|
|
828
|
+
participants: existing?.participants ?? [],
|
|
829
|
+
...(conversation.name !== undefined
|
|
830
|
+
? { name: conversation.name }
|
|
831
|
+
: {}),
|
|
832
|
+
});
|
|
833
|
+
}));
|
|
834
|
+
}
|
|
816
835
|
markConversationArchived(conversationId) {
|
|
817
836
|
this.archivedConversationIds.add(conversationId);
|
|
818
837
|
Effect.runSync(Effect.all([
|