@clawchatsai/connector 0.0.91 → 0.0.92
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/package.json +1 -1
- package/server/controllers/messages.js +1 -3
- package/server/gateway.js +29 -7
- package/server/index.js +1 -1
package/package.json
CHANGED
|
@@ -33,9 +33,7 @@ export class MessageController {
|
|
|
33
33
|
const metadata = body.metadata ? JSON.stringify(body.metadata) : null;
|
|
34
34
|
const existing = db.prepare('SELECT id, status, metadata FROM messages WHERE id = ?').get(body.id);
|
|
35
35
|
if (existing) {
|
|
36
|
-
|
|
37
|
-
db.prepare('UPDATE messages SET status = ?, content = ?, metadata = ? WHERE id = ?').run(body.status, body.content, metadata || existing.metadata, body.id);
|
|
38
|
-
}
|
|
36
|
+
db.prepare('UPDATE messages SET status = ?, content = ?, metadata = ? WHERE id = ?').run(body.status || existing.status, body.content, metadata || existing.metadata, body.id);
|
|
39
37
|
} else {
|
|
40
38
|
db.prepare('INSERT INTO messages (id, thread_id, role, content, status, metadata, seq, timestamp, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)').run(body.id, params.id, body.role, body.content, body.status || 'sent', metadata, body.seq || null, body.timestamp, Date.now());
|
|
41
39
|
db.prepare('UPDATE threads SET updated_at = ? WHERE id = ?').run(Date.now(), params.id);
|
package/server/gateway.js
CHANGED
|
@@ -121,9 +121,9 @@ export class GatewayClient {
|
|
|
121
121
|
if (!db.prepare('SELECT id FROM threads WHERE id = ?').get(parsed.threadId)) { console.log(`Ignoring response for deleted thread: ${parsed.threadId}`); return; }
|
|
122
122
|
|
|
123
123
|
let content = sanitizeAssistantContent(extractContent(message));
|
|
124
|
-
if (!content?.trim()) { console.log(`Skipping empty assistant response for thread ${parsed.threadId}`); return; }
|
|
125
124
|
|
|
126
|
-
// Attach media (MEDIA: lines from exec stdout captured by after_tool_call hook)
|
|
125
|
+
// Attach media (MEDIA: lines from exec stdout captured by after_tool_call hook).
|
|
126
|
+
// Stash is read before the empty-content guard — media-only responses (no text) must not be dropped.
|
|
127
127
|
const pendingPaths = this.mediaStash?.get(sessionKey) ?? [];
|
|
128
128
|
this.mediaStash?.delete(sessionKey);
|
|
129
129
|
const IMAGE_EXTS = new Set(['png','jpg','jpeg','gif','webp','bmp','svg','ico','avif','tiff']);
|
|
@@ -134,9 +134,12 @@ export class GatewayClient {
|
|
|
134
134
|
if (IMAGE_EXTS.has(ext)) imagePaths.push(p);
|
|
135
135
|
else pendingAttachments.push({ path: p, name: p.split('/').pop(), type: AUDIO_EXTS.has(ext) ? 'audio' : 'file' });
|
|
136
136
|
}
|
|
137
|
-
if (imagePaths.length > 0) content = content
|
|
137
|
+
if (imagePaths.length > 0) content = (content?.trimEnd() || '') + '\n\n' + imagePaths.map(p => ``).join('\n');
|
|
138
138
|
if (pendingPaths.length > 0) console.log(`[clawchats] media-attach: ${imagePaths.length} image(s), ${pendingAttachments.length} attachment(s) for ${sessionKey}`);
|
|
139
139
|
|
|
140
|
+
// Skip only if there is truly nothing to save — no text and no pending media.
|
|
141
|
+
if (!content?.trim() && pendingPaths.length === 0) { console.log(`Skipping empty assistant response for thread ${parsed.threadId}`); return; }
|
|
142
|
+
|
|
140
143
|
const now = Date.now();
|
|
141
144
|
const pendingMsg = db.prepare(`SELECT id, metadata FROM messages WHERE thread_id = ? AND role = 'assistant' AND json_extract(metadata, '$.pending') = 1 ORDER BY timestamp DESC LIMIT 1`).get(parsed.threadId);
|
|
142
145
|
let messageId;
|
|
@@ -228,9 +231,15 @@ export class GatewayClient {
|
|
|
228
231
|
if (stream === 'assistant') {
|
|
229
232
|
const text = data?.text || '';
|
|
230
233
|
if (text) {
|
|
234
|
+
const offset = log._assistantTextOffset || 0;
|
|
231
235
|
let seg = log._currentAssistantSegment;
|
|
232
|
-
if (!seg || seg._sealed) {
|
|
233
|
-
|
|
236
|
+
if (!seg || seg._sealed) {
|
|
237
|
+
seg = { type: 'assistant', timestamp: Date.now(), text: text.substring(offset), _sealed: false };
|
|
238
|
+
log._currentAssistantSegment = seg;
|
|
239
|
+
log.steps.push(seg);
|
|
240
|
+
} else {
|
|
241
|
+
seg.text = text.substring(offset);
|
|
242
|
+
}
|
|
234
243
|
}
|
|
235
244
|
return;
|
|
236
245
|
}
|
|
@@ -243,7 +252,11 @@ export class GatewayClient {
|
|
|
243
252
|
if (!log._lastThinkingBroadcast || now - log._lastThinkingBroadcast >= 300) { log._lastThinkingBroadcast = now; this._broadcastActivityUpdate(runId, log); }
|
|
244
253
|
}
|
|
245
254
|
if (stream === 'tool') {
|
|
246
|
-
if (log._currentAssistantSegment && !log._currentAssistantSegment._sealed)
|
|
255
|
+
if (log._currentAssistantSegment && !log._currentAssistantSegment._sealed) {
|
|
256
|
+
const seg = log._currentAssistantSegment;
|
|
257
|
+
seg._sealed = true;
|
|
258
|
+
log._assistantTextOffset = (log._assistantTextOffset || 0) + seg.text.length;
|
|
259
|
+
}
|
|
247
260
|
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') || '') : '';
|
|
248
261
|
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 };
|
|
249
262
|
if (data?.phase === 'result') {
|
|
@@ -258,10 +271,19 @@ export class GatewayClient {
|
|
|
258
271
|
this._broadcastActivityUpdate(runId, log);
|
|
259
272
|
}
|
|
260
273
|
if (stream === 'lifecycle' && (data?.phase === 'end' || data?.phase === 'error')) {
|
|
261
|
-
if (log._currentAssistantSegment && !log._currentAssistantSegment._sealed)
|
|
274
|
+
if (log._currentAssistantSegment && !log._currentAssistantSegment._sealed) {
|
|
275
|
+
const seg = log._currentAssistantSegment;
|
|
276
|
+
seg._sealed = true;
|
|
277
|
+
log._assistantTextOffset = (log._assistantTextOffset || 0) + seg.text.length;
|
|
278
|
+
}
|
|
262
279
|
const idx = log.steps.findLastIndex(s => s.type === 'assistant');
|
|
263
280
|
if (idx >= 0) log.steps.splice(idx, 1);
|
|
264
281
|
writeActivityToDb(this.getDb, this.broadcastToBrowsers.bind(this), runId, log);
|
|
282
|
+
// Broadcast final state so browser can clean up any in-flight timers (e.g. aborted runs)
|
|
283
|
+
if (log._parsed && log._messageId) {
|
|
284
|
+
const cleanSteps = log.steps.map(s => { const c = { ...s }; delete c._sealed; return c; });
|
|
285
|
+
this.broadcastToBrowsers(JSON.stringify({ type: 'clawchats', event: 'activity-updated', workspace: log._parsed.workspace, threadId: log._parsed.threadId, messageId: log._messageId, activityLog: cleanSteps, activitySummary: generateActivitySummary(log.steps), final: true }));
|
|
286
|
+
}
|
|
265
287
|
this.activityLogs.delete(runId);
|
|
266
288
|
}
|
|
267
289
|
}
|
package/server/index.js
CHANGED
|
@@ -81,7 +81,7 @@ export function createApp(config = {}) {
|
|
|
81
81
|
const { getWorkspaces, setWorkspaces } = createWorkspaceStore(WORKSPACES_FILE);
|
|
82
82
|
|
|
83
83
|
const debugLogger = new DebugLogger(DATA_DIR);
|
|
84
|
-
const mediaStash = new Map();
|
|
84
|
+
const mediaStash = config.mediaStash ?? new Map();
|
|
85
85
|
|
|
86
86
|
const memoryConfig = discoverMemoryConfig(config.memoryEnv || {});
|
|
87
87
|
const memoryProvider = createMemoryProvider(memoryConfig);
|