@agentbean/daemon 0.1.35 → 0.2.0

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 (53) hide show
  1. package/dist/apps/daemon-next/src/bin.d.ts +2 -0
  2. package/dist/apps/daemon-next/src/bin.js +6 -0
  3. package/dist/apps/daemon-next/src/cli.d.ts +26 -0
  4. package/dist/apps/daemon-next/src/cli.js +124 -0
  5. package/dist/apps/daemon-next/src/executor.d.ts +6 -0
  6. package/dist/apps/daemon-next/src/executor.js +51 -0
  7. package/dist/apps/daemon-next/src/index.d.ts +60 -0
  8. package/dist/apps/daemon-next/src/index.js +87 -0
  9. package/dist/apps/daemon-next/src/scanner.d.ts +15 -0
  10. package/dist/apps/daemon-next/src/scanner.js +94 -0
  11. package/dist/packages/contracts/src/agent.d.ts +69 -0
  12. package/dist/packages/contracts/src/agent.js +4 -0
  13. package/dist/packages/contracts/src/auth.d.ts +20 -0
  14. package/dist/packages/contracts/src/auth.js +1 -0
  15. package/dist/packages/contracts/src/channel.d.ts +58 -0
  16. package/dist/packages/contracts/src/channel.js +1 -0
  17. package/dist/packages/contracts/src/common.d.ts +17 -0
  18. package/dist/packages/contracts/src/common.js +27 -0
  19. package/dist/packages/contracts/src/device.d.ts +27 -0
  20. package/dist/packages/contracts/src/device.js +1 -0
  21. package/dist/packages/contracts/src/dispatch.d.ts +46 -0
  22. package/dist/packages/contracts/src/dispatch.js +1 -0
  23. package/dist/packages/contracts/src/index.d.ts +9 -0
  24. package/dist/packages/contracts/src/index.js +9 -0
  25. package/dist/packages/contracts/src/message.d.ts +20 -0
  26. package/dist/packages/contracts/src/message.js +1 -0
  27. package/dist/packages/contracts/src/socket.d.ts +74 -0
  28. package/dist/packages/contracts/src/socket.js +74 -0
  29. package/dist/packages/contracts/src/team.d.ts +13 -0
  30. package/dist/packages/contracts/src/team.js +1 -0
  31. package/package.json +14 -25
  32. package/README.md +0 -158
  33. package/dist/adapters/adapter.js +0 -9
  34. package/dist/adapters/claude-code.js +0 -83
  35. package/dist/adapters/codex.js +0 -280
  36. package/dist/adapters/factory.js +0 -38
  37. package/dist/adapters/hermes.js +0 -178
  38. package/dist/adapters/openclaw.js +0 -129
  39. package/dist/agent-instance.js +0 -181
  40. package/dist/auth-store.js +0 -44
  41. package/dist/bin.js +0 -6
  42. package/dist/config.js +0 -148
  43. package/dist/connection.js +0 -211
  44. package/dist/device-daemon.js +0 -530
  45. package/dist/index.js +0 -368
  46. package/dist/log.js +0 -7
  47. package/dist/post-process.js +0 -177
  48. package/dist/profile-paths.js +0 -39
  49. package/dist/sandbox.js +0 -53
  50. package/dist/scanner.js +0 -423
  51. package/dist/uploader.js +0 -46
  52. package/dist/workspace-manager.js +0 -196
  53. package/dist/workspace-sync.js +0 -69
@@ -1,181 +0,0 @@
1
- import { writeFileSync } from 'node:fs';
2
- import { basename, join } from 'node:path';
3
- import { logger } from './log.js';
4
- import { uploadArtifact } from './uploader.js';
5
- import { postProcess } from './post-process.js';
6
- import { generateSandboxProfile, getWorkspaceDir, isSandboxAvailable } from './sandbox.js';
7
- import { archiveOutputFiles, beginAgentWorkspaceRun, finishAgentWorkspaceRun, formatWorkspaceReply, workspaceEnv, } from './workspace-manager.js';
8
- function errorMessage(err) {
9
- if (err instanceof Error && err.message)
10
- return err.message;
11
- if (typeof err === 'string' && err.trim())
12
- return err;
13
- try {
14
- const serialized = JSON.stringify(err);
15
- if (serialized && serialized !== '{}')
16
- return serialized;
17
- }
18
- catch { }
19
- return 'unknown error';
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
- }
54
- function promptWithWorkspaceOutput(prompt, outputDir) {
55
- return `${prompt}\n\n如果本次任务会生成图片、文档、数据或其他文件,请把最终产物保存到这个 AgentBean 输出目录:\n${outputDir}\n保存后在回复中说明文件名即可,系统会自动同步并在聊天中展示预览。`;
56
- }
57
- export class AgentInstance {
58
- config;
59
- adapter;
60
- id;
61
- name;
62
- role;
63
- visibility;
64
- activeControllers = new Map();
65
- constructor(config, adapter) {
66
- this.config = config;
67
- this.adapter = adapter;
68
- this.id = config.id;
69
- this.name = config.name;
70
- this.role = config.role;
71
- this.visibility = config.visibility;
72
- }
73
- get publicMeta() {
74
- return {
75
- id: this.id,
76
- name: this.name,
77
- role: this.role,
78
- category: this.config.category ?? 'executor-hosted',
79
- adapterKind: this.adapter.kind,
80
- visibility: this.visibility,
81
- };
82
- }
83
- async handleDispatch(opts) {
84
- const { socket, req, serverUrl, token, networkId, deviceId } = opts;
85
- const ctl = new AbortController();
86
- this.activeControllers.set(req.requestId, ctl);
87
- const dispatchStart = Date.now();
88
- const teamId = req.teamId ?? req.networkId ?? networkId;
89
- const projectWorkspace = req.sandboxed ? getWorkspaceDir(this.id) : this.config.adapter.workspace;
90
- const run = beginAgentWorkspaceRun({
91
- teamId,
92
- teamName: req.teamName,
93
- agentId: this.id,
94
- agentName: this.name,
95
- runId: req.requestId,
96
- prompt: req.prompt,
97
- projectDir: projectWorkspace,
98
- });
99
- let archivedFiles = [];
100
- try {
101
- const downloadedAttachments = await downloadAttachments({ serverUrl, token, run, attachments: req.attachments });
102
- const prompt = promptWithWorkspaceOutput(promptWithAttachments(req.prompt, downloadedAttachments), run.outputDir);
103
- const rawBody = await this.adapter.ask({
104
- prompt,
105
- history: req.history ?? [],
106
- systemPrompt: this.config.adapter.systemPrompt,
107
- workspace: projectWorkspace,
108
- sandboxProfilePath: req.sandboxed && isSandboxAvailable()
109
- ? generateSandboxProfile(this.id, this.config.adapter.command, [run.runDir])
110
- : undefined,
111
- env: { ...(this.config.adapter.env ?? {}), ...workspaceEnv(run) },
112
- }, ctl.signal);
113
- const processed = await postProcess(rawBody, projectWorkspace, this.adapter.kind, dispatchStart, {
114
- outputDirs: [run.outputDir, run.intermediateDir],
115
- });
116
- archivedFiles = archiveOutputFiles(run, processed.outputFiles);
117
- const replyText = formatWorkspaceReply(rawBody, archivedFiles, { exposeLocalPaths: false });
118
- finishAgentWorkspaceRun(run, { replyText, files: archivedFiles, status: 'completed' });
119
- const artifactIds = [];
120
- if (archivedFiles.length > 0) {
121
- for (const file of archivedFiles) {
122
- try {
123
- const result = await uploadArtifact({
124
- serverUrl,
125
- token,
126
- networkId: teamId,
127
- filePath: file.archivedPath,
128
- channelId: req.channelId,
129
- uploaderId: this.id,
130
- metaJson: JSON.stringify({
131
- kind: 'agent-workspace-file',
132
- teamId,
133
- agentId: this.id,
134
- runId: req.requestId,
135
- deviceId: deviceId ?? null,
136
- pathKind: file.pathKind,
137
- relativePath: file.relativePath,
138
- sha256: file.sha256,
139
- }),
140
- });
141
- if (result)
142
- artifactIds.push(result.id);
143
- }
144
- catch (err) {
145
- logger.warn({ err: err.message, filePath: file.archivedPath }, 'artifact upload failed');
146
- }
147
- }
148
- }
149
- socket.emit('reply', {
150
- agentId: this.id,
151
- channelId: req.channelId,
152
- body: replyText,
153
- requestId: req.requestId,
154
- artifactIds: artifactIds.length > 0 ? artifactIds : undefined,
155
- });
156
- }
157
- catch (err) {
158
- const message = errorMessage(err);
159
- finishAgentWorkspaceRun(run, { files: archivedFiles, status: 'failed', error: message });
160
- logger.error({ err: message, requestId: req.requestId, agentId: this.id }, 'dispatch failed');
161
- socket.emit('error_event', {
162
- agentId: this.id,
163
- at: Date.now(),
164
- message,
165
- scope: 'reply',
166
- requestId: req.requestId,
167
- });
168
- }
169
- finally {
170
- this.activeControllers.delete(req.requestId);
171
- }
172
- }
173
- cancelDispatch(requestId) {
174
- const entries = requestId
175
- ? [...this.activeControllers.entries()].filter(([id]) => id === requestId)
176
- : [...this.activeControllers.entries()];
177
- for (const [, ctl] of entries)
178
- ctl.abort();
179
- return entries.length;
180
- }
181
- }
@@ -1,44 +0,0 @@
1
- import { existsSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { agentbeanHome, authFile, ensureProfileRoot, profileIdForNetwork } from './profile-paths.js';
4
- export function loadAuth(options = {}) {
5
- const file = authFile(options.profileId);
6
- if (!existsSync(file))
7
- return null;
8
- try {
9
- return JSON.parse(readFileSync(file, 'utf8'));
10
- }
11
- catch {
12
- return null;
13
- }
14
- }
15
- export function saveAuth(data, options = {}) {
16
- const profileId = options.profileId ?? null;
17
- ensureProfileRoot(profileId);
18
- writeFileSync(authFile(profileId), JSON.stringify(data, null, 2));
19
- }
20
- export function clearAuth(options = {}) {
21
- const file = authFile(options.profileId);
22
- if (existsSync(file))
23
- unlinkSync(file);
24
- }
25
- export function listAuthProfiles() {
26
- const profiles = [];
27
- const teamsDir = join(agentbeanHome(), 'teams');
28
- if (existsSync(teamsDir)) {
29
- for (const entry of readdirSync(teamsDir, { withFileTypes: true })) {
30
- if (!entry.isDirectory())
31
- continue;
32
- const auth = loadAuth({ profileId: entry.name });
33
- if (auth?.networkId)
34
- profiles.push({ ...auth, profileId: entry.name });
35
- }
36
- }
37
- const legacy = loadAuth();
38
- if (legacy?.networkId) {
39
- const profileId = profileIdForNetwork(legacy.networkId);
40
- if (!profiles.some((profile) => profile.profileId === profileId))
41
- profiles.push({ ...legacy, profileId });
42
- }
43
- return profiles;
44
- }
package/dist/bin.js DELETED
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env node
2
- import { main } from './index.js';
3
- main().catch((err) => {
4
- console.error('fatal:', err.message);
5
- process.exit(1);
6
- });
package/dist/config.js DELETED
@@ -1,148 +0,0 @@
1
- import { readFileSync } from 'node:fs';
2
- import { load as parseYaml } from 'js-yaml';
3
- const KINDS = ['codex', 'claude-code', 'openclaw', 'hermes', 'standalone'];
4
- const ENV_PATTERN = /\$\{([A-Z0-9_]+)\}/g;
5
- function interpolate(value) {
6
- return value.replace(ENV_PATTERN, (_match, name) => {
7
- const v = process.env[name];
8
- if (v === undefined)
9
- throw new Error(`config references missing env var: ${name}`);
10
- return v;
11
- });
12
- }
13
- function deepInterpolate(node) {
14
- if (typeof node === 'string')
15
- return interpolate(node);
16
- if (Array.isArray(node))
17
- return node.map(deepInterpolate);
18
- if (node && typeof node === 'object') {
19
- const out = {};
20
- for (const [k, v] of Object.entries(node))
21
- out[k] = deepInterpolate(v);
22
- return out;
23
- }
24
- return node;
25
- }
26
- function parseEnvMap(value) {
27
- if (!value || typeof value !== 'object' || Array.isArray(value))
28
- return undefined;
29
- const out = {};
30
- for (const [key, raw] of Object.entries(value)) {
31
- if (key.trim())
32
- out[key.trim()] = String(raw ?? '');
33
- }
34
- return Object.keys(out).length > 0 ? out : undefined;
35
- }
36
- export function loadConfig(path) {
37
- const raw = parseYaml(readFileSync(path, 'utf8'));
38
- if (!raw || typeof raw !== 'object')
39
- throw new Error('config: top-level must be a mapping');
40
- const interp = deepInterpolate(raw);
41
- const need = ['id', 'name', 'role'];
42
- for (const k of need) {
43
- if (typeof interp[k] !== 'string' || interp[k].length === 0) {
44
- throw new Error(`config: ${k} is required (non-empty string)`);
45
- }
46
- }
47
- const a = interp.adapter ?? {};
48
- if (!KINDS.includes(a.kind)) {
49
- throw new Error(`config: adapter.kind must be one of ${KINDS.join(', ')}`);
50
- }
51
- if (typeof a.command !== 'string') {
52
- throw new Error('config: adapter.command is required');
53
- }
54
- const s = interp.server ?? {};
55
- if (typeof s.url !== 'string' || typeof s.token !== 'string') {
56
- throw new Error('config: server.url and server.token are required');
57
- }
58
- const inferredCategory = a.kind === 'codex' || a.kind === 'claude-code' ? 'executor-hosted' :
59
- 'agentos-hosted';
60
- const category = typeof interp.category === 'string' && ['executor-hosted', 'agentos-hosted'].includes(interp.category)
61
- ? interp.category
62
- : inferredCategory;
63
- return {
64
- id: interp.id,
65
- name: interp.name,
66
- role: interp.role,
67
- category,
68
- adapter: {
69
- kind: a.kind,
70
- command: a.command,
71
- args: Array.isArray(a.args) ? a.args.map(String) : [],
72
- cwd: typeof a.cwd === 'string' ? a.cwd : undefined,
73
- workspace: typeof a.workspace === 'string' ? a.workspace : undefined,
74
- systemPrompt: typeof a.systemPrompt === 'string' ? a.systemPrompt : undefined,
75
- env: parseEnvMap(a.env),
76
- },
77
- server: { url: s.url, token: s.token },
78
- heartbeatIntervalMs: isValidHeartbeat(interp.heartbeatIntervalMs) ? interp.heartbeatIntervalMs : 10_000,
79
- };
80
- }
81
- function isValidHeartbeat(v) {
82
- return typeof v === 'number' && v > 0 && Number.isFinite(v);
83
- }
84
- export function loadDeviceConfig(path) {
85
- const raw = parseYaml(readFileSync(path, 'utf8'));
86
- if (!raw || typeof raw !== 'object')
87
- throw new Error('config: top-level must be a mapping');
88
- const interp = deepInterpolate(raw);
89
- if (typeof interp.deviceId !== 'string' || interp.deviceId.length === 0) {
90
- throw new Error('config: deviceId is required (non-empty string)');
91
- }
92
- if (typeof interp.networkId !== 'string' || interp.networkId.length === 0) {
93
- throw new Error('config: networkId is required (non-empty string)');
94
- }
95
- const s = interp.server ?? {};
96
- if (typeof s.url !== 'string' || typeof s.token !== 'string') {
97
- throw new Error('config: server.url and server.token are required');
98
- }
99
- const agents = interp.agents ?? [];
100
- if (!Array.isArray(agents) || agents.length === 0) {
101
- throw new Error('config: agents array is required (at least one agent)');
102
- }
103
- const parsedAgents = [];
104
- for (const a of agents) {
105
- if (typeof a.id !== 'string' || a.id.length === 0) {
106
- throw new Error('config: each agent must have an id (non-empty string)');
107
- }
108
- if (typeof a.name !== 'string' || a.name.length === 0) {
109
- throw new Error('config: each agent must have a name (non-empty string)');
110
- }
111
- const ad = a.adapter ?? {};
112
- if (!KINDS.includes(ad.kind)) {
113
- throw new Error(`config: adapter.kind must be one of ${KINDS.join(', ')}`);
114
- }
115
- if (typeof ad.command !== 'string') {
116
- throw new Error('config: adapter.command is required');
117
- }
118
- const inferredCategory = ad.kind === 'codex' || ad.kind === 'claude-code' ? 'executor-hosted' :
119
- 'agentos-hosted';
120
- const category = typeof a.category === 'string' && ['executor-hosted', 'agentos-hosted'].includes(a.category)
121
- ? a.category
122
- : inferredCategory;
123
- parsedAgents.push({
124
- id: a.id,
125
- name: a.name,
126
- role: typeof a.role === 'string' ? a.role : '',
127
- category,
128
- adapter: {
129
- kind: ad.kind,
130
- command: ad.command,
131
- args: Array.isArray(ad.args) ? ad.args.map(String) : [],
132
- cwd: typeof ad.cwd === 'string' ? ad.cwd : undefined,
133
- workspace: typeof ad.workspace === 'string' ? ad.workspace : undefined,
134
- systemPrompt: typeof ad.systemPrompt === 'string' ? ad.systemPrompt : undefined,
135
- env: parseEnvMap(ad.env),
136
- },
137
- visibility: a.visibility === 'public' ? 'public' : 'private',
138
- sandboxed: a.sandboxed === true,
139
- });
140
- }
141
- return {
142
- deviceId: interp.deviceId,
143
- networkId: interp.networkId,
144
- server: { url: s.url, token: s.token },
145
- heartbeatIntervalMs: isValidHeartbeat(interp.heartbeatIntervalMs) ? interp.heartbeatIntervalMs : 10_000,
146
- agents: parsedAgents,
147
- };
148
- }
@@ -1,211 +0,0 @@
1
- import { io } from 'socket.io-client';
2
- import { writeFileSync } from 'node:fs';
3
- import { basename, join } from 'node:path';
4
- import { logger } from './log.js';
5
- import { uploadArtifact } from './uploader.js';
6
- import { postProcess } from './post-process.js';
7
- import { archiveOutputFiles, beginAgentWorkspaceRun, finishAgentWorkspaceRun, formatWorkspaceReply, workspaceEnv, } from './workspace-manager.js';
8
- function errorMessage(err) {
9
- if (err instanceof Error && err.message)
10
- return err.message;
11
- if (typeof err === 'string' && err.trim())
12
- return err;
13
- try {
14
- const serialized = JSON.stringify(err);
15
- if (serialized && serialized !== '{}')
16
- return serialized;
17
- }
18
- catch { }
19
- return 'unknown error';
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
- }
53
- function promptWithWorkspaceOutput(prompt, outputDir) {
54
- return `${prompt}\n\n如果本次任务会生成图片、文档、数据或其他文件,请把最终产物保存到这个 AgentBean 输出目录:\n${outputDir}\n保存后在回复中说明文件名即可,系统会自动同步并在聊天中展示预览。`;
55
- }
56
- export function createConnection(cfg, adapter) {
57
- let socket = null;
58
- let heartbeatTimer = null;
59
- let queue = Promise.resolve();
60
- const activeControllers = new Map();
61
- return {
62
- async start() {
63
- const agentUrl = cfg.server.url.endsWith('/agent') ? cfg.server.url : cfg.server.url + '/agent';
64
- socket = io(agentUrl, {
65
- auth: {
66
- token: cfg.server.token,
67
- agentId: cfg.id,
68
- name: cfg.name,
69
- role: cfg.role,
70
- adapterKind: cfg.adapter.kind,
71
- },
72
- reconnection: true,
73
- reconnectionDelay: 1_000,
74
- });
75
- socket.on('connect', () => {
76
- logger.info({ id: cfg.id }, 'connected to server');
77
- socket.emit('register', {
78
- id: cfg.id, name: cfg.name, role: cfg.role,
79
- adapterKind: cfg.adapter.kind,
80
- });
81
- if (heartbeatTimer)
82
- clearInterval(heartbeatTimer);
83
- heartbeatTimer = setInterval(() => {
84
- socket?.emit('heartbeat', { at: Date.now() });
85
- }, cfg.heartbeatIntervalMs);
86
- });
87
- socket.on('connect_error', (err) => {
88
- logger.error({ err: err.message }, 'connect_error');
89
- });
90
- socket.on('dispatch', (req) => {
91
- const currentSocket = socket;
92
- if (!currentSocket) {
93
- logger.warn({ requestId: req.requestId }, 'dispatch received but socket is null');
94
- return;
95
- }
96
- queue = queue.then(async () => {
97
- const ctl = new AbortController();
98
- activeControllers.set(req.requestId, ctl);
99
- const dispatchStart = Date.now();
100
- const teamId = 'default';
101
- const run = beginAgentWorkspaceRun({
102
- teamId,
103
- agentId: cfg.id,
104
- agentName: cfg.name,
105
- runId: req.requestId,
106
- prompt: req.prompt,
107
- projectDir: cfg.adapter.workspace,
108
- });
109
- let archivedFiles = [];
110
- try {
111
- const httpBase = cfg.server.url.replace(/\/agent$/, '');
112
- const downloadedAttachments = await downloadAttachments({
113
- serverUrl: httpBase,
114
- token: cfg.server.token,
115
- run,
116
- attachments: req.attachments,
117
- });
118
- const prompt = promptWithWorkspaceOutput(promptWithAttachments(req.prompt, downloadedAttachments), run.outputDir);
119
- const rawBody = await adapter.ask({
120
- prompt,
121
- history: req.history ?? [],
122
- systemPrompt: cfg.adapter.systemPrompt,
123
- workspace: cfg.adapter.workspace,
124
- env: workspaceEnv(run),
125
- }, ctl.signal);
126
- const processed = await postProcess(rawBody, cfg.adapter.workspace, cfg.adapter.kind, dispatchStart, {
127
- outputDirs: [run.outputDir, run.intermediateDir],
128
- });
129
- archivedFiles = archiveOutputFiles(run, processed.outputFiles);
130
- const replyText = formatWorkspaceReply(rawBody, archivedFiles, { exposeLocalPaths: false });
131
- finishAgentWorkspaceRun(run, { replyText, files: archivedFiles, status: 'completed' });
132
- const artifactIds = [];
133
- if (archivedFiles.length > 0) {
134
- for (const file of archivedFiles) {
135
- try {
136
- const result = await uploadArtifact({
137
- serverUrl: httpBase,
138
- token: cfg.server.token,
139
- networkId: teamId,
140
- filePath: file.archivedPath,
141
- channelId: req.channelId,
142
- uploaderId: cfg.id,
143
- metaJson: JSON.stringify({
144
- kind: 'agent-workspace-file',
145
- teamId,
146
- agentId: cfg.id,
147
- runId: req.requestId,
148
- pathKind: file.pathKind,
149
- relativePath: file.relativePath,
150
- sha256: file.sha256,
151
- }),
152
- });
153
- if (result)
154
- artifactIds.push(result.id);
155
- }
156
- catch (err) {
157
- logger.warn({ err: err.message, filePath: file.archivedPath }, 'artifact upload failed');
158
- }
159
- }
160
- }
161
- currentSocket.emit('reply', {
162
- channelId: req.channelId,
163
- body: replyText,
164
- requestId: req.requestId,
165
- artifactIds: artifactIds.length > 0 ? artifactIds : undefined,
166
- });
167
- }
168
- catch (err) {
169
- const message = errorMessage(err);
170
- finishAgentWorkspaceRun(run, { files: archivedFiles, status: 'failed', error: message });
171
- logger.error({ err: message, requestId: req.requestId }, 'dispatch failed');
172
- currentSocket.emit('error_event', {
173
- at: Date.now(),
174
- message,
175
- scope: 'reply',
176
- requestId: req.requestId,
177
- });
178
- }
179
- finally {
180
- activeControllers.delete(req.requestId);
181
- }
182
- }).catch((err) => {
183
- logger.error({ err: err?.message, requestId: req.requestId }, 'dispatch queue error');
184
- });
185
- });
186
- socket.on('dispatch:cancel', (payload) => {
187
- const entries = payload.requestId
188
- ? [...activeControllers.entries()].filter(([id]) => id === payload.requestId)
189
- : [...activeControllers.entries()];
190
- for (const [, ctl] of entries)
191
- ctl.abort();
192
- logger.info({ requestId: payload.requestId, cancelled: entries.length, reason: payload.reason }, 'dispatch cancel requested');
193
- });
194
- socket.on('disconnect', (reason) => {
195
- logger.warn({ reason }, 'disconnected');
196
- if (heartbeatTimer) {
197
- clearInterval(heartbeatTimer);
198
- heartbeatTimer = null;
199
- }
200
- });
201
- },
202
- async stop() {
203
- if (heartbeatTimer) {
204
- clearInterval(heartbeatTimer);
205
- heartbeatTimer = null;
206
- }
207
- socket?.close();
208
- socket = null;
209
- },
210
- };
211
- }