@lightcone-ai/daemon 0.9.67 → 0.9.68

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/chat-bridge.js +56 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightcone-ai/daemon",
3
- "version": "0.9.67",
3
+ "version": "0.9.68",
4
4
  "type": "module",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -2,6 +2,8 @@
2
2
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
4
  import { z } from 'zod';
5
+ import { readFileSync } from 'fs';
6
+ import { extname } from 'path';
5
7
 
6
8
  const cliArgs = process.argv.slice(2);
7
9
  function getArg(name) {
@@ -17,6 +19,38 @@ const TEAM_ID = process.env.TEAM_ID || getArg('--team-id') || ''; // injec
17
19
  // Current active teamId for memory isolation (defaults to spawn-time TEAM_ID)
18
20
  let currentTeamId = TEAM_ID;
19
21
 
22
+ const WORKSPACE_BINARY_MIME = {
23
+ '.png': 'image/png',
24
+ '.jpg': 'image/jpeg',
25
+ '.jpeg': 'image/jpeg',
26
+ '.webp': 'image/webp',
27
+ '.gif': 'image/gif',
28
+ '.pdf': 'application/pdf',
29
+ };
30
+
31
+ function dataUrlSummary(content) {
32
+ if (typeof content !== 'string' || !content.startsWith('data:')) return null;
33
+ const commaIdx = content.indexOf(',');
34
+ if (commaIdx === -1) return null;
35
+ const header = content.slice(5, commaIdx);
36
+ const mime = header.split(';')[0] || 'application/octet-stream';
37
+ const isBase64 = header.split(';').includes('base64');
38
+ let bytes = null;
39
+ if (isBase64) {
40
+ const encoded = content.slice(commaIdx + 1).replace(/\s/g, '');
41
+ const padding = encoded.endsWith('==') ? 2 : encoded.endsWith('=') ? 1 : 0;
42
+ bytes = Math.floor(encoded.length * 3 / 4) - padding;
43
+ }
44
+ return { mime, bytes };
45
+ }
46
+
47
+ function formatBytes(bytes) {
48
+ if (!Number.isFinite(bytes)) return 'unknown size';
49
+ if (bytes < 1024) return `${bytes} B`;
50
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
51
+ return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
52
+ }
53
+
20
54
  async function api(method, path, body) {
21
55
  const url = `${SERVER_URL}/internal/agent/${AGENT_ID}${path}`;
22
56
  const res = await fetch(url, {
@@ -277,6 +311,15 @@ server.tool('read_workspace', 'Read a file from the shared team workspace (e.g.
277
311
  if (!currentTeamId) return { content: [{ type: 'text', text: 'No team context.' }] };
278
312
  try {
279
313
  const data = await api('GET', `/team-memory?path=${encodeURIComponent(path)}&teamId=${encodeURIComponent(currentTeamId)}`);
314
+ const summary = dataUrlSummary(data.content);
315
+ if (summary) {
316
+ return {
317
+ content: [{
318
+ type: 'text',
319
+ text: `${path} is a binary workspace file (${summary.mime}, ${formatBytes(summary.bytes)}). Do not read it as text. Use it by path in the Files tab, or use upload_image/read_file_base64 if you need a public URL or local base64.`,
320
+ }],
321
+ };
322
+ }
280
323
  return { content: [{ type: 'text', text: data.content }] };
281
324
  } catch (err) {
282
325
  if (err.message.includes('404')) return { content: [{ type: 'text', text: `(empty — ${path} has no content yet)` }] };
@@ -294,6 +337,19 @@ server.tool('write_workspace', 'Write a file to the shared team workspace. Use t
294
337
  return { content: [{ type: 'text', text: `Saved to team workspace: ${path}` }] };
295
338
  });
296
339
 
340
+ server.tool('write_workspace_file', 'Write a local file directly to the shared team workspace. Prefer this over write_workspace for images/PDFs/binary files so large base64 content never enters the model context.', {
341
+ file_path: z.string().describe('Absolute path to the local file, e.g. "/home/ubuntu/lightcone/public/cover.png"'),
342
+ path: z.string().describe('Destination path relative to team workspace root, e.g. "artifacts/cover.png"'),
343
+ }, async ({ file_path, path }) => {
344
+ if (!currentTeamId) return { content: [{ type: 'text', text: 'No team context.' }] };
345
+ const ext = extname(path || file_path).toLowerCase();
346
+ const mime = WORKSPACE_BINARY_MIME[ext] ?? 'application/octet-stream';
347
+ const buf = readFileSync(file_path);
348
+ const content = `data:${mime};base64,${buf.toString('base64')}`;
349
+ await api('PUT', `/team-memory?path=${encodeURIComponent(path)}&teamId=${encodeURIComponent(currentTeamId)}`, { content });
350
+ return { content: [{ type: 'text', text: `Saved local file to team workspace: ${path} (${mime}, ${formatBytes(buf.length)})` }] };
351
+ });
352
+
297
353
  // ── get_credential ───────────────────────────────────────────────────────────
298
354
  server.tool('get_credential',
299
355
  'Retrieve decrypted credential fields for a platform granted to this agent (e.g. XHS_COOKIE for "xhs"). Use when you need to inject credentials into a browser session or external call.',