@clawchatsai/connector 0.0.47 → 0.0.48

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 CHANGED
@@ -47,6 +47,15 @@ interface PluginApi {
47
47
  text: string;
48
48
  };
49
49
  }) => void;
50
+ registerHook: (events: string | string[], handler: (event: Record<string, unknown>, ctx: Record<string, unknown>) => void | Promise<void>, opts: {
51
+ name: string;
52
+ description?: string;
53
+ }) => void;
54
+ registerTypedHook: (hookName: string, handler: (event: Record<string, unknown>, ctx: Record<string, unknown>) => void | Promise<void>, opts?: {
55
+ name?: string;
56
+ description?: string;
57
+ priority?: number;
58
+ }) => void;
50
59
  runtime: {
51
60
  requestRestart?: (reason: string) => void;
52
61
  };
package/dist/index.js CHANGED
@@ -123,7 +123,7 @@ async function ensureNativeModules(ctx) {
123
123
  }
124
124
  }
125
125
  }
126
- async function startClawChats(ctx, api) {
126
+ async function startClawChats(ctx, api, mediaStash) {
127
127
  _stopRequested = false;
128
128
  let config = loadConfig();
129
129
  if (!config) {
@@ -175,6 +175,7 @@ async function startClawChats(ctx, api) {
175
175
  gatewayUrl: 'ws://localhost:18789',
176
176
  authToken: '', // P2P: DataChannel is the auth boundary (signaling authenticates both sides)
177
177
  gatewayToken, // For WS auth to local OpenClaw gateway
178
+ mediaStash, // Shared Map populated by after_tool_call hook (captures MEDIA: paths from exec)
178
179
  });
179
180
  // 4. Connect createApp's gateway client (handles persistence + event relay)
180
181
  app.gatewayClient.connect();
@@ -958,10 +959,45 @@ const plugin = {
958
959
  name: 'ClawChats',
959
960
  description: 'Connects your gateway to ClawChats via WebRTC P2P',
960
961
  register(api) {
962
+ // Shared stash: after_tool_call hook populates this; saveAssistantMessage reads + clears it.
963
+ // Keyed by sessionKey → absolute paths extracted from MEDIA: lines in exec stdout.
964
+ const mediaStash = new Map();
965
+ // Capture MEDIA: paths from exec tool results before the gateway strips them from message text.
966
+ // Fires after every tool call; we filter to exec only and check for MEDIA: lines.
967
+ api.registerTypedHook('after_tool_call', (event, ctx) => {
968
+ // Diagnostic: log every tool call so we can confirm hook fires + see real tool names
969
+ console.log(`[clawchats] hook:after_tool_call toolName=${String(event.toolName)} sessionKey=${String(ctx.sessionKey)}`);
970
+ if (event.toolName !== 'exec')
971
+ return;
972
+ // result is { content: [{ type: 'text', text: '...' }] } — extract text defensively
973
+ const result = event.result;
974
+ let text = '';
975
+ if (typeof result?.text === 'string') {
976
+ text = result.text;
977
+ }
978
+ else if (Array.isArray(result?.content)) {
979
+ text = result.content
980
+ .filter(c => c.type === 'text' && typeof c.text === 'string')
981
+ .map(c => c.text)
982
+ .join('\n');
983
+ }
984
+ else if (result != null) {
985
+ text = String(result);
986
+ }
987
+ const paths = [...text.matchAll(/^MEDIA:\s*(\S+)/gm)].map(m => m[1].trim());
988
+ if (paths.length === 0)
989
+ return;
990
+ const sessionKey = ctx.sessionKey;
991
+ if (!sessionKey)
992
+ return;
993
+ const existing = mediaStash.get(sessionKey) ?? [];
994
+ mediaStash.set(sessionKey, [...existing, ...paths]);
995
+ console.log(`[clawchats] media-capture: stashed ${paths.length} path(s) for ${sessionKey}:`, paths);
996
+ }, { name: 'clawchats-media-capture', description: 'Captures MEDIA: paths from exec results for inline image rendering' });
961
997
  // Background service: signaling + gateway bridge + future WebRTC
962
998
  api.registerService({
963
999
  id: 'connector-service',
964
- start: (ctx) => startClawChats(ctx, api),
1000
+ start: (ctx) => startClawChats(ctx, api, mediaStash),
965
1001
  stop: (ctx) => stopClawChats(ctx),
966
1002
  });
967
1003
  // CLI commands
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawchatsai/connector",
3
- "version": "0.0.47",
3
+ "version": "0.0.48",
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
@@ -4061,8 +4061,20 @@ export function createApp(config = {}) {
4061
4061
  const db = _getDb(parsed.workspace);
4062
4062
  const thread = db.prepare('SELECT id FROM threads WHERE id = ?').get(parsed.threadId);
4063
4063
  if (!thread) { console.log(`Ignoring response for deleted thread: ${parsed.threadId}`); return; }
4064
- const content = extractContent(message);
4064
+ let content = extractContent(message);
4065
4065
  if (!content || !content.trim()) { console.log(`Skipping empty assistant response for thread ${parsed.threadId}`); return; }
4066
+
4067
+ // Attach any images captured by the after_tool_call hook (MEDIA: lines from exec stdout).
4068
+ // Always clear the stash entry regardless of whether we find paths, to prevent leaking across turns.
4069
+ const _mediaStash = config.mediaStash;
4070
+ const _pendingPaths = _mediaStash?.get(sessionKey) ?? [];
4071
+ _mediaStash?.delete(sessionKey);
4072
+ if (_pendingPaths.length > 0) {
4073
+ const imageMarkdown = _pendingPaths.map(p => `![image](${p})`).join('\n');
4074
+ content = content.trimEnd() + '\n\n' + imageMarkdown;
4075
+ console.log(`[clawchats] media-attach: appended ${_pendingPaths.length} image(s) to message for ${sessionKey}`);
4076
+ }
4077
+
4066
4078
  const now = Date.now();
4067
4079
 
4068
4080
  // Check for pending activity message
@@ -4633,7 +4645,7 @@ if (isDirectRun) {
4633
4645
  app.gatewayClient.connect();
4634
4646
 
4635
4647
  // Initialize global DB (custom emojis, etc.)
4636
- getGlobalDb(_DATA_DIR);
4648
+ getGlobalDb(DATA_DIR);
4637
4649
  });
4638
4650
 
4639
4651
  // Graceful shutdown