@agentbean/daemon 0.1.7 → 0.1.9
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/post-process.js +129 -23
- package/package.json +1 -1
package/dist/post-process.js
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
|
-
import { readdirSync, statSync, existsSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
1
|
+
import { readdirSync, realpathSync, statSync, existsSync } from 'node:fs';
|
|
2
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { logger } from './log.js';
|
|
5
5
|
const CODE_BLOCK_RE = /```python\n([\s\S]*?)```/g;
|
|
6
6
|
const CODEX_IMG_DIR = join(homedir(), '.codex', 'generated_images');
|
|
7
|
+
const OUTPUT_FILE_EXT_RE = /\.(png|jpe?g|gif|webp|svg|pdf|txt|csv|json|md|mp4|mov|zip)$/i;
|
|
8
|
+
const IGNORED_OUTPUT_DIRS = new Set([
|
|
9
|
+
'.git',
|
|
10
|
+
'.hg',
|
|
11
|
+
'.svn',
|
|
12
|
+
'.cache',
|
|
13
|
+
'.next',
|
|
14
|
+
'.nuxt',
|
|
15
|
+
'.turbo',
|
|
16
|
+
'node_modules',
|
|
17
|
+
'vendor',
|
|
18
|
+
]);
|
|
19
|
+
const MAX_OUTPUT_FILES_PER_ROOT = 2000;
|
|
20
|
+
const OUTPUT_DIR_ENV = 'AGENT_BEAN_OUTPUT_DIRS';
|
|
7
21
|
export function listAllFiles(dir, maxDepth = 10, depth = 0) {
|
|
8
22
|
if (!existsSync(dir) || depth > maxDepth)
|
|
9
23
|
return [];
|
|
@@ -12,45 +26,136 @@ export function listAllFiles(dir, maxDepth = 10, depth = 0) {
|
|
|
12
26
|
const full = join(dir, entry.name);
|
|
13
27
|
if (entry.isSymbolicLink())
|
|
14
28
|
continue;
|
|
15
|
-
if (entry.isDirectory())
|
|
29
|
+
if (entry.isDirectory()) {
|
|
30
|
+
if (IGNORED_OUTPUT_DIRS.has(entry.name))
|
|
31
|
+
continue;
|
|
16
32
|
results.push(...listAllFiles(full, maxDepth, depth + 1));
|
|
33
|
+
}
|
|
17
34
|
else
|
|
18
35
|
results.push(full);
|
|
36
|
+
if (results.length >= MAX_OUTPUT_FILES_PER_ROOT)
|
|
37
|
+
break;
|
|
19
38
|
}
|
|
20
39
|
return results;
|
|
21
40
|
}
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
41
|
+
function normalizeCandidatePath(raw) {
|
|
42
|
+
const cleaned = raw
|
|
43
|
+
.trim()
|
|
44
|
+
.replace(/^file:\/\//, '')
|
|
45
|
+
.replace(/^["'`<({\[]+/, '')
|
|
46
|
+
.replace(/["'`>)}\].,;:]+$/, '');
|
|
47
|
+
if (!cleaned || !OUTPUT_FILE_EXT_RE.test(cleaned))
|
|
48
|
+
return null;
|
|
49
|
+
return cleaned.replace(/^~(?=$|\/)/, homedir());
|
|
50
|
+
}
|
|
51
|
+
function extractMentionedFiles(reply, workspace, dispatchStart) {
|
|
52
|
+
const candidates = new Set();
|
|
53
|
+
const markdownLinkRe = /!?\[[^\]]*]\(([^)\s]+)\)/g;
|
|
54
|
+
const plainPathRe = /(?:^|[\s"'`(<])((?:~?\/|\.{1,2}\/)?[\w@%+=:,./-]+\.(?:png|jpe?g|gif|webp|svg|pdf|txt|csv|json|md|mp4|mov|zip))(?:$|[\s"'`)>.,;:])/gim;
|
|
55
|
+
let match;
|
|
56
|
+
while ((match = markdownLinkRe.exec(reply)) !== null) {
|
|
57
|
+
const normalized = normalizeCandidatePath(match[1]);
|
|
58
|
+
if (normalized)
|
|
59
|
+
candidates.add(normalized);
|
|
60
|
+
}
|
|
61
|
+
while ((match = plainPathRe.exec(reply)) !== null) {
|
|
62
|
+
const normalized = normalizeCandidatePath(match[1]);
|
|
63
|
+
if (normalized)
|
|
64
|
+
candidates.add(normalized);
|
|
65
|
+
}
|
|
66
|
+
const files = [];
|
|
67
|
+
for (const candidate of candidates) {
|
|
68
|
+
const abs = isAbsolute(candidate) ? candidate : workspace ? resolve(workspace, candidate) : null;
|
|
69
|
+
if (!abs)
|
|
70
|
+
continue;
|
|
71
|
+
try {
|
|
72
|
+
const st = statSync(abs);
|
|
73
|
+
if (st.isFile() && st.mtimeMs > dispatchStart)
|
|
74
|
+
files.push(abs);
|
|
75
|
+
}
|
|
76
|
+
catch { }
|
|
77
|
+
}
|
|
78
|
+
return files;
|
|
79
|
+
}
|
|
80
|
+
function outputDirsFromEnv() {
|
|
81
|
+
const raw = process.env[OUTPUT_DIR_ENV];
|
|
82
|
+
if (!raw?.trim())
|
|
83
|
+
return [];
|
|
84
|
+
return raw
|
|
85
|
+
.split(/[,:;]/)
|
|
86
|
+
.map((item) => item.trim())
|
|
87
|
+
.filter(Boolean);
|
|
88
|
+
}
|
|
89
|
+
function canonicalPath(path) {
|
|
90
|
+
try {
|
|
91
|
+
return realpathSync(path);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return path;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function resolveOutputRoots(workspace, outputDirs = []) {
|
|
98
|
+
const roots = new Set();
|
|
99
|
+
if (workspace)
|
|
100
|
+
roots.add(resolve(workspace));
|
|
101
|
+
for (const raw of [...outputDirs, ...outputDirsFromEnv()]) {
|
|
102
|
+
const expanded = raw.replace(/^~(?=$|\/)/, homedir());
|
|
103
|
+
const root = isAbsolute(expanded)
|
|
104
|
+
? expanded
|
|
105
|
+
: workspace
|
|
106
|
+
? resolve(workspace, expanded)
|
|
107
|
+
: resolve(expanded);
|
|
108
|
+
roots.add(root);
|
|
109
|
+
}
|
|
110
|
+
return [...roots].filter((root) => {
|
|
111
|
+
try {
|
|
112
|
+
return statSync(root).isDirectory();
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function collectRecentOutputFiles(roots, dispatchStart) {
|
|
120
|
+
const files = new Set();
|
|
121
|
+
for (const root of roots) {
|
|
122
|
+
for (const filePath of listAllFiles(root, 8)) {
|
|
123
|
+
if (!OUTPUT_FILE_EXT_RE.test(filePath))
|
|
124
|
+
continue;
|
|
28
125
|
try {
|
|
29
|
-
const st = statSync(
|
|
30
|
-
if (st.mtimeMs > dispatchStart) {
|
|
31
|
-
|
|
126
|
+
const st = statSync(filePath);
|
|
127
|
+
if (st.isFile() && st.mtimeMs > dispatchStart) {
|
|
128
|
+
files.add(canonicalPath(filePath));
|
|
32
129
|
}
|
|
33
130
|
}
|
|
34
131
|
catch { }
|
|
35
132
|
}
|
|
36
133
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
134
|
+
return [...files];
|
|
135
|
+
}
|
|
136
|
+
export async function postProcess(reply, workspace, kind, dispatchStart, options = {}) {
|
|
137
|
+
const outputFiles = new Set();
|
|
138
|
+
// Codex native image detection
|
|
139
|
+
if (kind === 'codex') {
|
|
140
|
+
const allCodexFiles = listAllFiles(CODEX_IMG_DIR, 4);
|
|
141
|
+
for (const f of allCodexFiles) {
|
|
142
|
+
if (!OUTPUT_FILE_EXT_RE.test(f))
|
|
44
143
|
continue;
|
|
45
144
|
try {
|
|
46
145
|
const st = statSync(f);
|
|
47
146
|
if (st.mtimeMs > dispatchStart) {
|
|
48
|
-
outputFiles.
|
|
147
|
+
outputFiles.add(canonicalPath(f));
|
|
49
148
|
}
|
|
50
149
|
}
|
|
51
150
|
catch { }
|
|
52
151
|
}
|
|
53
152
|
}
|
|
153
|
+
for (const filePath of extractMentionedFiles(reply, workspace, dispatchStart)) {
|
|
154
|
+
outputFiles.add(canonicalPath(filePath));
|
|
155
|
+
}
|
|
156
|
+
for (const filePath of collectRecentOutputFiles(resolveOutputRoots(workspace, options.outputDirs), dispatchStart)) {
|
|
157
|
+
outputFiles.add(filePath);
|
|
158
|
+
}
|
|
54
159
|
// Extract code blocks for logging but do NOT auto-execute (security)
|
|
55
160
|
if (workspace) {
|
|
56
161
|
const codeBlocks = [];
|
|
@@ -64,8 +169,9 @@ export async function postProcess(reply, workspace, kind, dispatchStart) {
|
|
|
64
169
|
}
|
|
65
170
|
}
|
|
66
171
|
let replyText = reply;
|
|
67
|
-
|
|
68
|
-
|
|
172
|
+
const files = [...outputFiles];
|
|
173
|
+
if (files.length > 0) {
|
|
174
|
+
replyText += '\n\n已生成文件:\n' + files.map((f) => `- ${f}`).join('\n');
|
|
69
175
|
}
|
|
70
|
-
return { replyText, outputFiles };
|
|
176
|
+
return { replyText, outputFiles: files };
|
|
71
177
|
}
|