@anteros/core 0.0.44 → 0.0.47
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/LICENSE +21 -0
- package/README.md +1 -1
- package/database/field.crypto.ts +38 -13
- package/database/mongodbadapter.ts +85 -25
- package/database/vars.ts +22 -2
- package/index.ts +22 -0
- package/lib/agent.ts +707 -79
- package/lib/agentMemory.ts +199 -0
- package/lib/agents.ts +133 -52
- package/lib/attachment.ts +24 -3
- package/lib/define.ts +5 -1
- package/lib/eval.ts +123 -0
- package/lib/files.ts +21 -15
- package/lib/jsonSchema.ts +8 -0
- package/lib/mcp.ts +52 -30
- package/lib/messages.ts +202 -19
- package/lib/partialJson.ts +79 -0
- package/lib/providers.ts +26 -2
- package/lib/redact.ts +11 -0
- package/lib/tracing.ts +20 -0
- package/lib/zip.ts +23 -3
- package/package.json +4 -5
- package/server/agents.ts +66 -1
- package/server/api.ts +302 -20
- package/server/boot.ts +13 -4
- package/server/hono.ts +146 -6
- package/tests/access-log-body.test.ts +253 -0
- package/tests/agent-checkpoint.test.ts +195 -0
- package/tests/agent-hardening.test.ts +238 -0
- package/tests/agent-memory.test.ts +175 -1
- package/tests/agent-stream-advanced.test.ts +246 -0
- package/tests/agent-tracing-eval.test.ts +219 -0
- package/tests/agent-working-memory.test.ts +7 -4
- package/tests/agent.test.ts +54 -3
- package/tests/agents-api.test.ts +21 -0
- package/tests/api-external-guards.test.ts +304 -0
- package/tests/attachment.test.ts +63 -0
- package/tests/audit-redact.test.ts +26 -1
- package/tests/field-encryption.test.ts +47 -3
- package/tests/filters-hardening.test.ts +81 -0
- package/tests/fixtures/ai-tenant/ai/agents/support/billing.agent.ts +9 -0
- package/tests/fixtures/ai-tenant/ai/agents/support/billing.tool.ts +9 -0
- package/tests/fixtures/ai-tenant/ai/agents/support.agent.ts +9 -0
- package/tests/fixtures/ai-tenant/ai/agents/support.tool.ts +9 -0
- package/tests/fixtures/ai-tenant/ai/mcp/resources/readme.resource.ts +9 -0
- package/tests/fixtures/ai-tenant/ai/mcp/tools/echo.tool.ts +9 -0
- package/tests/fixtures/fake-provider.ts +33 -0
- package/tests/fixtures/tool-tenant/ai/agents/report.agent.ts +12 -0
- package/tests/fixtures/tool-tenant/ai/agents/report.tool.ts +12 -0
- package/tests/fixtures/vars-tenant/vars/open.var.ts +18 -0
- package/tests/hono.test.ts +81 -1
- package/tests/jwt-tenant.test.ts +165 -0
- package/tests/logger.test.ts +62 -10
- package/tests/messages.test.ts +88 -32
- package/tests/tool-runtime.test.ts +291 -0
- package/tests/tool-types.ts +33 -0
- package/tests/upload.test.ts +175 -3
- package/tests/vars-api.test.ts +73 -1
- package/tests/vars.test.ts +1 -1
- package/types/agent.d.ts +386 -4
- package/types/config.d.ts +56 -2
- package/types/file.d.ts +1 -1
- package/types/mcp.d.ts +2 -2
- package/utils/func.ts +20 -0
- package/utils/logger.ts +125 -33
package/utils/logger.ts
CHANGED
|
@@ -3,6 +3,9 @@ import path from 'node:path';
|
|
|
3
3
|
|
|
4
4
|
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
5
5
|
|
|
6
|
+
/** Rotation trigger: size (default) or a calendar period. */
|
|
7
|
+
export type LogRotation = 'size' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly';
|
|
8
|
+
|
|
6
9
|
const LEVEL_ORDER: Record<LogLevel, number> = { debug: 10, info: 20, warn: 30, error: 40 };
|
|
7
10
|
|
|
8
11
|
/** ANSI colors (no dependency on @colors/colors import order) */
|
|
@@ -19,25 +22,55 @@ export type LoggerOptions = {
|
|
|
19
22
|
level?: LogLevel;
|
|
20
23
|
/** Also write to the console (default: true) */
|
|
21
24
|
console?: boolean;
|
|
22
|
-
/** File logging: `true` → `<dir>/
|
|
25
|
+
/** File logging: `true` → `<dir>/app.log`, `string` → custom path, `false` → disabled (default: true) */
|
|
23
26
|
file?: boolean | string;
|
|
24
27
|
/** Directory used when `file` is `true` (default: '.logs') */
|
|
25
28
|
dir?: string;
|
|
26
|
-
/**
|
|
29
|
+
/** Rotation trigger (default: 'size'). Calendar modes ignore `maxSize`. */
|
|
30
|
+
rotate?: LogRotation;
|
|
31
|
+
/** Rotate when the file exceeds this size in bytes — `rotate: 'size'` only (default: 10MB) */
|
|
27
32
|
maxSize?: number;
|
|
28
33
|
/** Keep this many rotated files (default: 5) */
|
|
29
34
|
maxFiles?: number;
|
|
30
35
|
};
|
|
31
36
|
|
|
32
|
-
const DEFAULT_OPTIONS: Required<Pick<LoggerOptions, 'level' | 'console' | 'dir' | 'maxSize' | 'maxFiles'>> & { file: boolean | string } = {
|
|
37
|
+
const DEFAULT_OPTIONS: Required<Pick<LoggerOptions, 'level' | 'console' | 'dir' | 'rotate' | 'maxSize' | 'maxFiles'>> & { file: boolean | string } = {
|
|
33
38
|
level: 'info',
|
|
34
39
|
console: true,
|
|
35
40
|
file: true,
|
|
36
41
|
dir: '.logs',
|
|
42
|
+
rotate: 'size',
|
|
37
43
|
maxSize: 10 * 1024 * 1024, // 10MB
|
|
38
44
|
maxFiles: 5,
|
|
39
45
|
};
|
|
40
46
|
|
|
47
|
+
const pad2 = (value: number): string => String(value).padStart(2, '0');
|
|
48
|
+
|
|
49
|
+
/** ISO week-numbering year + week (2026-W17). */
|
|
50
|
+
function isoWeek(date: Date): { year: number; week: number } {
|
|
51
|
+
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
|
|
52
|
+
const day = d.getUTCDay() || 7;
|
|
53
|
+
d.setUTCDate(d.getUTCDate() + 4 - day);
|
|
54
|
+
const year = d.getUTCFullYear();
|
|
55
|
+
const yearStart = Date.UTC(year, 0, 1);
|
|
56
|
+
return { year, week: Math.ceil((((d.getTime() - yearStart) / 86_400_000) + 1) / 7) };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The rotation period of a date, used both to detect a boundary and to name the
|
|
61
|
+
* archive (`2026-04.log`, `2026-04-18.log`, `2026-W17.log`, `2026-04-18-14.log`, …).
|
|
62
|
+
*/
|
|
63
|
+
function periodOf(date: Date, rotate: LogRotation): string | null {
|
|
64
|
+
if (rotate === 'size') return null;
|
|
65
|
+
const day = `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;
|
|
66
|
+
if (rotate === 'yearly') return String(date.getFullYear());
|
|
67
|
+
if (rotate === 'monthly') return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}`;
|
|
68
|
+
if (rotate === 'daily') return day;
|
|
69
|
+
if (rotate === 'hourly') return `${day}-${pad2(date.getHours())}`;
|
|
70
|
+
const week = isoWeek(date);
|
|
71
|
+
return `${week.year}-W${pad2(week.week)}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
41
74
|
function safeStringify(value: unknown): string {
|
|
42
75
|
if (value === undefined) return '';
|
|
43
76
|
try {
|
|
@@ -58,6 +91,8 @@ function safeStringify(value: unknown): string {
|
|
|
58
91
|
class Logger {
|
|
59
92
|
#options: LoggerOptions & typeof DEFAULT_OPTIONS;
|
|
60
93
|
#filePath: string = '';
|
|
94
|
+
/** Period the live file belongs to (calendar modes only). */
|
|
95
|
+
#period: string = '';
|
|
61
96
|
#size = 0;
|
|
62
97
|
#pending: Promise<void>[] = [];
|
|
63
98
|
#queue: Promise<void> = Promise.resolve();
|
|
@@ -85,57 +120,101 @@ class Logger {
|
|
|
85
120
|
// Keep relative dirs explicit (`./logs`), but never double-prefix
|
|
86
121
|
// dot-dirs (`.logs`, `./logs`, `../logs`) or absolute paths
|
|
87
122
|
const prefix = /^(\/|\.|[A-Za-z]:)/.test(dir) ? dir : `./${dir}`;
|
|
88
|
-
return `${prefix}/
|
|
123
|
+
return `${prefix}/app.log`;
|
|
89
124
|
}
|
|
90
125
|
|
|
91
126
|
#openFile() {
|
|
92
127
|
if (!this.#options.file) {
|
|
93
128
|
this.#filePath = '';
|
|
94
129
|
this.#size = 0;
|
|
130
|
+
this.#period = '';
|
|
95
131
|
return;
|
|
96
132
|
}
|
|
97
133
|
this.#filePath = this.#resolvePath();
|
|
98
134
|
fs.mkdirSync(path.dirname(this.#filePath), { recursive: true });
|
|
135
|
+
const rotate = this.#options.rotate;
|
|
99
136
|
try {
|
|
100
|
-
|
|
137
|
+
const stat = fs.statSync(this.#filePath);
|
|
138
|
+
this.#size = stat.size;
|
|
139
|
+
// The live file may be from a previous period (server restart): its mtime
|
|
140
|
+
// tells us when it was last written, so the next write archives it under
|
|
141
|
+
// that period instead of mixing two months in one file.
|
|
142
|
+
this.#period = rotate === 'size' || stat.size === 0
|
|
143
|
+
? ''
|
|
144
|
+
: periodOf(stat.mtime, rotate) ?? '';
|
|
101
145
|
} catch {
|
|
102
146
|
this.#size = 0;
|
|
147
|
+
this.#period = '';
|
|
103
148
|
}
|
|
104
149
|
}
|
|
105
150
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
this.#
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
151
|
+
/** Archive-name matcher for the active rotation mode (retention only). */
|
|
152
|
+
#archivePattern(): RegExp {
|
|
153
|
+
switch (this.#options.rotate) {
|
|
154
|
+
case 'hourly': return /^\d{4}-\d{2}-\d{2}-\d{2}\.log$/;
|
|
155
|
+
case 'daily': return /^\d{4}-\d{2}-\d{2}\.log$/;
|
|
156
|
+
case 'weekly': return /^\d{4}-W\d{2}\.log$/;
|
|
157
|
+
case 'monthly': return /^\d{4}-\d{2}\.log$/;
|
|
158
|
+
case 'yearly': return /^\d{4}\.log$/;
|
|
159
|
+
case 'size':
|
|
160
|
+
default: return /^\d{4}-\d{2}-\d{2}-\d+\.log$/;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
114
163
|
|
|
115
|
-
|
|
116
|
-
|
|
164
|
+
/** Keep only the `maxFiles` most recent archives matching the active pattern. */
|
|
165
|
+
#prune(dir: string): Promise<void> {
|
|
166
|
+
const { maxFiles } = this.#options;
|
|
167
|
+
const re = this.#archivePattern();
|
|
168
|
+
return (async () => {
|
|
117
169
|
try {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
170
|
+
let files = (await fs.promises.readdir(dir)).filter((file) => re.test(file));
|
|
171
|
+
if (this.#options.rotate === 'size') {
|
|
172
|
+
const withTime = await Promise.all(
|
|
173
|
+
files.map(async (file) => ({
|
|
174
|
+
file,
|
|
175
|
+
time: (await fs.promises.stat(path.join(dir, file))).mtimeMs,
|
|
176
|
+
})),
|
|
177
|
+
);
|
|
178
|
+
withTime.sort((a, b) => b.time - a.time);
|
|
179
|
+
files = withTime.map((entry) => entry.file);
|
|
180
|
+
} else {
|
|
181
|
+
// Calendar names are chronological (zero-padded): sort by name,
|
|
182
|
+
// not mtime — a renamed archive keeps the old file's mtime.
|
|
183
|
+
files.sort((a, b) => b.localeCompare(a));
|
|
123
184
|
}
|
|
124
|
-
|
|
125
|
-
|
|
185
|
+
for (const file of files.slice(maxFiles)) {
|
|
186
|
+
await fs.promises.unlink(path.join(dir, file)).catch(() => {});
|
|
187
|
+
}
|
|
188
|
+
} catch {
|
|
189
|
+
// Retention is best-effort: never break a log write
|
|
190
|
+
}
|
|
191
|
+
})();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Rename the live file to its period archive (`2026-04.log`), then prune. */
|
|
195
|
+
#rotateToPeriod(filePath: string, period: string) {
|
|
196
|
+
this.#enqueue(async () => {
|
|
197
|
+
const dir = path.dirname(filePath);
|
|
198
|
+
await fs.promises.rename(filePath, path.join(dir, `${period}.log`)).catch(() => {});
|
|
199
|
+
await this.#prune(dir);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
126
202
|
|
|
127
|
-
|
|
203
|
+
/** Size rotation: `2026-04-18-1.log`, `-2`, … (index resets per day). */
|
|
204
|
+
#rotateBySize(filePath: string) {
|
|
205
|
+
this.#enqueue(async () => {
|
|
206
|
+
const dir = path.dirname(filePath);
|
|
207
|
+
const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
|
|
208
|
+
let index = 1;
|
|
128
209
|
try {
|
|
129
|
-
const re = new RegExp(`^${
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
);
|
|
134
|
-
withTime.sort((a, b) => b.t - a.t);
|
|
135
|
-
for (const { f } of withTime.slice(maxFiles)) {
|
|
136
|
-
await fs.promises.unlink(path.join(dir, f)).catch(() => {});
|
|
210
|
+
const re = new RegExp(`^${today}-(\\d+)\\.log$`);
|
|
211
|
+
for (const file of await fs.promises.readdir(dir)) {
|
|
212
|
+
const match = file.match(re);
|
|
213
|
+
if (match) index = Math.max(index, parseInt(match[1]!, 10) + 1);
|
|
137
214
|
}
|
|
138
215
|
} catch {}
|
|
216
|
+
await fs.promises.rename(filePath, path.join(dir, `${today}-${index}.log`)).catch(() => {});
|
|
217
|
+
await this.#prune(dir);
|
|
139
218
|
});
|
|
140
219
|
}
|
|
141
220
|
|
|
@@ -173,10 +252,23 @@ class Logger {
|
|
|
173
252
|
const line = safeStringify(payload) + '\n';
|
|
174
253
|
const bytes = Buffer.byteLength(line);
|
|
175
254
|
const filePath = this.#filePath;
|
|
176
|
-
|
|
255
|
+
const rotate = this.#options.rotate;
|
|
256
|
+
|
|
257
|
+
if (rotate !== 'size') {
|
|
258
|
+
const current = periodOf(new Date(), rotate)!;
|
|
259
|
+
if (this.#period && current !== this.#period) {
|
|
260
|
+
// The live file holds the previous period — archive it under its own name.
|
|
261
|
+
this.#size = 0;
|
|
262
|
+
this.#rotateToPeriod(filePath, this.#period);
|
|
263
|
+
this.#period = current;
|
|
264
|
+
} else if (!this.#period) {
|
|
265
|
+
this.#period = current;
|
|
266
|
+
}
|
|
267
|
+
} else if (this.#size + bytes > this.#options.maxSize) {
|
|
177
268
|
this.#size = 0;
|
|
178
|
-
this.#
|
|
269
|
+
this.#rotateBySize(filePath);
|
|
179
270
|
}
|
|
271
|
+
|
|
180
272
|
this.#size += bytes;
|
|
181
273
|
this.#enqueue(() => fs.promises.appendFile(filePath, line));
|
|
182
274
|
}
|