@clawchatsai/connector 0.1.7 → 0.1.8
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.js +6 -34
- package/package.json +1 -1
- package/server/gateway.js +18 -6
- package/server/index.js +1 -2
package/dist/index.js
CHANGED
|
@@ -144,7 +144,7 @@ Notes:
|
|
|
144
144
|
- Works for images (png, jpg, gif, webp, svg, etc.), documents, markdown, code files, and more
|
|
145
145
|
- Output each path once — duplicates are ignored automatically
|
|
146
146
|
`;
|
|
147
|
-
async function startClawChats(ctx, api
|
|
147
|
+
async function startClawChats(ctx, api) {
|
|
148
148
|
_stopRequested = false;
|
|
149
149
|
// Bootstrap CLAWCHATS.md in the agent workspace so the agent always knows the MEDIA: protocol.
|
|
150
150
|
// Written once on plugin start; never overwrites an existing file (user may have customised it).
|
|
@@ -255,7 +255,6 @@ async function startClawChats(ctx, api, mediaStash) {
|
|
|
255
255
|
return process.env.OPENAI_API_KEY || null;
|
|
256
256
|
})(),
|
|
257
257
|
memoryEnv: memoryEnvFiltered,
|
|
258
|
-
mediaStash, // Shared Map populated by after_tool_call hook (captures MEDIA: paths from exec)
|
|
259
258
|
});
|
|
260
259
|
// 4. Connect createApp's gateway client (handles persistence + event relay)
|
|
261
260
|
app.gatewayClient.connect();
|
|
@@ -1284,49 +1283,22 @@ const plugin = {
|
|
|
1284
1283
|
name: 'ClawChats',
|
|
1285
1284
|
description: 'Connects your gateway to ClawChats via WebRTC P2P',
|
|
1286
1285
|
register(api) {
|
|
1287
|
-
// Shared stash: after_tool_call hook populates this; saveAssistantMessage reads + clears it.
|
|
1288
|
-
// Keyed by sessionKey → absolute paths extracted from MEDIA: lines in exec stdout.
|
|
1289
|
-
const mediaStash = new Map();
|
|
1290
1286
|
// Inject MEDIA: capability note into the system prompt via before_prompt_build.
|
|
1291
1287
|
// Appended to system context (trusted, not user-turn) so it's always present and
|
|
1292
1288
|
// never flagged as prompt injection. Survives compaction automatically.
|
|
1289
|
+
//
|
|
1290
|
+
// Path parsing lives in server/gateway.js handleAgentEvent (stable singleton) —
|
|
1291
|
+
// do NOT hold state in this closure; OpenClaw may call register() multiple times
|
|
1292
|
+
// during the plugin lifecycle and each call creates a fresh closure.
|
|
1293
1293
|
api.on('before_prompt_build', (_event, _ctx) => {
|
|
1294
1294
|
return {
|
|
1295
1295
|
appendSystemContext: 'ClawChats inline preview: after writing a file with the Write tool, run `echo "MEDIA:/absolute/path/to/file"` via the exec tool to display it inline in the chat UI.',
|
|
1296
1296
|
};
|
|
1297
1297
|
}, { name: 'clawchats-media-hint', description: 'Appends MEDIA: file preview capability note to system prompt' });
|
|
1298
|
-
api.on('after_tool_call', (event, ctx) => {
|
|
1299
|
-
if (event.toolName !== 'exec' && event.toolName !== 'process')
|
|
1300
|
-
return;
|
|
1301
|
-
// result is { content: [{ type: 'text', text: '...' }] } — extract text defensively
|
|
1302
|
-
const result = event.result;
|
|
1303
|
-
let text = '';
|
|
1304
|
-
if (typeof result?.text === 'string') {
|
|
1305
|
-
text = result.text;
|
|
1306
|
-
}
|
|
1307
|
-
else if (Array.isArray(result?.content)) {
|
|
1308
|
-
text = result.content
|
|
1309
|
-
.filter(c => c.type === 'text' && typeof c.text === 'string')
|
|
1310
|
-
.map(c => c.text)
|
|
1311
|
-
.join('\n');
|
|
1312
|
-
}
|
|
1313
|
-
else if (result != null) {
|
|
1314
|
-
text = String(result);
|
|
1315
|
-
}
|
|
1316
|
-
const paths = [...text.matchAll(/^MEDIA:\s*(\S+)/gm)].map(m => m[1].trim());
|
|
1317
|
-
if (paths.length === 0)
|
|
1318
|
-
return;
|
|
1319
|
-
const sessionKey = ctx.sessionKey;
|
|
1320
|
-
if (!sessionKey)
|
|
1321
|
-
return;
|
|
1322
|
-
const existing = mediaStash.get(sessionKey) ?? [];
|
|
1323
|
-
mediaStash.set(sessionKey, [...new Set([...existing, ...paths])]);
|
|
1324
|
-
console.log(`[clawchats] media-capture: stashed ${paths.length} path(s) for ${sessionKey}:`, paths);
|
|
1325
|
-
}, { name: 'clawchats-media-capture', description: 'Captures MEDIA: paths from exec results for inline image rendering' });
|
|
1326
1298
|
// Background service: signaling + gateway bridge + future WebRTC
|
|
1327
1299
|
api.registerService({
|
|
1328
1300
|
id: 'connector-service',
|
|
1329
|
-
start: (ctx) => startClawChats(ctx, api
|
|
1301
|
+
start: (ctx) => startClawChats(ctx, api),
|
|
1330
1302
|
stop: (ctx) => stopClawChats(ctx),
|
|
1331
1303
|
});
|
|
1332
1304
|
// CLI commands
|
package/package.json
CHANGED
package/server/gateway.js
CHANGED
|
@@ -4,14 +4,16 @@ import { loadOrCreateDeviceIdentity, buildDeviceAuth } from './bootstrap/identit
|
|
|
4
4
|
import { parseSessionKey, extractContent, isSilentReplyExact, isSilentReplyPrefix, sanitizeAssistantContent, syncThreadUnreadCount, generateActivitySummary, writeActivityToDb } from './util/helpers.js';
|
|
5
5
|
|
|
6
6
|
export class GatewayClient {
|
|
7
|
-
constructor({ getDb, getWorkspaces, dataDir, debugLogger, gatewayWsUrl, authToken
|
|
7
|
+
constructor({ getDb, getWorkspaces, dataDir, debugLogger, gatewayWsUrl, authToken }) {
|
|
8
8
|
this.getDb = getDb;
|
|
9
9
|
this.getWorkspaces = getWorkspaces;
|
|
10
10
|
this.dataDir = dataDir;
|
|
11
11
|
this.debugLogger = debugLogger;
|
|
12
12
|
this.gatewayWsUrl = gatewayWsUrl;
|
|
13
13
|
this.authToken = authToken;
|
|
14
|
-
|
|
14
|
+
// Per-session buffer of MEDIA: paths extracted from exec tool args (echo "MEDIA:/...").
|
|
15
|
+
// Lives on the stable singleton — safe from plugin re-registration that broke the old closure-based stash.
|
|
16
|
+
this._runMediaPaths = new Map();
|
|
15
17
|
|
|
16
18
|
this.ws = null;
|
|
17
19
|
this.connected = false;
|
|
@@ -228,10 +230,10 @@ export class GatewayClient {
|
|
|
228
230
|
// Intermediate narration lives in activityLog steps; message.content is the clean final answer.
|
|
229
231
|
let content = sanitizeAssistantContent(extractContent(message).substring(thoughtStartOffset));
|
|
230
232
|
|
|
231
|
-
// Attach media (MEDIA:
|
|
232
|
-
//
|
|
233
|
-
const pendingPaths = this.
|
|
234
|
-
this.
|
|
233
|
+
// Attach media (MEDIA: paths extracted from exec tool args by handleAgentEvent).
|
|
234
|
+
// Buffer is read before the empty-content guard — media-only responses (no text) must not be dropped.
|
|
235
|
+
const pendingPaths = this._runMediaPaths.get(sessionKey) ?? [];
|
|
236
|
+
this._runMediaPaths.delete(sessionKey);
|
|
235
237
|
const IMAGE_EXTS = new Set(['png','jpg','jpeg','gif','webp','bmp','svg','ico','avif','tiff']);
|
|
236
238
|
const AUDIO_EXTS = new Set(['mp3','wav','ogg','m4a','flac','aac','opus','wma']);
|
|
237
239
|
const imagePaths = [], pendingAttachments = [];
|
|
@@ -367,6 +369,16 @@ export class GatewayClient {
|
|
|
367
369
|
log._currentAssistantSegment._sealed = true;
|
|
368
370
|
}
|
|
369
371
|
const argsMeta = data?.args ? (data.args.command || data.args.path || data.args.query || data.args.url || Object.values(data.args).find(v => typeof v === 'string') || '') : '';
|
|
372
|
+
// Extract MEDIA: paths from exec tool's echo command at phase:start.
|
|
373
|
+
// The agent signals inline media with `echo "MEDIA:/absolute/path"` (see plugin's before_prompt_build hint).
|
|
374
|
+
// Parsing the tool args here keeps state on this stable singleton instead of the plugin's re-registered closure.
|
|
375
|
+
if (sessionKey && (data?.name === 'exec' || data?.name === 'process') && data?.phase === 'start' && typeof data?.args?.command === 'string') {
|
|
376
|
+
const paths = [...data.args.command.matchAll(/MEDIA:([^\s"'`]+)/g)].map(m => m[1]).filter(Boolean);
|
|
377
|
+
if (paths.length > 0) {
|
|
378
|
+
const existing = this._runMediaPaths.get(sessionKey) ?? [];
|
|
379
|
+
this._runMediaPaths.set(sessionKey, [...new Set([...existing, ...paths])]);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
370
382
|
const step = { type: 'tool', timestamp: Date.now(), name: data?.name || 'unknown', phase: data?.phase || 'start', toolCallId: data?.toolCallId, meta: data?.meta || (argsMeta ? String(argsMeta) : undefined), isError: data?.isError || false };
|
|
371
383
|
if (data?.phase === 'result') {
|
|
372
384
|
const existing = log.steps.findLast(s => s.toolCallId === data.toolCallId && (s.phase === 'start' || s.phase === 'running'));
|
package/server/index.js
CHANGED
|
@@ -83,7 +83,6 @@ export function createApp(config = {}) {
|
|
|
83
83
|
const { getWorkspaces, setWorkspaces } = createWorkspaceStore(WORKSPACES_FILE);
|
|
84
84
|
|
|
85
85
|
const debugLogger = new DebugLogger(DATA_DIR);
|
|
86
|
-
const mediaStash = config.mediaStash ?? new Map();
|
|
87
86
|
|
|
88
87
|
const memoryConfig = discoverMemoryConfig(config.memoryEnv || {});
|
|
89
88
|
const memoryProvider = createMemoryProvider(memoryConfig);
|
|
@@ -91,7 +90,7 @@ export function createApp(config = {}) {
|
|
|
91
90
|
const MEMORY_FILES_DIR = path.join(memoryConfig.workspaceDir, 'memory');
|
|
92
91
|
|
|
93
92
|
// Instantiate the gateway client with all dependencies injected
|
|
94
|
-
const gatewayClient = new GatewayClient({ getDb, getWorkspaces, dataDir: DATA_DIR, debugLogger, gatewayWsUrl: gatewayUrl, authToken: gatewayToken
|
|
93
|
+
const gatewayClient = new GatewayClient({ getDb, getWorkspaces, dataDir: DATA_DIR, debugLogger, gatewayWsUrl: gatewayUrl, authToken: gatewayToken });
|
|
95
94
|
const broadcast = msg => gatewayClient.broadcastToBrowsers(msg);
|
|
96
95
|
|
|
97
96
|
// Instantiate controllers
|