@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.
- package/dist/webrtc-peer.js +15 -0
- package/package.json +1 -1
- package/server.js +115 -161
package/dist/webrtc-peer.js
CHANGED
|
@@ -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
package/server.js
CHANGED
|
@@ -3047,54 +3047,7 @@ class GatewayClient {
|
|
|
3047
3047
|
}
|
|
3048
3048
|
|
|
3049
3049
|
_writeActivityToDb(runId, log) {
|
|
3050
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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); }
|