@keyou007/agent-bot 0.1.15-alpha.0 → 0.1.15-alpha.1
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/CHANGELOG.md +10 -0
- package/README.md +23 -20
- package/README.zh.md +23 -20
- package/config.example.yaml +1 -0
- package/dist/cli/FeishuAppConfiguration.js +42 -3
- package/dist/cli/FeishuAppConfiguration.js.map +1 -1
- package/dist/cli.js +4 -1
- package/dist/cli.js.map +1 -1
- package/dist/feishu/CardRenderer.js +51 -8
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuConnector.js +17 -5
- package/dist/feishu/FeishuConnector.js.map +1 -1
- package/dist/feishu/FeishuMarkdown.js +58 -6
- package/dist/feishu/FeishuMarkdown.js.map +1 -1
- package/dist/feishu/FeishuMessageClient.js +33 -4
- package/dist/feishu/FeishuMessageClient.js.map +1 -1
- package/dist/feishu/FeishuTurnPresenter.js +7 -6
- package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
- package/dist/feishu/FinalResponseBranding.js +15 -0
- package/dist/feishu/FinalResponseBranding.js.map +1 -0
- package/dist/logging/DailyLogStream.js +73 -0
- package/dist/logging/DailyLogStream.js.map +1 -0
- package/dist/logging/logger.js +2 -1
- package/dist/logging/logger.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +192 -36
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/state/StateStore.js +126 -0
- package/dist/state/StateStore.js.map +1 -1
- package/dist/state/migrations.js +11 -0
- package/dist/state/migrations.js.map +1 -1
- package/dist/supervision/SupervisorDiagnostics.js +37 -21
- package/dist/supervision/SupervisorDiagnostics.js.map +1 -1
- package/dist/supervisor.js +11 -4
- package/dist/supervisor.js.map +1 -1
- package/dist/utils/executeShellCommand.js +83 -16
- package/dist/utils/executeShellCommand.js.map +1 -1
- package/docs/technical-reference.md +9 -8
- package/docs/technical-reference.zh.md +9 -8
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/src/cli/FeishuAppConfiguration.ts +76 -3
- package/src/cli.ts +6 -1
- package/src/feishu/CardRenderer.ts +75 -9
- package/src/feishu/FeishuConnector.ts +28 -6
- package/src/feishu/FeishuMarkdown.ts +64 -5
- package/src/feishu/FeishuMessageClient.ts +41 -12
- package/src/feishu/FeishuTurnPresenter.ts +9 -6
- package/src/feishu/FinalResponseBranding.ts +18 -0
- package/src/logging/DailyLogStream.ts +88 -0
- package/src/logging/logger.ts +2 -1
- package/src/proxy/ProxySessionController.ts +208 -35
- package/src/state/StateStore.ts +180 -0
- package/src/state/migrations.ts +11 -0
- package/src/supervision/SupervisorDiagnostics.ts +41 -25
- package/src/supervisor.ts +10 -4
- package/src/utils/executeShellCommand.ts +103 -15
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
1
3
|
interface FenceState {
|
|
2
4
|
indent: string;
|
|
3
5
|
marker: "`" | "~";
|
|
@@ -9,7 +11,7 @@ const FENCE_OPENER = /^([ \t]*)((?:`{3,})|(?:~{3,}))(.*)$/;
|
|
|
9
11
|
const MARKDOWN_LINK = /(!?)\[([^\]\r\n]*)\]\((<?[^)\r\n]+>?)\)/g;
|
|
10
12
|
const LOCAL_FILE_LINE_REFERENCE = /(:\d+(?::\d+)?)$/;
|
|
11
13
|
|
|
12
|
-
export function normalizeFeishuMarkdown(markdown: string): string {
|
|
14
|
+
export function normalizeFeishuMarkdown(markdown: string, projectCwd?: string): string {
|
|
13
15
|
const lines = markdown.match(/[^\r\n]*(?:\r\n|\n)|[^\r\n]+$/g) ?? [];
|
|
14
16
|
let fence: FenceState | undefined;
|
|
15
17
|
|
|
@@ -19,7 +21,7 @@ export function normalizeFeishuMarkdown(markdown: string): string {
|
|
|
19
21
|
|
|
20
22
|
if (!fence) {
|
|
21
23
|
const opener = FENCE_OPENER.exec(body);
|
|
22
|
-
if (!opener?.[2]) return `${includeLocalFileLineReferences(body)}${ending}`;
|
|
24
|
+
if (!opener?.[2]) return `${includeLocalFileLineReferences(body, projectCwd)}${ending}`;
|
|
23
25
|
|
|
24
26
|
const indent = opener[1] ?? "";
|
|
25
27
|
const delimiter = opener[2];
|
|
@@ -40,7 +42,7 @@ export function normalizeFeishuMarkdown(markdown: string): string {
|
|
|
40
42
|
}).join("");
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
function includeLocalFileLineReferences(markdown: string): string {
|
|
45
|
+
function includeLocalFileLineReferences(markdown: string, projectCwd?: string): string {
|
|
44
46
|
return markdown.replace(MARKDOWN_LINK, (link, imageMarker: string, label: string, rawTarget: string) => {
|
|
45
47
|
if (imageMarker) return link;
|
|
46
48
|
|
|
@@ -48,11 +50,68 @@ function includeLocalFileLineReferences(markdown: string): string {
|
|
|
48
50
|
if (!isLocalFileTarget(target)) return link;
|
|
49
51
|
|
|
50
52
|
const reference = LOCAL_FILE_LINE_REFERENCE.exec(target)?.[1];
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
const projectPathLabel = projectCwd ? projectFilePathLabel(target, projectCwd, reference) : undefined;
|
|
54
|
+
const normalizedLabel = projectPathLabel
|
|
55
|
+
?? (reference && !label.endsWith(reference) ? `${label}${reference}` : label);
|
|
56
|
+
return normalizedLabel === label ? link : `[${normalizedLabel}](${rawTarget})`;
|
|
53
57
|
});
|
|
54
58
|
}
|
|
55
59
|
|
|
60
|
+
function projectFilePathLabel(
|
|
61
|
+
target: string,
|
|
62
|
+
projectCwd: string,
|
|
63
|
+
reference: string | undefined,
|
|
64
|
+
): string | undefined {
|
|
65
|
+
const targetWithoutReference = reference ? target.slice(0, -reference.length) : target;
|
|
66
|
+
const filePath = localFilePath(targetWithoutReference);
|
|
67
|
+
if (!filePath) return undefined;
|
|
68
|
+
if (!isInsideProject(filePath, projectCwd)) {
|
|
69
|
+
return `${displayAbsolutePath(filePath)}${reference ?? ""}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const pathApi = usesWindowsPaths(filePath, projectCwd) ? path.win32 : path.posix;
|
|
73
|
+
const parsed = pathApi.parse(filePath);
|
|
74
|
+
if (Array.from(parsed.name).length > 5) return undefined;
|
|
75
|
+
|
|
76
|
+
const parentName = pathApi.basename(pathApi.dirname(filePath));
|
|
77
|
+
if (!parentName) return undefined;
|
|
78
|
+
return `${parentName}${pathApi.sep}${parsed.base}${reference ?? ""}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function localFilePath(target: string): string | undefined {
|
|
82
|
+
if (!/^file:\/\//i.test(target)) return normalizeWindowsDrivePrefix(target);
|
|
83
|
+
|
|
84
|
+
let value = target.slice("file://".length);
|
|
85
|
+
try {
|
|
86
|
+
value = decodeURIComponent(value);
|
|
87
|
+
} catch {
|
|
88
|
+
// Keep malformed percent escapes visible instead of dropping the link.
|
|
89
|
+
}
|
|
90
|
+
if (/^\/[a-z]:[\\/]/i.test(value)) return value.slice(1);
|
|
91
|
+
if (value.startsWith("/")) return value;
|
|
92
|
+
return `//${value}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function normalizeWindowsDrivePrefix(value: string): string {
|
|
96
|
+
return /^\/[a-z]:[\\/]/i.test(value) ? value.slice(1) : value;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function isInsideProject(filePath: string, projectCwd: string): boolean {
|
|
100
|
+
const pathApi = usesWindowsPaths(filePath, projectCwd) ? path.win32 : path.posix;
|
|
101
|
+
const relative = pathApi.relative(pathApi.resolve(projectCwd), pathApi.resolve(filePath));
|
|
102
|
+
return relative === ""
|
|
103
|
+
|| (relative !== ".." && !relative.startsWith(`..${pathApi.sep}`) && !pathApi.isAbsolute(relative));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function displayAbsolutePath(filePath: string): string {
|
|
107
|
+
if (!usesWindowsPaths(filePath)) return path.posix.normalize(filePath);
|
|
108
|
+
return path.win32.normalize(filePath);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function usesWindowsPaths(...values: string[]): boolean {
|
|
112
|
+
return values.some((value) => /^\/?[a-z]:[\\/]/i.test(value) || /^\\\\/.test(value));
|
|
113
|
+
}
|
|
114
|
+
|
|
56
115
|
function unwrapMarkdownTarget(target: string): string {
|
|
57
116
|
return target.startsWith("<") && target.endsWith(">") ? target.slice(1, -1) : target;
|
|
58
117
|
}
|
|
@@ -7,6 +7,7 @@ import type { AppConfig } from "../config/schema.js";
|
|
|
7
7
|
import { normalizeFeishuMarkdown } from "./FeishuMarkdown.js";
|
|
8
8
|
import { LOCAL_CARD_IMAGE_PATH } from "./LocalCardImage.js";
|
|
9
9
|
import { renderMarkdownWithLocalImages } from "./LocalImageMarkdown.js";
|
|
10
|
+
import { splitFinalResponseBranding } from "./FinalResponseBranding.js";
|
|
10
11
|
import {
|
|
11
12
|
renderMergedForwardPrompt,
|
|
12
13
|
renderReferencedMessage,
|
|
@@ -101,6 +102,7 @@ export class FeishuMessageClient implements FeishuOutbound {
|
|
|
101
102
|
expiresAt: number;
|
|
102
103
|
};
|
|
103
104
|
private readonly sendQueues = new Map<string, Promise<void>>();
|
|
105
|
+
private readonly cardUpdateQueues = new Map<string, Promise<void>>();
|
|
104
106
|
private readonly lastSendAt = new Map<string, number>();
|
|
105
107
|
private readonly imageUploads = new Map<string, { mtimeMs: number; upload: Promise<string> }>();
|
|
106
108
|
private readonly imageDownloads = new Map<string, Promise<string>>();
|
|
@@ -288,11 +290,7 @@ export class FeishuMessageClient implements FeishuOutbound {
|
|
|
288
290
|
|
|
289
291
|
async sendMarkdown(contextKey: string, markdown: string, idempotencyKey?: string): Promise<string | undefined> {
|
|
290
292
|
const normalizedMarkdown = normalizeFeishuMarkdown(markdown);
|
|
291
|
-
const elements = await
|
|
292
|
-
normalizedMarkdown,
|
|
293
|
-
(filePath) => this.uploadImageCached(filePath),
|
|
294
|
-
(error, filePath) => this.logger.warn({ error, filePath }, "Failed to upload local image to Feishu."),
|
|
295
|
-
);
|
|
293
|
+
const elements = await this.renderFinalAnswerElements(normalizedMarkdown);
|
|
296
294
|
return this.sendMessage(
|
|
297
295
|
contextKey,
|
|
298
296
|
"interactive",
|
|
@@ -334,11 +332,7 @@ export class FeishuMessageClient implements FeishuOutbound {
|
|
|
334
332
|
idempotencyKey?: string,
|
|
335
333
|
): Promise<string | undefined> {
|
|
336
334
|
const normalizedMarkdown = normalizeFeishuMarkdown(markdown);
|
|
337
|
-
const elements = await
|
|
338
|
-
normalizedMarkdown,
|
|
339
|
-
(filePath) => this.uploadImageCached(filePath),
|
|
340
|
-
(error, filePath) => this.logger.warn({ error, filePath }, "Failed to upload local image to Feishu."),
|
|
341
|
-
);
|
|
335
|
+
const elements = await this.renderFinalAnswerElements(normalizedMarkdown);
|
|
342
336
|
return this.replyMessage(
|
|
343
337
|
contextKey,
|
|
344
338
|
target,
|
|
@@ -364,9 +358,28 @@ export class FeishuMessageClient implements FeishuOutbound {
|
|
|
364
358
|
);
|
|
365
359
|
}
|
|
366
360
|
|
|
361
|
+
private async renderFinalAnswerElements(markdown: string): Promise<Array<Record<string, unknown>>> {
|
|
362
|
+
const { content, branding } = splitFinalResponseBranding(markdown);
|
|
363
|
+
const elements = content
|
|
364
|
+
? await renderMarkdownWithLocalImages(
|
|
365
|
+
content,
|
|
366
|
+
(filePath) => this.uploadImageCached(filePath),
|
|
367
|
+
(error, filePath) => this.logger.warn({ error, filePath }, "Failed to upload local image to Feishu."),
|
|
368
|
+
)
|
|
369
|
+
: [];
|
|
370
|
+
if (!branding) return elements;
|
|
371
|
+
return [
|
|
372
|
+
...elements,
|
|
373
|
+
{ tag: "hr" },
|
|
374
|
+
{ tag: "markdown", content: branding, text_size: "notation" },
|
|
375
|
+
];
|
|
376
|
+
}
|
|
377
|
+
|
|
367
378
|
async updateInteractiveCard(messageId: string, card: Record<string, unknown>): Promise<void> {
|
|
368
|
-
|
|
369
|
-
|
|
379
|
+
await this.enqueueCardUpdate(messageId, async () => {
|
|
380
|
+
const preparedCard = await this.prepareInteractiveCard(card);
|
|
381
|
+
await this.updateInteractiveCardNow(messageId, preparedCard);
|
|
382
|
+
});
|
|
370
383
|
}
|
|
371
384
|
|
|
372
385
|
private async updateInteractiveCardNow(
|
|
@@ -626,6 +639,22 @@ export class FeishuMessageClient implements FeishuOutbound {
|
|
|
626
639
|
return queued;
|
|
627
640
|
}
|
|
628
641
|
|
|
642
|
+
private enqueueCardUpdate(messageId: string, task: () => Promise<void>): Promise<void> {
|
|
643
|
+
const previous = this.cardUpdateQueues.get(messageId) ?? Promise.resolve();
|
|
644
|
+
const queued = previous.catch(() => undefined).then(task);
|
|
645
|
+
const stored = queued.then(
|
|
646
|
+
() => undefined,
|
|
647
|
+
() => undefined,
|
|
648
|
+
);
|
|
649
|
+
this.cardUpdateQueues.set(messageId, stored);
|
|
650
|
+
stored.finally(() => {
|
|
651
|
+
if (this.cardUpdateQueues.get(messageId) === stored) {
|
|
652
|
+
this.cardUpdateQueues.delete(messageId);
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
return queued;
|
|
656
|
+
}
|
|
657
|
+
|
|
629
658
|
private async getTenantAccessToken(): Promise<string> {
|
|
630
659
|
const now = Date.now();
|
|
631
660
|
if (this.token && this.token.expiresAt > now + 60_000) {
|
|
@@ -8,7 +8,11 @@ import { splitMarkdown } from "../presentation/splitMarkdown.js";
|
|
|
8
8
|
import type { TurnViewState } from "../presentation/turnViewTypes.js";
|
|
9
9
|
import { CardRenderer } from "./CardRenderer.js";
|
|
10
10
|
import { CardUpdateScheduler } from "./CardUpdateScheduler.js";
|
|
11
|
+
import { normalizeFeishuMarkdown } from "./FeishuMarkdown.js";
|
|
11
12
|
import type { FeishuOutbound, MessageReplyTarget } from "./types.js";
|
|
13
|
+
import {
|
|
14
|
+
FINAL_RESPONSE_BRANDING_BLOCK,
|
|
15
|
+
} from "./FinalResponseBranding.js";
|
|
12
16
|
import { createId } from "../utils/id.js";
|
|
13
17
|
import { createHash } from "node:crypto";
|
|
14
18
|
|
|
@@ -50,7 +54,6 @@ export interface FeishuTurnPresenterOptions {
|
|
|
50
54
|
|
|
51
55
|
const MAX_FINAL_TABLES_PER_CARD = 5;
|
|
52
56
|
const DEFAULT_ELAPSED_UPDATE_INTERVAL_MS = 3_000;
|
|
53
|
-
const FINAL_RESPONSE_BRANDING = "> Powered by [AgentBot](https://github.com/keyou/agent-bot)";
|
|
54
57
|
const FINAL_RESPONSE_BRANDING_MIN_LENGTH = 1_000;
|
|
55
58
|
|
|
56
59
|
interface TurnEntry {
|
|
@@ -536,7 +539,7 @@ export class FeishuTurnPresenter {
|
|
|
536
539
|
if (delivery?.finalDelivered || !state.finalResponse) return;
|
|
537
540
|
const finalChunkLength = this.options.finalChunkLength ?? 4_000;
|
|
538
541
|
const chunks = brandedFinalChunks(
|
|
539
|
-
state.finalResponse,
|
|
542
|
+
normalizeFeishuMarkdown(state.finalResponse, state.projectCwd),
|
|
540
543
|
finalChunkLength,
|
|
541
544
|
);
|
|
542
545
|
const messageIds = [...(delivery?.finalMessageIds ?? [])];
|
|
@@ -582,17 +585,17 @@ function brandedFinalChunks(response: string, maxLength: number): string[] {
|
|
|
582
585
|
|
|
583
586
|
const last = chunks.pop();
|
|
584
587
|
if (last === undefined) return chunks;
|
|
585
|
-
const reservedLength =
|
|
588
|
+
const reservedLength = FINAL_RESPONSE_BRANDING_BLOCK.length + 2;
|
|
586
589
|
const brandedContentLimit = maxLength - reservedLength;
|
|
587
|
-
if (brandedContentLimit < 32) return [...chunks, last,
|
|
590
|
+
if (brandedContentLimit < 32) return [...chunks, last, FINAL_RESPONSE_BRANDING_BLOCK];
|
|
588
591
|
|
|
589
592
|
const lastParts = splitMarkdown(last, brandedContentLimit, MAX_FINAL_TABLES_PER_CARD);
|
|
590
593
|
const lastContent = lastParts.pop();
|
|
591
594
|
if (lastContent === undefined || lastContent.length > brandedContentLimit) {
|
|
592
|
-
return [...chunks, ...lastParts, lastContent ?? last,
|
|
595
|
+
return [...chunks, ...lastParts, lastContent ?? last, FINAL_RESPONSE_BRANDING_BLOCK];
|
|
593
596
|
}
|
|
594
597
|
const separator = lastContent.endsWith("\n\n") ? "" : lastContent.endsWith("\n") ? "\n" : "\n\n";
|
|
595
|
-
return [...chunks, ...lastParts, `${lastContent}${separator}${
|
|
598
|
+
return [...chunks, ...lastParts, `${lastContent}${separator}${FINAL_RESPONSE_BRANDING_BLOCK}`];
|
|
596
599
|
}
|
|
597
600
|
|
|
598
601
|
function eventPriority(event: AgentEvent): "normal" | "critical" {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const FINAL_RESPONSE_BRANDING = "> Powered by [AgentBot](https://github.com/keyou/agent-bot)";
|
|
2
|
+
export const FINAL_RESPONSE_BRANDING_BLOCK = `----\n\n${FINAL_RESPONSE_BRANDING}`;
|
|
3
|
+
|
|
4
|
+
export function splitFinalResponseBranding(markdown: string): {
|
|
5
|
+
content: string;
|
|
6
|
+
branding?: string;
|
|
7
|
+
} {
|
|
8
|
+
if (markdown === FINAL_RESPONSE_BRANDING_BLOCK) {
|
|
9
|
+
return { content: "", branding: FINAL_RESPONSE_BRANDING };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const suffix = `\n\n${FINAL_RESPONSE_BRANDING_BLOCK}`;
|
|
13
|
+
if (!markdown.endsWith(suffix)) return { content: markdown };
|
|
14
|
+
return {
|
|
15
|
+
content: markdown.slice(0, -suffix.length),
|
|
16
|
+
branding: FINAL_RESPONSE_BRANDING,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Writable } from "node:stream";
|
|
4
|
+
|
|
5
|
+
interface DailyLogStreamOptions {
|
|
6
|
+
clock?: () => Date;
|
|
7
|
+
mode?: number;
|
|
8
|
+
beforeOpen?: (filePath: string) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class DailyLogStream extends Writable {
|
|
12
|
+
private currentPath?: string;
|
|
13
|
+
private lastPath?: string;
|
|
14
|
+
private currentStream?: fs.WriteStream;
|
|
15
|
+
private readonly clock: () => Date;
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
private readonly basePath: string,
|
|
19
|
+
private readonly options: DailyLogStreamOptions = {},
|
|
20
|
+
) {
|
|
21
|
+
super();
|
|
22
|
+
this.clock = options.clock ?? (() => new Date());
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get filePath(): string | undefined {
|
|
26
|
+
return this.currentPath ?? this.lastPath;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
override _write(
|
|
30
|
+
chunk: string | Buffer,
|
|
31
|
+
encoding: BufferEncoding,
|
|
32
|
+
callback: (error?: Error | null) => void,
|
|
33
|
+
): void {
|
|
34
|
+
let stream: fs.WriteStream;
|
|
35
|
+
try {
|
|
36
|
+
stream = this.streamFor(this.clock());
|
|
37
|
+
} catch (error) {
|
|
38
|
+
callback(error instanceof Error ? error : new Error(String(error)));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
stream.write(chunk, encoding, callback);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
override _final(callback: (error?: Error | null) => void): void {
|
|
45
|
+
const stream = this.currentStream;
|
|
46
|
+
this.currentStream = undefined;
|
|
47
|
+
this.currentPath = undefined;
|
|
48
|
+
if (!stream) {
|
|
49
|
+
callback();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
stream.end(callback);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
override _destroy(error: Error | null, callback: (error?: Error | null) => void): void {
|
|
56
|
+
this.currentStream?.destroy();
|
|
57
|
+
this.currentStream = undefined;
|
|
58
|
+
this.currentPath = undefined;
|
|
59
|
+
callback(error);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private streamFor(date: Date): fs.WriteStream {
|
|
63
|
+
const targetPath = dailyLogPath(this.basePath, date);
|
|
64
|
+
if (this.currentStream && this.currentPath === targetPath) return this.currentStream;
|
|
65
|
+
|
|
66
|
+
const previous = this.currentStream;
|
|
67
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
68
|
+
this.options.beforeOpen?.(targetPath);
|
|
69
|
+
this.currentPath = targetPath;
|
|
70
|
+
this.lastPath = targetPath;
|
|
71
|
+
this.currentStream = fs.createWriteStream(targetPath, {
|
|
72
|
+
flags: "a",
|
|
73
|
+
...(this.options.mode === undefined ? {} : { mode: this.options.mode }),
|
|
74
|
+
});
|
|
75
|
+
previous?.end();
|
|
76
|
+
return this.currentStream;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function dailyLogPath(basePath: string, date: Date): string {
|
|
81
|
+
const parsed = path.parse(basePath);
|
|
82
|
+
const dateStamp = [
|
|
83
|
+
date.getFullYear(),
|
|
84
|
+
String(date.getMonth() + 1).padStart(2, "0"),
|
|
85
|
+
String(date.getDate()).padStart(2, "0"),
|
|
86
|
+
].join("-");
|
|
87
|
+
return path.join(parsed.dir, `${parsed.name}.${dateStamp}${parsed.ext}`);
|
|
88
|
+
}
|
package/src/logging/logger.ts
CHANGED
|
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import pino, { type Logger } from "pino";
|
|
4
4
|
import type { AppConfig } from "../config/schema.js";
|
|
5
|
+
import { DailyLogStream } from "./DailyLogStream.js";
|
|
5
6
|
|
|
6
7
|
export function createLogger(config: AppConfig): Logger {
|
|
7
8
|
const streams: pino.StreamEntry[] = [
|
|
@@ -13,7 +14,7 @@ export function createLogger(config: AppConfig): Logger {
|
|
|
13
14
|
if (config.logging.path) {
|
|
14
15
|
fs.mkdirSync(path.dirname(config.logging.path), { recursive: true });
|
|
15
16
|
streams.push({
|
|
16
|
-
stream:
|
|
17
|
+
stream: new DailyLogStream(config.logging.path),
|
|
17
18
|
});
|
|
18
19
|
}
|
|
19
20
|
|