@clawchatsai/connector 0.0.13 → 0.0.15
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/index.d.ts +2 -2
- package/dist/index.js +73 -34
- package/dist/migrate.d.ts +1 -1
- package/dist/migrate.js +1 -1
- package/dist/shim.js +1 -1
- package/dist/signaling-client.d.ts +1 -1
- package/dist/signaling-client.js +1 -1
- package/dist/webrtc-peer.d.ts +1 -1
- package/dist/webrtc-peer.js +1 -1
- package/openclaw.plugin.json +2 -2
- package/package.json +2 -2
- package/server.js +32 -32
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @
|
|
2
|
+
* @clawchatsai/connector — OpenClaw plugin entry point
|
|
3
3
|
*
|
|
4
|
-
* Registers
|
|
4
|
+
* Registers ClawChats as a gateway plugin, providing:
|
|
5
5
|
* - Local HTTP API bridge via createApp()
|
|
6
6
|
* - WebRTC DataChannel for browser connections
|
|
7
7
|
* - Signaling client for NAT traversal
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @
|
|
2
|
+
* @clawchatsai/connector — OpenClaw plugin entry point
|
|
3
3
|
*
|
|
4
|
-
* Registers
|
|
4
|
+
* Registers ClawChats as a gateway plugin, providing:
|
|
5
5
|
* - Local HTTP API bridge via createApp()
|
|
6
6
|
* - WebRTC DataChannel for browser connections
|
|
7
7
|
* - Signaling client for NAT traversal
|
|
@@ -24,6 +24,8 @@ export const PLUGIN_VERSION = '0.0.13';
|
|
|
24
24
|
const MAX_DC_MESSAGE_SIZE = 256 * 1024;
|
|
25
25
|
/** Active DataChannel connections: connectionId → send function */
|
|
26
26
|
const connectedClients = new Map();
|
|
27
|
+
/** Reassembly buffers for chunked gateway-msg from browser (large payloads like image attachments). */
|
|
28
|
+
const gatewayMsgChunkBuffers = new Map();
|
|
27
29
|
let app = null;
|
|
28
30
|
let signaling = null;
|
|
29
31
|
let webrtcPeer = null;
|
|
@@ -32,7 +34,7 @@ let _stopRequested = false;
|
|
|
32
34
|
// ---------------------------------------------------------------------------
|
|
33
35
|
// Config helpers
|
|
34
36
|
// ---------------------------------------------------------------------------
|
|
35
|
-
const CONFIG_DIR = path.join(process.env.HOME || '/root', '.openclaw', '
|
|
37
|
+
const CONFIG_DIR = path.join(process.env.HOME || '/root', '.openclaw', 'clawchats');
|
|
36
38
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
37
39
|
const RUNTIME_FILE = path.join(CONFIG_DIR, 'runtime.json');
|
|
38
40
|
function loadConfig() {
|
|
@@ -73,11 +75,11 @@ async function ensureNativeModules(ctx) {
|
|
|
73
75
|
ctx.logger.error('Try running manually: cd ~/.openclaw/extensions/connector && npm rebuild better-sqlite3');
|
|
74
76
|
}
|
|
75
77
|
}
|
|
76
|
-
async function
|
|
78
|
+
async function startClawChats(ctx, api) {
|
|
77
79
|
_stopRequested = false;
|
|
78
80
|
let config = loadConfig();
|
|
79
81
|
if (!config) {
|
|
80
|
-
ctx.logger.info('
|
|
82
|
+
ctx.logger.info('ClawChats not configured. Waiting for setup...');
|
|
81
83
|
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
82
84
|
while (!config && !_stopRequested) {
|
|
83
85
|
await new Promise(r => setTimeout(r, 2000));
|
|
@@ -85,7 +87,7 @@ async function startShellChat(ctx, api) {
|
|
|
85
87
|
}
|
|
86
88
|
if (_stopRequested || !config)
|
|
87
89
|
return;
|
|
88
|
-
ctx.logger.info('Setup detected — connecting to
|
|
90
|
+
ctx.logger.info('Setup detected — connecting to ClawChats...');
|
|
89
91
|
}
|
|
90
92
|
// 1. Check for updates
|
|
91
93
|
const update = await checkForUpdates();
|
|
@@ -95,7 +97,7 @@ async function startShellChat(ctx, api) {
|
|
|
95
97
|
try {
|
|
96
98
|
await performUpdate();
|
|
97
99
|
ctx.logger.info(`Updated to ${update.latest}. Requesting graceful restart...`);
|
|
98
|
-
api.runtime.requestRestart?.('
|
|
100
|
+
api.runtime.requestRestart?.('clawchats update');
|
|
99
101
|
return; // will restart with new version
|
|
100
102
|
}
|
|
101
103
|
catch (e) {
|
|
@@ -108,14 +110,14 @@ async function startShellChat(ctx, api) {
|
|
|
108
110
|
const gwAuth = gwCfg?.['gateway']?.['auth'];
|
|
109
111
|
const gatewayToken = gwAuth?.['token'] || config.gatewayToken || '';
|
|
110
112
|
if (!gatewayToken) {
|
|
111
|
-
ctx.logger.error('No gateway token available. Re-run: openclaw
|
|
113
|
+
ctx.logger.error('No gateway token available. Re-run: openclaw clawchats setup <token>');
|
|
112
114
|
return;
|
|
113
115
|
}
|
|
114
116
|
// 3. Ensure native modules are built (OpenClaw installs with --ignore-scripts)
|
|
115
117
|
await ensureNativeModules(ctx);
|
|
116
118
|
// 4. Import server.js and create app instance with plugin paths
|
|
117
|
-
const dataDir = path.join(ctx.stateDir, '
|
|
118
|
-
const uploadsDir = path.join(ctx.stateDir, '
|
|
119
|
+
const dataDir = path.join(ctx.stateDir, 'clawchats', 'data');
|
|
120
|
+
const uploadsDir = path.join(ctx.stateDir, 'clawchats', 'uploads');
|
|
119
121
|
// Dynamic import of server.js (plain JS, no type declarations)
|
|
120
122
|
// @ts-expect-error — server.js is plain JS with no .d.ts
|
|
121
123
|
const serverModule = await import('../server.js');
|
|
@@ -228,11 +230,11 @@ async function startShellChat(ctx, api) {
|
|
|
228
230
|
}, null, 2), { mode: 0o600 });
|
|
229
231
|
ctx.logger.info(`Health endpoint on 127.0.0.1:${addr.port}`);
|
|
230
232
|
});
|
|
231
|
-
ctx.logger.info('
|
|
233
|
+
ctx.logger.info('ClawChats service started');
|
|
232
234
|
}
|
|
233
|
-
async function
|
|
235
|
+
async function stopClawChats(ctx) {
|
|
234
236
|
_stopRequested = true;
|
|
235
|
-
ctx.logger.info('
|
|
237
|
+
ctx.logger.info('ClawChats service stopping...');
|
|
236
238
|
// 0. Tear down health endpoint
|
|
237
239
|
if (healthServer) {
|
|
238
240
|
healthServer.close();
|
|
@@ -259,7 +261,7 @@ async function stopShellChat(ctx) {
|
|
|
259
261
|
// 4. Close SQLite databases
|
|
260
262
|
app?.shutdown();
|
|
261
263
|
app = null;
|
|
262
|
-
ctx.logger.info('
|
|
264
|
+
ctx.logger.info('ClawChats service stopped');
|
|
263
265
|
}
|
|
264
266
|
// ---------------------------------------------------------------------------
|
|
265
267
|
// DataChannel message handler (spec section 6.4)
|
|
@@ -286,6 +288,43 @@ function setupDataChannelHandler(dc, connectionId, ctx) {
|
|
|
286
288
|
app.gatewayClient.sendToGateway(msg['payload']);
|
|
287
289
|
}
|
|
288
290
|
break;
|
|
291
|
+
case 'gateway-msg-chunk': {
|
|
292
|
+
// Reassemble chunked gateway messages from the browser.
|
|
293
|
+
// Large payloads (e.g. image attachments as base64) exceed the
|
|
294
|
+
// DataChannel's safe single-message limit (~64 KiB) and are split
|
|
295
|
+
// by the browser's transport.js into indexed chunks.
|
|
296
|
+
const chunkId = msg['id'];
|
|
297
|
+
const index = msg['index'];
|
|
298
|
+
const total = msg['total'];
|
|
299
|
+
const chunkData = msg['data'];
|
|
300
|
+
if (!chunkId || typeof index !== 'number' || typeof total !== 'number' || !chunkData) {
|
|
301
|
+
dc.send(JSON.stringify({ type: 'error', message: 'malformed gateway-msg-chunk' }));
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
if (!gatewayMsgChunkBuffers.has(chunkId)) {
|
|
305
|
+
gatewayMsgChunkBuffers.set(chunkId, {
|
|
306
|
+
chunks: new Array(total),
|
|
307
|
+
received: 0,
|
|
308
|
+
total,
|
|
309
|
+
createdAt: Date.now(),
|
|
310
|
+
});
|
|
311
|
+
// Expire stale buffers after 30s to prevent memory leaks
|
|
312
|
+
setTimeout(() => gatewayMsgChunkBuffers.delete(chunkId), 30_000);
|
|
313
|
+
}
|
|
314
|
+
const buf = gatewayMsgChunkBuffers.get(chunkId);
|
|
315
|
+
if (!buf.chunks[index]) {
|
|
316
|
+
buf.chunks[index] = chunkData;
|
|
317
|
+
buf.received++;
|
|
318
|
+
}
|
|
319
|
+
if (buf.received === buf.total) {
|
|
320
|
+
gatewayMsgChunkBuffers.delete(chunkId);
|
|
321
|
+
const fullPayload = buf.chunks.join('');
|
|
322
|
+
if (app?.gatewayClient) {
|
|
323
|
+
app.gatewayClient.sendToGateway(fullPayload);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
289
328
|
default:
|
|
290
329
|
dc.send(JSON.stringify({ type: 'error', message: 'unknown message type' }));
|
|
291
330
|
}
|
|
@@ -386,7 +425,7 @@ function broadcastToClients(msg) {
|
|
|
386
425
|
// ---------------------------------------------------------------------------
|
|
387
426
|
function formatStatus() {
|
|
388
427
|
const lines = [];
|
|
389
|
-
lines.push(`
|
|
428
|
+
lines.push(`ClawChats Plugin v${PLUGIN_VERSION}`);
|
|
390
429
|
lines.push(`Gateway: ${app?.gatewayClient?.connected ? 'connected' : 'disconnected'}`);
|
|
391
430
|
lines.push(`Signaling: ${signaling?.isConnected ? 'connected' : 'disconnected'}`);
|
|
392
431
|
lines.push(`Clients: ${connectedClients.size}`);
|
|
@@ -410,7 +449,7 @@ async function handleSetup(token) {
|
|
|
410
449
|
console.error('Setup token has expired. Generate a new one from clawchats.ai.');
|
|
411
450
|
return;
|
|
412
451
|
}
|
|
413
|
-
console.log('Setting up
|
|
452
|
+
console.log('Setting up ClawChats...');
|
|
414
453
|
console.log(` Server: ${tokenData.serverUrl}`);
|
|
415
454
|
// Generate API key for signaling server auth
|
|
416
455
|
const { randomBytes } = await import('node:crypto');
|
|
@@ -467,7 +506,7 @@ async function handleSetup(token) {
|
|
|
467
506
|
const uploadsDir = path.join(CONFIG_DIR, 'uploads');
|
|
468
507
|
fs.mkdirSync(dataDir, { recursive: true });
|
|
469
508
|
fs.mkdirSync(uploadsDir, { recursive: true });
|
|
470
|
-
console.log('
|
|
509
|
+
console.log(' ClawChats is ready!');
|
|
471
510
|
console.log(' Open clawchats.ai in your browser to start chatting.');
|
|
472
511
|
ws.close();
|
|
473
512
|
resolve();
|
|
@@ -497,7 +536,7 @@ async function handleStatus() {
|
|
|
497
536
|
runtime = JSON.parse(fs.readFileSync(RUNTIME_FILE, 'utf8'));
|
|
498
537
|
}
|
|
499
538
|
catch {
|
|
500
|
-
console.log('
|
|
539
|
+
console.log('ClawChats: offline (service not running)');
|
|
501
540
|
return;
|
|
502
541
|
}
|
|
503
542
|
// Verify PID is alive
|
|
@@ -505,7 +544,7 @@ async function handleStatus() {
|
|
|
505
544
|
process.kill(runtime.pid, 0);
|
|
506
545
|
}
|
|
507
546
|
catch {
|
|
508
|
-
console.log('
|
|
547
|
+
console.log('ClawChats: offline (stale runtime file)');
|
|
509
548
|
try {
|
|
510
549
|
fs.unlinkSync(RUNTIME_FILE);
|
|
511
550
|
}
|
|
@@ -524,20 +563,20 @@ async function handleStatus() {
|
|
|
524
563
|
req.setTimeout(3000, () => { req.destroy(); reject(new Error('timeout')); });
|
|
525
564
|
});
|
|
526
565
|
const status = JSON.parse(body);
|
|
527
|
-
console.log(`
|
|
566
|
+
console.log(`ClawChats Plugin v${status.version}`);
|
|
528
567
|
console.log(`Uptime: ${Math.floor(status.uptime)}s`);
|
|
529
568
|
console.log(`Gateway: ${status.gateway.connected ? 'connected' : 'disconnected'}`);
|
|
530
569
|
console.log(`Signaling: ${status.signaling.connected ? 'connected' : 'disconnected'}`);
|
|
531
570
|
console.log(`Clients: ${status.clients.active}`);
|
|
532
571
|
}
|
|
533
572
|
catch {
|
|
534
|
-
console.log('
|
|
573
|
+
console.log('ClawChats: offline (could not reach service)');
|
|
535
574
|
}
|
|
536
575
|
}
|
|
537
576
|
async function handleReset() {
|
|
538
577
|
try {
|
|
539
578
|
fs.rmSync(CONFIG_DIR, { recursive: true, force: true });
|
|
540
|
-
console.log('
|
|
579
|
+
console.log('ClawChats data removed. Plugin disconnected.');
|
|
541
580
|
}
|
|
542
581
|
catch (e) {
|
|
543
582
|
console.error(`Reset failed: ${e.message}`);
|
|
@@ -554,7 +593,7 @@ async function handleImport(sourcePath) {
|
|
|
554
593
|
console.error(`Source must be a directory: ${resolvedSource}`);
|
|
555
594
|
return;
|
|
556
595
|
}
|
|
557
|
-
// Destination: ~/.openclaw/
|
|
596
|
+
// Destination: ~/.openclaw/clawchats/data/
|
|
558
597
|
const destDataDir = path.join(CONFIG_DIR, 'data');
|
|
559
598
|
fs.mkdirSync(destDataDir, { recursive: true });
|
|
560
599
|
// Import .db files
|
|
@@ -586,7 +625,7 @@ async function handleImport(sourcePath) {
|
|
|
586
625
|
}
|
|
587
626
|
}
|
|
588
627
|
// Also try to migrate config.json from the parent directory
|
|
589
|
-
// e.g. if source is ~/.openclaw/
|
|
628
|
+
// e.g. if source is ~/.openclaw/clawchats/data/, config is at ~/.openclaw/clawchats/config.json
|
|
590
629
|
const parentConfigPath = path.join(path.dirname(resolvedSource), 'config.json');
|
|
591
630
|
if (fs.existsSync(parentConfigPath)) {
|
|
592
631
|
try {
|
|
@@ -605,26 +644,26 @@ async function handleImport(sourcePath) {
|
|
|
605
644
|
// ---------------------------------------------------------------------------
|
|
606
645
|
const plugin = {
|
|
607
646
|
id: PLUGIN_ID,
|
|
608
|
-
name: '
|
|
609
|
-
description: 'Connects your gateway to
|
|
647
|
+
name: 'ClawChats',
|
|
648
|
+
description: 'Connects your gateway to ClawChats via WebRTC P2P',
|
|
610
649
|
register(api) {
|
|
611
650
|
// Background service: signaling + gateway bridge + future WebRTC
|
|
612
651
|
api.registerService({
|
|
613
652
|
id: 'connector-service',
|
|
614
|
-
start: (ctx) =>
|
|
615
|
-
stop: (ctx) =>
|
|
653
|
+
start: (ctx) => startClawChats(ctx, api),
|
|
654
|
+
stop: (ctx) => stopClawChats(ctx),
|
|
616
655
|
});
|
|
617
656
|
// CLI commands
|
|
618
657
|
api.registerCli((ctx) => {
|
|
619
|
-
const cmd = ctx.program.command('
|
|
658
|
+
const cmd = ctx.program.command('clawchats');
|
|
620
659
|
cmd.command('setup <token>')
|
|
621
|
-
.description('Set up
|
|
660
|
+
.description('Set up ClawChats with a setup token')
|
|
622
661
|
.action((token) => handleSetup(String(token)));
|
|
623
662
|
cmd.command('status')
|
|
624
|
-
.description('Show
|
|
663
|
+
.description('Show ClawChats connection status')
|
|
625
664
|
.action(() => handleStatus());
|
|
626
665
|
cmd.command('reset')
|
|
627
|
-
.description('Disconnect and remove all
|
|
666
|
+
.description('Disconnect and remove all ClawChats data')
|
|
628
667
|
.action(() => handleReset());
|
|
629
668
|
cmd.command('import <path>')
|
|
630
669
|
.description('Import databases and config from a folder (e.g. migrate from old data directory)')
|
|
@@ -632,8 +671,8 @@ const plugin = {
|
|
|
632
671
|
});
|
|
633
672
|
// Slash command for status from any channel
|
|
634
673
|
api.registerCommand({
|
|
635
|
-
name: '
|
|
636
|
-
description: 'Show
|
|
674
|
+
name: 'clawchats',
|
|
675
|
+
description: 'Show ClawChats tunnel status',
|
|
637
676
|
handler: () => ({ text: formatStatus() }),
|
|
638
677
|
});
|
|
639
678
|
},
|
package/dist/migrate.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Schema migration runner for the
|
|
2
|
+
* Schema migration runner for the ClawChats plugin SQLite database.
|
|
3
3
|
*
|
|
4
4
|
* - Tracks schema version in a `_schema_version` table
|
|
5
5
|
* - Runs migrations sequentially from current version to target
|
package/dist/migrate.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Schema migration runner for the
|
|
2
|
+
* Schema migration runner for the ClawChats plugin SQLite database.
|
|
3
3
|
*
|
|
4
4
|
* - Tracks schema version in a `_schema_version` table
|
|
5
5
|
* - Runs migrations sequentially from current version to target
|
package/dist/shim.js
CHANGED
|
@@ -46,7 +46,7 @@ class FakeReq extends Readable {
|
|
|
46
46
|
}
|
|
47
47
|
else if (parsed && parsed['_multipart']) {
|
|
48
48
|
// Multipart form data: { _multipart: true, fields: { key: string | { filename, contentType, data } } }
|
|
49
|
-
const boundary = '----
|
|
49
|
+
const boundary = '----ClawChatsBoundary' + Date.now();
|
|
50
50
|
this.headers['content-type'] = `multipart/form-data; boundary=${boundary}`;
|
|
51
51
|
const fields = parsed['fields'] || {};
|
|
52
52
|
const parts = [];
|
package/dist/signaling-client.js
CHANGED
package/dist/webrtc-peer.d.ts
CHANGED
|
@@ -109,7 +109,7 @@ export declare class WebRTCPeerManager extends EventEmitter {
|
|
|
109
109
|
handleIceCandidate(connectionId: string, candidate: unknown): void;
|
|
110
110
|
/**
|
|
111
111
|
* Close all active peer connections and DataChannels.
|
|
112
|
-
* Called during plugin shutdown (
|
|
112
|
+
* Called during plugin shutdown (stopClawChats).
|
|
113
113
|
*/
|
|
114
114
|
closeAll(): void;
|
|
115
115
|
/**
|
package/dist/webrtc-peer.js
CHANGED
|
@@ -194,7 +194,7 @@ export class WebRTCPeerManager extends EventEmitter {
|
|
|
194
194
|
}
|
|
195
195
|
/**
|
|
196
196
|
* Close all active peer connections and DataChannels.
|
|
197
|
-
* Called during plugin shutdown (
|
|
197
|
+
* Called during plugin shutdown (stopClawChats).
|
|
198
198
|
*/
|
|
199
199
|
closeAll() {
|
|
200
200
|
console.log(`[WebRTCPeerManager] Closing all connections (${this.peerConnections.size} peers, ${this.activeChannels.size} channels)`);
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "connector",
|
|
3
|
-
"name": "
|
|
4
|
-
"description": "Connects your OpenClaw gateway to
|
|
3
|
+
"name": "ClawChats",
|
|
4
|
+
"description": "Connects your OpenClaw gateway to ClawChats via WebRTC P2P",
|
|
5
5
|
"kind": "integration",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clawchatsai/connector",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "ClawChats OpenClaw plugin — P2P tunnel + local API bridge",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"files": [
|
package/server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// ClawChats Backend Server
|
|
2
2
|
// Single-file Node.js HTTP server with SQLite storage
|
|
3
3
|
// See specs/backend-session-architecture.md for full spec
|
|
4
4
|
|
|
@@ -64,7 +64,7 @@ function _buildDeviceAuth(identity, { clientId, clientMode, role, scopes, token,
|
|
|
64
64
|
|
|
65
65
|
// ─── Configuration ──────────────────────────────────────────────────────────
|
|
66
66
|
|
|
67
|
-
const PORT = parseInt(process.env.SHELLCHAT_PORT || '3001', 10);
|
|
67
|
+
const PORT = parseInt(process.env.CLAWCHATS_PORT || process.env.SHELLCHAT_PORT || '3001', 10);
|
|
68
68
|
const DATA_DIR = path.join(__dirname, 'data');
|
|
69
69
|
const UPLOADS_DIR = path.join(__dirname, 'uploads');
|
|
70
70
|
const WORKSPACES_FILE = path.join(DATA_DIR, 'workspaces.json');
|
|
@@ -174,9 +174,9 @@ function parseConfigField(field) {
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
// Load auth token from config.js or env var
|
|
177
|
-
let AUTH_TOKEN = process.env.SHELLCHAT_AUTH_TOKEN || parseConfigField('authToken') || '';
|
|
177
|
+
let AUTH_TOKEN = process.env.CLAWCHATS_AUTH_TOKEN || process.env.SHELLCHAT_AUTH_TOKEN || parseConfigField('authToken') || '';
|
|
178
178
|
if (!AUTH_TOKEN) {
|
|
179
|
-
console.error('WARNING: No auth token configured. Set
|
|
179
|
+
console.error('WARNING: No auth token configured. Set CLAWCHATS_AUTH_TOKEN or create config.js');
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
// Load gateway WebSocket URL
|
|
@@ -883,7 +883,7 @@ async function handleMarkMessagesRead(req, res, params) {
|
|
|
883
883
|
// Broadcast unread-update to ALL browser clients (so other tabs/devices sync)
|
|
884
884
|
const workspace = getWorkspaces().active;
|
|
885
885
|
gatewayClient.broadcastToBrowsers(JSON.stringify({
|
|
886
|
-
type: '
|
|
886
|
+
type: 'clawchats',
|
|
887
887
|
event: 'unread-update',
|
|
888
888
|
workspace,
|
|
889
889
|
threadId,
|
|
@@ -2369,7 +2369,7 @@ class GatewayClient {
|
|
|
2369
2369
|
|
|
2370
2370
|
saveAssistantMessage(sessionKey, message, seq) {
|
|
2371
2371
|
const parsed = parseSessionKey(sessionKey);
|
|
2372
|
-
if (!parsed) return; // Non-
|
|
2372
|
+
if (!parsed) return; // Non-ClawChats session key, silently ignore
|
|
2373
2373
|
|
|
2374
2374
|
// Guard: verify workspace still exists
|
|
2375
2375
|
const ws = getWorkspaces();
|
|
@@ -2424,7 +2424,7 @@ class GatewayClient {
|
|
|
2424
2424
|
|
|
2425
2425
|
// Broadcast message-saved for active thread reload
|
|
2426
2426
|
this.broadcastToBrowsers(JSON.stringify({
|
|
2427
|
-
type: '
|
|
2427
|
+
type: 'clawchats',
|
|
2428
2428
|
event: 'message-saved',
|
|
2429
2429
|
threadId: parsed.threadId,
|
|
2430
2430
|
workspace: parsed.workspace,
|
|
@@ -2438,7 +2438,7 @@ class GatewayClient {
|
|
|
2438
2438
|
// Always broadcast unread-update — browser sends read receipts to clear
|
|
2439
2439
|
const workspaceUnreadTotal = db.prepare('SELECT COALESCE(SUM(unread_count), 0) as total FROM threads').get().total;
|
|
2440
2440
|
this.broadcastToBrowsers(JSON.stringify({
|
|
2441
|
-
type: '
|
|
2441
|
+
type: 'clawchats',
|
|
2442
2442
|
event: 'unread-update',
|
|
2443
2443
|
workspace: parsed.workspace,
|
|
2444
2444
|
threadId: parsed.threadId,
|
|
@@ -2616,7 +2616,7 @@ class GatewayClient {
|
|
|
2616
2616
|
if (!parsed) return;
|
|
2617
2617
|
|
|
2618
2618
|
this.broadcastToBrowsers(JSON.stringify({
|
|
2619
|
-
type: '
|
|
2619
|
+
type: 'clawchats',
|
|
2620
2620
|
event: 'agent-activity',
|
|
2621
2621
|
workspace: parsed.workspace,
|
|
2622
2622
|
threadId: parsed.threadId,
|
|
@@ -2761,7 +2761,7 @@ class GatewayClient {
|
|
|
2761
2761
|
|
|
2762
2762
|
// Notify browsers to re-render this message with activity data
|
|
2763
2763
|
this.broadcastToBrowsers(JSON.stringify({
|
|
2764
|
-
type: '
|
|
2764
|
+
type: 'clawchats',
|
|
2765
2765
|
event: 'activity-saved',
|
|
2766
2766
|
workspace: parsed.workspace,
|
|
2767
2767
|
threadId: parsed.threadId,
|
|
@@ -2844,7 +2844,7 @@ class GatewayClient {
|
|
|
2844
2844
|
|
|
2845
2845
|
broadcastGatewayStatus(connected) {
|
|
2846
2846
|
const msg = JSON.stringify({
|
|
2847
|
-
type: '
|
|
2847
|
+
type: 'clawchats',
|
|
2848
2848
|
event: 'gateway-status',
|
|
2849
2849
|
connected
|
|
2850
2850
|
});
|
|
@@ -2873,7 +2873,7 @@ class GatewayClient {
|
|
|
2873
2873
|
// Send current gateway status
|
|
2874
2874
|
if (ws.readyState === WS.OPEN) {
|
|
2875
2875
|
ws.send(JSON.stringify({
|
|
2876
|
-
type: '
|
|
2876
|
+
type: 'clawchats',
|
|
2877
2877
|
event: 'gateway-status',
|
|
2878
2878
|
connected: this.connected
|
|
2879
2879
|
}));
|
|
@@ -2887,7 +2887,7 @@ class GatewayClient {
|
|
|
2887
2887
|
}
|
|
2888
2888
|
if (streams.length > 0) {
|
|
2889
2889
|
ws.send(JSON.stringify({
|
|
2890
|
-
type: '
|
|
2890
|
+
type: 'clawchats',
|
|
2891
2891
|
event: 'stream-sync',
|
|
2892
2892
|
streams
|
|
2893
2893
|
}));
|
|
@@ -2924,7 +2924,7 @@ class GatewayClient {
|
|
|
2924
2924
|
// Broadcast unread-update clear to ALL browser clients
|
|
2925
2925
|
const workspaceUnreadTotal = db.prepare('SELECT COALESCE(SUM(unread_count), 0) as total FROM threads').get().total;
|
|
2926
2926
|
this.broadcastToBrowsers(JSON.stringify({
|
|
2927
|
-
type: '
|
|
2927
|
+
type: 'clawchats',
|
|
2928
2928
|
event: 'unread-update',
|
|
2929
2929
|
workspace,
|
|
2930
2930
|
threadId,
|
|
@@ -2952,7 +2952,7 @@ function syncThreadUnreadCount(db, threadId) {
|
|
|
2952
2952
|
function parseSessionKey(sessionKey) {
|
|
2953
2953
|
if (!sessionKey) return null;
|
|
2954
2954
|
const match = sessionKey.match(/^agent:main:([^:]+):chat:([^:]+)$/);
|
|
2955
|
-
if (!match) return null; // Non-
|
|
2955
|
+
if (!match) return null; // Non-ClawChats keys — silently ignore
|
|
2956
2956
|
return { workspace: match[1], threadId: match[2] };
|
|
2957
2957
|
}
|
|
2958
2958
|
|
|
@@ -2973,7 +2973,7 @@ const gatewayClient = new GatewayClient();
|
|
|
2973
2973
|
|
|
2974
2974
|
// ─── createApp Factory ───────────────────────────────────────────────────────
|
|
2975
2975
|
// Returns an isolated instance of the app state + handlers.
|
|
2976
|
-
// Used by the plugin (signaling/index.js) to embed
|
|
2976
|
+
// Used by the plugin (signaling/index.js) to embed ClawChats logic without
|
|
2977
2977
|
// spinning up a standalone HTTP server.
|
|
2978
2978
|
|
|
2979
2979
|
export function createApp(config = {}) {
|
|
@@ -2986,7 +2986,7 @@ export function createApp(config = {}) {
|
|
|
2986
2986
|
|
|
2987
2987
|
let _AUTH_TOKEN = config.authToken !== undefined
|
|
2988
2988
|
? config.authToken
|
|
2989
|
-
: (process.env.SHELLCHAT_AUTH_TOKEN || parseConfigField('authToken') || '');
|
|
2989
|
+
: (process.env.CLAWCHATS_AUTH_TOKEN || process.env.SHELLCHAT_AUTH_TOKEN || parseConfigField('authToken') || '');
|
|
2990
2990
|
|
|
2991
2991
|
// Separate token for gateway WS auth (falls back to _AUTH_TOKEN for direct mode)
|
|
2992
2992
|
const _GATEWAY_TOKEN = config.gatewayToken !== undefined
|
|
@@ -3232,7 +3232,7 @@ export function createApp(config = {}) {
|
|
|
3232
3232
|
const remaining = syncThreadUnreadCount(db, threadId);
|
|
3233
3233
|
const workspace = _getWorkspaces().active;
|
|
3234
3234
|
_gatewayClient.broadcastToBrowsers(JSON.stringify({
|
|
3235
|
-
type: '
|
|
3235
|
+
type: 'clawchats', event: 'unread-update', workspace, threadId,
|
|
3236
3236
|
action: 'read', messageIds, unreadCount: remaining, timestamp: Date.now()
|
|
3237
3237
|
}));
|
|
3238
3238
|
send(res, 200, { unread_count: remaining });
|
|
@@ -3679,9 +3679,9 @@ export function createApp(config = {}) {
|
|
|
3679
3679
|
const threadInfo = db.prepare('SELECT title FROM threads WHERE id = ?').get(parsed.threadId);
|
|
3680
3680
|
const unreadCount = db.prepare('SELECT COUNT(*) as c FROM unread_messages WHERE thread_id = ?').get(parsed.threadId).c;
|
|
3681
3681
|
const preview = content.length > 120 ? content.substring(0, 120) + '...' : content;
|
|
3682
|
-
this.broadcastToBrowsers(JSON.stringify({ type: '
|
|
3682
|
+
this.broadcastToBrowsers(JSON.stringify({ type: 'clawchats', event: 'message-saved', threadId: parsed.threadId, workspace: parsed.workspace, messageId, timestamp: now, title: threadInfo?.title || 'Chat', preview, unreadCount }));
|
|
3683
3683
|
const workspaceUnreadTotal = db.prepare('SELECT COALESCE(SUM(unread_count), 0) as total FROM threads').get().total;
|
|
3684
|
-
this.broadcastToBrowsers(JSON.stringify({ type: '
|
|
3684
|
+
this.broadcastToBrowsers(JSON.stringify({ type: 'clawchats', event: 'unread-update', workspace: parsed.workspace, threadId: parsed.threadId, messageId, action: 'new', unreadCount, workspaceUnreadTotal, title: threadInfo?.title || 'Chat', preview, timestamp: now }));
|
|
3685
3685
|
console.log(`Saved assistant message to ${parsed.workspace}/${parsed.threadId} (seq: ${seq})`);
|
|
3686
3686
|
} catch (e) { console.error(`Failed to save assistant message:`, e.message); }
|
|
3687
3687
|
}
|
|
@@ -3759,7 +3759,7 @@ export function createApp(config = {}) {
|
|
|
3759
3759
|
broadcastActivityUpdate(runId, log) {
|
|
3760
3760
|
const parsed = log.sessionKey ? parseSessionKey(log.sessionKey) : null;
|
|
3761
3761
|
if (!parsed) return;
|
|
3762
|
-
this.broadcastToBrowsers(JSON.stringify({ type: '
|
|
3762
|
+
this.broadcastToBrowsers(JSON.stringify({ type: 'clawchats', event: 'agent-activity', workspace: parsed.workspace, threadId: parsed.threadId, runId, steps: log.steps, summary: this.generateActivitySummary(log.steps) }));
|
|
3763
3763
|
}
|
|
3764
3764
|
|
|
3765
3765
|
finalizeActivityLog(runId, log) {
|
|
@@ -3824,7 +3824,7 @@ export function createApp(config = {}) {
|
|
|
3824
3824
|
metadata.activitySummary = this.generateActivitySummary(log.steps);
|
|
3825
3825
|
db.prepare('UPDATE messages SET metadata = ? WHERE id = ?').run(JSON.stringify(metadata), msg.id);
|
|
3826
3826
|
console.log(`[ActivityLog] Saved ${toolSteps.length} tool steps for message ${msg.id}`);
|
|
3827
|
-
this.broadcastToBrowsers(JSON.stringify({ type: '
|
|
3827
|
+
this.broadcastToBrowsers(JSON.stringify({ type: 'clawchats', event: 'activity-saved', workspace: parsed.workspace, threadId: parsed.threadId, messageId: msg.id }));
|
|
3828
3828
|
}
|
|
3829
3829
|
}
|
|
3830
3830
|
} catch (e) { console.error('Failed to save activity log:', e.message); }
|
|
@@ -3864,7 +3864,7 @@ export function createApp(config = {}) {
|
|
|
3864
3864
|
}
|
|
3865
3865
|
|
|
3866
3866
|
broadcastGatewayStatus(connected) {
|
|
3867
|
-
this.broadcastToBrowsers(JSON.stringify({ type: '
|
|
3867
|
+
this.broadcastToBrowsers(JSON.stringify({ type: 'clawchats', event: 'gateway-status', connected }));
|
|
3868
3868
|
}
|
|
3869
3869
|
|
|
3870
3870
|
sendToGateway(data) {
|
|
@@ -3883,12 +3883,12 @@ export function createApp(config = {}) {
|
|
|
3883
3883
|
addBrowserClient(ws) {
|
|
3884
3884
|
this.browserClients.set(ws, { activeWorkspace: null, activeThreadId: null });
|
|
3885
3885
|
if (ws.readyState === WS.OPEN) {
|
|
3886
|
-
ws.send(JSON.stringify({ type: '
|
|
3886
|
+
ws.send(JSON.stringify({ type: 'clawchats', event: 'gateway-status', connected: this.connected }));
|
|
3887
3887
|
const streams = [];
|
|
3888
3888
|
for (const [sessionKey, state] of this.streamState.entries()) {
|
|
3889
3889
|
if (state.state === 'streaming') streams.push({ sessionKey, threadId: state.threadId, buffer: state.buffer });
|
|
3890
3890
|
}
|
|
3891
|
-
if (streams.length > 0) ws.send(JSON.stringify({ type: '
|
|
3891
|
+
if (streams.length > 0) ws.send(JSON.stringify({ type: 'clawchats', event: 'stream-sync', streams }));
|
|
3892
3892
|
}
|
|
3893
3893
|
}
|
|
3894
3894
|
|
|
@@ -3908,7 +3908,7 @@ export function createApp(config = {}) {
|
|
|
3908
3908
|
if (deleted.changes > 0) {
|
|
3909
3909
|
syncThreadUnreadCount(db, threadId);
|
|
3910
3910
|
const workspaceUnreadTotal = db.prepare('SELECT COALESCE(SUM(unread_count), 0) as total FROM threads').get().total;
|
|
3911
|
-
this.broadcastToBrowsers(JSON.stringify({ type: '
|
|
3911
|
+
this.broadcastToBrowsers(JSON.stringify({ type: 'clawchats', event: 'unread-update', workspace, threadId, action: 'clear', unreadCount: 0, workspaceUnreadTotal, timestamp: Date.now() }));
|
|
3912
3912
|
}
|
|
3913
3913
|
} catch (e) { console.error('Failed to auto-clear unreads on active-thread:', e.message); }
|
|
3914
3914
|
}
|
|
@@ -4024,7 +4024,7 @@ export function createApp(config = {}) {
|
|
|
4024
4024
|
const token = msg.params?.auth?.token;
|
|
4025
4025
|
if (token === _AUTH_TOKEN || !_AUTH_TOKEN) {
|
|
4026
4026
|
console.log('Browser client authenticated');
|
|
4027
|
-
ws.send(JSON.stringify({ type: 'res', id: msg.id, ok: true, payload: { type: 'hello-ok', protocol: 3, server: { version: '0.1.0', host: '
|
|
4027
|
+
ws.send(JSON.stringify({ type: 'res', id: msg.id, ok: true, payload: { type: 'hello-ok', protocol: 3, server: { version: '0.1.0', host: 'clawchats-backend' } } }));
|
|
4028
4028
|
} else {
|
|
4029
4029
|
console.log('Browser client auth failed');
|
|
4030
4030
|
ws.send(JSON.stringify({ type: 'res', id: msg.id, ok: false, error: { code: 'AUTH_FAILED', message: 'Invalid auth token' } }));
|
|
@@ -4032,12 +4032,12 @@ export function createApp(config = {}) {
|
|
|
4032
4032
|
}
|
|
4033
4033
|
return;
|
|
4034
4034
|
}
|
|
4035
|
-
if (msg.type === 'shellchat') {
|
|
4035
|
+
if (msg.type === 'clawchats' || msg.type === 'shellchat') { // backward compat: accept legacy 'shellchat' type
|
|
4036
4036
|
if (msg.action === 'active-thread') { _gatewayClient.setActiveThread(ws, msg.workspace, msg.threadId); console.log(`Browser client set active thread: ${msg.workspace}/${msg.threadId}`); return; }
|
|
4037
|
-
if (msg.action === 'debug-start') { const result = _debugLogger.start(msg.ts, ws); if (result.error === 'already-active') ws.send(JSON.stringify({ type: '
|
|
4038
|
-
if (msg.action === 'debug-dump') { const { sessionId, files } = _debugLogger.saveDump(msg); ws.send(JSON.stringify({ type: '
|
|
4037
|
+
if (msg.action === 'debug-start') { const result = _debugLogger.start(msg.ts, ws); if (result.error === 'already-active') ws.send(JSON.stringify({ type: 'clawchats', event: 'debug-error', error: 'Recording already active in another tab', sessionId: result.sessionId })); else ws.send(JSON.stringify({ type: 'clawchats', event: 'debug-started', sessionId: result.sessionId })); return; }
|
|
4038
|
+
if (msg.action === 'debug-dump') { const { sessionId, files } = _debugLogger.saveDump(msg); ws.send(JSON.stringify({ type: 'clawchats', event: 'debug-saved', sessionId, files })); return; }
|
|
4039
4039
|
}
|
|
4040
|
-
} catch { /* Not JSON or not a
|
|
4040
|
+
} catch { /* Not JSON or not a ClawChats message, forward to gateway */ }
|
|
4041
4041
|
_gatewayClient.sendToGateway(msgStr);
|
|
4042
4042
|
});
|
|
4043
4043
|
|
|
@@ -4086,7 +4086,7 @@ if (isDirectRun) {
|
|
|
4086
4086
|
});
|
|
4087
4087
|
|
|
4088
4088
|
server.listen(PORT, () => {
|
|
4089
|
-
console.log(`
|
|
4089
|
+
console.log(`ClawChats backend listening on port ${PORT}`);
|
|
4090
4090
|
console.log(`Active workspace: ${app.getWorkspaces().active}`);
|
|
4091
4091
|
console.log(`Data dir: ${app.dataDir}`);
|
|
4092
4092
|
|