@openlap/openlap 1.1.1 → 1.1.3
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/channel.d.ts +2 -1
- package/dist/channel.js +26 -22
- package/dist/proxy.js +17 -1
- package/package.json +1 -1
package/dist/channel.d.ts
CHANGED
|
@@ -33,7 +33,6 @@ export declare class ChannelManager {
|
|
|
33
33
|
private onPresence;
|
|
34
34
|
private getToken;
|
|
35
35
|
private mySessionId;
|
|
36
|
-
private heartbeatInterval;
|
|
37
36
|
constructor(baseUrl: string, onUpdate: UpdateCallback, getToken: () => string | undefined, onPresence?: PresenceCallback);
|
|
38
37
|
setSessionId(sessionId: string): void;
|
|
39
38
|
join(channel: string, name?: string): Promise<JoinResponse | null>;
|
|
@@ -43,6 +42,8 @@ export declare class ChannelManager {
|
|
|
43
42
|
getFocusedChannel(): string | undefined;
|
|
44
43
|
isJoined(channel: string): boolean;
|
|
45
44
|
isMuted(channel: string): boolean;
|
|
45
|
+
hasChannels(): boolean;
|
|
46
|
+
heartbeat(): Promise<void>;
|
|
46
47
|
private connectSSE;
|
|
47
48
|
private readStream;
|
|
48
49
|
private handleSSEEvent;
|
package/dist/channel.js
CHANGED
|
@@ -9,7 +9,6 @@ export class ChannelManager {
|
|
|
9
9
|
onPresence;
|
|
10
10
|
getToken;
|
|
11
11
|
mySessionId;
|
|
12
|
-
heartbeatInterval = null;
|
|
13
12
|
constructor(baseUrl, onUpdate, getToken, onPresence) {
|
|
14
13
|
this.baseUrl = baseUrl;
|
|
15
14
|
this.onUpdate = onUpdate;
|
|
@@ -39,6 +38,10 @@ export class ChannelManager {
|
|
|
39
38
|
}
|
|
40
39
|
const data = (await res.json());
|
|
41
40
|
process.stderr.write(`[openlap] joined ${channel} (${data.present?.length ?? 0} present)\n`);
|
|
41
|
+
// Set session_id for echo suppression BEFORE opening SSE (fixes race condition)
|
|
42
|
+
if (data.session_id) {
|
|
43
|
+
this.mySessionId = data.session_id;
|
|
44
|
+
}
|
|
42
45
|
// Start SSE subscription
|
|
43
46
|
const controller = new AbortController();
|
|
44
47
|
this.channels.set(channel, { controller, muted: false, focused: false });
|
|
@@ -118,6 +121,24 @@ export class ChannelManager {
|
|
|
118
121
|
isMuted(channel) {
|
|
119
122
|
return this.channels.get(channel)?.muted ?? false;
|
|
120
123
|
}
|
|
124
|
+
hasChannels() {
|
|
125
|
+
return this.channels.size > 0;
|
|
126
|
+
}
|
|
127
|
+
// Public heartbeat -- called by proxy on every tool invocation.
|
|
128
|
+
async heartbeat() {
|
|
129
|
+
const token = this.getToken();
|
|
130
|
+
if (!token)
|
|
131
|
+
return;
|
|
132
|
+
try {
|
|
133
|
+
await fetch(`${this.baseUrl}/api/presence/heartbeat`, {
|
|
134
|
+
method: "POST",
|
|
135
|
+
headers: { "Authorization": `Bearer ${token}` },
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// not critical
|
|
140
|
+
}
|
|
141
|
+
}
|
|
121
142
|
// SSE connection for a channel
|
|
122
143
|
connectSSE(channel, controller) {
|
|
123
144
|
const url = `${this.baseUrl}/feed/${channel}/sse`;
|
|
@@ -221,30 +242,13 @@ export class ChannelManager {
|
|
|
221
242
|
}
|
|
222
243
|
}
|
|
223
244
|
}
|
|
224
|
-
//
|
|
245
|
+
// Immediate heartbeat on join. Ongoing heartbeats piggyback on tool calls
|
|
246
|
+
// (setInterval doesn't fire reliably in MCP stdio context).
|
|
225
247
|
startHeartbeat() {
|
|
226
|
-
|
|
227
|
-
return;
|
|
228
|
-
this.heartbeatInterval = setInterval(async () => {
|
|
229
|
-
const token = this.getToken();
|
|
230
|
-
if (!token)
|
|
231
|
-
return;
|
|
232
|
-
try {
|
|
233
|
-
await fetch(`${this.baseUrl}/api/presence/heartbeat`, {
|
|
234
|
-
method: "POST",
|
|
235
|
-
headers: { "Authorization": `Bearer ${token}` },
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
catch {
|
|
239
|
-
// heartbeat failure is not critical
|
|
240
|
-
}
|
|
241
|
-
}, 30_000);
|
|
248
|
+
this.heartbeat().catch(() => { });
|
|
242
249
|
}
|
|
243
250
|
stopHeartbeat() {
|
|
244
|
-
|
|
245
|
-
clearInterval(this.heartbeatInterval);
|
|
246
|
-
this.heartbeatInterval = null;
|
|
247
|
-
}
|
|
251
|
+
// No interval to clear -- heartbeats piggyback on tool calls
|
|
248
252
|
}
|
|
249
253
|
stop() {
|
|
250
254
|
this.stopHeartbeat();
|
package/dist/proxy.js
CHANGED
|
@@ -180,7 +180,19 @@ export async function startProxy() {
|
|
|
180
180
|
if (!channel)
|
|
181
181
|
return { content: [{ type: "text", text: "channel required" }], isError: true };
|
|
182
182
|
const result = await channels.join(channel, args.name);
|
|
183
|
-
|
|
183
|
+
// session_id set internally by ChannelManager.join (before SSE opens)
|
|
184
|
+
if (!result)
|
|
185
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "already joined or failed" }) }] };
|
|
186
|
+
const r = result;
|
|
187
|
+
const lines = [`Joined ${channel}.`];
|
|
188
|
+
if (r.feed_url)
|
|
189
|
+
lines.push(`Feed: ${r.feed_url}`);
|
|
190
|
+
if (r.present?.length)
|
|
191
|
+
lines.push(`Present: ${r.present.join(", ")}`);
|
|
192
|
+
if (r.recent?.length)
|
|
193
|
+
lines.push(`Recent: ${r.recent.length} posts`);
|
|
194
|
+
lines.push("", JSON.stringify(result));
|
|
195
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
184
196
|
}
|
|
185
197
|
if (name === "leave_channel") {
|
|
186
198
|
const channel = args.channel;
|
|
@@ -218,6 +230,10 @@ export async function startProxy() {
|
|
|
218
230
|
process.stderr.write(`[openlap] auto-saved: ${saved}\n`);
|
|
219
231
|
}
|
|
220
232
|
}
|
|
233
|
+
// -- Heartbeat on every tool call (replaces unreliable setInterval) ------
|
|
234
|
+
if (channels.hasChannels()) {
|
|
235
|
+
channels.heartbeat().catch(() => { });
|
|
236
|
+
}
|
|
221
237
|
// -- Forward to remote --------------------------------------------------
|
|
222
238
|
const result = await remote.callTool({ name, arguments: args });
|
|
223
239
|
// v1 compat: auto-subscribe to feeds from track tool usage
|