@clawchatsai/connector 0.0.60 → 0.0.62

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.
@@ -70,6 +70,21 @@ export class WebRTCPeerManager extends EventEmitter {
70
70
  }
71
71
  async handleOffer(offer) {
72
72
  const { connectionId, sdp, candidates } = offer;
73
+ // ICE restart: existing PC — renegotiate on it, DataChannel stays open.
74
+ const existingPc = this.peerConnections.get(connectionId);
75
+ if (existingPc) {
76
+ console.log(`[WebRTCPeerManager] ICE restart for connection ${connectionId}`);
77
+ await existingPc.setRemoteDescription(new RTCSessionDescription({ sdp, type: 'offer' }));
78
+ for (const rawCandidate of candidates) {
79
+ try {
80
+ await existingPc.addIceCandidate(new RTCIceCandidate(rawCandidate));
81
+ }
82
+ catch { /* ignore */ }
83
+ }
84
+ const answer = await existingPc.createAnswer();
85
+ await existingPc.setLocalDescription(answer);
86
+ return { connectionId, sdp: answer.sdp, candidates: [] };
87
+ }
73
88
  console.log(`[WebRTCPeerManager] Handling ICE offer for connection ${connectionId}`);
74
89
  const iceServers = this.pendingIceServers.get(connectionId) ?? [
75
90
  { urls: 'stun:stun.l.google.com:19302' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawchatsai/connector",
3
- "version": "0.0.60",
3
+ "version": "0.0.62",
4
4
  "type": "module",
5
5
  "description": "ClawChats OpenClaw plugin — P2P tunnel + local API bridge",
6
6
  "main": "dist/index.js",
package/server.js CHANGED
@@ -3047,54 +3047,7 @@ class GatewayClient {
3047
3047
  }
3048
3048
 
3049
3049
  _writeActivityToDb(runId, log) {
3050
- if (!log._parsed) {
3051
- log._parsed = parseSessionKey(log.sessionKey);
3052
- }
3053
- const parsed = log._parsed;
3054
- if (!parsed) return;
3055
-
3056
- const db = getDb(parsed.workspace);
3057
- if (!db) return;
3058
-
3059
- const cleanSteps = log.steps.map(s => { const c = {...s}; delete c._sealed; return c; });
3060
- const summary = this.generateActivitySummary(log.steps);
3061
- const now = Date.now();
3062
-
3063
- if (!log._messageId) {
3064
- // First write — INSERT the assistant message row
3065
- const thread = db.prepare('SELECT id FROM threads WHERE id = ?').get(parsed.threadId);
3066
- if (!thread) return;
3067
-
3068
- const messageId = `gw-activity-${runId}`;
3069
- const metadata = { activityLog: cleanSteps, activitySummary: summary, pending: true };
3070
-
3071
- db.prepare(`
3072
- INSERT INTO messages (id, thread_id, role, content, status, metadata, timestamp, created_at)
3073
- VALUES (?, ?, 'assistant', '', 'sent', ?, ?, ?)
3074
- `).run(messageId, parsed.threadId, JSON.stringify(metadata), now, now);
3075
-
3076
- log._messageId = messageId;
3077
-
3078
- // First event — broadcast message-saved so browser creates the message element
3079
- this.broadcastToBrowsers(JSON.stringify({
3080
- type: 'clawchats',
3081
- event: 'message-saved',
3082
- threadId: parsed.threadId,
3083
- workspace: parsed.workspace,
3084
- messageId,
3085
- timestamp: now
3086
- }));
3087
- } else {
3088
- // Subsequent writes — UPDATE metadata on existing row
3089
- const existing = db.prepare('SELECT metadata FROM messages WHERE id = ?').get(log._messageId);
3090
- const metadata = existing?.metadata ? JSON.parse(existing.metadata) : {};
3091
- metadata.activityLog = cleanSteps;
3092
- metadata.activitySummary = summary;
3093
- metadata.pending = true;
3094
-
3095
- db.prepare('UPDATE messages SET metadata = ? WHERE id = ?')
3096
- .run(JSON.stringify(metadata), log._messageId);
3097
- }
3050
+ writeActivityToDb(getDb, this.broadcastToBrowsers.bind(this), runId, log);
3098
3051
  }
3099
3052
 
3100
3053
  _broadcastActivityUpdate(runId, log) {
@@ -3115,59 +3068,7 @@ class GatewayClient {
3115
3068
  }
3116
3069
 
3117
3070
  generateActivitySummary(steps) {
3118
- const toolSteps = steps.filter(s => s.type === 'tool' && s.phase !== 'result' && s.phase !== 'update');
3119
- const hasThinking = steps.some(s => s.type === 'thinking' && s.text);
3120
- const hasNarration = steps.some(s => s.type === 'assistant' && s.text?.trim());
3121
- if (toolSteps.length === 0 && !hasThinking && !hasNarration) return null;
3122
- if (toolSteps.length === 0 && hasThinking) return 'Reasoned through the problem';
3123
- if (toolSteps.length === 0 && hasNarration) return 'Processed in multiple steps';
3124
-
3125
- // Count by tool name
3126
- const counts = {};
3127
- for (const s of toolSteps) {
3128
- const name = s.name || 'unknown';
3129
- counts[name] = (counts[name] || 0) + 1;
3130
- }
3131
-
3132
- // Build description
3133
- const parts = [];
3134
- const toolNames = {
3135
- 'web_search': 'searched the web',
3136
- 'web_fetch': 'fetched web pages',
3137
- 'Read': 'read files',
3138
- 'read': 'read files',
3139
- 'Write': 'wrote files',
3140
- 'write': 'wrote files',
3141
- 'Edit': 'edited files',
3142
- 'edit': 'edited files',
3143
- 'exec': 'ran commands',
3144
- 'Bash': 'ran commands',
3145
- 'browser': 'browsed the web',
3146
- 'memory_search': 'searched memory',
3147
- 'memory_store': 'saved to memory',
3148
- 'image': 'analyzed images',
3149
- 'message': 'sent messages',
3150
- 'sessions_spawn': 'spawned sub-agents',
3151
- 'cron': 'managed cron jobs',
3152
- 'Grep': 'searched code',
3153
- 'grep': 'searched code',
3154
- 'Glob': 'found files',
3155
- 'glob': 'found files'
3156
- };
3157
-
3158
- for (const [name, count] of Object.entries(counts)) {
3159
- const friendly = toolNames[name];
3160
- if (friendly) {
3161
- parts.push(count > 1 ? `${friendly} (${count}×)` : friendly);
3162
- } else {
3163
- parts.push(count > 1 ? `used ${name} (${count}×)` : `used ${name}`);
3164
- }
3165
- }
3166
-
3167
- if (parts.length === 0) return null;
3168
- if (parts.length === 1) return parts[0].charAt(0).toUpperCase() + parts[0].slice(1);
3169
- const last = parts.pop();
3170
- return (parts.join(', ') + ' and ' + last).replace(/^./, c => c.toUpperCase());
3071
+ return generateActivitySummary(steps);
3171
3072
  }
3172
3073
 
3173
3074
  broadcastToBrowsers(data) {
@@ -3306,6 +3207,117 @@ function extractContent(message) {
3306
3207
  return '';
3307
3208
  }
3308
3209
 
3210
+ // ─── Shared activity log helpers ─────────────────────────────────────────────
3211
+ // Pure functions extracted from GatewayClient / _GatewayClient so both classes
3212
+ // share a single implementation. Pass the workspace-scoped DB getter and a
3213
+ // bound broadcastToBrowsers fn as arguments.
3214
+
3215
+ function generateActivitySummary(steps) {
3216
+ const toolSteps = steps.filter(s => s.type === 'tool' && s.phase !== 'result' && s.phase !== 'update');
3217
+ const hasThinking = steps.some(s => s.type === 'thinking' && s.text);
3218
+ const hasNarration = steps.some(s => s.type === 'assistant' && s.text?.trim());
3219
+ if (toolSteps.length === 0 && !hasThinking && !hasNarration) return null;
3220
+ if (toolSteps.length === 0 && hasThinking) return 'Reasoned through the problem';
3221
+ if (toolSteps.length === 0 && hasNarration) return 'Processed in multiple steps';
3222
+
3223
+ const counts = {};
3224
+ for (const s of toolSteps) {
3225
+ const name = s.name || 'unknown';
3226
+ counts[name] = (counts[name] || 0) + 1;
3227
+ }
3228
+
3229
+ const parts = [];
3230
+ const toolNames = {
3231
+ 'web_search': 'searched the web',
3232
+ 'web_fetch': 'fetched web pages',
3233
+ 'Read': 'read files',
3234
+ 'read': 'read files',
3235
+ 'Write': 'wrote files',
3236
+ 'write': 'wrote files',
3237
+ 'Edit': 'edited files',
3238
+ 'edit': 'edited files',
3239
+ 'exec': 'ran commands',
3240
+ 'Bash': 'ran commands',
3241
+ 'browser': 'browsed the web',
3242
+ 'memory_search': 'searched memory',
3243
+ 'memory_store': 'saved to memory',
3244
+ 'image': 'analyzed images',
3245
+ 'message': 'sent messages',
3246
+ 'sessions_spawn': 'spawned sub-agents',
3247
+ 'cron': 'managed cron jobs',
3248
+ 'Grep': 'searched code',
3249
+ 'grep': 'searched code',
3250
+ 'Glob': 'found files',
3251
+ 'glob': 'found files'
3252
+ };
3253
+
3254
+ for (const [name, count] of Object.entries(counts)) {
3255
+ const friendly = toolNames[name];
3256
+ if (friendly) {
3257
+ parts.push(count > 1 ? `${friendly} (${count}×)` : friendly);
3258
+ } else {
3259
+ parts.push(count > 1 ? `used ${name} (${count}×)` : `used ${name}`);
3260
+ }
3261
+ }
3262
+
3263
+ if (parts.length === 0) return null;
3264
+ if (parts.length === 1) return parts[0].charAt(0).toUpperCase() + parts[0].slice(1);
3265
+ const last = parts.pop();
3266
+ return (parts.join(', ') + ' and ' + last).replace(/^./, c => c.toUpperCase());
3267
+ }
3268
+
3269
+ function writeActivityToDb(getDbFn, broadcastFn, runId, log) {
3270
+ if (!log._parsed) {
3271
+ log._parsed = parseSessionKey(log.sessionKey);
3272
+ }
3273
+ const parsed = log._parsed;
3274
+ if (!parsed) return;
3275
+
3276
+ const db = getDbFn(parsed.workspace);
3277
+ if (!db) return;
3278
+
3279
+ const cleanSteps = log.steps.map(s => { const c = {...s}; delete c._sealed; return c; });
3280
+ const summary = generateActivitySummary(log.steps);
3281
+ const now = Date.now();
3282
+
3283
+ if (!log._messageId) {
3284
+ const thread = db.prepare('SELECT id FROM threads WHERE id = ?').get(parsed.threadId);
3285
+ if (!thread) return;
3286
+
3287
+ const messageId = `gw-activity-${runId}`;
3288
+ const metadata = { activityLog: cleanSteps, activitySummary: summary, pending: true };
3289
+
3290
+ try {
3291
+ db.prepare(`
3292
+ INSERT OR IGNORE INTO messages (id, thread_id, role, content, status, metadata, timestamp, created_at)
3293
+ VALUES (?, ?, 'assistant', '', 'sent', ?, ?, ?)
3294
+ `).run(messageId, parsed.threadId, JSON.stringify(metadata), now, now);
3295
+
3296
+ log._messageId = messageId;
3297
+
3298
+ broadcastFn(JSON.stringify({
3299
+ type: 'clawchats',
3300
+ event: 'message-saved',
3301
+ threadId: parsed.threadId,
3302
+ workspace: parsed.workspace,
3303
+ messageId,
3304
+ timestamp: now
3305
+ }));
3306
+ } catch (err) {
3307
+ console.error(`[activity] Failed to write activity ${messageId}:`, err.message);
3308
+ }
3309
+ } else {
3310
+ const existing = db.prepare('SELECT metadata FROM messages WHERE id = ?').get(log._messageId);
3311
+ const metadata = existing?.metadata ? JSON.parse(existing.metadata) : {};
3312
+ metadata.activityLog = cleanSteps;
3313
+ metadata.activitySummary = summary;
3314
+ metadata.pending = true;
3315
+
3316
+ db.prepare('UPDATE messages SET metadata = ? WHERE id = ?')
3317
+ .run(JSON.stringify(metadata), log._messageId);
3318
+ }
3319
+ }
3320
+
3309
3321
  const gatewayClient = new GatewayClient();
3310
3322
 
3311
3323
  // ─── createApp Factory ───────────────────────────────────────────────────────
@@ -4264,51 +4276,7 @@ export function createApp(config = {}) {
4264
4276
  }
4265
4277
 
4266
4278
  _writeActivityToDb(runId, log) {
4267
- if (!log._parsed) {
4268
- log._parsed = parseSessionKey(log.sessionKey);
4269
- }
4270
- const parsed = log._parsed;
4271
- if (!parsed) return;
4272
-
4273
- const db = _getDb(parsed.workspace);
4274
- if (!db) return;
4275
-
4276
- const cleanSteps = log.steps.map(s => { const c = {...s}; delete c._sealed; return c; });
4277
- const summary = this.generateActivitySummary(log.steps);
4278
- const now = Date.now();
4279
-
4280
- if (!log._messageId) {
4281
- const thread = db.prepare('SELECT id FROM threads WHERE id = ?').get(parsed.threadId);
4282
- if (!thread) return;
4283
-
4284
- const messageId = `gw-activity-${runId}`;
4285
- const metadata = { activityLog: cleanSteps, activitySummary: summary, pending: true };
4286
-
4287
- db.prepare(`
4288
- INSERT INTO messages (id, thread_id, role, content, status, metadata, timestamp, created_at)
4289
- VALUES (?, ?, 'assistant', '', 'sent', ?, ?, ?)
4290
- `).run(messageId, parsed.threadId, JSON.stringify(metadata), now, now);
4291
-
4292
- log._messageId = messageId;
4293
-
4294
- this.broadcastToBrowsers(JSON.stringify({
4295
- type: 'clawchats',
4296
- event: 'message-saved',
4297
- threadId: parsed.threadId,
4298
- workspace: parsed.workspace,
4299
- messageId,
4300
- timestamp: now
4301
- }));
4302
- } else {
4303
- const existing = db.prepare('SELECT metadata FROM messages WHERE id = ?').get(log._messageId);
4304
- const metadata = existing?.metadata ? JSON.parse(existing.metadata) : {};
4305
- metadata.activityLog = cleanSteps;
4306
- metadata.activitySummary = summary;
4307
- metadata.pending = true;
4308
-
4309
- db.prepare('UPDATE messages SET metadata = ? WHERE id = ?')
4310
- .run(JSON.stringify(metadata), log._messageId);
4311
- }
4279
+ writeActivityToDb(_getDb, this.broadcastToBrowsers.bind(this), runId, log);
4312
4280
  }
4313
4281
 
4314
4282
  _broadcastActivityUpdate(runId, log) {
@@ -4329,21 +4297,7 @@ export function createApp(config = {}) {
4329
4297
  }
4330
4298
 
4331
4299
  generateActivitySummary(steps) {
4332
- const toolSteps = steps.filter(s => s.type === 'tool' && s.phase !== 'result' && s.phase !== 'update');
4333
- const hasThinking = steps.some(s => s.type === 'thinking' && s.text);
4334
- const hasNarration = steps.some(s => s.type === 'assistant' && s.text?.trim());
4335
- if (toolSteps.length === 0 && !hasThinking && !hasNarration) return null;
4336
- if (toolSteps.length === 0 && hasThinking) return 'Reasoned through the problem';
4337
- if (toolSteps.length === 0 && hasNarration) return 'Processed in multiple steps';
4338
- const counts = {};
4339
- for (const s of toolSteps) { const name = s.name || 'unknown'; counts[name] = (counts[name] || 0) + 1; }
4340
- const parts = [];
4341
- const toolNames = { 'web_search': 'searched the web', 'web_fetch': 'fetched web pages', 'Read': 'read files', 'read': 'read files', 'Write': 'wrote files', 'write': 'wrote files', 'Edit': 'edited files', 'edit': 'edited files', 'exec': 'ran commands', 'Bash': 'ran commands', 'browser': 'browsed the web', 'memory_search': 'searched memory', 'memory_store': 'saved to memory', 'image': 'analyzed images', 'message': 'sent messages', 'sessions_spawn': 'spawned sub-agents', 'cron': 'managed cron jobs', 'Grep': 'searched code', 'grep': 'searched code', 'Glob': 'found files', 'glob': 'found files' };
4342
- for (const [name, count] of Object.entries(counts)) { const friendly = toolNames[name]; if (friendly) parts.push(count > 1 ? `${friendly} (${count}x)` : friendly); else parts.push(count > 1 ? `used ${name} (${count}x)` : `used ${name}`); }
4343
- if (parts.length === 0) return null;
4344
- if (parts.length === 1) return parts[0].charAt(0).toUpperCase() + parts[0].slice(1);
4345
- const last = parts.pop();
4346
- return (parts.join(', ') + ' and ' + last).replace(/^./, c => c.toUpperCase());
4300
+ return generateActivitySummary(steps);
4347
4301
  }
4348
4302
 
4349
4303
  addBroadcastTarget(fn) { this._externalBroadcastTargets.push(fn); }