@openlap/openlap 1.1.2 → 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 -30
- package/dist/proxy.js +17 -5
- 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,38 +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
|
-
// Fire immediate heartbeat on first join
|
|
229
|
-
const doHeartbeat = async () => {
|
|
230
|
-
const token = this.getToken();
|
|
231
|
-
if (!token) {
|
|
232
|
-
process.stderr.write(`[openlap] heartbeat skipped: no token\n`);
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
try {
|
|
236
|
-
const res = await fetch(`${this.baseUrl}/api/presence/heartbeat`, {
|
|
237
|
-
method: "POST",
|
|
238
|
-
headers: { "Authorization": `Bearer ${token}` },
|
|
239
|
-
});
|
|
240
|
-
if (!res.ok) {
|
|
241
|
-
process.stderr.write(`[openlap] heartbeat failed: ${res.status}\n`);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
catch (err) {
|
|
245
|
-
process.stderr.write(`[openlap] heartbeat error: ${err}\n`);
|
|
246
|
-
}
|
|
247
|
-
};
|
|
248
|
-
doHeartbeat(); // immediate first heartbeat
|
|
249
|
-
this.heartbeatInterval = setInterval(doHeartbeat, 30_000);
|
|
248
|
+
this.heartbeat().catch(() => { });
|
|
250
249
|
}
|
|
251
250
|
stopHeartbeat() {
|
|
252
|
-
|
|
253
|
-
clearInterval(this.heartbeatInterval);
|
|
254
|
-
this.heartbeatInterval = null;
|
|
255
|
-
}
|
|
251
|
+
// No interval to clear -- heartbeats piggyback on tool calls
|
|
256
252
|
}
|
|
257
253
|
stop() {
|
|
258
254
|
this.stopHeartbeat();
|
package/dist/proxy.js
CHANGED
|
@@ -180,11 +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
|
-
//
|
|
184
|
-
if (result
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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") }] };
|
|
188
196
|
}
|
|
189
197
|
if (name === "leave_channel") {
|
|
190
198
|
const channel = args.channel;
|
|
@@ -222,6 +230,10 @@ export async function startProxy() {
|
|
|
222
230
|
process.stderr.write(`[openlap] auto-saved: ${saved}\n`);
|
|
223
231
|
}
|
|
224
232
|
}
|
|
233
|
+
// -- Heartbeat on every tool call (replaces unreliable setInterval) ------
|
|
234
|
+
if (channels.hasChannels()) {
|
|
235
|
+
channels.heartbeat().catch(() => { });
|
|
236
|
+
}
|
|
225
237
|
// -- Forward to remote --------------------------------------------------
|
|
226
238
|
const result = await remote.callTool({ name, arguments: args });
|
|
227
239
|
// v1 compat: auto-subscribe to feeds from track tool usage
|