@openlap/openlap 1.1.0 → 1.1.2

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.js CHANGED
@@ -225,20 +225,28 @@ export class ChannelManager {
225
225
  startHeartbeat() {
226
226
  if (this.heartbeatInterval)
227
227
  return;
228
- this.heartbeatInterval = setInterval(async () => {
228
+ // Fire immediate heartbeat on first join
229
+ const doHeartbeat = async () => {
229
230
  const token = this.getToken();
230
- if (!token)
231
+ if (!token) {
232
+ process.stderr.write(`[openlap] heartbeat skipped: no token\n`);
231
233
  return;
234
+ }
232
235
  try {
233
- await fetch(`${this.baseUrl}/api/presence/heartbeat`, {
236
+ const res = await fetch(`${this.baseUrl}/api/presence/heartbeat`, {
234
237
  method: "POST",
235
238
  headers: { "Authorization": `Bearer ${token}` },
236
239
  });
240
+ if (!res.ok) {
241
+ process.stderr.write(`[openlap] heartbeat failed: ${res.status}\n`);
242
+ }
237
243
  }
238
- catch {
239
- // heartbeat failure is not critical
244
+ catch (err) {
245
+ process.stderr.write(`[openlap] heartbeat error: ${err}\n`);
240
246
  }
241
- }, 30_000);
247
+ };
248
+ doHeartbeat(); // immediate first heartbeat
249
+ this.heartbeatInterval = setInterval(doHeartbeat, 30_000);
242
250
  }
243
251
  stopHeartbeat() {
244
252
  if (this.heartbeatInterval) {
package/dist/proxy.js CHANGED
@@ -117,10 +117,58 @@ export async function startProxy() {
117
117
  }
118
118
  });
119
119
  }, getAuthToken);
120
- // Forward tool listing from remote
120
+ // Forward tool listing from remote + inject proxy-side channel tools
121
121
  server.setRequestHandler(ListToolsRequestSchema, async () => {
122
122
  const result = await remote.listTools();
123
- return { tools: result.tools };
123
+ // v2: proxy-side channel tools (not registered on server)
124
+ const proxyTools = [
125
+ {
126
+ name: "join_channel",
127
+ description: "Join a channel. Registers presence, opens SSE subscription, returns who's here and recent posts. You'll receive live updates from this channel. Use leave_channel to unsubscribe.",
128
+ inputSchema: {
129
+ type: "object",
130
+ properties: {
131
+ channel: { type: "string", description: "Channel name (e.g. \"openlap-v2\")" },
132
+ name: { type: "string", description: "Display name (defaults to GitHub login). Use for role names like \"worker\" or \"adversary\"." },
133
+ },
134
+ required: ["channel"],
135
+ },
136
+ },
137
+ {
138
+ name: "leave_channel",
139
+ description: "Leave a channel. Removes presence and stops SSE subscription. Other members see you leave.",
140
+ inputSchema: {
141
+ type: "object",
142
+ properties: {
143
+ channel: { type: "string", description: "Channel name" },
144
+ },
145
+ required: ["channel"],
146
+ },
147
+ },
148
+ {
149
+ name: "mute_channel",
150
+ description: "Mute a channel. Still present (heartbeat continues) but notifications stop. Other members still see you as present. Use focus_channel to unmute.",
151
+ inputSchema: {
152
+ type: "object",
153
+ properties: {
154
+ channel: { type: "string", description: "Channel name" },
155
+ },
156
+ required: ["channel"],
157
+ },
158
+ },
159
+ {
160
+ name: "focus_channel",
161
+ description: "Focus on a channel. Unmutes it and pins it to briefings. Only one channel can be focused at a time.",
162
+ inputSchema: {
163
+ type: "object",
164
+ properties: {
165
+ channel: { type: "string", description: "Channel name" },
166
+ },
167
+ required: ["channel"],
168
+ },
169
+ },
170
+ ];
171
+ return { tools: [...result.tools, ...proxyTools] };
124
172
  });
125
173
  // Forward tool calls with local enhancements
126
174
  server.setRequestHandler(CallToolRequestSchema, async (req) => {
@@ -132,6 +180,10 @@ export async function startProxy() {
132
180
  if (!channel)
133
181
  return { content: [{ type: "text", text: "channel required" }], isError: true };
134
182
  const result = await channels.join(channel, args.name);
183
+ // Set session_id for echo suppression (returned by server on join)
184
+ if (result && result.session_id) {
185
+ channels.setSessionId(result.session_id);
186
+ }
135
187
  return { content: [{ type: "text", text: JSON.stringify(result ?? { error: "already joined or failed" }) }] };
136
188
  }
137
189
  if (name === "leave_channel") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openlap/openlap",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Local MCP proxy for openlap.app -- auto-save, live feeds, project detection, one install",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",