@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.
- package/dist/agent-instance.js +42 -5
- package/dist/connection.js +47 -6
- package/dist/workspace-manager.js +25 -1
- package/package.json +1 -1
package/dist/agent-instance.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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:
|
|
147
|
+
body: replyText,
|
|
111
148
|
requestId: req.requestId,
|
|
112
149
|
artifactIds: artifactIds.length > 0 ? artifactIds : undefined,
|
|
113
150
|
});
|
package/dist/connection.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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:
|
|
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);
|