@oyasmi/pipiclaw 0.5.0 → 0.5.2
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/agent/channel-runner.d.ts +46 -0
- package/dist/agent/channel-runner.d.ts.map +1 -0
- package/dist/agent/channel-runner.js +434 -0
- package/dist/agent/channel-runner.js.map +1 -0
- package/dist/agent/index.d.ts +4 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +3 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/progress-formatter.d.ts +5 -0
- package/dist/agent/progress-formatter.d.ts.map +1 -0
- package/dist/agent/progress-formatter.js +53 -0
- package/dist/agent/progress-formatter.js.map +1 -0
- package/dist/agent/run-queue.d.ts +8 -0
- package/dist/agent/run-queue.d.ts.map +1 -0
- package/dist/agent/run-queue.js +27 -0
- package/dist/agent/run-queue.js.map +1 -0
- package/dist/agent/runner-factory.d.ts +4 -0
- package/dist/agent/runner-factory.d.ts.map +1 -0
- package/dist/agent/runner-factory.js +11 -0
- package/dist/agent/runner-factory.js.map +1 -0
- package/dist/agent/session-events.d.ts +15 -0
- package/dist/agent/session-events.d.ts.map +1 -0
- package/dist/agent/session-events.js +216 -0
- package/dist/agent/session-events.js.map +1 -0
- package/dist/agent/type-guards.d.ts +23 -0
- package/dist/agent/type-guards.d.ts.map +1 -0
- package/dist/agent/type-guards.js +107 -0
- package/dist/agent/type-guards.js.map +1 -0
- package/dist/agent/types.d.ts +161 -0
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent/types.js +23 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/agent.d.ts +2 -15
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +1 -781
- package/dist/agent.js.map +1 -1
- package/dist/context.d.ts +58 -14
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +50 -7
- package/dist/context.js.map +1 -1
- package/dist/index.d.ts +12 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -12
- package/dist/index.js.map +1 -1
- package/dist/main.js +5 -404
- package/dist/main.js.map +1 -1
- package/dist/memory/bootstrap.d.ts +7 -0
- package/dist/memory/bootstrap.d.ts.map +1 -0
- package/dist/memory/bootstrap.js +47 -0
- package/dist/memory/bootstrap.js.map +1 -0
- package/dist/{memory-candidates.d.ts → memory/candidates.d.ts} +2 -1
- package/dist/memory/candidates.d.ts.map +1 -0
- package/dist/{memory-candidates.js → memory/candidates.js} +34 -21
- package/dist/memory/candidates.js.map +1 -0
- package/dist/memory/chinese-words.d.ts +2 -0
- package/dist/memory/chinese-words.d.ts.map +1 -0
- package/dist/memory/chinese-words.js +210 -0
- package/dist/memory/chinese-words.js.map +1 -0
- package/dist/{memory-consolidation.d.ts → memory/consolidation.d.ts} +1 -1
- package/dist/memory/consolidation.d.ts.map +1 -0
- package/dist/{memory-consolidation.js → memory/consolidation.js} +27 -35
- package/dist/memory/consolidation.js.map +1 -0
- package/dist/{memory-files.d.ts → memory/files.d.ts} +1 -6
- package/dist/memory/files.d.ts.map +1 -0
- package/dist/{memory-files.js → memory/files.js} +12 -36
- package/dist/memory/files.js.map +1 -0
- package/dist/{memory-lifecycle.d.ts → memory/lifecycle.d.ts} +24 -6
- package/dist/memory/lifecycle.d.ts.map +1 -0
- package/dist/memory/lifecycle.js +247 -0
- package/dist/memory/lifecycle.js.map +1 -0
- package/dist/{memory-recall.d.ts → memory/recall.d.ts} +2 -2
- package/dist/memory/recall.d.ts.map +1 -0
- package/dist/memory/recall.js +435 -0
- package/dist/memory/recall.js.map +1 -0
- package/dist/{session-memory.d.ts → memory/session.d.ts} +2 -1
- package/dist/memory/session.d.ts.map +1 -0
- package/dist/{session-memory.js → memory/session.js} +32 -62
- package/dist/memory/session.js.map +1 -0
- package/dist/runtime/bootstrap.d.ts +48 -0
- package/dist/runtime/bootstrap.d.ts.map +1 -0
- package/dist/runtime/bootstrap.js +451 -0
- package/dist/runtime/bootstrap.js.map +1 -0
- package/dist/runtime/delivery.d.ts.map +1 -0
- package/dist/{delivery.js → runtime/delivery.js} +1 -1
- package/dist/runtime/delivery.js.map +1 -0
- package/dist/{dingtalk.d.ts → runtime/dingtalk.d.ts} +10 -0
- package/dist/runtime/dingtalk.d.ts.map +1 -0
- package/dist/{dingtalk.js → runtime/dingtalk.js} +87 -27
- package/dist/runtime/dingtalk.js.map +1 -0
- package/dist/runtime/events.d.ts.map +1 -0
- package/dist/{events.js → runtime/events.js} +1 -1
- package/dist/runtime/events.js.map +1 -0
- package/dist/{store.d.ts → runtime/store.d.ts} +5 -0
- package/dist/runtime/store.d.ts.map +1 -0
- package/dist/{store.js → runtime/store.js} +60 -19
- package/dist/runtime/store.js.map +1 -0
- package/dist/shared/markdown-sections.d.ts +7 -0
- package/dist/shared/markdown-sections.d.ts.map +1 -0
- package/dist/{markdown-sections.js → shared/markdown-sections.js} +10 -3
- package/dist/shared/markdown-sections.js.map +1 -0
- package/dist/shared/text-utils.d.ts +10 -0
- package/dist/shared/text-utils.d.ts.map +1 -0
- package/dist/shared/text-utils.js +37 -0
- package/dist/shared/text-utils.js.map +1 -0
- package/dist/shared/type-guards.d.ts +6 -0
- package/dist/shared/type-guards.d.ts.map +1 -0
- package/dist/shared/type-guards.js +13 -0
- package/dist/shared/type-guards.js.map +1 -0
- package/dist/shared/types.d.ts +15 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.js +2 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/sidecar-worker.d.ts.map +1 -1
- package/dist/sidecar-worker.js +1 -7
- package/dist/sidecar-worker.js.map +1 -1
- package/dist/{sub-agents.d.ts → subagents/discovery.d.ts} +1 -1
- package/dist/subagents/discovery.d.ts.map +1 -0
- package/dist/{sub-agents.js → subagents/discovery.js} +3 -3
- package/dist/subagents/discovery.js.map +1 -0
- package/dist/{tools/subagent.d.ts → subagents/tool.d.ts} +3 -16
- package/dist/{tools/subagent.d.ts.map → subagents/tool.d.ts.map} +1 -1
- package/dist/{tools/subagent.js → subagents/tool.js} +17 -38
- package/dist/subagents/tool.js.map +1 -0
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1 -1
- package/dist/tools/index.js.map +1 -1
- package/docs/memory-audit.md +330 -0
- package/docs/memory-optimization-round2.md +319 -0
- package/package.json +1 -1
- package/dist/delivery.d.ts.map +0 -1
- package/dist/delivery.js.map +0 -1
- package/dist/dingtalk.d.ts.map +0 -1
- package/dist/dingtalk.js.map +0 -1
- package/dist/events.d.ts.map +0 -1
- package/dist/events.js.map +0 -1
- package/dist/markdown-sections.d.ts +0 -6
- package/dist/markdown-sections.d.ts.map +0 -1
- package/dist/markdown-sections.js.map +0 -1
- package/dist/memory-candidates.d.ts.map +0 -1
- package/dist/memory-candidates.js.map +0 -1
- package/dist/memory-consolidation.d.ts.map +0 -1
- package/dist/memory-consolidation.js.map +0 -1
- package/dist/memory-files.d.ts.map +0 -1
- package/dist/memory-files.js.map +0 -1
- package/dist/memory-lifecycle.d.ts.map +0 -1
- package/dist/memory-lifecycle.js +0 -150
- package/dist/memory-lifecycle.js.map +0 -1
- package/dist/memory-recall.d.ts.map +0 -1
- package/dist/memory-recall.js +0 -218
- package/dist/memory-recall.js.map +0 -1
- package/dist/session-memory-files.d.ts +0 -2
- package/dist/session-memory-files.d.ts.map +0 -1
- package/dist/session-memory-files.js +0 -2
- package/dist/session-memory-files.js.map +0 -1
- package/dist/session-memory.d.ts.map +0 -1
- package/dist/session-memory.js.map +0 -1
- package/dist/store.d.ts.map +0 -1
- package/dist/store.js.map +0 -1
- package/dist/sub-agents.d.ts.map +0 -1
- package/dist/sub-agents.js.map +0 -1
- package/dist/tools/subagent.js.map +0 -1
- package/docs/proj-review.md +0 -188
- package/docs/test-supplementation-plan.md +0 -553
- /package/dist/{delivery.d.ts → runtime/delivery.d.ts} +0 -0
- /package/dist/{events.d.ts → runtime/events.d.ts} +0 -0
- /package/docs/{memory-rfc.md → specs/001-implement-memory/memory-rfc.md} +0 -0
- /package/docs/{subagent → specs/002-subagent}/pi-subagent-analyse.txt +0 -0
- /package/docs/{subagent → specs/002-subagent}/pi-subagent-design.txt +0 -0
- /package/docs/{subagent → specs/002-subagent}/pi-subagent-phase1-plan.txt +0 -0
- /package/docs/{improve-memory → specs/003-improve-memory}/design.md +0 -0
- /package/docs/{improve-memory → specs/003-improve-memory}/interfaces-and-tests.md +0 -0
- /package/docs/{improve-memory → specs/003-improve-memory}/spec.md +0 -0
|
@@ -11,8 +11,9 @@ import axios from "axios";
|
|
|
11
11
|
import { DWClient, TOPIC_ROBOT } from "dingtalk-stream";
|
|
12
12
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
13
13
|
import { dirname, join } from "path";
|
|
14
|
-
import { parseBuiltInCommand, renderBuiltInHelp } from "
|
|
15
|
-
import * as log from "
|
|
14
|
+
import { parseBuiltInCommand, renderBuiltInHelp } from "../commands.js";
|
|
15
|
+
import * as log from "../log.js";
|
|
16
|
+
import { isRecord } from "../shared/type-guards.js";
|
|
16
17
|
class ChannelQueue {
|
|
17
18
|
constructor() {
|
|
18
19
|
this.queue = [];
|
|
@@ -72,6 +73,7 @@ export class DingTalkBot {
|
|
|
72
73
|
this.lastSocketAvailableTime = Date.now();
|
|
73
74
|
this.activeMessageProcessing = false;
|
|
74
75
|
this.keepAliveTimer = null;
|
|
76
|
+
this.reconnectTimer = null;
|
|
75
77
|
this.isReconnecting = false;
|
|
76
78
|
this.isStopped = false;
|
|
77
79
|
this.reconnectAttempts = 0;
|
|
@@ -95,6 +97,67 @@ export class DingTalkBot {
|
|
|
95
97
|
}
|
|
96
98
|
return true;
|
|
97
99
|
}
|
|
100
|
+
getSocket() {
|
|
101
|
+
if (!this.client) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
const socket = Reflect.get(this.client, "socket");
|
|
105
|
+
return this.isSocketLike(socket) ? socket : null;
|
|
106
|
+
}
|
|
107
|
+
isSocketLike(value) {
|
|
108
|
+
if (!isRecord(value)) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
return typeof value.on === "function";
|
|
112
|
+
}
|
|
113
|
+
setTrackedTimeout(callback, delayMs) {
|
|
114
|
+
const timer = setTimeout(() => {
|
|
115
|
+
callback();
|
|
116
|
+
}, delayMs);
|
|
117
|
+
timer.unref?.();
|
|
118
|
+
return timer;
|
|
119
|
+
}
|
|
120
|
+
setTrackedInterval(callback, intervalMs) {
|
|
121
|
+
const timer = setInterval(callback, intervalMs);
|
|
122
|
+
timer.unref?.();
|
|
123
|
+
return timer;
|
|
124
|
+
}
|
|
125
|
+
clearKeepAliveTimer() {
|
|
126
|
+
if (this.keepAliveTimer) {
|
|
127
|
+
clearInterval(this.keepAliveTimer);
|
|
128
|
+
this.keepAliveTimer = null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
clearReconnectTimer() {
|
|
132
|
+
if (this.reconnectTimer) {
|
|
133
|
+
clearTimeout(this.reconnectTimer);
|
|
134
|
+
this.reconnectTimer = null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
clearAllTimers() {
|
|
138
|
+
this.clearKeepAliveTimer();
|
|
139
|
+
this.clearReconnectTimer();
|
|
140
|
+
}
|
|
141
|
+
async waitForDelay(delayMs) {
|
|
142
|
+
await new Promise((resolve) => {
|
|
143
|
+
this.reconnectTimer = this.setTrackedTimeout(() => {
|
|
144
|
+
this.reconnectTimer = null;
|
|
145
|
+
resolve();
|
|
146
|
+
}, delayMs);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
scheduleReconnect(delayMs, immediate) {
|
|
150
|
+
if (this.isStopped) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
this.clearReconnectTimer();
|
|
154
|
+
this.reconnectTimer = this.setTrackedTimeout(() => {
|
|
155
|
+
this.reconnectTimer = null;
|
|
156
|
+
this.doReconnect(immediate).catch((err) => {
|
|
157
|
+
log.logWarning("DingTalk: reconnect failed", err instanceof Error ? err.message : String(err));
|
|
158
|
+
});
|
|
159
|
+
}, delayMs);
|
|
160
|
+
}
|
|
98
161
|
// ==========================================================================
|
|
99
162
|
// Public API
|
|
100
163
|
// ==========================================================================
|
|
@@ -110,10 +173,10 @@ export class DingTalkBot {
|
|
|
110
173
|
if (process.env.DINGTALK_FORCE_PROXY !== "true") {
|
|
111
174
|
axios.defaults.proxy = false;
|
|
112
175
|
}
|
|
176
|
+
this.clearAllTimers();
|
|
113
177
|
this.client = new DWClient({
|
|
114
178
|
clientId: this.config.clientId,
|
|
115
179
|
clientSecret: this.config.clientSecret,
|
|
116
|
-
autoReconnect: false,
|
|
117
180
|
keepAlive: false,
|
|
118
181
|
});
|
|
119
182
|
this.client.registerCallbackListener(TOPIC_ROBOT, (msg) => {
|
|
@@ -133,7 +196,8 @@ export class DingTalkBot {
|
|
|
133
196
|
return { status: "SUCCESS", message: "OK" };
|
|
134
197
|
}
|
|
135
198
|
try {
|
|
136
|
-
const
|
|
199
|
+
const parsedData = typeof msg.data === "string" ? JSON.parse(msg.data) : msg.data;
|
|
200
|
+
const data = isRecord(parsedData) ? parsedData : {};
|
|
137
201
|
// 3. Business logic deduplication
|
|
138
202
|
const msgId = data.msgId;
|
|
139
203
|
if (msgId && !this.markProcessed(msgId)) {
|
|
@@ -157,21 +221,24 @@ export class DingTalkBot {
|
|
|
157
221
|
if (!immediate && this.reconnectAttempts > 0) {
|
|
158
222
|
const delay = Math.min(1000 * 2 ** this.reconnectAttempts + Math.random() * 1000, 30000);
|
|
159
223
|
log.logInfo(`DingTalk: waiting ${Math.round(delay / 1000)}s before reconnecting...`);
|
|
160
|
-
await
|
|
224
|
+
await this.waitForDelay(delay);
|
|
225
|
+
if (this.isStopped || !this.client) {
|
|
226
|
+
this.isReconnecting = false;
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
161
229
|
}
|
|
162
230
|
try {
|
|
163
|
-
const socket = this.
|
|
231
|
+
const socket = this.getSocket();
|
|
164
232
|
if (socket?.readyState === 1 || socket?.readyState === 3) {
|
|
165
|
-
await this.client.disconnect();
|
|
233
|
+
await Promise.resolve(this.client.disconnect());
|
|
166
234
|
}
|
|
167
235
|
await this.client.connect();
|
|
168
236
|
this.lastSocketAvailableTime = Date.now();
|
|
169
237
|
this.reconnectAttempts = 0; // Success, reset backoff
|
|
170
238
|
log.logInfo("DingTalk: connected to stream.");
|
|
171
239
|
// Setup keep alive
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
this.keepAliveTimer = setInterval(() => {
|
|
240
|
+
this.clearKeepAliveTimer();
|
|
241
|
+
this.keepAliveTimer = this.setTrackedInterval(() => {
|
|
175
242
|
if (this.isStopped)
|
|
176
243
|
return;
|
|
177
244
|
const elapsed = Date.now() - this.lastSocketAvailableTime;
|
|
@@ -179,9 +246,9 @@ export class DingTalkBot {
|
|
|
179
246
|
log.logWarning("DingTalk: connection timeout detected (>90s). Keeping active where possible...");
|
|
180
247
|
}
|
|
181
248
|
try {
|
|
182
|
-
const s = this.
|
|
249
|
+
const s = this.getSocket();
|
|
183
250
|
if (s?.readyState === 1) {
|
|
184
|
-
s.ping();
|
|
251
|
+
s.ping?.();
|
|
185
252
|
}
|
|
186
253
|
}
|
|
187
254
|
catch (_err) {
|
|
@@ -189,7 +256,7 @@ export class DingTalkBot {
|
|
|
189
256
|
}
|
|
190
257
|
}, 30 * 1000);
|
|
191
258
|
// Setup native socket events
|
|
192
|
-
const s = this.
|
|
259
|
+
const s = this.getSocket();
|
|
193
260
|
s?.on("pong", () => {
|
|
194
261
|
this.lastSocketAvailableTime = Date.now();
|
|
195
262
|
});
|
|
@@ -197,11 +264,7 @@ export class DingTalkBot {
|
|
|
197
264
|
log.logWarning(`DingTalk: WebSocket closed: code=${code}, reason=${reason}`);
|
|
198
265
|
if (this.isStopped)
|
|
199
266
|
return;
|
|
200
|
-
|
|
201
|
-
this.doReconnect(true).catch((err) => {
|
|
202
|
-
log.logWarning("DingTalk: reconnect failed", err instanceof Error ? err.message : String(err));
|
|
203
|
-
});
|
|
204
|
-
}, 1000);
|
|
267
|
+
this.scheduleReconnect(1000, true);
|
|
205
268
|
});
|
|
206
269
|
s?.on("message", (raw) => {
|
|
207
270
|
try {
|
|
@@ -228,20 +291,19 @@ export class DingTalkBot {
|
|
|
228
291
|
}
|
|
229
292
|
// Auto-retry on failure with exponential backoff
|
|
230
293
|
if (connectionFailed && !this.isStopped) {
|
|
231
|
-
this.
|
|
294
|
+
this.scheduleReconnect(0, false);
|
|
232
295
|
}
|
|
233
296
|
}
|
|
234
297
|
async stop() {
|
|
235
298
|
log.logInfo("DingTalk: stopping bot");
|
|
236
299
|
this.isStopped = true;
|
|
237
|
-
|
|
238
|
-
clearInterval(this.keepAliveTimer);
|
|
300
|
+
this.clearAllTimers();
|
|
239
301
|
for (const queue of this.queues.values()) {
|
|
240
302
|
queue.stop();
|
|
241
303
|
}
|
|
242
304
|
if (this.client) {
|
|
243
305
|
try {
|
|
244
|
-
await Promise.resolve(this.client.disconnect
|
|
306
|
+
await Promise.resolve(this.client.disconnect());
|
|
245
307
|
}
|
|
246
308
|
catch (err) {
|
|
247
309
|
log.logWarning("DingTalk: failed to disconnect cleanly", err instanceof Error ? err.message : String(err));
|
|
@@ -553,11 +615,9 @@ export class DingTalkBot {
|
|
|
553
615
|
if (textContent)
|
|
554
616
|
return textContent;
|
|
555
617
|
// 2. richText 类型消息:从 content.richText 列表提取文本片段
|
|
556
|
-
|
|
557
|
-
const contentObj = raw.content;
|
|
558
|
-
if (contentObj?.richText) {
|
|
618
|
+
if (data.content?.richText) {
|
|
559
619
|
const parts = [];
|
|
560
|
-
for (const item of
|
|
620
|
+
for (const item of data.content.richText) {
|
|
561
621
|
if (item.text)
|
|
562
622
|
parts.push(item.text);
|
|
563
623
|
}
|
|
@@ -577,7 +637,7 @@ export class DingTalkBot {
|
|
|
577
637
|
const conversationId = data.conversationId || "";
|
|
578
638
|
const conversationType = data.conversationType || "1";
|
|
579
639
|
if (!content) {
|
|
580
|
-
const msgtype = data.msgtype
|
|
640
|
+
const msgtype = typeof data.msgtype === "string" ? data.msgtype : "unknown";
|
|
581
641
|
log.logWarning(`DingTalk: empty message (type=${msgtype})`);
|
|
582
642
|
return;
|
|
583
643
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dingtalk.js","sourceRoot":"","sources":["../../src/runtime/dingtalk.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DingTalk communication layer using dingtalk-stream SDK with AI Card streaming.\n *\n * Handles:\n * - Receiving messages via DingTalk Stream Mode (DWClient)\n * - Responding via AI Card (streaming) or plain markdown (fallback)\n * - Access token management\n * - Per-channel message queuing\n */\nimport axios from \"axios\";\nimport { DWClient, type DWClientDownStream, TOPIC_ROBOT } from \"dingtalk-stream\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport { parseBuiltInCommand, renderBuiltInHelp } from \"../commands.js\";\nimport * as log from \"../log.js\";\nimport { isRecord } from \"../shared/type-guards.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DingTalkConfig {\n\tclientId: string;\n\tclientSecret: string;\n\trobotCode?: string;\n\tcardTemplateId?: string;\n\tcardTemplateKey?: string;\n\tallowFrom?: string[];\n\tstateDir?: string;\n}\n\nexport interface DingTalkEvent {\n\ttype: \"dm\" | \"group\";\n\tchannelId: string; // dm_{staffId} or group_{conversationId}\n\tts: string;\n\tuser: string; // sender staff id\n\tuserName: string; // sender nickname\n\ttext: string;\n\tconversationId: string;\n\tconversationType: string; // \"1\" = DM, \"2\" = group\n}\n\nexport interface DingTalkContext {\n\tmessage: {\n\t\ttext: string;\n\t\trawText: string;\n\t\tuser: string;\n\t\tuserName?: string;\n\t\tchannel: string;\n\t\tts: string;\n\t};\n\tchannelName?: string;\n\trespond: (text: string, shouldLog?: boolean) => Promise<void>;\n\trespondPlain: (text: string, shouldLog?: boolean) => Promise<boolean>;\n\treplaceMessage: (text: string) => Promise<void>;\n\trespondInThread: (text: string) => Promise<void>;\n\tsetTyping: (isTyping: boolean) => Promise<void>;\n\tsetWorking: (working: boolean) => Promise<void>;\n\tdeleteMessage: () => Promise<void>;\n\tflush: () => Promise<void>;\n\tclose: () => Promise<void>;\n}\n\nexport type BusyMessageMode = \"steer\" | \"followUp\";\n\nexport interface DingTalkHandler {\n\tisRunning(channelId: string): boolean;\n\thandleEvent(event: DingTalkEvent, bot: DingTalkBot, isEvent?: boolean): Promise<void>;\n\thandleStop(channelId: string, bot: DingTalkBot): Promise<void>;\n\thandleBusyMessage(event: DingTalkEvent, bot: DingTalkBot, mode: BusyMessageMode, queueText: string): Promise<void>;\n}\n\n// ============================================================================\n// AI Card State\n// ============================================================================\n\ninterface AICard {\n\tinstanceId: string;\n\tconversationId: string;\n\taccessToken: string;\n\ttemplateKey: string;\n\tcreatedAt: number;\n\tlastUpdated: number;\n\tcontent: string;\n\tfinished: boolean;\n}\n\ninterface ConversationMeta {\n\tconversationId: string;\n\tconversationType: string;\n\tsenderId: string;\n}\n\ninterface DingTalkIncomingMessage {\n\tmsgId?: string;\n\tsenderStaffId?: string;\n\tsenderId?: string;\n\tsenderNick?: string;\n\tconversationId?: string;\n\tconversationType?: string;\n\tmsgtype?: string;\n\ttext?: {\n\t\tcontent?: string;\n\t};\n\tcontent?: {\n\t\trichText?: Array<Record<string, string>>;\n\t};\n}\n\ninterface DingTalkSocketLike {\n\treadyState?: number;\n\tping?: () => void;\n\ton(event: \"pong\", listener: () => void): void;\n\ton(event: \"close\", listener: (code: number, reason: string) => void): void;\n\ton(event: \"message\", listener: (raw: string) => void): void;\n}\n\n// ============================================================================\n// Per-channel queue for sequential processing\n// ============================================================================\n\ntype QueuedWork = () => Promise<void>;\n\nclass ChannelQueue {\n\tprivate queue: QueuedWork[] = [];\n\tprivate processing = false;\n\tprivate stopped = false;\n\n\tenqueue(work: QueuedWork): void {\n\t\tif (this.stopped) return;\n\t\tthis.queue.push(work);\n\t\tthis.processNext();\n\t}\n\n\tsize(): number {\n\t\treturn this.queue.length;\n\t}\n\n\tprivate async processNext(): Promise<void> {\n\t\tif (this.processing || this.stopped || this.queue.length === 0) return;\n\t\tthis.processing = true;\n\t\tconst work = this.queue.shift()!;\n\t\ttry {\n\t\t\tawait work();\n\t\t} catch (err) {\n\t\t\tlog.logWarning(\"Queue error\", err instanceof Error ? err.message : String(err));\n\t\t}\n\t\tthis.processing = false;\n\t\tthis.processNext();\n\t}\n\n\tstop(): void {\n\t\tthis.stopped = true;\n\t\tthis.queue = [];\n\t}\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst DINGTALK_API = \"https://api.dingtalk.com\";\nconst TOKEN_REFRESH_SECS = 90 * 60; // 1.5 hours (tokens expire after 2 hours)\n\n// ============================================================================\n// DingTalkBot\n// ============================================================================\n\nexport class DingTalkBot {\n\tprivate handler: DingTalkHandler;\n\tprivate config: DingTalkConfig;\n\n\t// Access token cache\n\tprivate accessToken: string | null = null;\n\tprivate tokenExpiry = 0;\n\tprivate tokenRefreshPromise: Promise<string | null> | null = null;\n\n\t// Active AI cards: channelId → AICard\n\tprivate activeCards = new Map<string, AICard>();\n\n\t// Conversation metadata cache: channelId → metadata\n\tprivate convMeta = new Map<string, ConversationMeta>();\n\n\t// Per-channel queues\n\tprivate queues = new Map<string, ChannelQueue>();\n\n\t// Connection stability\n\tprivate client: DWClient | null = null;\n\tprivate lastSocketAvailableTime = Date.now();\n\tprivate activeMessageProcessing = false;\n\tprivate keepAliveTimer: NodeJS.Timeout | null = null;\n\tprivate reconnectTimer: NodeJS.Timeout | null = null;\n\tprivate isReconnecting = false;\n\tprivate isStopped = false;\n\tprivate reconnectAttempts = 0;\n\n\t// Deduplication cache (Set for O(1) lookup, order array for FIFO eviction)\n\tprivate processedIds = new Set<string>();\n\tprivate processedIdsOrder: string[] = [];\n\n\tconstructor(handler: DingTalkHandler, config: DingTalkConfig) {\n\t\tthis.handler = handler;\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * Mark an ID as processed. Returns true if this is a new ID, false if already seen.\n\t * Maintains a FIFO buffer of at most 200 entries.\n\t */\n\tprivate markProcessed(id: string): boolean {\n\t\tif (this.processedIds.has(id)) return false;\n\t\tthis.processedIds.add(id);\n\t\tthis.processedIdsOrder.push(id);\n\t\twhile (this.processedIdsOrder.length > 200) {\n\t\t\tthis.processedIds.delete(this.processedIdsOrder.shift()!);\n\t\t}\n\t\treturn true;\n\t}\n\n\tprivate getSocket(): DingTalkSocketLike | null {\n\t\tif (!this.client) {\n\t\t\treturn null;\n\t\t}\n\t\tconst socket = Reflect.get(this.client as object, \"socket\");\n\t\treturn this.isSocketLike(socket) ? socket : null;\n\t}\n\n\tprivate isSocketLike(value: unknown): value is DingTalkSocketLike {\n\t\tif (!isRecord(value)) {\n\t\t\treturn false;\n\t\t}\n\t\treturn typeof value.on === \"function\";\n\t}\n\n\tprivate setTrackedTimeout(callback: () => void, delayMs: number): NodeJS.Timeout {\n\t\tconst timer = setTimeout(() => {\n\t\t\tcallback();\n\t\t}, delayMs);\n\t\ttimer.unref?.();\n\t\treturn timer;\n\t}\n\n\tprivate setTrackedInterval(callback: () => void, intervalMs: number): NodeJS.Timeout {\n\t\tconst timer = setInterval(callback, intervalMs);\n\t\ttimer.unref?.();\n\t\treturn timer;\n\t}\n\n\tprivate clearKeepAliveTimer(): void {\n\t\tif (this.keepAliveTimer) {\n\t\t\tclearInterval(this.keepAliveTimer);\n\t\t\tthis.keepAliveTimer = null;\n\t\t}\n\t}\n\n\tprivate clearReconnectTimer(): void {\n\t\tif (this.reconnectTimer) {\n\t\t\tclearTimeout(this.reconnectTimer);\n\t\t\tthis.reconnectTimer = null;\n\t\t}\n\t}\n\n\tprivate clearAllTimers(): void {\n\t\tthis.clearKeepAliveTimer();\n\t\tthis.clearReconnectTimer();\n\t}\n\n\tprivate async waitForDelay(delayMs: number): Promise<void> {\n\t\tawait new Promise<void>((resolve) => {\n\t\t\tthis.reconnectTimer = this.setTrackedTimeout(() => {\n\t\t\t\tthis.reconnectTimer = null;\n\t\t\t\tresolve();\n\t\t\t}, delayMs);\n\t\t});\n\t}\n\n\tprivate scheduleReconnect(delayMs: number, immediate: boolean): void {\n\t\tif (this.isStopped) {\n\t\t\treturn;\n\t\t}\n\t\tthis.clearReconnectTimer();\n\t\tthis.reconnectTimer = this.setTrackedTimeout(() => {\n\t\t\tthis.reconnectTimer = null;\n\t\t\tthis.doReconnect(immediate).catch((err) => {\n\t\t\t\tlog.logWarning(\"DingTalk: reconnect failed\", err instanceof Error ? err.message : String(err));\n\t\t\t});\n\t\t}, delayMs);\n\t}\n\n\t// ==========================================================================\n\t// Public API\n\t// ==========================================================================\n\n\tasync start(): Promise<void> {\n\t\tif (!this.config.clientId || !this.config.clientSecret) {\n\t\t\tlog.logWarning(\"DingTalk: clientId / clientSecret not configured\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.config.cardTemplateId) {\n\t\t\tlog.logWarning(\"DingTalk: cardTemplateId not configured — AI Card streaming will not work\");\n\t\t}\n\n\t\tlog.logInfo(`DingTalk: initializing stream (clientId=${this.config.clientId.substring(0, 8)}…)`);\n\n\t\tif (process.env.DINGTALK_FORCE_PROXY !== \"true\") {\n\t\t\taxios.defaults.proxy = false;\n\t\t}\n\t\tthis.clearAllTimers();\n\n\t\tthis.client = new DWClient({\n\t\t\tclientId: this.config.clientId,\n\t\t\tclientSecret: this.config.clientSecret,\n\t\t\tkeepAlive: false,\n\t\t});\n\n\t\tthis.client.registerCallbackListener(TOPIC_ROBOT, (msg: DWClientDownStream) => {\n\t\t\treturn this.handleRawMessage(msg);\n\t\t});\n\n\t\tlog.logConnected();\n\t\tawait this.doReconnect(true); // Initial connection\n\t}\n\n\tprivate handleRawMessage(msg: DWClientDownStream): { status: \"SUCCESS\"; message: string } {\n\t\t// 1. Immediate ACK\n\t\tif (msg.headers?.messageId && this.client) {\n\t\t\tthis.client.socketCallBackResponse(msg.headers.messageId, { status: \"SUCCESS\", message: \"OK\" });\n\t\t}\n\n\t\t// 2. Protocol deduplication\n\t\tconst messageId = msg.headers?.messageId;\n\t\tif (messageId && !this.markProcessed(messageId)) {\n\t\t\treturn { status: \"SUCCESS\", message: \"OK\" };\n\t\t}\n\n\t\ttry {\n\t\t\tconst parsedData = typeof msg.data === \"string\" ? JSON.parse(msg.data) : msg.data;\n\t\t\tconst data: DingTalkIncomingMessage = isRecord(parsedData) ? parsedData : {};\n\n\t\t\t// 3. Business logic deduplication\n\t\t\tconst msgId = data.msgId;\n\t\t\tif (msgId && !this.markProcessed(msgId)) {\n\t\t\t\treturn { status: \"SUCCESS\", message: \"OK\" };\n\t\t\t}\n\n\t\t\t// Fire-and-forget processing\n\t\t\tthis.onStreamMessage(data).catch((err: unknown) => {\n\t\t\t\tlog.logWarning(\"DingTalk handler error\", err instanceof Error ? err.message : String(err));\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tlog.logWarning(\"DingTalk: failed to parse message\", err instanceof Error ? err.message : String(err));\n\t\t}\n\n\t\treturn { status: \"SUCCESS\", message: \"OK\" };\n\t}\n\n\tprivate async doReconnect(immediate = false) {\n\t\tif (this.isReconnecting || this.isStopped || !this.client) return;\n\t\tthis.isReconnecting = true;\n\t\tlet connectionFailed = false;\n\n\t\tif (!immediate && this.reconnectAttempts > 0) {\n\t\t\tconst delay = Math.min(1000 * 2 ** this.reconnectAttempts + Math.random() * 1000, 30000);\n\t\t\tlog.logInfo(`DingTalk: waiting ${Math.round(delay / 1000)}s before reconnecting...`);\n\t\t\tawait this.waitForDelay(delay);\n\t\t\tif (this.isStopped || !this.client) {\n\t\t\t\tthis.isReconnecting = false;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\tconst socket = this.getSocket();\n\t\t\tif (socket?.readyState === 1 || socket?.readyState === 3) {\n\t\t\t\tawait Promise.resolve(this.client.disconnect());\n\t\t\t}\n\n\t\t\tawait this.client.connect();\n\n\t\t\tthis.lastSocketAvailableTime = Date.now();\n\t\t\tthis.reconnectAttempts = 0; // Success, reset backoff\n\t\t\tlog.logInfo(\"DingTalk: connected to stream.\");\n\n\t\t\t// Setup keep alive\n\t\t\tthis.clearKeepAliveTimer();\n\t\t\tthis.keepAliveTimer = this.setTrackedInterval(() => {\n\t\t\t\tif (this.isStopped) return;\n\n\t\t\t\tconst elapsed = Date.now() - this.lastSocketAvailableTime;\n\t\t\t\tif (elapsed > 90 * 1000 && !this.activeMessageProcessing) {\n\t\t\t\t\tlog.logWarning(\"DingTalk: connection timeout detected (>90s). Keeping active where possible...\");\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tconst s = this.getSocket();\n\t\t\t\t\tif (s?.readyState === 1) {\n\t\t\t\t\t\ts.ping?.();\n\t\t\t\t\t}\n\t\t\t\t} catch (_err) {\n\t\t\t\t\t// Ignore\n\t\t\t\t}\n\t\t\t}, 30 * 1000);\n\n\t\t\t// Setup native socket events\n\t\t\tconst s = this.getSocket();\n\n\t\t\ts?.on(\"pong\", () => {\n\t\t\t\tthis.lastSocketAvailableTime = Date.now();\n\t\t\t});\n\n\t\t\ts?.on(\"close\", (code: number, reason: string) => {\n\t\t\t\tlog.logWarning(`DingTalk: WebSocket closed: code=${code}, reason=${reason}`);\n\t\t\t\tif (this.isStopped) return;\n\t\t\t\tthis.scheduleReconnect(1000, true);\n\t\t\t});\n\n\t\t\ts?.on(\"message\", (raw: string) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst msg = JSON.parse(raw);\n\t\t\t\t\tif (msg.type === \"SYSTEM\" && msg.headers?.topic === \"disconnect\") {\n\t\t\t\t\t\tlog.logWarning(\"DingTalk: disconnect event received from server.\");\n\t\t\t\t\t\tif (!this.isStopped) {\n\t\t\t\t\t\t\tthis.doReconnect(true).catch(() => {});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (_e) {\n\t\t\t\t\t// skip\n\t\t\t\t}\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tthis.reconnectAttempts++;\n\t\t\tconnectionFailed = true;\n\t\t\tlog.logWarning(\"DingTalk: connection failed\", err instanceof Error ? err.message : String(err));\n\t\t} finally {\n\t\t\tthis.isReconnecting = false;\n\t\t}\n\n\t\t// Auto-retry on failure with exponential backoff\n\t\tif (connectionFailed && !this.isStopped) {\n\t\t\tthis.scheduleReconnect(0, false);\n\t\t}\n\t}\n\n\tasync stop(): Promise<void> {\n\t\tlog.logInfo(\"DingTalk: stopping bot\");\n\t\tthis.isStopped = true;\n\t\tthis.clearAllTimers();\n\t\tfor (const queue of this.queues.values()) {\n\t\t\tqueue.stop();\n\t\t}\n\t\tif (this.client) {\n\t\t\ttry {\n\t\t\t\tawait Promise.resolve(this.client.disconnect());\n\t\t\t} catch (err) {\n\t\t\t\tlog.logWarning(\"DingTalk: failed to disconnect cleanly\", err instanceof Error ? err.message : String(err));\n\t\t\t} finally {\n\t\t\t\tthis.client = null;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Enqueue an event for processing.\n\t * Returns true if enqueued, false if queue is full (max 5).\n\t */\n\tenqueueEvent(event: DingTalkEvent): boolean {\n\t\tif (this.isStopped) {\n\t\t\treturn false;\n\t\t}\n\t\tconst queue = this.getQueue(event.channelId);\n\t\tif (queue.size() >= 5) {\n\t\t\tlog.logWarning(`Event queue full for ${event.channelId}, discarding: ${event.text.substring(0, 50)}`);\n\t\t\treturn false;\n\t\t}\n\t\tlog.logInfo(`Enqueueing event for ${event.channelId}: ${event.text.substring(0, 50)}`);\n\t\tqueue.enqueue(async () => {\n\t\t\tthis.activeMessageProcessing = true;\n\t\t\ttry {\n\t\t\t\tawait this.handler.handleEvent(event, this, true);\n\t\t\t} finally {\n\t\t\t\tthis.activeMessageProcessing = false;\n\t\t\t\tthis.lastSocketAvailableTime = Date.now();\n\t\t\t}\n\t\t});\n\t\treturn true;\n\t}\n\n\t// ==========================================================================\n\t// AI Card operations\n\t// ==========================================================================\n\n\t/**\n\t * Get or create an AI Card for a channel.\n\t */\n\tasync ensureCard(channelId: string): Promise<void> {\n\t\tif (!this.config.cardTemplateId) return;\n\t\tconst existing = this.activeCards.get(channelId);\n\t\tif (existing && !existing.finished) return;\n\t\tawait this.createCard(channelId);\n\t}\n\n\t/**\n\t * Stream content to the active AI Card for a channel.\n\t */\n\tasync streamToCard(channelId: string, content: string, finalize: boolean = false): Promise<boolean> {\n\t\tlet card = this.activeCards.get(channelId);\n\t\tif ((!card || card.finished) && !finalize && this.config.cardTemplateId && content.trim()) {\n\t\t\tawait this.ensureCard(channelId);\n\t\t\tcard = this.activeCards.get(channelId);\n\t\t}\n\t\tif (!card || card.finished) {\n\t\t\tif (finalize) {\n\t\t\t\treturn this.sendPlain(channelId, content);\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t\tconst streamed = await this.streamCard(card, content, finalize);\n\t\tif (!streamed) {\n\t\t\tthis.activeCards.delete(channelId);\n\t\t}\n\t\treturn streamed;\n\t}\n\n\t/**\n\t * Finalize the active card for a channel without falling back to a plain message.\n\t * Returns true if a card was finalized, false if no active card existed.\n\t */\n\tasync finalizeExistingCard(channelId: string, content: string): Promise<boolean> {\n\t\tlet card = this.activeCards.get(channelId);\n\t\tif ((!card || card.finished) && this.config.cardTemplateId && content.trim()) {\n\t\t\tawait this.ensureCard(channelId);\n\t\t\tcard = this.activeCards.get(channelId);\n\t\t}\n\t\tif (!card || card.finished) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst finalized = await this.streamCard(card, content, true);\n\t\tthis.activeCards.delete(channelId);\n\t\treturn finalized;\n\t}\n\n\t/**\n\t * Finalize and remove the active card for a channel.\n\t */\n\tasync finalizeCard(channelId: string, content: string): Promise<boolean> {\n\t\tconst finalized = await this.finalizeExistingCard(channelId, content);\n\t\tif (!finalized) {\n\t\t\treturn this.sendPlain(channelId, content);\n\t\t}\n\t\treturn true;\n\t}\n\n\tdiscardCard(channelId: string): void {\n\t\tthis.activeCards.delete(channelId);\n\t}\n\n\t/**\n\t * Send a normal message natively mapping DM and Group to correct endpoints (fallback when no card).\n\t */\n\tasync sendPlain(channelId: string, text: string): Promise<boolean> {\n\t\tconst token = await this.getAccessToken();\n\t\tif (!token) return false;\n\n\t\tconst meta = this.getConversationMeta(channelId);\n\t\tif (!meta) {\n\t\t\tlog.logWarning(`No conversation metadata for ${channelId}, cannot send plain message`);\n\t\t\treturn false;\n\t\t}\n\n\t\tconst robotCode = this.config.robotCode || this.config.clientId;\n\t\tconst isGroup = meta.conversationType === \"2\";\n\n\t\tconst hasMarkdown = /^#{1,6}\\s|^\\s*[-*]\\s|\\*\\*.*\\*\\*|```|`[^`]+`|\\[.*?\\]\\(.*?\\)/m.test(text);\n\n\t\tconst msgKey = hasMarkdown ? \"sampleMarkdown\" : \"sampleText\";\n\t\tconst msgParam = hasMarkdown ? JSON.stringify({ text, title: \"Bot\" }) : JSON.stringify({ content: text });\n\n\t\tconst url = isGroup\n\t\t\t? `${DINGTALK_API}/v1.0/robot/groupMessages/send`\n\t\t\t: `${DINGTALK_API}/v1.0/robot/oToMessages/batchSend`;\n\n\t\tconst body: any = {\n\t\t\trobotCode,\n\t\t\tmsgKey,\n\t\t\tmsgParam,\n\t\t};\n\n\t\tif (isGroup) {\n\t\t\tbody.openConversationId = meta.conversationId;\n\t\t} else {\n\t\t\tbody.userIds = [meta.senderId];\n\t\t}\n\n\t\ttry {\n\t\t\tawait axios.post(url, body, {\n\t\t\t\theaders: {\n\t\t\t\t\t\"x-acs-dingtalk-access-token\": token,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\tif (axios.isAxiosError(err) && err.response) {\n\t\t\t\tlog.logWarning(`DingTalk plain send failed (${err.response.status})`, JSON.stringify(err.response.data));\n\t\t\t} else {\n\t\t\t\tlog.logWarning(\"DingTalk plain send error\", err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// ==========================================================================\n\t// Private - AI Card implementation\n\t// ==========================================================================\n\n\tprivate async createCard(channelId: string): Promise<AICard | null> {\n\t\tconst token = await this.getAccessToken();\n\t\tif (!token) return null;\n\n\t\tconst meta = this.getConversationMeta(channelId);\n\t\tif (!meta) {\n\t\t\tlog.logWarning(`No conversation metadata for ${channelId}, cannot create card`);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst isGroup = meta.conversationType === \"2\";\n\t\tconst instanceId = `card_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;\n\t\tconst robotCode = this.config.robotCode || this.config.clientId;\n\n\t\t// openSpaceId format:\n\t\t// 群聊: dtv1.card//IM_GROUP.{openConversationId}\n\t\t// 单聊: dtv1.card//IM_ROBOT.{userId}\n\t\tconst openSpaceId = isGroup\n\t\t\t? `dtv1.card//IM_GROUP.${meta.conversationId}`\n\t\t\t: `dtv1.card//IM_ROBOT.${meta.senderId}`;\n\n\t\tconst body: Record<string, unknown> = {\n\t\t\tcardTemplateId: this.config.cardTemplateId,\n\t\t\toutTrackId: instanceId,\n\t\t\tcardData: { cardParamMap: {} },\n\t\t\tcallbackType: \"STREAM\",\n\t\t\timGroupOpenSpaceModel: { supportForward: true },\n\t\t\timRobotOpenSpaceModel: { supportForward: true },\n\t\t\topenSpaceId,\n\t\t\tuserIdType: 1,\n\t\t};\n\n\t\tif (isGroup) {\n\t\t\tbody.imGroupOpenDeliverModel = { robotCode };\n\t\t} else {\n\t\t\tbody.imRobotOpenDeliverModel = { spaceType: \"IM_ROBOT\" };\n\t\t}\n\n\t\ttry {\n\t\t\tawait axios.post(`${DINGTALK_API}/v1.0/card/instances/createAndDeliver`, body, {\n\t\t\t\theaders: {\n\t\t\t\t\t\"x-acs-dingtalk-access-token\": token,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tif (axios.isAxiosError(err) && err.response) {\n\t\t\t\tlog.logWarning(`DingTalk Card: create failed (${err.response.status})`, JSON.stringify(err.response.data));\n\t\t\t} else {\n\t\t\t\tlog.logWarning(\"DingTalk Card: create failed\", err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\n\t\tconst card: AICard = {\n\t\t\tinstanceId,\n\t\t\tconversationId: meta.conversationId,\n\t\t\taccessToken: token,\n\t\t\ttemplateKey: this.config.cardTemplateKey || \"content\",\n\t\t\tcreatedAt: Date.now() / 1000,\n\t\t\tlastUpdated: Date.now() / 1000,\n\t\t\tcontent: \"\",\n\t\t\tfinished: false,\n\t\t};\n\t\tthis.activeCards.set(channelId, card);\n\t\treturn card;\n\t}\n\n\tprivate async streamCard(card: AICard, content: string, finalize: boolean = false): Promise<boolean> {\n\t\t// Refresh token if needed\n\t\tconst ageSecs = Date.now() / 1000 - card.createdAt;\n\t\tif (ageSecs > TOKEN_REFRESH_SECS) {\n\t\t\tconst token = await this.getAccessToken();\n\t\t\tif (token) {\n\t\t\t\tcard.accessToken = token;\n\t\t\t}\n\t\t}\n\n\t\tconst body = {\n\t\t\toutTrackId: card.instanceId,\n\t\t\tguid: `${Date.now()}_${Math.random().toString(36).substring(2, 8)}`,\n\t\t\tkey: card.templateKey,\n\t\t\tcontent,\n\t\t\tisFull: true,\n\t\t\tisFinalize: finalize,\n\t\t\tisError: false,\n\t\t};\n\n\t\tconst start = Date.now();\n\t\ttry {\n\t\t\tawait axios.put(`${DINGTALK_API}/v1.0/card/streaming`, body, {\n\t\t\t\theaders: {\n\t\t\t\t\t\"x-acs-dingtalk-access-token\": card.accessToken,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tconst duration = Date.now() - start;\n\t\t\tif (duration > 1000) {\n\t\t\t\tlog.logWarning(`DingTalk Card: streaming request took ${duration}ms (slow)`);\n\t\t\t}\n\n\t\t\tcard.lastUpdated = Date.now() / 1000;\n\t\t\tcard.content = content;\n\t\t\tif (finalize) {\n\t\t\t\tcard.finished = true;\n\t\t\t}\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\tif (axios.isAxiosError(err) && err.response) {\n\t\t\t\tlog.logWarning(\n\t\t\t\t\t`DingTalk Card: streaming failed (${err.response.status})`,\n\t\t\t\t\tJSON.stringify(err.response.data),\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tlog.logWarning(\"DingTalk Card: streaming failed\", err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// ==========================================================================\n\t// Private - Access Token\n\t// ==========================================================================\n\n\tprivate async getAccessToken(): Promise<string | null> {\n\t\tif (this.accessToken && Date.now() / 1000 < this.tokenExpiry) {\n\t\t\treturn this.accessToken;\n\t\t}\n\n\t\t// Coalesce concurrent refresh requests into a single HTTP call\n\t\tif (!this.tokenRefreshPromise) {\n\t\t\tthis.tokenRefreshPromise = this.refreshAccessToken().finally(() => {\n\t\t\t\tthis.tokenRefreshPromise = null;\n\t\t\t});\n\t\t}\n\t\treturn this.tokenRefreshPromise;\n\t}\n\n\tprivate async refreshAccessToken(): Promise<string | null> {\n\t\ttry {\n\t\t\tconst resp = await axios.post(\n\t\t\t\t`${DINGTALK_API}/v1.0/oauth2/accessToken`,\n\t\t\t\t{\n\t\t\t\t\tappKey: this.config.clientId,\n\t\t\t\t\tappSecret: this.config.clientSecret,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tconst data = resp.data as { accessToken?: string; expireIn?: number };\n\t\t\tthis.accessToken = data.accessToken || null;\n\t\t\tthis.tokenExpiry = Date.now() / 1000 + (data.expireIn || 7200) - 60;\n\t\t\treturn this.accessToken;\n\t\t} catch (err) {\n\t\t\tif (axios.isAxiosError(err) && err.response) {\n\t\t\t\tlog.logWarning(\n\t\t\t\t\t`DingTalk: failed to get access token (${err.response.status})`,\n\t\t\t\t\tJSON.stringify(err.response.data),\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tlog.logWarning(\"DingTalk: failed to get access token\", err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t// ==========================================================================\n\t// Private - Message handling\n\t// ==========================================================================\n\n\tprivate extractContent(data: DingTalkIncomingMessage): string {\n\t\t// 1. text 类型消息:从 text.content 提取\n\t\tconst textContent = (data.text?.content || \"\").trim();\n\t\tif (textContent) return textContent;\n\n\t\t// 2. richText 类型消息:从 content.richText 列表提取文本片段\n\t\tif (data.content?.richText) {\n\t\t\tconst parts: string[] = [];\n\t\t\tfor (const item of data.content.richText) {\n\t\t\t\tif (item.text) parts.push(item.text);\n\t\t\t}\n\t\t\tconst joined = parts.join(\"\").trim();\n\t\t\tif (joined) return joined;\n\t\t}\n\n\t\treturn \"\";\n\t}\n\n\tprivate async onStreamMessage(data: DingTalkIncomingMessage): Promise<void> {\n\t\tif (this.isStopped) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst content = this.extractContent(data);\n\t\tconst senderId = data.senderStaffId || data.senderId || \"\";\n\t\tconst senderName = data.senderNick || \"Unknown\";\n\t\tconst conversationId = data.conversationId || \"\";\n\t\tconst conversationType = data.conversationType || \"1\";\n\n\t\tif (!content) {\n\t\t\tconst msgtype = typeof data.msgtype === \"string\" ? data.msgtype : \"unknown\";\n\t\t\tlog.logWarning(`DingTalk: empty message (type=${msgtype})`);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.config.allowFrom && this.config.allowFrom.length > 0) {\n\t\t\tif (!this.config.allowFrom.includes(senderId)) {\n\t\t\t\tlog.logWarning(`DingTalk: ignoring message from unauthorized user ${senderName} (${senderId})`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Determine channel ID\n\t\tconst channelId = conversationType === \"2\" ? `group_${conversationId}` : `dm_${senderId}`;\n\n\t\tlog.logInfo(`DingTalk ← ${senderName} (${senderId}) [${channelId}]: ${content.substring(0, 80)}`);\n\n\t\t// Cache conversation metadata for card creation\n\t\tthis.setConversationMeta(channelId, {\n\t\t\tconversationId,\n\t\t\tconversationType,\n\t\t\tsenderId,\n\t\t});\n\n\t\t// Build event\n\t\tconst event: DingTalkEvent = {\n\t\t\ttype: conversationType === \"2\" ? \"group\" : \"dm\",\n\t\t\tchannelId,\n\t\t\tts: Date.now().toString(),\n\t\t\tuser: senderId,\n\t\t\tuserName: senderName,\n\t\t\ttext: content,\n\t\t\tconversationId,\n\t\t\tconversationType,\n\t\t};\n\n\t\tconst builtInCommand = parseBuiltInCommand(content);\n\t\tconst isSlashCommand = content.trim().startsWith(\"/\");\n\n\t\t// Check if busy\n\t\tif (this.handler.isRunning(channelId)) {\n\t\t\tif (builtInCommand?.name === \"help\") {\n\t\t\t\tawait this.sendPlain(channelId, renderBuiltInHelp());\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (builtInCommand?.name === \"stop\") {\n\t\t\t\tawait this.handler.handleStop(channelId, this);\n\t\t\t\tawait this.sendPlain(channelId, \"Stopping the current task.\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (builtInCommand?.name === \"steer\") {\n\t\t\t\tawait this.handler.handleBusyMessage(event, this, \"steer\", builtInCommand.args);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (builtInCommand?.name === \"followup\") {\n\t\t\t\tawait this.handler.handleBusyMessage(event, this, \"followUp\", builtInCommand.args);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (builtInCommand) {\n\t\t\t\tawait this.sendPlain(\n\t\t\t\t\tchannelId,\n\t\t\t\t\t\"A task is already running. Use `/stop`, `/steer <message>`, or `/followup <message>`. Plain messages default to steer.\",\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (isSlashCommand) {\n\t\t\t\tawait this.sendPlain(\n\t\t\t\t\tchannelId,\n\t\t\t\t\t\"A task is already running. Only `/stop`, `/steer <message>`, and `/followup <message>` are available while streaming.\",\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tawait this.handler.handleBusyMessage(event, this, \"steer\", content);\n\t\t\treturn;\n\t\t}\n\n\t\t// Enqueue for processing\n\t\tthis.getQueue(channelId).enqueue(async () => {\n\t\t\tthis.activeMessageProcessing = true;\n\t\t\ttry {\n\t\t\t\tawait this.handler.handleEvent(event, this);\n\t\t\t} finally {\n\t\t\t\tthis.activeMessageProcessing = false;\n\t\t\t\tthis.lastSocketAvailableTime = Date.now();\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate getQueue(channelId: string): ChannelQueue {\n\t\tlet queue = this.queues.get(channelId);\n\t\tif (!queue) {\n\t\t\tqueue = new ChannelQueue();\n\t\t\tthis.queues.set(channelId, queue);\n\t\t}\n\t\treturn queue;\n\t}\n\n\tprivate getConversationMeta(channelId: string): ConversationMeta | null {\n\t\tconst cached = this.convMeta.get(channelId);\n\t\tif (cached) return cached;\n\n\t\tconst metaPath = this.getConversationMetaPath(channelId);\n\t\tif (!metaPath || !existsSync(metaPath)) {\n\t\t\treturn null;\n\t\t}\n\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(readFileSync(metaPath, \"utf-8\")) as Partial<ConversationMeta>;\n\t\t\tif (!parsed.conversationId || !parsed.conversationType || !parsed.senderId) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst meta: ConversationMeta = {\n\t\t\t\tconversationId: parsed.conversationId,\n\t\t\t\tconversationType: parsed.conversationType,\n\t\t\t\tsenderId: parsed.senderId,\n\t\t\t};\n\t\t\tthis.convMeta.set(channelId, meta);\n\t\t\treturn meta;\n\t\t} catch (err) {\n\t\t\tlog.logWarning(\n\t\t\t\t`Failed to load conversation metadata for ${channelId}`,\n\t\t\t\terr instanceof Error ? err.message : String(err),\n\t\t\t);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate setConversationMeta(channelId: string, meta: ConversationMeta): void {\n\t\tthis.convMeta.set(channelId, meta);\n\n\t\tconst metaPath = this.getConversationMetaPath(channelId);\n\t\tif (!metaPath) return;\n\n\t\ttry {\n\t\t\tmkdirSync(dirname(metaPath), { recursive: true });\n\t\t\twriteFileSync(metaPath, JSON.stringify(meta, null, 2), \"utf-8\");\n\t\t} catch (err) {\n\t\t\tlog.logWarning(\n\t\t\t\t`Failed to persist conversation metadata for ${channelId}`,\n\t\t\t\terr instanceof Error ? err.message : String(err),\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate getConversationMetaPath(channelId: string): string | null {\n\t\tif (!this.config.stateDir) return null;\n\t\treturn join(this.config.stateDir, channelId, \".channel-meta.json\");\n\t}\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/runtime/events.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAiB,MAAM,eAAe,CAAC;AAMhE,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,WAAW,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,UAAU,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,UAAU,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,cAAc,GAAG,cAAc,GAAG,YAAY,GAAG,aAAa,CAAC;AAW3E,qBAAa,aAAa;IASxB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,GAAG;IATZ,OAAO,CAAC,MAAM,CAA0C;IACxD,OAAO,CAAC,KAAK,CAAgC;IAC7C,OAAO,CAAC,cAAc,CAA0C;IAChE,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,UAAU,CAA0B;gBAGnC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,WAAW;IAKzB,KAAK,IAAI,IAAI;IAiBb,IAAI,IAAI,IAAI;IAyBZ,OAAO,CAAC,QAAQ;IAchB,OAAO,CAAC,YAAY;IAcpB,OAAO,CAAC,gBAAgB;IAaxB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,eAAe;YAcT,UAAU;IAwCxB,OAAO,CAAC,UAAU;IAqClB,OAAO,CAAC,eAAe;IAkBvB,OAAO,CAAC,aAAa;IAoCrB,OAAO,CAAC,cAAc;IAiBtB,OAAO,CAAC,OAAO;IAwCf,OAAO,CAAC,UAAU;IAYlB,OAAO,CAAC,KAAK;CAGb;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,GAAG,aAAa,CAGzF"}
|
|
@@ -2,7 +2,7 @@ import { Cron } from "croner";
|
|
|
2
2
|
import { existsSync, mkdirSync, readdirSync, statSync, unlinkSync, watch } from "fs";
|
|
3
3
|
import { readFile } from "fs/promises";
|
|
4
4
|
import { join } from "path";
|
|
5
|
-
import * as log from "
|
|
5
|
+
import * as log from "../log.js";
|
|
6
6
|
// ============================================================================
|
|
7
7
|
// EventsWatcher
|
|
8
8
|
// ============================================================================
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/runtime/events.ts"],"names":[],"mappings":"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","sourcesContent":["import { Cron } from \"croner\";\nimport { existsSync, type FSWatcher, mkdirSync, readdirSync, statSync, unlinkSync, watch } from \"fs\";\nimport { readFile } from \"fs/promises\";\nimport { join } from \"path\";\nimport * as log from \"../log.js\";\nimport type { DingTalkBot, DingTalkEvent } from \"./dingtalk.js\";\n\n// ============================================================================\n// Event Types\n// ============================================================================\n\nexport interface ImmediateEvent {\n\ttype: \"immediate\";\n\tchannelId: string;\n\ttext: string;\n}\n\nexport interface OneShotEvent {\n\ttype: \"one-shot\";\n\tchannelId: string;\n\ttext: string;\n\tat: string; // ISO 8601 with timezone offset\n}\n\nexport interface PeriodicEvent {\n\ttype: \"periodic\";\n\tchannelId: string;\n\ttext: string;\n\tschedule: string; // cron syntax\n\ttimezone: string; // IANA timezone\n}\n\nexport type ScheduledEvent = ImmediateEvent | OneShotEvent | PeriodicEvent;\n\n// ============================================================================\n// EventsWatcher\n// ============================================================================\n\nconst DEBOUNCE_MS = 100;\nconst MAX_RETRIES = 3;\nconst RETRY_BASE_MS = 100;\nconst MAX_TIMEOUT_MS = 2_147_483_647;\n\nexport class EventsWatcher {\n\tprivate timers: Map<string, NodeJS.Timeout> = new Map();\n\tprivate crons: Map<string, Cron> = new Map();\n\tprivate debounceTimers: Map<string, NodeJS.Timeout> = new Map();\n\tprivate startTime: number;\n\tprivate watcher: FSWatcher | null = null;\n\tprivate knownFiles: Set<string> = new Set();\n\n\tconstructor(\n\t\tprivate eventsDir: string,\n\t\tprivate bot: DingTalkBot,\n\t) {\n\t\tthis.startTime = Date.now();\n\t}\n\n\tstart(): void {\n\t\tif (!existsSync(this.eventsDir)) {\n\t\t\tmkdirSync(this.eventsDir, { recursive: true });\n\t\t}\n\n\t\tlog.logInfo(`Events watcher starting, dir: ${this.eventsDir}`);\n\n\t\tthis.scanExisting();\n\n\t\tthis.watcher = watch(this.eventsDir, (_eventType, filename) => {\n\t\t\tif (!filename || !filename.endsWith(\".json\")) return;\n\t\t\tthis.debounce(filename, () => this.handleFileChange(filename));\n\t\t});\n\n\t\tlog.logInfo(`Events watcher started, tracking ${this.knownFiles.size} files`);\n\t}\n\n\tstop(): void {\n\t\tif (this.watcher) {\n\t\t\tthis.watcher.close();\n\t\t\tthis.watcher = null;\n\t\t}\n\n\t\tfor (const timer of this.debounceTimers.values()) {\n\t\t\tclearTimeout(timer);\n\t\t}\n\t\tthis.debounceTimers.clear();\n\n\t\tfor (const timer of this.timers.values()) {\n\t\t\tclearTimeout(timer);\n\t\t}\n\t\tthis.timers.clear();\n\n\t\tfor (const cron of this.crons.values()) {\n\t\t\tcron.stop();\n\t\t}\n\t\tthis.crons.clear();\n\n\t\tthis.knownFiles.clear();\n\t\tlog.logInfo(\"Events watcher stopped\");\n\t}\n\n\tprivate debounce(filename: string, fn: () => void): void {\n\t\tconst existing = this.debounceTimers.get(filename);\n\t\tif (existing) {\n\t\t\tclearTimeout(existing);\n\t\t}\n\t\tthis.debounceTimers.set(\n\t\t\tfilename,\n\t\t\tsetTimeout(() => {\n\t\t\t\tthis.debounceTimers.delete(filename);\n\t\t\t\tfn();\n\t\t\t}, DEBOUNCE_MS),\n\t\t);\n\t}\n\n\tprivate scanExisting(): void {\n\t\tlet files: string[];\n\t\ttry {\n\t\t\tfiles = readdirSync(this.eventsDir).filter((f) => f.endsWith(\".json\"));\n\t\t} catch (err) {\n\t\t\tlog.logWarning(\"Failed to read events directory\", String(err));\n\t\t\treturn;\n\t\t}\n\n\t\tfor (const filename of files) {\n\t\t\tthis.handleFile(filename);\n\t\t}\n\t}\n\n\tprivate handleFileChange(filename: string): void {\n\t\tconst filePath = join(this.eventsDir, filename);\n\n\t\tif (!existsSync(filePath)) {\n\t\t\tthis.handleDelete(filename);\n\t\t} else if (this.knownFiles.has(filename)) {\n\t\t\tthis.cancelScheduled(filename);\n\t\t\tthis.handleFile(filename);\n\t\t} else {\n\t\t\tthis.handleFile(filename);\n\t\t}\n\t}\n\n\tprivate handleDelete(filename: string): void {\n\t\tif (!this.knownFiles.has(filename)) return;\n\n\t\tlog.logInfo(`Event file deleted: ${filename}`);\n\t\tthis.cancelScheduled(filename);\n\t\tthis.knownFiles.delete(filename);\n\t}\n\n\tprivate cancelScheduled(filename: string): void {\n\t\tconst timer = this.timers.get(filename);\n\t\tif (timer) {\n\t\t\tclearTimeout(timer);\n\t\t\tthis.timers.delete(filename);\n\t\t}\n\n\t\tconst cron = this.crons.get(filename);\n\t\tif (cron) {\n\t\t\tcron.stop();\n\t\t\tthis.crons.delete(filename);\n\t\t}\n\t}\n\n\tprivate async handleFile(filename: string): Promise<void> {\n\t\tconst filePath = join(this.eventsDir, filename);\n\n\t\tlet event: ScheduledEvent | null = null;\n\t\tlet lastError: Error | null = null;\n\n\t\tfor (let i = 0; i < MAX_RETRIES; i++) {\n\t\t\ttry {\n\t\t\t\tconst content = await readFile(filePath, \"utf-8\");\n\t\t\t\tevent = this.parseEvent(content, filename);\n\t\t\t\tbreak;\n\t\t\t} catch (err) {\n\t\t\t\tlastError = err instanceof Error ? err : new Error(String(err));\n\t\t\t\tif (i < MAX_RETRIES - 1) {\n\t\t\t\t\tawait this.sleep(RETRY_BASE_MS * 2 ** i);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (!event) {\n\t\t\tlog.logWarning(`Failed to parse event file after ${MAX_RETRIES} retries: ${filename}`, lastError?.message);\n\t\t\tthis.deleteFile(filename);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.knownFiles.add(filename);\n\n\t\tswitch (event.type) {\n\t\t\tcase \"immediate\":\n\t\t\t\tthis.handleImmediate(filename, event);\n\t\t\t\tbreak;\n\t\t\tcase \"one-shot\":\n\t\t\t\tthis.handleOneShot(filename, event);\n\t\t\t\tbreak;\n\t\t\tcase \"periodic\":\n\t\t\t\tthis.handlePeriodic(filename, event);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tprivate parseEvent(content: string, filename: string): ScheduledEvent | null {\n\t\tconst data = JSON.parse(content);\n\n\t\tif (!data.type || !data.channelId || !data.text) {\n\t\t\tthrow new Error(`Missing required fields (type, channelId, text) in ${filename}`);\n\t\t}\n\n\t\tswitch (data.type) {\n\t\t\tcase \"immediate\":\n\t\t\t\treturn { type: \"immediate\", channelId: data.channelId, text: data.text };\n\n\t\t\tcase \"one-shot\":\n\t\t\t\tif (!data.at) {\n\t\t\t\t\tthrow new Error(`Missing 'at' field for one-shot event in ${filename}`);\n\t\t\t\t}\n\t\t\t\treturn { type: \"one-shot\", channelId: data.channelId, text: data.text, at: data.at };\n\n\t\t\tcase \"periodic\":\n\t\t\t\tif (!data.schedule) {\n\t\t\t\t\tthrow new Error(`Missing 'schedule' field for periodic event in ${filename}`);\n\t\t\t\t}\n\t\t\t\tif (!data.timezone) {\n\t\t\t\t\tthrow new Error(`Missing 'timezone' field for periodic event in ${filename}`);\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"periodic\",\n\t\t\t\t\tchannelId: data.channelId,\n\t\t\t\t\ttext: data.text,\n\t\t\t\t\tschedule: data.schedule,\n\t\t\t\t\ttimezone: data.timezone,\n\t\t\t\t};\n\n\t\t\tdefault:\n\t\t\t\tthrow new Error(`Unknown event type '${data.type}' in ${filename}`);\n\t\t}\n\t}\n\n\tprivate handleImmediate(filename: string, event: ImmediateEvent): void {\n\t\tconst filePath = join(this.eventsDir, filename);\n\n\t\ttry {\n\t\t\tconst stat = statSync(filePath);\n\t\t\tif (stat.mtimeMs < this.startTime) {\n\t\t\t\tlog.logInfo(`Stale immediate event, deleting: ${filename}`);\n\t\t\t\tthis.deleteFile(filename);\n\t\t\t\treturn;\n\t\t\t}\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\n\t\tlog.logInfo(`Executing immediate event: ${filename}`);\n\t\tthis.execute(filename, event);\n\t}\n\n\tprivate handleOneShot(filename: string, event: OneShotEvent): void {\n\t\tconst atTime = new Date(event.at).getTime();\n\t\tconst now = Date.now();\n\n\t\tif (!Number.isFinite(atTime)) {\n\t\t\tlog.logWarning(`Invalid one-shot time for ${filename}: ${event.at}`);\n\t\t\tthis.deleteFile(filename);\n\t\t\treturn;\n\t\t}\n\n\t\tif (atTime <= now) {\n\t\t\tlog.logInfo(`One-shot event in the past, deleting: ${filename}`);\n\t\t\tthis.deleteFile(filename);\n\t\t\treturn;\n\t\t}\n\n\t\tconst delay = atTime - now;\n\t\tif (delay > MAX_TIMEOUT_MS) {\n\t\t\tlog.logWarning(\n\t\t\t\t`One-shot event exceeds maximum supported delay for ${filename}: ${event.at}. Use a periodic cron event instead.`,\n\t\t\t);\n\t\t\tthis.deleteFile(filename);\n\t\t\treturn;\n\t\t}\n\n\t\tlog.logInfo(`Scheduling one-shot event: ${filename} in ${Math.round(delay / 1000)}s`);\n\n\t\tconst timer = setTimeout(() => {\n\t\t\tthis.timers.delete(filename);\n\t\t\tlog.logInfo(`Executing one-shot event: ${filename}`);\n\t\t\tthis.execute(filename, event);\n\t\t}, delay);\n\n\t\tthis.timers.set(filename, timer);\n\t}\n\n\tprivate handlePeriodic(filename: string, event: PeriodicEvent): void {\n\t\ttry {\n\t\t\tconst cron = new Cron(event.schedule, { timezone: event.timezone }, () => {\n\t\t\t\tlog.logInfo(`Executing periodic event: ${filename}`);\n\t\t\t\tthis.execute(filename, event, false);\n\t\t\t});\n\n\t\t\tthis.crons.set(filename, cron);\n\n\t\t\tconst next = cron.nextRun();\n\t\t\tlog.logInfo(`Scheduled periodic event: ${filename}, next run: ${next?.toISOString() ?? \"unknown\"}`);\n\t\t} catch (err) {\n\t\t\tlog.logWarning(`Invalid cron schedule for ${filename}: ${event.schedule}`, String(err));\n\t\t\tthis.deleteFile(filename);\n\t\t}\n\t}\n\n\tprivate execute(filename: string, event: ScheduledEvent, deleteAfter: boolean = true): void {\n\t\tlet scheduleInfo: string;\n\t\tswitch (event.type) {\n\t\t\tcase \"immediate\":\n\t\t\t\tscheduleInfo = \"immediate\";\n\t\t\t\tbreak;\n\t\t\tcase \"one-shot\":\n\t\t\t\tscheduleInfo = event.at;\n\t\t\t\tbreak;\n\t\t\tcase \"periodic\":\n\t\t\t\tscheduleInfo = event.schedule;\n\t\t\t\tbreak;\n\t\t}\n\n\t\tconst message = `[EVENT:${filename}:${event.type}:${scheduleInfo}] ${event.text}`;\n\n\t\t// Create synthetic DingTalkEvent\n\t\tconst syntheticEvent: DingTalkEvent = {\n\t\t\ttype: \"dm\",\n\t\t\tchannelId: event.channelId,\n\t\t\tuser: \"EVENT\",\n\t\t\tuserName: \"EVENT\",\n\t\t\ttext: message,\n\t\t\tts: Date.now().toString(),\n\t\t\tconversationId: \"\",\n\t\t\tconversationType: \"1\",\n\t\t};\n\n\t\tconst enqueued = this.bot.enqueueEvent(syntheticEvent);\n\n\t\tif (enqueued && deleteAfter) {\n\t\t\tthis.deleteFile(filename);\n\t\t} else if (!enqueued) {\n\t\t\tlog.logWarning(`Event queue full, discarded: ${filename}`);\n\t\t\tif (deleteAfter) {\n\t\t\t\tthis.deleteFile(filename);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate deleteFile(filename: string): void {\n\t\tconst filePath = join(this.eventsDir, filename);\n\t\ttry {\n\t\t\tunlinkSync(filePath);\n\t\t} catch (err) {\n\t\t\tif (err instanceof Error && \"code\" in err && err.code !== \"ENOENT\") {\n\t\t\t\tlog.logWarning(`Failed to delete event file: ${filename}`, String(err));\n\t\t\t}\n\t\t}\n\t\tthis.knownFiles.delete(filename);\n\t}\n\n\tprivate sleep(ms: number): Promise<void> {\n\t\treturn new Promise((resolve) => setTimeout(resolve, ms));\n\t}\n}\n\n/**\n * Create and start an events watcher.\n */\nexport function createEventsWatcher(workspaceDir: string, bot: DingTalkBot): EventsWatcher {\n\tconst eventsDir = join(workspaceDir, \"events\");\n\treturn new EventsWatcher(eventsDir, bot);\n}\n"]}
|
|
@@ -45,6 +45,8 @@ export interface LoggedSubAgentRun {
|
|
|
45
45
|
export declare class ChannelStore {
|
|
46
46
|
private workingDir;
|
|
47
47
|
private recentlyLogged;
|
|
48
|
+
private cleanupTimer;
|
|
49
|
+
private writeChains;
|
|
48
50
|
constructor(config: ChannelStoreConfig);
|
|
49
51
|
/**
|
|
50
52
|
* Get or create the directory for a channel/DM
|
|
@@ -71,5 +73,8 @@ export declare class ChannelStore {
|
|
|
71
73
|
* Returns null if no log exists
|
|
72
74
|
*/
|
|
73
75
|
getLastTimestamp(channelId: string): string | null;
|
|
76
|
+
private enqueueWrite;
|
|
77
|
+
private startCleanupTimer;
|
|
78
|
+
private cleanupExpiredEntries;
|
|
74
79
|
}
|
|
75
80
|
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/runtime/store.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC;IACpC,eAAe,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IAClC,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,YAAY,GAAG,QAAQ,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,OAAO,CAAC;IACzB,KAAK,EAAE;QACN,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE;YACL,KAAK,EAAE,MAAM,CAAC;YACd,MAAM,EAAE,MAAM,CAAC;YACf,SAAS,EAAE,MAAM,CAAC;YAClB,UAAU,EAAE,MAAM,CAAC;YACnB,KAAK,EAAE,MAAM,CAAC;SACd,CAAC;KACF,CAAC;CACF;AAED,qBAAa,YAAY;IACxB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,cAAc,CAA6B;IACnD,OAAO,CAAC,YAAY,CAA+B;IACnD,OAAO,CAAC,WAAW,CAAoC;gBAE3C,MAAM,EAAE,kBAAkB;IAStC;;OAEG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAQxC;;;;OAIG;IACG,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC;IA4BvE,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAS9E;;;OAGG;YACW,cAAc;IAkB5B;;OAEG;IACG,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUhF;;;OAGG;IACH,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAmElD,OAAO,CAAC,YAAY;IAgBpB,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,qBAAqB;CAY7B"}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { closeSync, existsSync, mkdirSync, openSync, readSync, renameSync, statSync } from "fs";
|
|
2
2
|
import { appendFile, writeFile } from "fs/promises";
|
|
3
3
|
import { dirname, join } from "path";
|
|
4
|
+
const MAX_LOG_SIZE_BYTES = 1_000_000;
|
|
5
|
+
const DEDUPE_TTL_MS = 60_000;
|
|
6
|
+
const DEDUPE_CLEANUP_INTERVAL_MS = 30_000;
|
|
4
7
|
export class ChannelStore {
|
|
5
8
|
constructor(config) {
|
|
6
|
-
// Track recently logged message timestamps to prevent duplicates
|
|
7
9
|
this.recentlyLogged = new Map();
|
|
10
|
+
this.cleanupTimer = null;
|
|
11
|
+
this.writeChains = new Map();
|
|
8
12
|
this.workingDir = config.workingDir;
|
|
9
13
|
// Ensure working directory exists
|
|
10
14
|
if (!existsSync(this.workingDir)) {
|
|
@@ -27,46 +31,50 @@ export class ChannelStore {
|
|
|
27
31
|
* Returns false if message was already logged (duplicate)
|
|
28
32
|
*/
|
|
29
33
|
async logMessage(channelId, message) {
|
|
30
|
-
// Check for duplicate (same channel + timestamp)
|
|
31
34
|
const dedupeKey = `${channelId}:${message.ts}`;
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
const now = Date.now();
|
|
36
|
+
const previousLogTime = this.recentlyLogged.get(dedupeKey);
|
|
37
|
+
if (previousLogTime !== undefined) {
|
|
38
|
+
if (now - previousLogTime < DEDUPE_TTL_MS) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
this.recentlyLogged.delete(dedupeKey);
|
|
34
42
|
}
|
|
35
|
-
|
|
36
|
-
this.
|
|
37
|
-
setTimeout(() => this.recentlyLogged.delete(dedupeKey), 60000);
|
|
43
|
+
this.recentlyLogged.set(dedupeKey, now);
|
|
44
|
+
this.startCleanupTimer();
|
|
38
45
|
const logPath = join(this.getChannelDir(channelId), "log.jsonl");
|
|
39
|
-
// Rotate if file exceeds size limit
|
|
40
|
-
this.rotateIfNeeded(logPath);
|
|
41
|
-
// Ensure message has a date field
|
|
42
46
|
if (!message.date) {
|
|
43
47
|
message.date = new Date().toISOString();
|
|
44
48
|
}
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
await this.enqueueWrite(logPath, async () => {
|
|
50
|
+
await this.rotateIfNeeded(logPath);
|
|
51
|
+
const line = `${JSON.stringify(message)}\n`;
|
|
52
|
+
await appendFile(logPath, line, "utf-8");
|
|
53
|
+
});
|
|
47
54
|
return true;
|
|
48
55
|
}
|
|
49
56
|
async logSubAgentRun(channelId, run) {
|
|
50
57
|
const logPath = join(this.getChannelDir(channelId), "subagent-runs.jsonl");
|
|
51
|
-
this.
|
|
52
|
-
|
|
53
|
-
|
|
58
|
+
await this.enqueueWrite(logPath, async () => {
|
|
59
|
+
await this.rotateIfNeeded(logPath);
|
|
60
|
+
const line = `${JSON.stringify(run)}\n`;
|
|
61
|
+
await appendFile(logPath, line, "utf-8");
|
|
62
|
+
});
|
|
54
63
|
}
|
|
55
64
|
/**
|
|
56
65
|
* Rotate log file if it exceeds 1MB.
|
|
57
66
|
* Keeps one backup (log.jsonl.1) and resets the sync offset.
|
|
58
67
|
*/
|
|
59
|
-
rotateIfNeeded(logPath) {
|
|
68
|
+
async rotateIfNeeded(logPath) {
|
|
60
69
|
try {
|
|
61
70
|
if (!existsSync(logPath))
|
|
62
71
|
return;
|
|
63
72
|
const stats = statSync(logPath);
|
|
64
|
-
if (stats.size >
|
|
73
|
+
if (stats.size > MAX_LOG_SIZE_BYTES) {
|
|
65
74
|
renameSync(logPath, `${logPath}.1`);
|
|
66
|
-
// Reset sync offset since log.jsonl was replaced
|
|
67
75
|
const syncOffsetPath = join(dirname(logPath), ".sync-offset");
|
|
68
76
|
try {
|
|
69
|
-
writeFile(syncOffsetPath, "0", "utf-8")
|
|
77
|
+
await writeFile(syncOffsetPath, "0", "utf-8");
|
|
70
78
|
}
|
|
71
79
|
catch {
|
|
72
80
|
/* ignore */
|
|
@@ -152,5 +160,38 @@ export class ChannelStore {
|
|
|
152
160
|
return null;
|
|
153
161
|
}
|
|
154
162
|
}
|
|
163
|
+
enqueueWrite(logPath, work) {
|
|
164
|
+
const previous = this.writeChains.get(logPath) ?? Promise.resolve();
|
|
165
|
+
const result = previous.catch(() => undefined).then(() => work());
|
|
166
|
+
const completion = result.then(() => undefined, () => undefined);
|
|
167
|
+
this.writeChains.set(logPath, completion);
|
|
168
|
+
completion.finally(() => {
|
|
169
|
+
if (this.writeChains.get(logPath) === completion) {
|
|
170
|
+
this.writeChains.delete(logPath);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
startCleanupTimer() {
|
|
176
|
+
if (this.cleanupTimer) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
this.cleanupTimer = setInterval(() => {
|
|
180
|
+
this.cleanupExpiredEntries();
|
|
181
|
+
}, DEDUPE_CLEANUP_INTERVAL_MS);
|
|
182
|
+
this.cleanupTimer.unref?.();
|
|
183
|
+
}
|
|
184
|
+
cleanupExpiredEntries(now = Date.now()) {
|
|
185
|
+
const cutoff = now - DEDUPE_TTL_MS;
|
|
186
|
+
for (const [key, loggedAt] of this.recentlyLogged) {
|
|
187
|
+
if (loggedAt <= cutoff) {
|
|
188
|
+
this.recentlyLogged.delete(key);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (this.recentlyLogged.size === 0 && this.cleanupTimer) {
|
|
192
|
+
clearInterval(this.cleanupTimer);
|
|
193
|
+
this.cleanupTimer = null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
155
196
|
}
|
|
156
197
|
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/runtime/store.ts"],"names":[],"mappings":"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","sourcesContent":["import { closeSync, existsSync, mkdirSync, openSync, readSync, renameSync, statSync } from \"fs\";\nimport { appendFile, writeFile } from \"fs/promises\";\nimport { dirname, join } from \"path\";\n\nconst MAX_LOG_SIZE_BYTES = 1_000_000;\nconst DEDUPE_TTL_MS = 60_000;\nconst DEDUPE_CLEANUP_INTERVAL_MS = 30_000;\n\nexport interface LoggedMessage {\n\tdate: string;\n\tts: string;\n\tuser: string;\n\tuserName?: string;\n\tdisplayName?: string;\n\ttext: string;\n\tisBot: boolean;\n\tdeliveryMode?: \"steer\" | \"followUp\";\n\tskipContextSync?: boolean;\n}\n\nexport interface ChannelStoreConfig {\n\tworkingDir: string;\n}\n\nexport interface LoggedSubAgentRun {\n\tdate: string;\n\ttoolCallId: string;\n\tlabel: string;\n\tagent: string;\n\tsource: \"predefined\" | \"inline\";\n\tmodel: string;\n\ttools: string[];\n\tturns: number;\n\ttoolCalls: number;\n\tdurationMs: number;\n\tfailed: boolean;\n\tfailureReason?: string;\n\toutput: string;\n\toutputTruncated: boolean;\n\tusage: {\n\t\tinput: number;\n\t\toutput: number;\n\t\tcacheRead: number;\n\t\tcacheWrite: number;\n\t\ttotal: number;\n\t\tcost: {\n\t\t\tinput: number;\n\t\t\toutput: number;\n\t\t\tcacheRead: number;\n\t\t\tcacheWrite: number;\n\t\t\ttotal: number;\n\t\t};\n\t};\n}\n\nexport class ChannelStore {\n\tprivate workingDir: string;\n\tprivate recentlyLogged = new Map<string, number>();\n\tprivate cleanupTimer: NodeJS.Timeout | null = null;\n\tprivate writeChains = new Map<string, Promise<void>>();\n\n\tconstructor(config: ChannelStoreConfig) {\n\t\tthis.workingDir = config.workingDir;\n\n\t\t// Ensure working directory exists\n\t\tif (!existsSync(this.workingDir)) {\n\t\t\tmkdirSync(this.workingDir, { recursive: true });\n\t\t}\n\t}\n\n\t/**\n\t * Get or create the directory for a channel/DM\n\t */\n\tgetChannelDir(channelId: string): string {\n\t\tconst dir = join(this.workingDir, channelId);\n\t\tif (!existsSync(dir)) {\n\t\t\tmkdirSync(dir, { recursive: true });\n\t\t}\n\t\treturn dir;\n\t}\n\n\t/**\n\t * Log a message to the channel's log.jsonl raw archive.\n\t * This file is cold storage and is not proactively loaded into memory context.\n\t * Returns false if message was already logged (duplicate)\n\t */\n\tasync logMessage(channelId: string, message: LoggedMessage): Promise<boolean> {\n\t\tconst dedupeKey = `${channelId}:${message.ts}`;\n\t\tconst now = Date.now();\n\t\tconst previousLogTime = this.recentlyLogged.get(dedupeKey);\n\t\tif (previousLogTime !== undefined) {\n\t\t\tif (now - previousLogTime < DEDUPE_TTL_MS) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tthis.recentlyLogged.delete(dedupeKey);\n\t\t}\n\n\t\tthis.recentlyLogged.set(dedupeKey, now);\n\t\tthis.startCleanupTimer();\n\n\t\tconst logPath = join(this.getChannelDir(channelId), \"log.jsonl\");\n\n\t\tif (!message.date) {\n\t\t\tmessage.date = new Date().toISOString();\n\t\t}\n\n\t\tawait this.enqueueWrite(logPath, async () => {\n\t\t\tawait this.rotateIfNeeded(logPath);\n\t\t\tconst line = `${JSON.stringify(message)}\\n`;\n\t\t\tawait appendFile(logPath, line, \"utf-8\");\n\t\t});\n\t\treturn true;\n\t}\n\n\tasync logSubAgentRun(channelId: string, run: LoggedSubAgentRun): Promise<void> {\n\t\tconst logPath = join(this.getChannelDir(channelId), \"subagent-runs.jsonl\");\n\t\tawait this.enqueueWrite(logPath, async () => {\n\t\t\tawait this.rotateIfNeeded(logPath);\n\t\t\tconst line = `${JSON.stringify(run)}\\n`;\n\t\t\tawait appendFile(logPath, line, \"utf-8\");\n\t\t});\n\t}\n\n\t/**\n\t * Rotate log file if it exceeds 1MB.\n\t * Keeps one backup (log.jsonl.1) and resets the sync offset.\n\t */\n\tprivate async rotateIfNeeded(logPath: string): Promise<void> {\n\t\ttry {\n\t\t\tif (!existsSync(logPath)) return;\n\t\t\tconst stats = statSync(logPath);\n\t\t\tif (stats.size > MAX_LOG_SIZE_BYTES) {\n\t\t\t\trenameSync(logPath, `${logPath}.1`);\n\t\t\t\tconst syncOffsetPath = join(dirname(logPath), \".sync-offset\");\n\t\t\t\ttry {\n\t\t\t\t\tawait writeFile(syncOffsetPath, \"0\", \"utf-8\");\n\t\t\t\t} catch {\n\t\t\t\t\t/* ignore */\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore rotation errors\n\t\t}\n\t}\n\n\t/**\n\t * Log a bot response\n\t */\n\tasync logBotResponse(channelId: string, text: string, ts: string): Promise<void> {\n\t\tawait this.logMessage(channelId, {\n\t\t\tdate: new Date().toISOString(),\n\t\t\tts,\n\t\t\tuser: \"bot\",\n\t\t\ttext,\n\t\t\tisBot: true,\n\t\t});\n\t}\n\n\t/**\n\t * Get the timestamp of the last logged message for a channel\n\t * Returns null if no log exists\n\t */\n\tgetLastTimestamp(channelId: string): string | null {\n\t\tconst logPath = join(this.workingDir, channelId, \"log.jsonl\");\n\t\tif (!existsSync(logPath)) {\n\t\t\treturn null;\n\t\t}\n\n\t\ttry {\n\t\t\tconst stats = statSync(logPath);\n\t\t\tif (stats.size === 0) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst fd = openSync(logPath, \"r\");\n\t\t\ttry {\n\t\t\t\tlet end = stats.size;\n\t\t\t\tconst trailing = Buffer.alloc(1);\n\t\t\t\twhile (end > 0) {\n\t\t\t\t\treadSync(fd, trailing, 0, 1, end - 1);\n\t\t\t\t\tif (trailing[0] !== 0x0a && trailing[0] !== 0x0d) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tend--;\n\t\t\t\t}\n\n\t\t\t\tif (end === 0) {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\n\t\t\t\tconst chunkSize = 4096;\n\t\t\t\tconst buffer = Buffer.alloc(chunkSize);\n\t\t\t\tlet lineStart = 0;\n\t\t\t\tlet position = end;\n\n\t\t\t\twhile (position > 0) {\n\t\t\t\t\tconst bytesToRead = Math.min(chunkSize, position);\n\t\t\t\t\tposition -= bytesToRead;\n\t\t\t\t\treadSync(fd, buffer, 0, bytesToRead, position);\n\n\t\t\t\t\tconst newlineIndex = buffer.subarray(0, bytesToRead).lastIndexOf(0x0a);\n\t\t\t\t\tif (newlineIndex !== -1) {\n\t\t\t\t\t\tlineStart = position + newlineIndex + 1;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst lineLength = end - lineStart;\n\t\t\t\tif (lineLength <= 0) {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\n\t\t\t\tconst lineBuffer = Buffer.alloc(lineLength);\n\t\t\t\treadSync(fd, lineBuffer, 0, lineLength, lineStart);\n\t\t\t\tconst lastLine = lineBuffer.toString(\"utf-8\").replace(/\\r+$/, \"\");\n\t\t\t\tif (!lastLine) {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\n\t\t\t\tconst message = JSON.parse(lastLine) as LoggedMessage;\n\t\t\t\treturn message.ts;\n\t\t\t} finally {\n\t\t\t\tcloseSync(fd);\n\t\t\t}\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate enqueueWrite<T>(logPath: string, work: () => Promise<T>): Promise<T> {\n\t\tconst previous = this.writeChains.get(logPath) ?? Promise.resolve();\n\t\tconst result = previous.catch(() => undefined).then(() => work());\n\t\tconst completion = result.then(\n\t\t\t() => undefined,\n\t\t\t() => undefined,\n\t\t);\n\t\tthis.writeChains.set(logPath, completion);\n\t\tcompletion.finally(() => {\n\t\t\tif (this.writeChains.get(logPath) === completion) {\n\t\t\t\tthis.writeChains.delete(logPath);\n\t\t\t}\n\t\t});\n\t\treturn result;\n\t}\n\n\tprivate startCleanupTimer(): void {\n\t\tif (this.cleanupTimer) {\n\t\t\treturn;\n\t\t}\n\t\tthis.cleanupTimer = setInterval(() => {\n\t\t\tthis.cleanupExpiredEntries();\n\t\t}, DEDUPE_CLEANUP_INTERVAL_MS);\n\t\tthis.cleanupTimer.unref?.();\n\t}\n\n\tprivate cleanupExpiredEntries(now = Date.now()): void {\n\t\tconst cutoff = now - DEDUPE_TTL_MS;\n\t\tfor (const [key, loggedAt] of this.recentlyLogged) {\n\t\t\tif (loggedAt <= cutoff) {\n\t\t\t\tthis.recentlyLogged.delete(key);\n\t\t\t}\n\t\t}\n\t\tif (this.recentlyLogged.size === 0 && this.cleanupTimer) {\n\t\t\tclearInterval(this.cleanupTimer);\n\t\t\tthis.cleanupTimer = null;\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface MarkdownSection {
|
|
2
|
+
heading: string;
|
|
3
|
+
content: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function splitH1Sections(content: string): MarkdownSection[];
|
|
6
|
+
export declare function splitH2Sections(content: string): MarkdownSection[];
|
|
7
|
+
//# sourceMappingURL=markdown-sections.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown-sections.d.ts","sourceRoot":"","sources":["../../src/shared/markdown-sections.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CAChB;AAyCD,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,EAAE,CAElE;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,EAAE,CAElE"}
|