@agentbean/daemon 0.1.11 → 0.1.13

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.
@@ -1,8 +1,10 @@
1
+ import { writeFileSync } from 'node:fs';
2
+ import { basename, join } from 'node:path';
1
3
  import { logger } from './log.js';
2
4
  import { uploadArtifact } from './uploader.js';
3
5
  import { postProcess } from './post-process.js';
4
6
  import { generateSandboxProfile, getWorkspaceDir, isSandboxAvailable } from './sandbox.js';
5
- import { archiveOutputFiles, beginAgentWorkspaceRun, finishAgentWorkspaceRun, workspaceEnv, } from './workspace-manager.js';
7
+ import { archiveOutputFiles, beginAgentWorkspaceRun, finishAgentWorkspaceRun, formatWorkspaceReply, workspaceEnv, } from './workspace-manager.js';
6
8
  function errorMessage(err) {
7
9
  if (err instanceof Error && err.message)
8
10
  return err.message;
@@ -16,6 +18,39 @@ function errorMessage(err) {
16
18
  catch { }
17
19
  return 'unknown error';
18
20
  }
21
+ function safeFilename(value) {
22
+ return basename(value).replace(/[^a-zA-Z0-9._-]/g, '-').replace(/^-+|-+$/g, '') || 'attachment';
23
+ }
24
+ async function downloadAttachments(input) {
25
+ const downloaded = [];
26
+ for (const attachment of input.attachments ?? []) {
27
+ const sep = attachment.downloadUrl.includes('?') ? '&' : '?';
28
+ const url = `${input.serverUrl}${attachment.downloadUrl}${sep}token=${encodeURIComponent(input.token)}`;
29
+ try {
30
+ const resp = await fetch(url);
31
+ if (!resp.ok) {
32
+ logger.warn({ id: attachment.id, status: resp.status }, 'attachment download rejected');
33
+ continue;
34
+ }
35
+ const bytes = Buffer.from(await resp.arrayBuffer());
36
+ const localPath = join(input.run.inputDir, `${attachment.id}-${safeFilename(attachment.filename)}`);
37
+ writeFileSync(localPath, bytes);
38
+ downloaded.push({ ...attachment, localPath });
39
+ }
40
+ catch (err) {
41
+ logger.warn({ id: attachment.id, err: err?.message }, 'attachment download failed');
42
+ }
43
+ }
44
+ return downloaded;
45
+ }
46
+ function promptWithAttachments(prompt, attachments) {
47
+ if (attachments.length === 0)
48
+ return prompt;
49
+ const list = attachments
50
+ .map((file) => `- ${file.filename} (${file.mimeType}, ${file.sizeBytes} bytes): ${file.localPath}`)
51
+ .join('\n');
52
+ return `${prompt}\n\n用户随消息附加了以下本地文件,请在需要时读取并使用:\n${list}`;
53
+ }
19
54
  export class AgentInstance {
20
55
  config;
21
56
  adapter;
@@ -58,8 +93,10 @@ export class AgentInstance {
58
93
  });
59
94
  let archivedFiles = [];
60
95
  try {
96
+ const downloadedAttachments = await downloadAttachments({ serverUrl, token, run, attachments: req.attachments });
97
+ const prompt = promptWithAttachments(req.prompt, downloadedAttachments);
61
98
  const rawBody = await this.adapter.ask({
62
- prompt: req.prompt,
99
+ prompt,
63
100
  history: req.history ?? [],
64
101
  systemPrompt: this.config.adapter.systemPrompt,
65
102
  workspace: projectWorkspace,
@@ -72,7 +109,8 @@ export class AgentInstance {
72
109
  outputDirs: [run.outputDir, run.intermediateDir],
73
110
  });
74
111
  archivedFiles = archiveOutputFiles(run, processed.outputFiles);
75
- finishAgentWorkspaceRun(run, { replyText: processed.replyText, files: archivedFiles, status: 'completed' });
112
+ const replyText = formatWorkspaceReply(rawBody, archivedFiles);
113
+ finishAgentWorkspaceRun(run, { replyText, files: archivedFiles, status: 'completed' });
76
114
  const artifactIds = [];
77
115
  if (archivedFiles.length > 0) {
78
116
  for (const file of archivedFiles) {
@@ -92,7 +130,6 @@ export class AgentInstance {
92
130
  deviceId: deviceId ?? null,
93
131
  pathKind: file.pathKind,
94
132
  relativePath: file.relativePath,
95
- originalPath: file.originalPath,
96
133
  sha256: file.sha256,
97
134
  }),
98
135
  });
@@ -107,7 +144,7 @@ export class AgentInstance {
107
144
  socket.emit('reply', {
108
145
  agentId: this.id,
109
146
  channelId: req.channelId,
110
- body: processed.replyText,
147
+ body: replyText,
111
148
  requestId: req.requestId,
112
149
  artifactIds: artifactIds.length > 0 ? artifactIds : undefined,
113
150
  });
@@ -1,8 +1,10 @@
1
1
  import { io } from 'socket.io-client';
2
+ import { writeFileSync } from 'node:fs';
3
+ import { basename, join } from 'node:path';
2
4
  import { logger } from './log.js';
3
5
  import { uploadArtifact } from './uploader.js';
4
6
  import { postProcess } from './post-process.js';
5
- import { archiveOutputFiles, beginAgentWorkspaceRun, finishAgentWorkspaceRun, workspaceEnv, } from './workspace-manager.js';
7
+ import { archiveOutputFiles, beginAgentWorkspaceRun, finishAgentWorkspaceRun, formatWorkspaceReply, workspaceEnv, } from './workspace-manager.js';
6
8
  function errorMessage(err) {
7
9
  if (err instanceof Error && err.message)
8
10
  return err.message;
@@ -16,6 +18,38 @@ function errorMessage(err) {
16
18
  catch { }
17
19
  return 'unknown error';
18
20
  }
21
+ function safeFilename(value) {
22
+ return basename(value).replace(/[^a-zA-Z0-9._-]/g, '-').replace(/^-+|-+$/g, '') || 'attachment';
23
+ }
24
+ async function downloadAttachments(input) {
25
+ const downloaded = [];
26
+ for (const attachment of input.attachments ?? []) {
27
+ const sep = attachment.downloadUrl.includes('?') ? '&' : '?';
28
+ const url = `${input.serverUrl}${attachment.downloadUrl}${sep}token=${encodeURIComponent(input.token)}`;
29
+ try {
30
+ const resp = await fetch(url);
31
+ if (!resp.ok) {
32
+ logger.warn({ id: attachment.id, status: resp.status }, 'attachment download rejected');
33
+ continue;
34
+ }
35
+ const localPath = join(input.run.inputDir, `${attachment.id}-${safeFilename(attachment.filename)}`);
36
+ writeFileSync(localPath, Buffer.from(await resp.arrayBuffer()));
37
+ downloaded.push({ ...attachment, localPath });
38
+ }
39
+ catch (err) {
40
+ logger.warn({ id: attachment.id, err: err?.message }, 'attachment download failed');
41
+ }
42
+ }
43
+ return downloaded;
44
+ }
45
+ function promptWithAttachments(prompt, attachments) {
46
+ if (attachments.length === 0)
47
+ return prompt;
48
+ const list = attachments
49
+ .map((file) => `- ${file.filename} (${file.mimeType}, ${file.sizeBytes} bytes): ${file.localPath}`)
50
+ .join('\n');
51
+ return `${prompt}\n\n用户随消息附加了以下本地文件,请在需要时读取并使用:\n${list}`;
52
+ }
19
53
  export function createConnection(cfg, adapter) {
20
54
  let socket = null;
21
55
  let heartbeatTimer = null;
@@ -69,8 +103,16 @@ export function createConnection(cfg, adapter) {
69
103
  });
70
104
  let archivedFiles = [];
71
105
  try {
106
+ const httpBase = cfg.server.url.replace(/\/agent$/, '');
107
+ const downloadedAttachments = await downloadAttachments({
108
+ serverUrl: httpBase,
109
+ token: cfg.server.token,
110
+ run,
111
+ attachments: req.attachments,
112
+ });
113
+ const prompt = promptWithAttachments(req.prompt, downloadedAttachments);
72
114
  const rawBody = await adapter.ask({
73
- prompt: req.prompt,
115
+ prompt,
74
116
  history: req.history ?? [],
75
117
  systemPrompt: cfg.adapter.systemPrompt,
76
118
  workspace: cfg.adapter.workspace,
@@ -80,10 +122,10 @@ export function createConnection(cfg, adapter) {
80
122
  outputDirs: [run.outputDir, run.intermediateDir],
81
123
  });
82
124
  archivedFiles = archiveOutputFiles(run, processed.outputFiles);
83
- finishAgentWorkspaceRun(run, { replyText: processed.replyText, files: archivedFiles, status: 'completed' });
125
+ const replyText = formatWorkspaceReply(rawBody, archivedFiles);
126
+ finishAgentWorkspaceRun(run, { replyText, files: archivedFiles, status: 'completed' });
84
127
  const artifactIds = [];
85
128
  if (archivedFiles.length > 0) {
86
- const httpBase = cfg.server.url.replace(/\/agent$/, '');
87
129
  for (const file of archivedFiles) {
88
130
  try {
89
131
  const result = await uploadArtifact({
@@ -100,7 +142,6 @@ export function createConnection(cfg, adapter) {
100
142
  runId: req.requestId,
101
143
  pathKind: file.pathKind,
102
144
  relativePath: file.relativePath,
103
- originalPath: file.originalPath,
104
145
  sha256: file.sha256,
105
146
  }),
106
147
  });
@@ -114,7 +155,7 @@ export function createConnection(cfg, adapter) {
114
155
  }
115
156
  currentSocket.emit('reply', {
116
157
  channelId: req.channelId,
117
- body: processed.replyText,
158
+ body: replyText,
118
159
  requestId: req.requestId,
119
160
  artifactIds: artifactIds.length > 0 ? artifactIds : undefined,
120
161
  });
@@ -34,6 +34,14 @@ function uniqueDestination(dir, filename) {
34
34
  }
35
35
  return candidate;
36
36
  }
37
+ function escapeRegExp(value) {
38
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
39
+ }
40
+ function replaceAllLiteral(value, search, replacement) {
41
+ if (!search || search === replacement)
42
+ return value;
43
+ return value.replace(new RegExp(escapeRegExp(search), 'g'), replacement);
44
+ }
37
45
  export function beginAgentWorkspaceRun(input) {
38
46
  const teamId = safeSegment(input.teamId);
39
47
  const agentId = safeSegment(input.agentId);
@@ -41,6 +49,7 @@ export function beginAgentWorkspaceRun(input) {
41
49
  const teamDir = ensureDir(join(rootDir(), 'teams', teamId));
42
50
  const agentDir = ensureDir(join(teamDir, 'agents', agentId));
43
51
  const runDir = ensureDir(join(agentDir, 'runs', runId));
52
+ const inputDir = ensureDir(join(runDir, 'inputs'));
44
53
  const outputDir = ensureDir(join(runDir, 'outputs'));
45
54
  const intermediateDir = ensureDir(join(runDir, 'intermediates'));
46
55
  const logDir = ensureDir(join(runDir, 'logs'));
@@ -64,7 +73,7 @@ export function beginAgentWorkspaceRun(input) {
64
73
  createdAt: new Date().toISOString(),
65
74
  files: [],
66
75
  });
67
- return { teamId: input.teamId, agentId: input.agentId, runId: input.runId, agentDir, runDir, outputDir, intermediateDir, logDir };
76
+ return { teamId: input.teamId, agentId: input.agentId, runId: input.runId, agentDir, runDir, inputDir, outputDir, intermediateDir, logDir };
68
77
  }
69
78
  export function workspaceEnv(run) {
70
79
  return {
@@ -72,6 +81,7 @@ export function workspaceEnv(run) {
72
81
  AGENTBEAN_AGENT_ID: run.agentId,
73
82
  AGENTBEAN_RUN_ID: run.runId,
74
83
  AGENTBEAN_WORKSPACE: run.agentDir,
84
+ AGENTBEAN_INPUT_DIR: run.inputDir,
75
85
  AGENTBEAN_OUTPUT_DIR: run.outputDir,
76
86
  AGENTBEAN_INTERMEDIATE_DIR: run.intermediateDir,
77
87
  AGENT_BEAN_OUTPUT_DIRS: [run.outputDir, run.intermediateDir].join(','),
@@ -112,6 +122,20 @@ export function archiveOutputFiles(run, files) {
112
122
  }
113
123
  return archived;
114
124
  }
125
+ export function formatWorkspaceReply(reply, files) {
126
+ let body = reply;
127
+ for (const file of files) {
128
+ body = replaceAllLiteral(body, `file://${file.originalPath}`, `file://${file.archivedPath}`);
129
+ body = replaceAllLiteral(body, file.originalPath, file.archivedPath);
130
+ }
131
+ const missingPaths = files
132
+ .map((file) => file.archivedPath)
133
+ .filter((path) => !body.includes(path));
134
+ if (missingPaths.length > 0) {
135
+ body += '\n\n已生成文件:\n' + missingPaths.map((path) => `- ${path}`).join('\n');
136
+ }
137
+ return body;
138
+ }
115
139
  export function finishAgentWorkspaceRun(run, input) {
116
140
  if (input.replyText !== undefined) {
117
141
  writeFileSync(join(run.runDir, 'response.md'), input.replyText);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agentbean/daemon",
3
3
  "private": false,
4
- "version": "0.1.11",
4
+ "version": "0.1.13",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "bin": {