@oyasmi/pipiclaw 0.6.0 → 0.6.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.
@@ -5,7 +5,7 @@ const IDLE_CONSOLIDATION_DELAY_MS = 60_000;
5
5
  export class MemoryLifecycle {
6
6
  constructor(options) {
7
7
  this.options = options;
8
- this.backgroundQueue = Promise.resolve();
8
+ this.durableMemoryQueue = Promise.resolve();
9
9
  this.sessionRefreshQueue = Promise.resolve();
10
10
  this.turnsSinceSessionUpdate = 0;
11
11
  this.toolCallsSinceSessionUpdate = 0;
@@ -75,15 +75,16 @@ export class MemoryLifecycle {
75
75
  }
76
76
  async flushForShutdown() {
77
77
  this.clearIdleConsolidationTimer();
78
- const run = async () => {
78
+ await this.runDurableMemoryJobSerial(async () => {
79
79
  if (!this.hasPendingAssistantSnapshot()) {
80
80
  return;
81
81
  }
82
- await this.runPreflightConsolidation("shutdown");
83
- };
84
- const resultPromise = this.backgroundQueue.then(run, run);
85
- this.backgroundQueue = resultPromise.then(() => undefined, () => undefined);
86
- await resultPromise;
82
+ const messageSnapshot = [...this.options.getMessages()];
83
+ const sessionEntrySnapshot = [...this.options.getSessionEntries()];
84
+ const revisionSnapshot = this.durableRevision;
85
+ const settings = this.options.getSessionMemorySettings();
86
+ await this.runPreflightConsolidationNow("shutdown", messageSnapshot, sessionEntrySnapshot, revisionSnapshot, settings);
87
+ });
87
88
  }
88
89
  clearIdleConsolidationTimer() {
89
90
  if (!this.idleConsolidationTimer) {
@@ -152,8 +153,13 @@ export class MemoryLifecycle {
152
153
  this.thresholdRefreshQueued = false;
153
154
  });
154
155
  }
155
- enqueueBackgroundJob(job, failureMessage) {
156
- this.backgroundQueue = this.backgroundQueue.then(job).catch((error) => {
156
+ runDurableMemoryJobSerial(job) {
157
+ const resultPromise = this.durableMemoryQueue.then(job, job);
158
+ this.durableMemoryQueue = resultPromise.then(() => undefined, () => undefined);
159
+ return resultPromise;
160
+ }
161
+ enqueueDurableMemoryJob(job, failureMessage) {
162
+ void this.runDurableMemoryJobSerial(job).catch((error) => {
157
163
  const message = error instanceof Error ? error.message : String(error);
158
164
  log.logWarning(failureMessage, message);
159
165
  });
@@ -186,7 +192,7 @@ export class MemoryLifecycle {
186
192
  const messageSnapshot = [...this.options.getMessages()];
187
193
  const sessionEntrySnapshot = [...this.options.getSessionEntries()];
188
194
  const revisionSnapshot = this.durableRevision;
189
- this.enqueueBackgroundJob(async () => {
195
+ this.enqueueDurableMemoryJob(async () => {
190
196
  try {
191
197
  log.logInfo(`[${this.options.channelId}] Memory consolidation starting (idle)`);
192
198
  const result = await runInlineConsolidation(this.buildRunOptions(messageSnapshot, sessionEntrySnapshot));
@@ -210,6 +216,11 @@ export class MemoryLifecycle {
210
216
  const sessionEntrySnapshot = sessionEntries ? [...sessionEntries] : [...this.options.getSessionEntries()];
211
217
  const revisionSnapshot = this.durableRevision;
212
218
  const settings = this.options.getSessionMemorySettings();
219
+ await this.runDurableMemoryJobSerial(async () => {
220
+ await this.runPreflightConsolidationNow(reason, messageSnapshot, sessionEntrySnapshot, revisionSnapshot, settings);
221
+ });
222
+ }
223
+ async runPreflightConsolidationNow(reason, messageSnapshot, sessionEntrySnapshot, revisionSnapshot = this.durableRevision, settings = this.options.getSessionMemorySettings()) {
213
224
  if (this.shouldForceRefreshFor(reason, settings)) {
214
225
  await this.runSessionRefreshSerial({
215
226
  reason,
@@ -246,7 +257,7 @@ export class MemoryLifecycle {
246
257
  this.enqueueBackgroundMaintenance();
247
258
  }
248
259
  enqueueBackgroundMaintenance() {
249
- this.enqueueBackgroundJob(async () => {
260
+ this.enqueueDurableMemoryJob(async () => {
250
261
  const result = await runBackgroundMaintenance(this.buildRunOptions([], []));
251
262
  this.logBackgroundResult(result);
252
263
  }, `[${this.options.channelId}] Background memory maintenance failed`);
@@ -1,5 +1,5 @@
1
1
  import type { Api, Model } from "@mariozechner/pi-ai";
2
- import { type MemoryCandidate, type MemoryCandidateCache } from "./candidates.js";
2
+ import { type MemoryCandidate, type MemoryCandidateStore } from "./candidates.js";
3
3
  export interface RecallRequest {
4
4
  query: string;
5
5
  workspaceDir: string;
@@ -12,7 +12,7 @@ export interface RecallRequest {
12
12
  autoRerank?: boolean;
13
13
  model: Model<Api>;
14
14
  resolveApiKey: (model: Model<Api>) => Promise<string>;
15
- candidateCache?: MemoryCandidateCache;
15
+ candidateStore?: MemoryCandidateStore;
16
16
  }
17
17
  export interface RecalledMemory {
18
18
  source: MemoryCandidate["source"];
@@ -1,6 +1,6 @@
1
1
  import { parseJsonObject } from "../shared/llm-json.js";
2
2
  import { HAN_REGEX } from "../shared/text-utils.js";
3
- import { buildMemoryCandidates } from "./candidates.js";
3
+ import { buildMemoryCandidates, createMemoryCandidateStore, } from "./candidates.js";
4
4
  import { COMMON_CHINESE_WORDS } from "./chinese-words.js";
5
5
  import { runSidecarTask } from "./sidecar-worker.js";
6
6
  const RERANK_SYSTEM_PROMPT = `You are selecting which memory snippets are most relevant to the current user turn.
@@ -466,8 +466,7 @@ export async function recallRelevantMemory(request) {
466
466
  const candidates = await buildMemoryCandidates({
467
467
  workspaceDir: request.workspaceDir,
468
468
  channelDir: request.channelDir,
469
- cache: request.candidateCache,
470
- });
469
+ }, request.candidateStore ?? createMemoryCandidateStore());
471
470
  const filteredCandidates = request.allowedSources?.length
472
471
  ? candidates.filter((candidate) => request.allowedSources?.includes(candidate.source))
473
472
  : candidates;
@@ -1,4 +1,4 @@
1
- import { type SandboxConfig } from "../sandbox.js";
1
+ import { type Executor, type SandboxConfig } from "../sandbox.js";
2
2
  import { DingTalkBot, type DingTalkConfig, type DingTalkHandler } from "./dingtalk.js";
3
3
  import { ChannelStore } from "./store.js";
4
4
  export interface BootstrapPaths {
@@ -55,7 +55,7 @@ interface RuntimeContextOptions {
55
55
  sandbox: SandboxConfig;
56
56
  dingtalkConfig: DingTalkConfig;
57
57
  createBot?: (handler: DingTalkHandler, config: DingTalkConfig) => DingTalkBot;
58
- createEventsWatcher?: (workspaceDir: string, bot: DingTalkBot) => {
58
+ createEventsWatcher?: (workspaceDir: string, bot: DingTalkBot, executor: Executor) => {
59
59
  start(): void;
60
60
  stop(): void;
61
61
  };
@@ -6,7 +6,7 @@ import { resetRunner } from "../agent/runner-factory.js";
6
6
  import * as log from "../log.js";
7
7
  import { ensureChannelMemoryFilesSync } from "../memory/files.js";
8
8
  import { APP_HOME_DIR, APP_NAME, AUTH_CONFIG_PATH, CHANNEL_CONFIG_PATH, MODELS_CONFIG_PATH, SECURITY_CONFIG_PATH, SETTINGS_CONFIG_PATH, TOOLS_CONFIG_PATH, WORKSPACE_DIR, } from "../paths.js";
9
- import { parseSandboxArg, validateSandbox } from "../sandbox.js";
9
+ import { createExecutor, parseSandboxArg, validateSandbox } from "../sandbox.js";
10
10
  import { loadSecurityConfigWithDiagnostics } from "../security/config.js";
11
11
  import { PipiclawSettingsManager } from "../settings.js";
12
12
  import { formatConfigDiagnostic } from "../shared/config-diagnostics.js";
@@ -137,7 +137,7 @@ const SECURITY_CONFIG_TEMPLATE = {
137
137
  },
138
138
  };
139
139
  const SHUTDOWN_WAIT_MS = 15000;
140
- const SHUTDOWN_FLUSH_WAIT_MS = 25000;
140
+ const SHUTDOWN_FLUSH_WAIT_MS = 45000;
141
141
  const SHUTDOWN_ABORT_WAIT_MS = 5000;
142
142
  export const DEFAULT_BOOTSTRAP_PATHS = {
143
143
  appName: APP_NAME,
@@ -450,9 +450,10 @@ export function createRuntimeContext(options) {
450
450
  const bot = options.createBot
451
451
  ? options.createBot(handler, options.dingtalkConfig)
452
452
  : new DingTalkBot(handler, options.dingtalkConfig);
453
+ const executor = createExecutor(options.sandbox);
453
454
  const eventsWatcher = options.createEventsWatcher
454
- ? options.createEventsWatcher(options.paths.workspaceDir, bot)
455
- : createEventsWatcher(options.paths.workspaceDir, bot);
455
+ ? options.createEventsWatcher(options.paths.workspaceDir, bot, executor)
456
+ : createEventsWatcher(options.paths.workspaceDir, bot, executor, loadSecurityConfigWithDiagnostics(options.paths.appHomeDir).config.commandGuard);
456
457
  const shutdownWithReason = async (reason = "manual") => {
457
458
  if (shutdownPromise) {
458
459
  return shutdownPromise;
@@ -6,7 +6,9 @@ class ChannelDeliveryController {
6
6
  this.event = event;
7
7
  this.bot = bot;
8
8
  this.store = store;
9
- this.progressText = "";
9
+ this.progressSegments = [];
10
+ this.cachedProgressText = "";
11
+ this.progressTextDirty = false;
10
12
  this.mode = "progress";
11
13
  this.desiredRevision = 0;
12
14
  this.appliedRevision = 0;
@@ -20,6 +22,9 @@ class ChannelDeliveryController {
20
22
  this.timer = null;
21
23
  this.cardWarmupTimer = null;
22
24
  this.flushWaiters = [];
25
+ this.sentProgressChars = 0;
26
+ this.replayRequired = false;
27
+ this.finalReplacementText = "";
23
28
  }
24
29
  buildContext() {
25
30
  return {
@@ -86,7 +91,11 @@ class ChannelDeliveryController {
86
91
  if (this.closed || this.finalResponseDelivered || !text.trim())
87
92
  return;
88
93
  this.clearCardWarmup();
89
- this.progressText = this.progressText ? `${this.progressText}\n\n${text}` : text;
94
+ if (this.progressSegments.length > 0) {
95
+ this.progressSegments.push("\n\n");
96
+ }
97
+ this.progressSegments.push(text);
98
+ this.progressTextDirty = true;
90
99
  if (this.progressWindowStartedAt === 0) {
91
100
  this.progressWindowStartedAt = Date.now();
92
101
  }
@@ -116,7 +125,7 @@ class ChannelDeliveryController {
116
125
  if (this.closed || this.finalResponseDelivered)
117
126
  return;
118
127
  this.clearCardWarmup();
119
- this.progressText = text;
128
+ this.finalReplacementText = text;
120
129
  this.mode = "finalize-with-fallback";
121
130
  this.bumpRevision(true);
122
131
  }
@@ -159,6 +168,7 @@ class ChannelDeliveryController {
159
168
  try {
160
169
  while (this.appliedRevision < this.desiredRevision) {
161
170
  const mode = this.mode;
171
+ const progressText = this.getProgressText();
162
172
  const throttleBaseAt = this.lastDeliveredAt > 0 ? this.lastDeliveredAt : this.progressWindowStartedAt;
163
173
  if (mode === "progress" && throttleBaseAt > 0) {
164
174
  const remaining = MIN_UPDATE_INTERVAL_MS - (Date.now() - throttleBaseAt);
@@ -171,31 +181,48 @@ class ChannelDeliveryController {
171
181
  }
172
182
  }
173
183
  const revision = this.desiredRevision;
174
- const content = this.progressText.trim();
184
+ const content = progressText.trim();
185
+ const replacementText = this.finalReplacementText;
175
186
  let touchedRemote = false;
176
187
  try {
177
188
  if (mode === "progress") {
178
189
  if (content) {
179
- touchedRemote = await this.bot.streamToCard(this.event.channelId, this.progressText);
190
+ const nextSentChars = progressText.length;
191
+ if (this.replayRequired) {
192
+ touchedRemote = await this.bot.replaceCard(this.event.channelId, progressText);
193
+ }
194
+ else {
195
+ const delta = progressText.slice(this.sentProgressChars);
196
+ touchedRemote = delta ? await this.bot.appendToCard(this.event.channelId, delta) : true;
197
+ }
180
198
  if (!touchedRemote) {
181
199
  this.bot.discardCard(this.event.channelId);
200
+ this.replayRequired = true;
201
+ }
202
+ else {
203
+ this.sentProgressChars = nextSentChars;
204
+ this.replayRequired = false;
182
205
  }
183
206
  }
184
207
  }
185
208
  else if (mode === "finalize-existing") {
186
209
  if (content || this.cardWarmupTriggered) {
187
- touchedRemote = await this.bot.finalizeExistingCard(this.event.channelId, content ? this.progressText : NO_CONTENT);
210
+ touchedRemote = await this.bot.replaceCard(this.event.channelId, content ? progressText : NO_CONTENT, true);
188
211
  if (!touchedRemote) {
189
212
  this.bot.discardCard(this.event.channelId);
190
213
  }
214
+ else {
215
+ this.sentProgressChars = progressText.length;
216
+ this.replayRequired = false;
217
+ }
191
218
  }
192
219
  else {
193
220
  this.bot.discardCard(this.event.channelId);
194
221
  }
195
222
  }
196
223
  else if (mode === "finalize-with-fallback") {
197
- if (content) {
198
- touchedRemote = await this.bot.finalizeCard(this.event.channelId, this.progressText);
224
+ if (replacementText.trim()) {
225
+ touchedRemote = await this.bot.finalizeCard(this.event.channelId, replacementText);
199
226
  if (!touchedRemote) {
200
227
  this.bot.discardCard(this.event.channelId);
201
228
  }
@@ -206,7 +233,7 @@ class ChannelDeliveryController {
206
233
  }
207
234
  else if (mode === "silent") {
208
235
  if (this.cardWarmupTriggered) {
209
- touchedRemote = await this.bot.finalizeExistingCard(this.event.channelId, NO_CONTENT);
236
+ touchedRemote = await this.bot.replaceCard(this.event.channelId, NO_CONTENT, true);
210
237
  }
211
238
  if (!touchedRemote) {
212
239
  this.bot.discardCard(this.event.channelId);
@@ -216,6 +243,9 @@ class ChannelDeliveryController {
216
243
  catch (err) {
217
244
  log.logWarning(`[${this.event.channelId}] Delivery sync failed`, err instanceof Error ? err.message : String(err));
218
245
  this.bot.discardCard(this.event.channelId);
246
+ if (mode === "progress") {
247
+ this.replayRequired = true;
248
+ }
219
249
  }
220
250
  if (touchedRemote) {
221
251
  this.lastDeliveredAt = Date.now();
@@ -262,6 +292,14 @@ class ChannelDeliveryController {
262
292
  this.clearCardWarmup();
263
293
  await this.flush();
264
294
  }
295
+ getProgressText() {
296
+ if (!this.progressTextDirty) {
297
+ return this.cachedProgressText;
298
+ }
299
+ this.cachedProgressText = this.progressSegments.join("");
300
+ this.progressTextDirty = false;
301
+ return this.cachedProgressText;
302
+ }
265
303
  }
266
304
  export function createDingTalkContext(event, bot, store) {
267
305
  return new ChannelDeliveryController(event, bot, store).buildContext();
@@ -94,7 +94,15 @@ export declare class DingTalkBot {
94
94
  */
95
95
  ensureCard(channelId: string): Promise<void>;
96
96
  /**
97
- * Stream content to the active AI Card for a channel.
97
+ * Replace the active card content with a full snapshot.
98
+ */
99
+ replaceCard(channelId: string, content: string, finalize?: boolean, failed?: boolean): Promise<boolean>;
100
+ /**
101
+ * Append a delta to the active card transcript.
102
+ */
103
+ appendToCard(channelId: string, content: string, finalize?: boolean, failed?: boolean): Promise<boolean>;
104
+ /**
105
+ * Stream content to the active AI Card for a channel using full replacement semantics.
98
106
  */
99
107
  streamToCard(channelId: string, content: string, finalize?: boolean): Promise<boolean>;
100
108
  /**
@@ -362,48 +362,80 @@ export class DingTalkBot {
362
362
  await this.createCard(channelId);
363
363
  }
364
364
  /**
365
- * Stream content to the active AI Card for a channel.
365
+ * Replace the active card content with a full snapshot.
366
366
  */
367
- async streamToCard(channelId, content, finalize = false) {
367
+ async replaceCard(channelId, content, finalize = false, failed = false) {
368
368
  let card = this.activeCards.get(channelId);
369
- if ((!card || card.finished) && !finalize && this.config.cardTemplateId && content.trim()) {
369
+ if ((!card || card.finished) && this.config.cardTemplateId && (content.trim() || !finalize || failed)) {
370
370
  await this.ensureCard(channelId);
371
371
  card = this.activeCards.get(channelId);
372
372
  }
373
373
  if (!card || card.finished) {
374
- if (finalize) {
374
+ if (finalize && content.trim()) {
375
375
  return this.sendPlain(channelId, content);
376
376
  }
377
377
  return false;
378
378
  }
379
- const streamed = await this.streamCard(card, content, finalize);
380
- if (!streamed) {
379
+ const streamed = await this.streamCard(card, content, {
380
+ append: false,
381
+ finalize,
382
+ failed,
383
+ });
384
+ if (!streamed || finalize || failed) {
381
385
  this.activeCards.delete(channelId);
382
386
  }
383
387
  return streamed;
384
388
  }
385
389
  /**
386
- * Finalize the active card for a channel without falling back to a plain message.
387
- * Returns true if a card was finalized, false if no active card existed.
390
+ * Append a delta to the active card transcript.
388
391
  */
389
- async finalizeExistingCard(channelId, content) {
392
+ async appendToCard(channelId, content, finalize = false, failed = false) {
393
+ if (!content && !finalize && !failed) {
394
+ return true;
395
+ }
390
396
  let card = this.activeCards.get(channelId);
391
- if ((!card || card.finished) && this.config.cardTemplateId && content.trim()) {
397
+ if ((!card || card.finished) && !finalize && !failed && this.config.cardTemplateId && content.trim()) {
392
398
  await this.ensureCard(channelId);
393
399
  card = this.activeCards.get(channelId);
394
400
  }
395
401
  if (!card || card.finished) {
402
+ if (finalize && content.trim()) {
403
+ return this.sendPlain(channelId, content);
404
+ }
396
405
  return false;
397
406
  }
398
- const finalized = await this.streamCard(card, content, true);
399
- this.activeCards.delete(channelId);
400
- return finalized;
407
+ const streamed = await this.streamCard(card, content, {
408
+ append: true,
409
+ finalize,
410
+ failed,
411
+ });
412
+ if (!streamed || finalize || failed) {
413
+ this.activeCards.delete(channelId);
414
+ }
415
+ return streamed;
416
+ }
417
+ /**
418
+ * Stream content to the active AI Card for a channel using full replacement semantics.
419
+ */
420
+ async streamToCard(channelId, content, finalize = false) {
421
+ return this.replaceCard(channelId, content, finalize, false);
422
+ }
423
+ /**
424
+ * Finalize the active card for a channel without falling back to a plain message.
425
+ * Returns true if a card was finalized, false if no active card existed.
426
+ */
427
+ async finalizeExistingCard(channelId, content) {
428
+ const finalized = await this.replaceCard(channelId, content, true, false);
429
+ if (!finalized) {
430
+ return false;
431
+ }
432
+ return true;
401
433
  }
402
434
  /**
403
435
  * Finalize and remove the active card for a channel.
404
436
  */
405
437
  async finalizeCard(channelId, content) {
406
- const finalized = await this.finalizeExistingCard(channelId, content);
438
+ const finalized = await this.replaceCard(channelId, content, true, false);
407
439
  if (!finalized) {
408
440
  return this.sendPlain(channelId, content);
409
441
  }
@@ -529,7 +561,7 @@ export class DingTalkBot {
529
561
  this.activeCards.set(channelId, card);
530
562
  return card;
531
563
  }
532
- async streamCard(card, content, finalize = false) {
564
+ async streamCard(card, content, options) {
533
565
  // Refresh token if needed
534
566
  const ageSecs = Date.now() / 1000 - card.createdAt;
535
567
  if (ageSecs > TOKEN_REFRESH_SECS) {
@@ -543,9 +575,12 @@ export class DingTalkBot {
543
575
  guid: `${Date.now()}_${Math.random().toString(36).substring(2, 8)}`,
544
576
  key: card.templateKey,
545
577
  content,
546
- isFull: true,
547
- isFinalize: finalize,
548
- isError: false,
578
+ append: options.append,
579
+ finished: options.finalize,
580
+ failed: options.failed,
581
+ isFull: !options.append,
582
+ isFinalize: options.finalize,
583
+ isError: options.failed,
549
584
  };
550
585
  const start = Date.now();
551
586
  try {
@@ -560,8 +595,8 @@ export class DingTalkBot {
560
595
  log.logWarning(`DingTalk Card: streaming request took ${duration}ms (slow)`);
561
596
  }
562
597
  card.lastUpdated = Date.now() / 1000;
563
- card.content = content;
564
- if (finalize) {
598
+ card.content = options.append ? `${card.content}${content}` : content;
599
+ if (options.finalize || options.failed) {
565
600
  card.finished = true;
566
601
  }
567
602
  return true;
@@ -1,14 +1,23 @@
1
+ import type { Executor } from "../sandbox.js";
2
+ import type { SecurityConfig } from "../security/types.js";
1
3
  import type { DingTalkBot } from "./dingtalk.js";
4
+ export interface EventAction {
5
+ type: "bash";
6
+ command: string;
7
+ timeout?: number;
8
+ }
2
9
  export interface ImmediateEvent {
3
10
  type: "immediate";
4
11
  channelId: string;
5
12
  text: string;
13
+ preAction?: EventAction;
6
14
  }
7
15
  export interface OneShotEvent {
8
16
  type: "one-shot";
9
17
  channelId: string;
10
18
  text: string;
11
19
  at: string;
20
+ preAction?: EventAction;
12
21
  }
13
22
  export interface PeriodicEvent {
14
23
  type: "periodic";
@@ -16,18 +25,21 @@ export interface PeriodicEvent {
16
25
  text: string;
17
26
  schedule: string;
18
27
  timezone: string;
28
+ preAction?: EventAction;
19
29
  }
20
30
  export type ScheduledEvent = ImmediateEvent | OneShotEvent | PeriodicEvent;
21
31
  export declare class EventsWatcher {
22
32
  private eventsDir;
23
33
  private bot;
34
+ private executor;
35
+ private commandGuardConfig?;
24
36
  private timers;
25
37
  private crons;
26
38
  private debounceTimers;
27
39
  private startTime;
28
40
  private watcher;
29
41
  private knownFiles;
30
- constructor(eventsDir: string, bot: DingTalkBot);
42
+ constructor(eventsDir: string, bot: DingTalkBot, executor: Executor, commandGuardConfig?: SecurityConfig["commandGuard"] | undefined);
31
43
  start(): void;
32
44
  stop(): void;
33
45
  private debounce;
@@ -36,11 +48,13 @@ export declare class EventsWatcher {
36
48
  private handleDelete;
37
49
  private cancelScheduled;
38
50
  private handleFile;
51
+ private parsePreAction;
39
52
  private parseEvent;
40
53
  private handleImmediate;
41
54
  private handleOneShot;
42
55
  private handlePeriodic;
43
56
  private execute;
57
+ private runPreAction;
44
58
  private deleteFile;
45
59
  private getInvalidMarkerPath;
46
60
  private markInvalid;
@@ -50,4 +64,4 @@ export declare class EventsWatcher {
50
64
  /**
51
65
  * Create and start an events watcher.
52
66
  */
53
- export declare function createEventsWatcher(workspaceDir: string, bot: DingTalkBot): EventsWatcher;
67
+ export declare function createEventsWatcher(workspaceDir: string, bot: DingTalkBot, executor: Executor, commandGuardConfig?: SecurityConfig["commandGuard"]): EventsWatcher;