@agentbean/daemon 0.1.15 → 0.1.17
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/adapters/codex.js +22 -5
- package/dist/adapters/hermes.js +29 -0
- package/dist/agent-instance.js +14 -1
- package/dist/connection.js +14 -1
- package/dist/device-daemon.js +11 -0
- package/dist/workspace-manager.js +20 -7
- package/package.json +1 -1
package/dist/adapters/codex.js
CHANGED
|
@@ -12,10 +12,23 @@ function renderPayload(input, systemPrompt) {
|
|
|
12
12
|
function stripAnsi(s) {
|
|
13
13
|
return s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
14
14
|
}
|
|
15
|
-
function
|
|
16
|
-
|
|
15
|
+
function removeEchoedPayload(output, payload) {
|
|
16
|
+
if (!payload)
|
|
17
|
+
return output;
|
|
18
|
+
const normalizedPayload = payload.replace(/\r\n/g, '\n');
|
|
19
|
+
const idx = output.lastIndexOf(normalizedPayload);
|
|
20
|
+
if (idx < 0)
|
|
21
|
+
return output;
|
|
22
|
+
const boundaryBefore = idx === 0 || output[idx - 1] === '\n' || output[idx - 1] === '\r';
|
|
23
|
+
if (!boundaryBefore)
|
|
24
|
+
return output;
|
|
25
|
+
const tail = output.slice(idx + normalizedPayload.length).trim();
|
|
26
|
+
return tail || output;
|
|
27
|
+
}
|
|
28
|
+
export function extractCodexReply(output, payload) {
|
|
29
|
+
const clean = removeEchoedPayload(stripAnsi(output).replace(/\r\n/g, '\n'), payload);
|
|
17
30
|
// Match "codex" label followed by reply content, ending before next hook or end
|
|
18
|
-
const match = clean.match(
|
|
31
|
+
const match = clean.match(/(?:^|\n)codex\n([\s\S]*?)(?:\nhook:|$)/i);
|
|
19
32
|
if (match)
|
|
20
33
|
return match[1].trim();
|
|
21
34
|
// Fallback: everything after last "user" prompt
|
|
@@ -35,6 +48,10 @@ function normalizeExecArgs(args) {
|
|
|
35
48
|
}
|
|
36
49
|
return baseArgs;
|
|
37
50
|
}
|
|
51
|
+
function adapterTimeoutMs() {
|
|
52
|
+
const fromEnv = Number.parseInt(process.env.AGENTBEAN_CODEX_TIMEOUT_MS ?? '', 10);
|
|
53
|
+
return Number.isFinite(fromEnv) && fromEnv > 0 ? fromEnv : 900_000;
|
|
54
|
+
}
|
|
38
55
|
export class CodexAdapter {
|
|
39
56
|
opts;
|
|
40
57
|
kind = 'codex';
|
|
@@ -61,7 +78,7 @@ export class CodexAdapter {
|
|
|
61
78
|
});
|
|
62
79
|
const chunks = [];
|
|
63
80
|
let finished = false;
|
|
64
|
-
const MAX_EXEC_MS =
|
|
81
|
+
const MAX_EXEC_MS = adapterTimeoutMs();
|
|
65
82
|
const onAbort = () => {
|
|
66
83
|
if (finished)
|
|
67
84
|
return;
|
|
@@ -100,7 +117,7 @@ export class CodexAdapter {
|
|
|
100
117
|
const detail = stripAnsi(raw).trim();
|
|
101
118
|
return reject(new Error(detail ? `codex exit ${exitCode}: ${detail}` : `codex exit ${exitCode}`));
|
|
102
119
|
}
|
|
103
|
-
const reply =
|
|
120
|
+
const reply = extractCodexReply(raw, payload);
|
|
104
121
|
resolve(reply || '(Codex 已完成处理)');
|
|
105
122
|
});
|
|
106
123
|
});
|
package/dist/adapters/hermes.js
CHANGED
|
@@ -32,6 +32,35 @@ export function extractHermesReply(output) {
|
|
|
32
32
|
.replace(ANSI_RE, '')
|
|
33
33
|
.replace(/\r\n?/g, '\n')
|
|
34
34
|
.split('\n');
|
|
35
|
+
let boxStart = -1;
|
|
36
|
+
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
37
|
+
if (lines[i]?.trim().startsWith('╭')) {
|
|
38
|
+
boxStart = i;
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (boxStart >= 0) {
|
|
43
|
+
const boxLines = [];
|
|
44
|
+
for (const line of lines.slice(boxStart + 1)) {
|
|
45
|
+
const trimmed = line.trim();
|
|
46
|
+
if (trimmed.startsWith('╰'))
|
|
47
|
+
break;
|
|
48
|
+
if (BOX_ONLY_RE.test(trimmed))
|
|
49
|
+
continue;
|
|
50
|
+
boxLines.push(line);
|
|
51
|
+
}
|
|
52
|
+
const boxedReply = boxLines
|
|
53
|
+
.map((line) => line.trimEnd())
|
|
54
|
+
.filter((line) => {
|
|
55
|
+
const trimmed = line.trim();
|
|
56
|
+
return !(trimmed.startsWith('╭') || trimmed.startsWith('╰') || BOX_ONLY_RE.test(trimmed));
|
|
57
|
+
})
|
|
58
|
+
.map((line) => line.replace(/^[│┃]\s?/, '').replace(/\s?[│┃]$/, '').replace(/^\s{2,}/, ''))
|
|
59
|
+
.join('\n')
|
|
60
|
+
.trim();
|
|
61
|
+
if (boxedReply)
|
|
62
|
+
return boxedReply;
|
|
63
|
+
}
|
|
35
64
|
const cleaned = lines
|
|
36
65
|
.map((line) => line.trimEnd())
|
|
37
66
|
.filter((line) => {
|
package/dist/agent-instance.js
CHANGED
|
@@ -58,6 +58,7 @@ export class AgentInstance {
|
|
|
58
58
|
name;
|
|
59
59
|
role;
|
|
60
60
|
visibility;
|
|
61
|
+
activeControllers = new Map();
|
|
61
62
|
constructor(config, adapter) {
|
|
62
63
|
this.config = config;
|
|
63
64
|
this.adapter = adapter;
|
|
@@ -79,6 +80,7 @@ export class AgentInstance {
|
|
|
79
80
|
async handleDispatch(opts) {
|
|
80
81
|
const { socket, req, serverUrl, token, networkId, deviceId } = opts;
|
|
81
82
|
const ctl = new AbortController();
|
|
83
|
+
this.activeControllers.set(req.requestId, ctl);
|
|
82
84
|
const dispatchStart = Date.now();
|
|
83
85
|
const teamId = req.teamId ?? req.networkId ?? networkId;
|
|
84
86
|
const projectWorkspace = req.sandboxed ? getWorkspaceDir(this.id) : this.config.adapter.workspace;
|
|
@@ -109,7 +111,7 @@ export class AgentInstance {
|
|
|
109
111
|
outputDirs: [run.outputDir, run.intermediateDir],
|
|
110
112
|
});
|
|
111
113
|
archivedFiles = archiveOutputFiles(run, processed.outputFiles);
|
|
112
|
-
const replyText = formatWorkspaceReply(rawBody, archivedFiles);
|
|
114
|
+
const replyText = formatWorkspaceReply(rawBody, archivedFiles, { exposeLocalPaths: false });
|
|
113
115
|
finishAgentWorkspaceRun(run, { replyText, files: archivedFiles, status: 'completed' });
|
|
114
116
|
const artifactIds = [];
|
|
115
117
|
if (archivedFiles.length > 0) {
|
|
@@ -161,5 +163,16 @@ export class AgentInstance {
|
|
|
161
163
|
requestId: req.requestId,
|
|
162
164
|
});
|
|
163
165
|
}
|
|
166
|
+
finally {
|
|
167
|
+
this.activeControllers.delete(req.requestId);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
cancelDispatch(requestId) {
|
|
171
|
+
const entries = requestId
|
|
172
|
+
? [...this.activeControllers.entries()].filter(([id]) => id === requestId)
|
|
173
|
+
: [...this.activeControllers.entries()];
|
|
174
|
+
for (const [, ctl] of entries)
|
|
175
|
+
ctl.abort();
|
|
176
|
+
return entries.length;
|
|
164
177
|
}
|
|
165
178
|
}
|
package/dist/connection.js
CHANGED
|
@@ -54,6 +54,7 @@ export function createConnection(cfg, adapter) {
|
|
|
54
54
|
let socket = null;
|
|
55
55
|
let heartbeatTimer = null;
|
|
56
56
|
let queue = Promise.resolve();
|
|
57
|
+
const activeControllers = new Map();
|
|
57
58
|
return {
|
|
58
59
|
async start() {
|
|
59
60
|
const agentUrl = cfg.server.url.endsWith('/agent') ? cfg.server.url : cfg.server.url + '/agent';
|
|
@@ -91,6 +92,7 @@ export function createConnection(cfg, adapter) {
|
|
|
91
92
|
}
|
|
92
93
|
queue = queue.then(async () => {
|
|
93
94
|
const ctl = new AbortController();
|
|
95
|
+
activeControllers.set(req.requestId, ctl);
|
|
94
96
|
const dispatchStart = Date.now();
|
|
95
97
|
const teamId = 'default';
|
|
96
98
|
const run = beginAgentWorkspaceRun({
|
|
@@ -122,7 +124,7 @@ export function createConnection(cfg, adapter) {
|
|
|
122
124
|
outputDirs: [run.outputDir, run.intermediateDir],
|
|
123
125
|
});
|
|
124
126
|
archivedFiles = archiveOutputFiles(run, processed.outputFiles);
|
|
125
|
-
const replyText = formatWorkspaceReply(rawBody, archivedFiles);
|
|
127
|
+
const replyText = formatWorkspaceReply(rawBody, archivedFiles, { exposeLocalPaths: false });
|
|
126
128
|
finishAgentWorkspaceRun(run, { replyText, files: archivedFiles, status: 'completed' });
|
|
127
129
|
const artifactIds = [];
|
|
128
130
|
if (archivedFiles.length > 0) {
|
|
@@ -171,10 +173,21 @@ export function createConnection(cfg, adapter) {
|
|
|
171
173
|
requestId: req.requestId,
|
|
172
174
|
});
|
|
173
175
|
}
|
|
176
|
+
finally {
|
|
177
|
+
activeControllers.delete(req.requestId);
|
|
178
|
+
}
|
|
174
179
|
}).catch((err) => {
|
|
175
180
|
logger.error({ err: err?.message, requestId: req.requestId }, 'dispatch queue error');
|
|
176
181
|
});
|
|
177
182
|
});
|
|
183
|
+
socket.on('dispatch:cancel', (payload) => {
|
|
184
|
+
const entries = payload.requestId
|
|
185
|
+
? [...activeControllers.entries()].filter(([id]) => id === payload.requestId)
|
|
186
|
+
: [...activeControllers.entries()];
|
|
187
|
+
for (const [, ctl] of entries)
|
|
188
|
+
ctl.abort();
|
|
189
|
+
logger.info({ requestId: payload.requestId, cancelled: entries.length, reason: payload.reason }, 'dispatch cancel requested');
|
|
190
|
+
});
|
|
178
191
|
socket.on('disconnect', (reason) => {
|
|
179
192
|
logger.warn({ reason }, 'disconnected');
|
|
180
193
|
if (heartbeatTimer) {
|
package/dist/device-daemon.js
CHANGED
|
@@ -315,6 +315,17 @@ export function createDeviceDaemon(cfg, agents) {
|
|
|
315
315
|
});
|
|
316
316
|
queues.set(req.agentId, next);
|
|
317
317
|
});
|
|
318
|
+
socket.on('dispatch:cancel', (payload) => {
|
|
319
|
+
const targets = payload.agentId ? [payload.agentId] : [...agents.keys()];
|
|
320
|
+
let cancelled = 0;
|
|
321
|
+
for (const agentId of targets) {
|
|
322
|
+
const agent = agents.get(agentId);
|
|
323
|
+
if (!agent)
|
|
324
|
+
continue;
|
|
325
|
+
cancelled += agent.cancelDispatch(payload.requestId);
|
|
326
|
+
}
|
|
327
|
+
logger.info({ agentId: payload.agentId, requestId: payload.requestId, cancelled, reason: payload.reason }, 'dispatch cancel requested');
|
|
328
|
+
});
|
|
318
329
|
socket.on('agents:discover', async () => {
|
|
319
330
|
await scanAndRegister(socket, false);
|
|
320
331
|
});
|
|
@@ -42,6 +42,11 @@ function replaceAllLiteral(value, search, replacement) {
|
|
|
42
42
|
return value;
|
|
43
43
|
return value.replace(new RegExp(escapeRegExp(search), 'g'), replacement);
|
|
44
44
|
}
|
|
45
|
+
function stripDevicePath(value, replacement) {
|
|
46
|
+
let body = replaceAllLiteral(value, `file://${replacement}`, basename(replacement));
|
|
47
|
+
body = replaceAllLiteral(body, replacement, basename(replacement));
|
|
48
|
+
return body;
|
|
49
|
+
}
|
|
45
50
|
export function beginAgentWorkspaceRun(input) {
|
|
46
51
|
const teamId = safeSegment(input.teamId);
|
|
47
52
|
const agentId = safeSegment(input.agentId);
|
|
@@ -122,17 +127,25 @@ export function archiveOutputFiles(run, files) {
|
|
|
122
127
|
}
|
|
123
128
|
return archived;
|
|
124
129
|
}
|
|
125
|
-
export function formatWorkspaceReply(reply, files) {
|
|
130
|
+
export function formatWorkspaceReply(reply, files, options = {}) {
|
|
131
|
+
const exposeLocalPaths = options.exposeLocalPaths ?? true;
|
|
126
132
|
let body = reply;
|
|
127
133
|
for (const file of files) {
|
|
128
|
-
|
|
129
|
-
|
|
134
|
+
if (exposeLocalPaths) {
|
|
135
|
+
body = replaceAllLiteral(body, `file://${file.originalPath}`, `file://${file.archivedPath}`);
|
|
136
|
+
body = replaceAllLiteral(body, file.originalPath, file.archivedPath);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
body = stripDevicePath(body, file.originalPath);
|
|
140
|
+
body = stripDevicePath(body, file.archivedPath);
|
|
141
|
+
}
|
|
130
142
|
}
|
|
131
|
-
const missingPaths = files
|
|
132
|
-
.
|
|
133
|
-
|
|
143
|
+
const missingPaths = files.filter((file) => {
|
|
144
|
+
const marker = exposeLocalPaths ? file.archivedPath : basename(file.archivedPath);
|
|
145
|
+
return !body.includes(marker);
|
|
146
|
+
});
|
|
134
147
|
if (missingPaths.length > 0) {
|
|
135
|
-
body += '\n\n已生成文件:\n' + missingPaths.map((
|
|
148
|
+
body += '\n\n已生成文件:\n' + missingPaths.map((file) => `- ${exposeLocalPaths ? file.archivedPath : basename(file.archivedPath)}`).join('\n');
|
|
136
149
|
}
|
|
137
150
|
return body;
|
|
138
151
|
}
|