@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.
Files changed (2) hide show
  1. package/dist/post-process.js +129 -23
  2. package/package.json +1 -1
@@ -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
- export async function postProcess(reply, workspace, kind, dispatchStart) {
23
- const outputFiles = [];
24
- // Codex native image detection
25
- if (kind === 'codex') {
26
- const allCodexFiles = listAllFiles(CODEX_IMG_DIR);
27
- for (const f of allCodexFiles) {
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(f);
30
- if (st.mtimeMs > dispatchStart) {
31
- outputFiles.push(f);
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
- // Detect files created during this dispatch
38
- if (workspace) {
39
- for (const entry of readdirSync(workspace, { withFileTypes: true })) {
40
- if (entry.isDirectory())
41
- continue;
42
- const f = join(workspace, entry.name);
43
- if (entry.name.startsWith('.agentbean-exec-'))
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.push(f);
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
- if (outputFiles.length > 0) {
68
- replyText += '\n\n已生成文件:\n' + outputFiles.map((f) => `- ${f}`).join('\n');
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
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agentbean/daemon",
3
3
  "private": false,
4
- "version": "0.1.7",
4
+ "version": "0.1.9",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "bin": {