@lightcone-ai/daemon 0.9.66 → 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.
@@ -10,8 +10,8 @@ const REQUIREMENTS = {
10
10
  max_text_length: 1000,
11
11
  max_images: 9,
12
12
  image_formats: ['jpg', 'jpeg', 'png', 'webp'],
13
- required_fields: ['text'],
14
- notes: '标题建议 2-20 字;图片建议 3:4 竖版;话题标签写在正文末尾如 #话题名',
13
+ required_fields: ['text', 'images'],
14
+ notes: '当前自动化支持上传图文,必须提供至少 1 张本地图片;标题建议 2-20 字;图片建议 3:4 竖版;话题标签写在正文末尾如 #话题名',
15
15
  },
16
16
  short_video: {
17
17
  max_text_length: 1000,
@@ -146,6 +146,9 @@ function validateLocalFile(filePath, { kind, required = false, allowedExts = []
146
146
  function validateMedia({ platform, contentType, images = [], video, cover }) {
147
147
  const limits = MEDIA_LIMITS[platform] ?? MEDIA_LIMITS.xhs;
148
148
  if (contentType === 'image_text') {
149
+ if (images.length === 0) {
150
+ throw new Error(`image_text requires at least 1 image on ${platform}. Provide absolute image paths inside the agent workspace artifacts directory.`);
151
+ }
149
152
  if (images.length > limits.maxImages) throw new Error(`image_text supports at most ${limits.maxImages} images on ${platform}`);
150
153
  return {
151
154
  images: images.map(filePath => validateLocalFile(filePath, { kind: 'image', required: true, allowedExts: limits.imageExts })),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightcone-ai/daemon",
3
- "version": "0.9.66",
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.',