@clawchatsai/connector 0.0.59 → 0.0.61

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.
Files changed (3) hide show
  1. package/dist/index.js +11 -26
  2. package/package.json +1 -1
  3. package/server.js +115 -161
package/dist/index.js CHANGED
@@ -764,7 +764,7 @@ async function handleSetup(token) {
764
764
  const msg = JSON.parse(raw.toString());
765
765
  if (msg.type === 'setup-complete') {
766
766
  clearTimeout(timeout);
767
- console.log(` User: ${msg.userId}`);
767
+ console.log(` User: ${msg.email || msg.userId}`);
768
768
  console.log(' Registering gateway... ✅');
769
769
  // Save initial config (schemaVersion 1 — will upgrade to 2 after TOTP enrollment)
770
770
  const config = {
@@ -792,21 +792,8 @@ async function handleSetup(token) {
792
792
  fs.mkdirSync(dataDir, { recursive: true });
793
793
  fs.mkdirSync(uploadsDir, { recursive: true });
794
794
  ws.close();
795
- // Ask if user wants to reuse TOTP from another gateway
796
- let reuseSecret;
797
- if (config.schemaVersion === 1) {
798
- const rlSetup = (await import('node:readline')).createInterface({ input: process.stdin, output: process.stdout });
799
- const askSetup = (q) => new Promise(r => rlSetup.question(q, r));
800
- console.log('');
801
- console.log(' 💡 Already have ClawChats on another gateway?');
802
- console.log(' Run \`openclaw clawchats show-totp\` on that machine and paste the secret below.');
803
- const reuseAnswer = await askSetup(' Paste existing TOTP secret to reuse it (or press Enter to set up new): ');
804
- rlSetup.close();
805
- if (reuseAnswer.trim())
806
- reuseSecret = reuseAnswer.trim();
807
- }
808
- // Enroll TOTP (interactive — requires stdin)
809
- const totpOk = await enrollTotp(config, reuseSecret);
795
+ // Enroll TOTP (interactive single readline for the whole flow)
796
+ const totpOk = await enrollTotp(config);
810
797
  if (!totpOk) {
811
798
  console.log('');
812
799
  console.log(' ⚠️ TOTP not configured. You can set it up later with: openclaw clawchats reauth');
@@ -839,11 +826,17 @@ async function handleSetup(token) {
839
826
  });
840
827
  });
841
828
  }
842
- async function enrollTotp(config, existingSecret) {
829
+ async function enrollTotp(config) {
843
830
  const readline = await import('node:readline');
844
831
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
845
832
  const ask = (q) => new Promise(r => rl.question(q, r));
846
833
  try {
834
+ // Ask once if the user wants to reuse a TOTP secret from another gateway
835
+ console.log('');
836
+ console.log(' 💡 Already have ClawChats on another gateway?');
837
+ console.log(' Run `openclaw clawchats show-totp` on that machine, then paste the secret below.');
838
+ const reuseAnswer = await ask(' Paste existing TOTP secret (or press Enter to set up new): ');
839
+ const existingSecret = reuseAnswer.trim() || undefined;
847
840
  let totpSecret;
848
841
  if (existingSecret) {
849
842
  // Reusing secret from another gateway — strip spaces, uppercase
@@ -953,15 +946,7 @@ async function handleReauth() {
953
946
  console.log(' ⚠️ This will invalidate all existing sessions.');
954
947
  console.log(' All connected browsers will need to re-authenticate.');
955
948
  console.log('');
956
- const readline = await import('node:readline');
957
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
958
- const ask = (q) => new Promise(r => rl.question(q, r));
959
- let existingSecret;
960
- const reuseAnswer = await ask(' Reuse TOTP from another gateway? Paste secret (or press Enter to generate new): ');
961
- rl.close();
962
- if (reuseAnswer.trim())
963
- existingSecret = reuseAnswer.trim();
964
- const success = await enrollTotp(config, existingSecret);
949
+ const success = await enrollTotp(config);
965
950
  if (success) {
966
951
  console.log(' All previous sessions have been invalidated.');
967
952
  console.log(' Restart the gateway for changes to take effect: systemctl --user restart openclaw-gateway');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawchatsai/connector",
3
- "version": "0.0.59",
3
+ "version": "0.0.61",
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); }