@moltzap/client 2026.726.0 → 2026.730.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/agent-client.d.ts +2 -0
- package/dist/agent-client.d.ts.map +1 -1
- package/dist/agent-client.js +1 -0
- package/dist/agent-client.js.map +1 -1
- package/dist/app-client.d.ts +2 -0
- package/dist/app-client.d.ts.map +1 -1
- package/dist/app-client.js +1 -0
- package/dist/app-client.js.map +1 -1
- package/dist/auth.d.ts +7 -2
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +10 -5
- package/dist/auth.js.map +1 -1
- package/dist/channel-base/format-cross-conv.d.ts +8 -3
- package/dist/channel-base/format-cross-conv.d.ts.map +1 -1
- package/dist/channel-base/format-cross-conv.js +10 -9
- package/dist/channel-base/format-cross-conv.js.map +1 -1
- package/dist/channel-base/format-group-block.d.ts +9 -2
- package/dist/channel-base/format-group-block.d.ts.map +1 -1
- package/dist/channel-base/format-group-block.js +12 -7
- package/dist/channel-base/format-group-block.js.map +1 -1
- package/dist/channel-base/index.d.ts +8 -0
- package/dist/channel-base/index.d.ts.map +1 -1
- package/dist/channel-base/index.js +8 -0
- package/dist/channel-base/index.js.map +1 -1
- package/dist/channel-base/lease-guard.d.ts +6 -3
- package/dist/channel-base/lease-guard.d.ts.map +1 -1
- package/dist/channel-base/lease-guard.js +10 -4
- package/dist/channel-base/lease-guard.js.map +1 -1
- package/dist/channel-base/lease-store.d.ts +29 -10
- package/dist/channel-base/lease-store.d.ts.map +1 -1
- package/dist/channel-base/lease-store.js +33 -13
- package/dist/channel-base/lease-store.js.map +1 -1
- package/dist/channel-base/lease.d.ts +9 -1
- package/dist/channel-base/lease.d.ts.map +1 -1
- package/dist/channel-base/lease.js +16 -4
- package/dist/channel-base/lease.js.map +1 -1
- package/dist/channel-core-enrichment.d.ts +9 -2
- package/dist/channel-core-enrichment.d.ts.map +1 -1
- package/dist/channel-core-enrichment.js +17 -5
- package/dist/channel-core-enrichment.js.map +1 -1
- package/dist/channel-core-test-support.d.ts +46 -0
- package/dist/channel-core-test-support.d.ts.map +1 -1
- package/dist/channel-core-test-support.js +67 -13
- package/dist/channel-core-test-support.js.map +1 -1
- package/dist/channel-core.d.ts +37 -22
- package/dist/channel-core.d.ts.map +1 -1
- package/dist/channel-core.js +45 -38
- package/dist/channel-core.js.map +1 -1
- package/dist/cli/adapters.d.ts +4 -1
- package/dist/cli/adapters.d.ts.map +1 -1
- package/dist/cli/adapters.js +11 -4
- package/dist/cli/adapters.js.map +1 -1
- package/dist/cli/commands/agents.js +3 -3
- package/dist/cli/commands/contacts.d.ts +3 -3
- package/dist/cli/commands/contacts.d.ts.map +1 -1
- package/dist/cli/commands/contacts.js +13 -9
- package/dist/cli/commands/contacts.js.map +1 -1
- package/dist/cli/commands/conversations.d.ts +3 -2
- package/dist/cli/commands/conversations.d.ts.map +1 -1
- package/dist/cli/commands/conversations.js +7 -6
- package/dist/cli/commands/conversations.js.map +1 -1
- package/dist/cli/commands/messages.d.ts +6 -2
- package/dist/cli/commands/messages.d.ts.map +1 -1
- package/dist/cli/commands/messages.js +8 -5
- package/dist/cli/commands/messages.js.map +1 -1
- package/dist/cli/commands/register.d.ts +1 -1
- package/dist/cli/commands/register.js +4 -4
- package/dist/cli/commands/send.d.ts +7 -6
- package/dist/cli/commands/send.d.ts.map +1 -1
- package/dist/cli/commands/send.js +7 -6
- package/dist/cli/commands/send.js.map +1 -1
- package/dist/cli/commands/start.d.ts +15 -7
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +12 -5
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/commands/status.js +2 -2
- package/dist/cli/commands/test-transport.d.ts +9 -1
- package/dist/cli/commands/test-transport.d.ts.map +1 -1
- package/dist/cli/commands/test-transport.js +5 -1
- package/dist/cli/commands/test-transport.js.map +1 -1
- package/dist/cli/index.js +8 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/output.d.ts +10 -0
- package/dist/cli/output.d.ts.map +1 -1
- package/dist/cli/output.js +10 -0
- package/dist/cli/output.js.map +1 -1
- package/dist/cli/socket-client.d.ts +13 -3
- package/dist/cli/socket-client.d.ts.map +1 -1
- package/dist/cli/socket-client.js +16 -5
- package/dist/cli/socket-client.js.map +1 -1
- package/dist/cli/transport.d.ts +16 -3
- package/dist/cli/transport.d.ts.map +1 -1
- package/dist/cli/transport.js +19 -6
- package/dist/cli/transport.js.map +1 -1
- package/dist/config.d.ts +15 -6
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +15 -5
- package/dist/config.js.map +1 -1
- package/dist/config.test-utils.d.ts +7 -2
- package/dist/config.test-utils.d.ts.map +1 -1
- package/dist/config.test-utils.js +8 -1
- package/dist/config.test-utils.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/local-daemon-rpc.d.ts +122 -99
- package/dist/local-daemon-rpc.d.ts.map +1 -1
- package/dist/local-daemon-rpc.js +128 -108
- package/dist/local-daemon-rpc.js.map +1 -1
- package/dist/local-history.d.ts +40 -20
- package/dist/local-history.d.ts.map +1 -1
- package/dist/local-history.js +45 -23
- package/dist/local-history.js.map +1 -1
- package/dist/local-paths.d.ts +17 -0
- package/dist/local-paths.d.ts.map +1 -1
- package/dist/local-paths.js +21 -4
- package/dist/local-paths.js.map +1 -1
- package/dist/local-socket-server.d.ts +13 -2
- package/dist/local-socket-server.d.ts.map +1 -1
- package/dist/local-socket-server.js +13 -2
- package/dist/local-socket-server.js.map +1 -1
- package/dist/message-rendering.d.ts +1 -0
- package/dist/message-rendering.d.ts.map +1 -1
- package/dist/message-rendering.js +1 -0
- package/dist/message-rendering.js.map +1 -1
- package/dist/notification/__tests__/subscribe-signatures.types-check.d.ts +50 -2
- package/dist/notification/__tests__/subscribe-signatures.types-check.d.ts.map +1 -1
- package/dist/notification/__tests__/subscribe-signatures.types-check.js +22 -48
- package/dist/notification/__tests__/subscribe-signatures.types-check.js.map +1 -1
- package/dist/notification/errors.d.ts +4 -2
- package/dist/notification/errors.d.ts.map +1 -1
- package/dist/notification/errors.js +3 -1
- package/dist/notification/errors.js.map +1 -1
- package/dist/notification/index.d.ts +1 -0
- package/dist/notification/index.d.ts.map +1 -1
- package/dist/notification/index.js +1 -0
- package/dist/notification/index.js.map +1 -1
- package/dist/notification/stream.d.ts +16 -9
- package/dist/notification/stream.d.ts.map +1 -1
- package/dist/notification/stream.js +5 -38
- package/dist/notification/stream.js.map +1 -1
- package/dist/pagination.d.ts +13 -16
- package/dist/pagination.d.ts.map +1 -1
- package/dist/pagination.js +9 -1
- package/dist/pagination.js.map +1 -1
- package/dist/profile.d.ts +21 -9
- package/dist/profile.d.ts.map +1 -1
- package/dist/profile.js +30 -16
- package/dist/profile.js.map +1 -1
- package/dist/refs.d.ts +9 -1
- package/dist/refs.d.ts.map +1 -1
- package/dist/refs.js +9 -1
- package/dist/refs.js.map +1 -1
- package/dist/service-archive-purge.d.ts +18 -0
- package/dist/service-archive-purge.d.ts.map +1 -1
- package/dist/service-archive-purge.js +18 -0
- package/dist/service-archive-purge.js.map +1 -1
- package/dist/service-event-trace.d.ts +15 -0
- package/dist/service-event-trace.d.ts.map +1 -0
- package/dist/service-event-trace.js +35 -0
- package/dist/service-event-trace.js.map +1 -0
- package/dist/service-helpers.d.ts +35 -6
- package/dist/service-helpers.d.ts.map +1 -1
- package/dist/service-helpers.js +38 -8
- package/dist/service-helpers.js.map +1 -1
- package/dist/service-local-daemon.d.ts +12 -2
- package/dist/service-local-daemon.d.ts.map +1 -1
- package/dist/service-local-daemon.js +38 -25
- package/dist/service-local-daemon.js.map +1 -1
- package/dist/service.d.ts +62 -23
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +138 -89
- package/dist/service.js.map +1 -1
- package/dist/test-utils/channel-service-fixture.d.ts +8 -1
- package/dist/test-utils/channel-service-fixture.d.ts.map +1 -1
- package/dist/test-utils/channel-service-fixture.js +31 -21
- package/dist/test-utils/channel-service-fixture.js.map +1 -1
- package/dist/test-utils/fake-service.d.ts +20 -7
- package/dist/test-utils/fake-service.d.ts.map +1 -1
- package/dist/test-utils/fake-service.js +20 -5
- package/dist/test-utils/fake-service.js.map +1 -1
- package/dist/test-utils/harness.d.ts +8 -4
- package/dist/test-utils/harness.d.ts.map +1 -1
- package/dist/test-utils/harness.js +3 -0
- package/dist/test-utils/harness.js.map +1 -1
- package/dist/test-utils/ids.d.ts +25 -0
- package/dist/test-utils/ids.d.ts.map +1 -1
- package/dist/test-utils/ids.js +27 -1
- package/dist/test-utils/ids.js.map +1 -1
- package/dist/test-utils/index.d.ts +7 -1
- package/dist/test-utils/index.d.ts.map +1 -1
- package/dist/test-utils/index.js +7 -1
- package/dist/test-utils/index.js.map +1 -1
- package/dist/test-utils/standalone-provisioning.d.ts +5 -0
- package/dist/test-utils/standalone-provisioning.d.ts.map +1 -1
- package/dist/test-utils/standalone-provisioning.js +3 -0
- package/dist/test-utils/standalone-provisioning.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
package/dist/service.js
CHANGED
|
@@ -1,26 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { ConversationArchivedError, } from "@moltzap/protocol/conversation";
|
|
8
|
-
import { MessageReceivedNotificationDefinition, MessagesList, MessagesSend, } from "@moltzap/protocol/message";
|
|
9
|
-
import { NotConnectedError, RpcTimeoutError, isNotificationDeliveryFor, } from "@moltzap/protocol/rpc";
|
|
10
|
-
import { AgentCallableGroup } from "@moltzap/protocol/socket/catalog";
|
|
1
|
+
import { agentId as AgentIdSchema, AgentNotFoundError, agentsList, } from "@moltzap/protocol/identity";
|
|
2
|
+
import { dispatchRequest, dispatchRelease, dispatchLeaseConsumed, dispatchLeaseExpired, } from "@moltzap/protocol/message/dispatch";
|
|
3
|
+
import { conversationCreatedNotificationDefinition, conversationArchivedNotificationDefinition, conversationUnarchivedNotificationDefinition, conversationList, ConversationArchivedError, } from "@moltzap/protocol/conversation";
|
|
4
|
+
import { DEFAULT_APP_ID, taskRequest, } from "@moltzap/protocol/task";
|
|
5
|
+
import { messageReceivedNotificationDefinition, messagesList, messagesSend, } from "@moltzap/protocol/message";
|
|
6
|
+
import { NotConnectedError, isNotificationDeliveryFor, } from "@moltzap/protocol/rpc";
|
|
11
7
|
import { BoundedMap } from "@moltzap/protocol/bounded-map";
|
|
12
|
-
import {
|
|
8
|
+
import { Effect, Exit, HashMap, Option, Ref, Schema, Scope, Stream, } from "effect";
|
|
13
9
|
import { MoltZapAgentClient } from "./agent-client.js";
|
|
14
10
|
import { loadServiceConfig, } from "./config.js";
|
|
15
11
|
import { getOr, snapshot } from "./refs.js";
|
|
16
12
|
import { getMoltZapAgentServiceSocketPath, getMoltZapServiceSocketPath, } from "./local-paths.js";
|
|
17
|
-
import {} from "./local-daemon-rpc.js";
|
|
18
13
|
import { makeLocalDaemonHandlers } from "./service-local-daemon.js";
|
|
19
14
|
import { startLocalSocketServer, stopLocalSocketServer, } from "./local-socket-server.js";
|
|
20
15
|
import { buildContextEntries, makeContextCandidate, newMessagesForConversation, notificationTraceRecord, } from "./service-helpers.js";
|
|
21
16
|
import { renderPart } from "./message-rendering.js";
|
|
22
17
|
import { formatHistoryMessage, lastReadIdsForSession, } from "./local-history.js";
|
|
23
18
|
import { purgeAgentCacheEntries, purgeLastNotified, purgeLastRead, } from "./service-archive-purge.js";
|
|
19
|
+
import { appendClientEventTrace } from "./service-event-trace.js";
|
|
24
20
|
const CROSS_CONTEXT_TEXT_LIMIT = 120;
|
|
25
21
|
const DEFAULT_MAX_CONTEXT_CONVERSATIONS = 5;
|
|
26
22
|
const DEFAULT_MAX_MESSAGES_PER_CONVERSATION = 3;
|
|
@@ -30,25 +26,15 @@ const AGENT_LOOKUP_MAX_PAGES = 20;
|
|
|
30
26
|
const REUSABLE_CONVERSATION_LOOKUP_PAGE_SIZE = 50;
|
|
31
27
|
const REUSABLE_CONVERSATION_LOOKUP_MAX_PAGES = 20;
|
|
32
28
|
const decodeAgentId = Schema.decodeUnknownOption(AgentIdSchema);
|
|
33
|
-
const ClientEventLogDir = Config.option(Config.string("MOLTZAP_CLIENT_EVENT_LOG_DIR"));
|
|
34
|
-
const getClientEventLogDir = () => Option.getOrUndefined(Effect.runSync(ClientEventLogDir.pipe(Effect.withConfigProvider(ConfigProvider.fromEnv()))));
|
|
35
|
-
function appendClientEventTrace(record) {
|
|
36
|
-
const dir = getClientEventLogDir();
|
|
37
|
-
if (!dir)
|
|
38
|
-
return Effect.void;
|
|
39
|
-
return Effect.gen(function* () {
|
|
40
|
-
const fileSystem = yield* FileSystem.FileSystem;
|
|
41
|
-
const path = yield* Path.Path;
|
|
42
|
-
const agentId = typeof record["agentId"] === "string" ? record["agentId"] : "unknown";
|
|
43
|
-
const safeAgentId = /^[A-Za-z0-9_-]+$/.test(agentId) ? agentId : "unknown";
|
|
44
|
-
yield* Effect.zipRight(fileSystem.makeDirectory(dir, { recursive: true }), fileSystem.writeFileString(path.join(dir, `client-events-${safeAgentId}.jsonl`), JSON.stringify(record) + "\n", { flag: "a" }));
|
|
45
|
-
}).pipe(Effect.withSpan("appendClientEventTrace"), Effect.provide(NodeContext.layer), Effect.catchAll((err) => Effect.logWarning("moltzap client event trace write failed", err)));
|
|
46
|
-
}
|
|
47
29
|
const agentNotFound = (agentName) => new AgentNotFoundError({
|
|
48
30
|
message: `Agent not found: ${agentName}`,
|
|
49
31
|
data: { agentName },
|
|
50
32
|
});
|
|
51
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Escape `<`, `>`, `&` so sender content can't escape a `<system-reminder>` block.
|
|
35
|
+
* @param s Value supplied to the operation.
|
|
36
|
+
* @returns The sanitize for system reminder result.
|
|
37
|
+
*/
|
|
52
38
|
export function sanitizeForSystemReminder(s) {
|
|
53
39
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
54
40
|
}
|
|
@@ -56,10 +42,15 @@ export function sanitizeForSystemReminder(s) {
|
|
|
56
42
|
* Format CrossConversationEntry[] as a `<system-reminder>` block. Adapters
|
|
57
43
|
* that inline context into prompt text (nanoclaw) and `MoltZapService.getContext`
|
|
58
44
|
* share this formatter so sanitization and line shape stay in one place.
|
|
45
|
+
* @param entries Value supplied to the operation.
|
|
46
|
+
* @param opts Value supplied to the operation.
|
|
47
|
+
* @param opts.header Value supplied to the operation.
|
|
48
|
+
* @returns The format cross conversation block result.
|
|
59
49
|
*/
|
|
60
50
|
function formatCrossConversationBlock(entries, opts) {
|
|
61
|
-
if (entries.length === 0)
|
|
51
|
+
if (entries.length === 0) {
|
|
62
52
|
return null;
|
|
53
|
+
}
|
|
63
54
|
const lines = entries.map((e) => {
|
|
64
55
|
const safeSender = sanitizeForSystemReminder(e.senderName);
|
|
65
56
|
const safeText = sanitizeForSystemReminder(e.text.slice(0, CROSS_CONTEXT_TEXT_LIMIT));
|
|
@@ -88,6 +79,8 @@ const DEDUP_WINDOW_PER_CONV = 1000;
|
|
|
88
79
|
/**
|
|
89
80
|
* Invoke every handler with `arg`, isolating throws so one bad handler
|
|
90
81
|
* doesn't abort the remaining fanout.
|
|
82
|
+
* @param handlers Value supplied to the operation.
|
|
83
|
+
* @param arg Value supplied to the operation.
|
|
91
84
|
*/
|
|
92
85
|
function fanout(handlers, arg) {
|
|
93
86
|
for (const h of handlers) {
|
|
@@ -109,9 +102,8 @@ function fanout(handlers, arg) {
|
|
|
109
102
|
* callers compose failures and cancellation explicitly.
|
|
110
103
|
*/
|
|
111
104
|
export class MoltZapService {
|
|
112
|
-
opts;
|
|
113
105
|
client = null;
|
|
114
|
-
|
|
106
|
+
connectedValue = false;
|
|
115
107
|
/**
|
|
116
108
|
* Service-owned scope. Opened in `connect()`, owns the
|
|
117
109
|
* `subscribeAll → Stream.runForEach` fan-out fiber. Closed in `close()` so
|
|
@@ -137,25 +129,25 @@ export class MoltZapService {
|
|
|
137
129
|
message: [],
|
|
138
130
|
rawNotification: [],
|
|
139
131
|
disconnect: [],
|
|
140
|
-
reconnect: [],
|
|
141
132
|
conversationArchived: [],
|
|
142
133
|
conversationUnarchived: [],
|
|
143
134
|
dispatchRelease: [],
|
|
144
135
|
dispatchLeaseConsumed: [],
|
|
145
136
|
dispatchLeaseExpired: [],
|
|
146
137
|
};
|
|
147
|
-
|
|
138
|
+
ownAgentIdValue;
|
|
139
|
+
opts;
|
|
148
140
|
constructor(opts) {
|
|
149
141
|
this.opts = opts;
|
|
150
142
|
// The empty HelloOk carries no identity; `ownAgentId` is the client's
|
|
151
143
|
// registered/stored id, available before the handshake.
|
|
152
|
-
this.
|
|
144
|
+
this.ownAgentIdValue = opts.agentId;
|
|
153
145
|
}
|
|
154
146
|
static fromConfig(config) {
|
|
155
147
|
return new MoltZapService(config);
|
|
156
148
|
}
|
|
157
149
|
static make(profileName) {
|
|
158
|
-
return loadServiceConfig(profileName).pipe(Effect.map(MoltZapService.fromConfig));
|
|
150
|
+
return loadServiceConfig(profileName).pipe(Effect.map((config) => MoltZapService.fromConfig(config)));
|
|
159
151
|
}
|
|
160
152
|
static startDaemon(profileName) {
|
|
161
153
|
return Effect.gen(function* () {
|
|
@@ -166,12 +158,15 @@ export class MoltZapService {
|
|
|
166
158
|
}).pipe(Effect.withSpan("MoltZapService.startDaemon"));
|
|
167
159
|
}
|
|
168
160
|
get connected() {
|
|
169
|
-
return this.
|
|
161
|
+
return this.connectedValue;
|
|
170
162
|
}
|
|
171
163
|
get ownAgentId() {
|
|
172
|
-
return this.
|
|
164
|
+
return this.ownAgentIdValue;
|
|
173
165
|
}
|
|
174
|
-
/**
|
|
166
|
+
/**
|
|
167
|
+
* Effect-native: compose via `yield*` or bridge at the edge via `Effect.runPromise`.
|
|
168
|
+
* @returns The client result.
|
|
169
|
+
*/
|
|
175
170
|
connect() {
|
|
176
171
|
return Effect.gen(this, function* () {
|
|
177
172
|
const client = new MoltZapAgentClient({
|
|
@@ -181,13 +176,9 @@ export class MoltZapService {
|
|
|
181
176
|
// kept explicit so a future disconnect-handler chain can plumb
|
|
182
177
|
// code/reason through.
|
|
183
178
|
onDisconnect: () => {
|
|
184
|
-
this.
|
|
179
|
+
this.connectedValue = false;
|
|
185
180
|
fanout(this.handlers.disconnect, undefined);
|
|
186
181
|
},
|
|
187
|
-
onReconnect: (helloOk) => {
|
|
188
|
-
this._connected = true;
|
|
189
|
-
fanout(this.handlers.reconnect, helloOk);
|
|
190
|
-
},
|
|
191
182
|
});
|
|
192
183
|
this.client = client;
|
|
193
184
|
// `subscribeAll().pipe(Stream.runForEach, …)` is forked into a
|
|
@@ -202,10 +193,12 @@ export class MoltZapService {
|
|
|
202
193
|
// before the fiber exits.
|
|
203
194
|
const serviceScope = yield* Scope.make();
|
|
204
195
|
this.serviceScope = serviceScope;
|
|
205
|
-
const fanoutEffect = client.subscribeAll().pipe(Stream.runForEach((notification) => Effect.sync(() =>
|
|
196
|
+
const fanoutEffect = client.subscribeAll().pipe(Stream.runForEach((notification) => Effect.sync(() => {
|
|
197
|
+
this.handleNotification(notification);
|
|
198
|
+
})), Effect.catchAll((cause) => Effect.logWarning("MoltZapService notification fan-out terminated", cause)), Effect.asVoid);
|
|
206
199
|
yield* Effect.forkIn(fanoutEffect, serviceScope);
|
|
207
200
|
const helloOk = yield* client.connect();
|
|
208
|
-
this.
|
|
201
|
+
this.connectedValue = true;
|
|
209
202
|
return helloOk;
|
|
210
203
|
});
|
|
211
204
|
}
|
|
@@ -216,7 +209,7 @@ export class MoltZapService {
|
|
|
216
209
|
* shutdown.
|
|
217
210
|
*/
|
|
218
211
|
close() {
|
|
219
|
-
this.
|
|
212
|
+
this.connectedValue = false;
|
|
220
213
|
Effect.runFork(this.stopSocketServer());
|
|
221
214
|
const scopeToClose = this.serviceScope;
|
|
222
215
|
const clientToClose = this.client;
|
|
@@ -237,10 +230,9 @@ export class MoltZapService {
|
|
|
237
230
|
]));
|
|
238
231
|
this.archivedConversationIds.clear();
|
|
239
232
|
this.seenMessageIds.clear();
|
|
240
|
-
// Handlers are preserved across close()/connect() cycles.
|
|
241
|
-
// subscribes once in its constructor; clearing handlers
|
|
242
|
-
// drop inbound
|
|
243
|
-
// service instance.
|
|
233
|
+
// Handlers are preserved across explicit close()/connect() cycles.
|
|
234
|
+
// MoltZapChannelCore subscribes once in its constructor; clearing handlers
|
|
235
|
+
// here would silently drop inbound dispatch after the next connect.
|
|
244
236
|
}
|
|
245
237
|
// --- Socket Server ---
|
|
246
238
|
socketServerScope = null;
|
|
@@ -252,11 +244,16 @@ export class MoltZapService {
|
|
|
252
244
|
* compromised or malicious server returns an id containing `..` or a
|
|
253
245
|
* path separator, a naive `path.join(... , agentId)` escapes `~/.moltzap`.
|
|
254
246
|
* Reject anything that isn't a safe identifier.
|
|
247
|
+
* @param id Value supplied to the operation.
|
|
248
|
+
* @returns The id result.
|
|
255
249
|
*/
|
|
256
250
|
static safeAgentIdSegment(id) {
|
|
257
251
|
return /^[A-Za-z0-9_-]+$/.test(id) ? id : "default";
|
|
258
252
|
}
|
|
259
|
-
/**
|
|
253
|
+
/**
|
|
254
|
+
* Per-instance socket path based on connected agentId.
|
|
255
|
+
* @returns The id result.
|
|
256
|
+
*/
|
|
260
257
|
get socketPath() {
|
|
261
258
|
const id = MoltZapService.safeAgentIdSegment(this.ownAgentId ?? "default");
|
|
262
259
|
return getMoltZapAgentServiceSocketPath(id);
|
|
@@ -295,8 +292,8 @@ export class MoltZapService {
|
|
|
295
292
|
}
|
|
296
293
|
localDaemonHandlers() {
|
|
297
294
|
return makeLocalDaemonHandlers({
|
|
298
|
-
ownAgentId: this.
|
|
299
|
-
connected: this.
|
|
295
|
+
ownAgentId: this.ownAgentIdValue,
|
|
296
|
+
connected: this.connectedValue,
|
|
300
297
|
conversationCount: () => this.getConversations().length,
|
|
301
298
|
call: this.call.bind(this),
|
|
302
299
|
handleHistoryRequest: (request) => this.handleHistoryRequest(request),
|
|
@@ -304,7 +301,7 @@ export class MoltZapService {
|
|
|
304
301
|
}
|
|
305
302
|
handleHistoryRequest(request) {
|
|
306
303
|
return Effect.gen(this, function* () {
|
|
307
|
-
const result = yield* this.call(
|
|
304
|
+
const result = yield* this.call(messagesList.name, {
|
|
308
305
|
taskId: request.taskId,
|
|
309
306
|
conversationId: request.conversationId,
|
|
310
307
|
limit: request.limit,
|
|
@@ -344,15 +341,16 @@ export class MoltZapService {
|
|
|
344
341
|
const unknownAgentIds = [
|
|
345
342
|
...new Set(messages.map((message) => message.senderId)),
|
|
346
343
|
].filter((id) => !HashMap.has(knownNames, id));
|
|
347
|
-
if (unknownAgentIds.length === 0)
|
|
344
|
+
if (unknownAgentIds.length === 0) {
|
|
348
345
|
return;
|
|
346
|
+
}
|
|
349
347
|
yield* this.cacheVisibleAgentNamesForIds(new Set(unknownAgentIds)).pipe(Effect.asVoid, Effect.catchAll(() => Effect.void));
|
|
350
348
|
});
|
|
351
349
|
}
|
|
352
350
|
fetchHistoryConversationMeta(convId) {
|
|
353
351
|
// The client filters `ConversationList` output for the matching
|
|
354
352
|
// conversation id (there is no per-conversation get RPC).
|
|
355
|
-
return this.call(
|
|
353
|
+
return this.call(conversationList.name, {}).pipe(Effect.map((result) => {
|
|
356
354
|
const hit = result.items.find((item) => item.conversation.id === convId);
|
|
357
355
|
return hit?.conversation;
|
|
358
356
|
}), Effect.catchAll(() => Effect.succeed(undefined)));
|
|
@@ -364,9 +362,11 @@ export class MoltZapService {
|
|
|
364
362
|
const { conversationId, sessionKey } = request;
|
|
365
363
|
return Ref.update(this.lastReadRef, (outer) => {
|
|
366
364
|
const perSession = getOr(outer, sessionKey, () => HashMap.empty());
|
|
367
|
-
const existing = getOr(perSession, conversationId, () =>
|
|
368
|
-
|
|
365
|
+
const existing = getOr(perSession, conversationId, () =>
|
|
366
|
+
/* Safe because the surrounding invariant establishes this asserted shape. */ new Set());
|
|
367
|
+
if (messages.every((message) => existing.has(message.id))) {
|
|
369
368
|
return outer;
|
|
369
|
+
}
|
|
370
370
|
const nextSet = new Set(existing);
|
|
371
371
|
for (const message of messages) {
|
|
372
372
|
nextSet.add(message.id);
|
|
@@ -398,15 +398,19 @@ export class MoltZapService {
|
|
|
398
398
|
* when the RPC errors or the server has no record. The error path logs
|
|
399
399
|
* so ops can see repeated lookup failures; the empty-response path is
|
|
400
400
|
* silent (a cold agent is an expected transient state).
|
|
401
|
+
* @param agentId Identifier of the agent targeted by the operation.
|
|
402
|
+
* @returns The decoded d agent id.
|
|
401
403
|
*/
|
|
402
404
|
resolveAgentName(agentId) {
|
|
403
405
|
return Effect.gen(this, function* () {
|
|
404
406
|
const decodedAgentId = Option.getOrUndefined(decodeAgentId(agentId));
|
|
405
|
-
if (decodedAgentId === undefined)
|
|
407
|
+
if (decodedAgentId === undefined) {
|
|
406
408
|
return agentId;
|
|
409
|
+
}
|
|
407
410
|
const cached = Option.getOrUndefined(HashMap.get(snapshot(this.agentNamesRef), agentId));
|
|
408
|
-
if (cached !== undefined)
|
|
411
|
+
if (cached !== undefined) {
|
|
409
412
|
return cached;
|
|
413
|
+
}
|
|
410
414
|
return yield* this.cacheVisibleAgentNamesForIds(new Set([decodedAgentId])).pipe(Effect.map(() => {
|
|
411
415
|
const resolved = Option.getOrUndefined(HashMap.get(snapshot(this.agentNamesRef), agentId));
|
|
412
416
|
return resolved ?? agentId;
|
|
@@ -443,12 +447,19 @@ export class MoltZapService {
|
|
|
443
447
|
* params frame. The server marks the lease consumed, blocking the
|
|
444
448
|
* app authorization timeout sweep. `MoltZapChannelCore.sendReply` forwards
|
|
445
449
|
* `leaseIdInFlight` automatically when the caller omits it.
|
|
450
|
+
* @param taskId Value supplied to the operation.
|
|
451
|
+
* @param conversationId Value supplied to the operation.
|
|
452
|
+
* @param text Text to process.
|
|
453
|
+
* @param opts Value supplied to the operation.
|
|
454
|
+
* @param opts.replyTo Value supplied to the operation.
|
|
455
|
+
* @param opts.dispatchLeaseId Value supplied to the operation.
|
|
456
|
+
* @returns The send result.
|
|
446
457
|
*/
|
|
447
458
|
send(taskId, conversationId, text, opts) {
|
|
448
459
|
if (this.isConversationArchived(conversationId)) {
|
|
449
460
|
return Effect.fail(new ConversationArchivedError({ message: "Conversation is archived" }));
|
|
450
461
|
}
|
|
451
|
-
return Effect.asVoid(this.call(
|
|
462
|
+
return Effect.asVoid(this.call(messagesSend.name, {
|
|
452
463
|
taskId,
|
|
453
464
|
conversationId,
|
|
454
465
|
parts: [{ type: "text", text }],
|
|
@@ -462,9 +473,11 @@ export class MoltZapService {
|
|
|
462
473
|
* Issue `agent/dispatch/request`. The server returns the ack
|
|
463
474
|
* `{leaseId, dispatchId}` immediately; the recipient observes the
|
|
464
475
|
* verdict asynchronously via the `dispatchRelease` event.
|
|
476
|
+
* @param params Request payload to process.
|
|
477
|
+
* @returns The cache result.
|
|
465
478
|
*/
|
|
466
479
|
requestDispatch(params) {
|
|
467
|
-
return this.call(
|
|
480
|
+
return this.call(dispatchRequest.name, params);
|
|
468
481
|
}
|
|
469
482
|
sendToAgent(agentName, text, opts) {
|
|
470
483
|
return Effect.gen(this, function* () {
|
|
@@ -475,7 +488,7 @@ export class MoltZapService {
|
|
|
475
488
|
if (!agent) {
|
|
476
489
|
return yield* Effect.fail(agentNotFound(agentName));
|
|
477
490
|
}
|
|
478
|
-
const createResult = yield* this.call(
|
|
491
|
+
const createResult = yield* this.call(taskRequest.name, {
|
|
479
492
|
appId: DEFAULT_APP_ID,
|
|
480
493
|
invitedAgentIds: [agent.id],
|
|
481
494
|
initialConversation: { participants: [agent.id] },
|
|
@@ -501,23 +514,27 @@ export class MoltZapService {
|
|
|
501
514
|
const params = {
|
|
502
515
|
limit: REUSABLE_CONVERSATION_LOOKUP_PAGE_SIZE,
|
|
503
516
|
};
|
|
504
|
-
if (cursor !== undefined)
|
|
517
|
+
if (cursor !== undefined) {
|
|
505
518
|
params.cursor = cursor;
|
|
506
|
-
|
|
519
|
+
}
|
|
520
|
+
const result = yield* this.call(conversationList.name, params);
|
|
507
521
|
const hit = result.items.find((item) => item.taskId === taskId &&
|
|
508
522
|
item.conversation.archivedAt === undefined);
|
|
509
|
-
if (hit !== undefined)
|
|
523
|
+
if (hit !== undefined) {
|
|
510
524
|
return hit.conversation.id;
|
|
511
|
-
|
|
525
|
+
}
|
|
526
|
+
if (result.nextCursor === undefined) {
|
|
512
527
|
break;
|
|
528
|
+
}
|
|
513
529
|
cursor = result.nextCursor;
|
|
514
530
|
}
|
|
515
531
|
return yield* Effect.fail(agentNotFound(agentName));
|
|
516
532
|
});
|
|
517
533
|
}
|
|
518
534
|
cacheAgentNames(agents) {
|
|
519
|
-
if (agents.length === 0)
|
|
535
|
+
if (agents.length === 0) {
|
|
520
536
|
return Effect.void;
|
|
537
|
+
}
|
|
521
538
|
return Ref.update(this.agentNamesRef, (names) => {
|
|
522
539
|
let next = names;
|
|
523
540
|
for (const agent of agents) {
|
|
@@ -537,12 +554,14 @@ export class MoltZapService {
|
|
|
537
554
|
let cursor = undefined;
|
|
538
555
|
for (let page = 0; page < AGENT_LOOKUP_MAX_PAGES; page++) {
|
|
539
556
|
const params = this.agentListParams(cursor);
|
|
540
|
-
const result = yield* this.call(
|
|
557
|
+
const result = yield* this.call(agentsList.name, params);
|
|
541
558
|
yield* this.cacheAgentNames(result.agents);
|
|
542
|
-
for (const agent of result.agents)
|
|
559
|
+
for (const agent of result.agents) {
|
|
543
560
|
missing.delete(agent.id);
|
|
544
|
-
|
|
561
|
+
}
|
|
562
|
+
if (missing.size === 0 || result.nextCursor === undefined) {
|
|
545
563
|
return;
|
|
564
|
+
}
|
|
546
565
|
cursor = result.nextCursor;
|
|
547
566
|
}
|
|
548
567
|
});
|
|
@@ -552,11 +571,12 @@ export class MoltZapService {
|
|
|
552
571
|
let cursor = undefined;
|
|
553
572
|
for (let page = 0; page < AGENT_LOOKUP_MAX_PAGES; page++) {
|
|
554
573
|
const params = this.agentListParams(cursor);
|
|
555
|
-
const result = yield* this.call(
|
|
574
|
+
const result = yield* this.call(agentsList.name, params);
|
|
556
575
|
yield* this.cacheAgentNames(result.agents);
|
|
557
576
|
const hit = result.agents.find((agent) => agent.name === agentName);
|
|
558
|
-
if (hit !== undefined || result.nextCursor === undefined)
|
|
577
|
+
if (hit !== undefined || result.nextCursor === undefined) {
|
|
559
578
|
return hit;
|
|
579
|
+
}
|
|
560
580
|
cursor = result.nextCursor;
|
|
561
581
|
}
|
|
562
582
|
return undefined;
|
|
@@ -567,12 +587,16 @@ export class MoltZapService {
|
|
|
567
587
|
* Generate a system reminder with updates from other conversations.
|
|
568
588
|
* Each conversation has its own view of what's "new" — markers are tracked
|
|
569
589
|
* per viewing conversation and advanced after notification.
|
|
590
|
+
* @param currentConvId Value supplied to the operation.
|
|
591
|
+
* @param opts Value supplied to the operation.
|
|
592
|
+
* @returns The context options result.
|
|
570
593
|
*/
|
|
571
594
|
getContext(currentConvId, opts) {
|
|
572
595
|
const contextOptions = opts ?? {};
|
|
573
596
|
const { entries, commit } = this.peekContextEntries(currentConvId, contextOptions);
|
|
574
|
-
if (entries.length === 0)
|
|
597
|
+
if (entries.length === 0) {
|
|
575
598
|
return null;
|
|
599
|
+
}
|
|
576
600
|
commit();
|
|
577
601
|
return formatCrossConversationBlock(entries, {
|
|
578
602
|
header: `Recent updates (you are in conv:${currentConvId}):`,
|
|
@@ -583,6 +607,11 @@ export class MoltZapService {
|
|
|
583
607
|
* Call `commit()` on the result to mark the returned messages as seen so
|
|
584
608
|
* subsequent peeks return only what's new. A caller that reads without
|
|
585
609
|
* committing can re-peek idempotently.
|
|
610
|
+
* @param currentConvId Value supplied to the operation.
|
|
611
|
+
* @param opts Value supplied to the operation.
|
|
612
|
+
* @param opts.maxConversations Value supplied to the operation.
|
|
613
|
+
* @param opts.maxMessagesPerConv Value supplied to the operation.
|
|
614
|
+
* @returns The max convs result.
|
|
586
615
|
*/
|
|
587
616
|
peekContextEntries(currentConvId, opts) {
|
|
588
617
|
const maxConvs = opts?.maxConversations ?? DEFAULT_MAX_CONTEXT_CONVERSATIONS;
|
|
@@ -592,13 +621,17 @@ export class MoltZapService {
|
|
|
592
621
|
const { entries, pendingAdvances } = buildContextEntries(candidates.slice(0, maxConvs), state, maxMsgsPerConv);
|
|
593
622
|
return {
|
|
594
623
|
entries,
|
|
595
|
-
commit: () =>
|
|
624
|
+
commit: () => {
|
|
625
|
+
this.advanceLastNotified(currentConvId, pendingAdvances);
|
|
626
|
+
},
|
|
596
627
|
};
|
|
597
628
|
}
|
|
598
629
|
/**
|
|
599
630
|
* Return all new messages from all other conversations as full transcripts,
|
|
600
631
|
* sorted chronologically. Uses the same lastNotified markers as
|
|
601
632
|
* peekContextEntries. Call commit() to advance markers.
|
|
633
|
+
* @param currentConvId Value supplied to the operation.
|
|
634
|
+
* @returns The all messages result.
|
|
602
635
|
*/
|
|
603
636
|
peekFullMessages(currentConvId) {
|
|
604
637
|
const { messagesMap, conversationsMap, agentNamesMap, viewMarkers } = this.readCrossConvState(currentConvId);
|
|
@@ -616,12 +649,17 @@ export class MoltZapService {
|
|
|
616
649
|
timestamp: m.createdAt,
|
|
617
650
|
});
|
|
618
651
|
}
|
|
619
|
-
pendingAdvances.push([
|
|
652
|
+
pendingAdvances.push([
|
|
653
|
+
convId,
|
|
654
|
+
/* Safe because the surrounding invariant establishes this asserted shape. */ newMsgs[newMsgs.length - 1].id,
|
|
655
|
+
]);
|
|
620
656
|
}
|
|
621
657
|
allMessages.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
622
658
|
return {
|
|
623
659
|
messages: allMessages,
|
|
624
|
-
commit: () =>
|
|
660
|
+
commit: () => {
|
|
661
|
+
this.advanceLastNotified(currentConvId, pendingAdvances);
|
|
662
|
+
},
|
|
625
663
|
};
|
|
626
664
|
}
|
|
627
665
|
readCrossConvState(currentConvId) {
|
|
@@ -644,13 +682,15 @@ export class MoltZapService {
|
|
|
644
682
|
*iterNewMessagesByConv(messagesMap, viewMarkers, currentConvId) {
|
|
645
683
|
for (const [convId, msgs] of messagesMap) {
|
|
646
684
|
const newMsgs = newMessagesForConversation(convId, msgs, viewMarkers, currentConvId);
|
|
647
|
-
if (newMsgs.length > 0)
|
|
685
|
+
if (newMsgs.length > 0) {
|
|
648
686
|
yield [convId, newMsgs];
|
|
687
|
+
}
|
|
649
688
|
}
|
|
650
689
|
}
|
|
651
690
|
advanceLastNotified(currentConvId, pendingAdvances) {
|
|
652
|
-
if (pendingAdvances.length === 0)
|
|
691
|
+
if (pendingAdvances.length === 0) {
|
|
653
692
|
return;
|
|
693
|
+
}
|
|
654
694
|
Effect.runSync(Ref.update(this.lastNotifiedRef, (outer) => {
|
|
655
695
|
let markers = getOr(outer, currentConvId, () => HashMap.empty());
|
|
656
696
|
for (const [convId, msgId] of pendingAdvances) {
|
|
@@ -668,6 +708,10 @@ export class MoltZapService {
|
|
|
668
708
|
* Outbound RPC, typed per method — delegates to the agent client's typed
|
|
669
709
|
* `call`. `call("agent/identity/agents/list", payload)` recovers the result and that
|
|
670
710
|
* method's tagged-error union; an app-only method does not typecheck.
|
|
711
|
+
* @param tag Value supplied to the operation.
|
|
712
|
+
* @param payload Value supplied to the operation.
|
|
713
|
+
* @param opts Value supplied to the operation.
|
|
714
|
+
* @returns The client result.
|
|
671
715
|
*/
|
|
672
716
|
call(tag, payload, opts) {
|
|
673
717
|
return Effect.suspend(() => {
|
|
@@ -694,47 +738,49 @@ export class MoltZapService {
|
|
|
694
738
|
this.dispatchTypedNotification(notification);
|
|
695
739
|
}
|
|
696
740
|
recordNotificationTrace(notification) {
|
|
697
|
-
Effect.runFork(appendClientEventTrace(notificationTraceRecord(notification, this.
|
|
741
|
+
Effect.runFork(appendClientEventTrace(notificationTraceRecord(notification, this.ownAgentIdValue)));
|
|
698
742
|
}
|
|
699
743
|
dispatchTypedNotification(notification) {
|
|
700
|
-
if (this.dispatchMessageNotification(notification))
|
|
744
|
+
if (this.dispatchMessageNotification(notification)) {
|
|
701
745
|
return;
|
|
702
|
-
|
|
746
|
+
}
|
|
747
|
+
if (this.dispatchConversationNotification(notification)) {
|
|
703
748
|
return;
|
|
749
|
+
}
|
|
704
750
|
this.dispatchAppNotification(notification);
|
|
705
751
|
}
|
|
706
752
|
dispatchMessageNotification(notification) {
|
|
707
|
-
if (isNotificationDeliveryFor(notification,
|
|
753
|
+
if (isNotificationDeliveryFor(notification, messageReceivedNotificationDefinition)) {
|
|
708
754
|
this.handleMessageReceivedNotification(notification.params);
|
|
709
755
|
return true;
|
|
710
756
|
}
|
|
711
757
|
return false;
|
|
712
758
|
}
|
|
713
759
|
dispatchConversationNotification(notification) {
|
|
714
|
-
if (isNotificationDeliveryFor(notification,
|
|
760
|
+
if (isNotificationDeliveryFor(notification, conversationCreatedNotificationDefinition)) {
|
|
715
761
|
this.handleConversationCreatedNotification(notification.params);
|
|
716
762
|
return true;
|
|
717
763
|
}
|
|
718
|
-
if (isNotificationDeliveryFor(notification,
|
|
764
|
+
if (isNotificationDeliveryFor(notification, conversationArchivedNotificationDefinition)) {
|
|
719
765
|
this.handleConversationArchivedNotification(notification.params);
|
|
720
766
|
return true;
|
|
721
767
|
}
|
|
722
|
-
if (isNotificationDeliveryFor(notification,
|
|
768
|
+
if (isNotificationDeliveryFor(notification, conversationUnarchivedNotificationDefinition)) {
|
|
723
769
|
this.handleConversationUnarchivedNotification(notification.params);
|
|
724
770
|
return true;
|
|
725
771
|
}
|
|
726
772
|
return false;
|
|
727
773
|
}
|
|
728
774
|
dispatchAppNotification(notification) {
|
|
729
|
-
if (isNotificationDeliveryFor(notification,
|
|
775
|
+
if (isNotificationDeliveryFor(notification, dispatchRelease)) {
|
|
730
776
|
fanout(this.handlers.dispatchRelease, notification.params);
|
|
731
777
|
return;
|
|
732
778
|
}
|
|
733
|
-
if (isNotificationDeliveryFor(notification,
|
|
779
|
+
if (isNotificationDeliveryFor(notification, dispatchLeaseConsumed)) {
|
|
734
780
|
fanout(this.handlers.dispatchLeaseConsumed, notification.params);
|
|
735
781
|
return;
|
|
736
782
|
}
|
|
737
|
-
if (isNotificationDeliveryFor(notification,
|
|
783
|
+
if (isNotificationDeliveryFor(notification, dispatchLeaseExpired)) {
|
|
738
784
|
fanout(this.handlers.dispatchLeaseExpired, notification.params);
|
|
739
785
|
}
|
|
740
786
|
}
|
|
@@ -749,6 +795,9 @@ export class MoltZapService {
|
|
|
749
795
|
* replay, dual subscription) is suppressed to one
|
|
750
796
|
* `on("message", ...)` event. `agent/message/list` returns raw history
|
|
751
797
|
* unfiltered; consumers that combine both feeds dedup themselves.
|
|
798
|
+
* @param convId Value supplied to the operation.
|
|
799
|
+
* @param msgId Value supplied to the operation.
|
|
800
|
+
* @returns The dedup window result.
|
|
752
801
|
*/
|
|
753
802
|
recordMessageIdIfNew(convId, msgId) {
|
|
754
803
|
let dedupWindow = this.seenMessageIds.get(convId);
|
|
@@ -774,7 +823,7 @@ export class MoltZapService {
|
|
|
774
823
|
// Name resolution is driven lazily by channel-core's serialized consumer
|
|
775
824
|
// via resolveAgentName(), which populates agentNamesRef on first miss and
|
|
776
825
|
// hits the cache on every subsequent message.
|
|
777
|
-
if (msg.senderId !== this.
|
|
826
|
+
if (msg.senderId !== this.ownAgentIdValue) {
|
|
778
827
|
fanout(this.handlers.message, {
|
|
779
828
|
taskId: notification.taskId,
|
|
780
829
|
message: msg,
|