@rivus/agent 0.10.3 → 0.12.4

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/index.js CHANGED
@@ -30,71 +30,76 @@ function createSerialExecutor() {
30
30
  //#endregion
31
31
  //#region src/application/feishu/feishu-stream-projector.ts
32
32
  function createFeishuStreamProjector() {
33
- const textByRun = /* @__PURE__ */ new Map();
34
- return {
35
- apply: (event) => {
36
- switch (event.type) {
37
- case "assistant_text_delta": {
38
- const text = `${textByRun.get(event.runId) ?? ""}${event.delta}`;
39
- textByRun.set(event.runId, text);
40
- return [{
41
- runId: event.runId,
42
- text,
43
- type: "update_text"
44
- }];
45
- }
46
- case "agent_run_completed": {
47
- const text = event.finalText || textByRun.get(event.runId) || "";
48
- textByRun.delete(event.runId);
49
- return [{
50
- runId: event.runId,
51
- text,
52
- type: "finish"
53
- }];
54
- }
55
- case "agent_run_failed":
56
- textByRun.delete(event.runId);
57
- return [{
58
- errorMessage: event.errorMessage,
59
- runId: event.runId,
60
- type: "fail"
61
- }];
62
- case "agent_run_cancelled":
63
- textByRun.delete(event.runId);
64
- return [{
65
- ...event.reason ? { reason: event.reason } : {},
66
- runId: event.runId,
67
- type: "cancel"
68
- }];
69
- default: return [];
33
+ const currentTextByRun = /* @__PURE__ */ new Map();
34
+ const lastTextByRun = /* @__PURE__ */ new Map();
35
+ const update = (runId, text) => [{
36
+ runId,
37
+ text,
38
+ type: "update_text"
39
+ }];
40
+ const clear = (runId) => {
41
+ currentTextByRun.delete(runId);
42
+ lastTextByRun.delete(runId);
43
+ };
44
+ const applyEvent = (event, canonicalText) => {
45
+ switch (event.type) {
46
+ case "agent_run_accepted":
47
+ currentTextByRun.set(event.runId, "");
48
+ lastTextByRun.set(event.runId, "");
49
+ return [];
50
+ case "agent_model_execution_started":
51
+ currentTextByRun.set(event.runId, "");
52
+ return update(event.runId, "正在分析…");
53
+ case "agent_skill_execution_started": return update(event.runId, `正在读取技能 \`${presentProgressIdentifier(event.skillId)}\`…`);
54
+ case "agent_tool_execution_started": return update(event.runId, `正在执行工具 \`${presentProgressIdentifier(event.toolName)}\`…`);
55
+ case "agent_tool_execution_ended": return update(event.runId, event.isError ? "工具执行失败,正在调整方案…" : "已取得工具结果,正在继续分析…");
56
+ case "assistant_text_delta": {
57
+ const text = `${currentTextByRun.get(event.runId) ?? ""}${event.delta}`;
58
+ currentTextByRun.set(event.runId, text);
59
+ if (text.trim()) lastTextByRun.set(event.runId, text);
60
+ return update(event.runId, text);
70
61
  }
71
- },
72
- applyUpdate: ({ event, state }) => {
73
- switch (event.type) {
74
- case "assistant_text_delta": return [{
62
+ case "agent_run_completed": {
63
+ const currentText = currentTextByRun.get(event.runId)?.trim();
64
+ const lastText = lastTextByRun.get(event.runId)?.trim();
65
+ const text = currentText || lastText || canonicalText || event.finalText;
66
+ clear(event.runId);
67
+ return [{
75
68
  runId: event.runId,
76
- text: state.finalText,
77
- type: "update_text"
78
- }];
79
- case "agent_run_completed": return [{
80
- runId: event.runId,
81
- text: state.finalText,
69
+ text,
82
70
  type: "finish"
83
71
  }];
84
- case "agent_run_failed": return [{
85
- errorMessage: state.errorMessage ?? event.errorMessage,
72
+ }
73
+ case "agent_run_failed":
74
+ clear(event.runId);
75
+ return [{
76
+ errorMessage: event.errorMessage,
86
77
  runId: event.runId,
87
78
  type: "fail"
88
79
  }];
89
- case "agent_run_cancelled": return [{
90
- ...state.cancellationReason ? { reason: state.cancellationReason } : {},
80
+ case "agent_run_cancelled":
81
+ clear(event.runId);
82
+ return [{
83
+ ...event.reason ? { reason: event.reason } : {},
91
84
  runId: event.runId,
92
85
  type: "cancel"
93
86
  }];
94
- default: return [];
95
- }
87
+ default: return [];
96
88
  }
97
89
  };
90
+ return {
91
+ apply: (event) => applyEvent(event),
92
+ applyUpdate: ({ event, state }) => event.type === "agent_run_failed" ? applyEvent({
93
+ ...event,
94
+ errorMessage: state.errorMessage ?? event.errorMessage
95
+ }, state.finalText) : event.type === "agent_run_cancelled" ? applyEvent(state.cancellationReason ? {
96
+ ...event,
97
+ reason: state.cancellationReason
98
+ } : event, state.finalText) : applyEvent(event, state.finalText)
99
+ };
100
+ }
101
+ function presentProgressIdentifier(value) {
102
+ return value.replace(/[^\p{L}\p{N}._:/-]+/gu, " ").trim().slice(0, 80) || "unknown";
98
103
  }
99
104
  //#endregion
100
105
  //#region src/application/feishu/feishu-card-delivery-ledger.ts
@@ -172,7 +177,7 @@ function initialRecord(runId) {
172
177
  }
173
178
  //#endregion
174
179
  //#region src/infrastructure/persistence/persistence-value.ts
175
- function isRecord$4(value) {
180
+ function isRecord$5(value) {
176
181
  return value !== null && typeof value === "object" && !Array.isArray(value);
177
182
  }
178
183
  function isNodeErrorWithCode(error, code) {
@@ -209,7 +214,7 @@ async function load$3(filePath) {
209
214
  for (const [index, line] of raw.split("\n").entries()) {
210
215
  if (line.trim().length === 0) continue;
211
216
  const envelope = JSON.parse(line);
212
- if (!isRecord$4(envelope) || envelope.version !== 1 || !isCardDeliveryRecord(envelope.record)) throw new Error(`invalid Feishu card delivery snapshot at line ${index + 1}`);
217
+ if (!isRecord$5(envelope) || envelope.version !== 1 || !isCardDeliveryRecord(envelope.record)) throw new Error(`invalid Feishu card delivery snapshot at line ${index + 1}`);
213
218
  const record = envelope.record;
214
219
  const previous = records.get(record.runId);
215
220
  if (record.revision !== (previous?.revision ?? 0) + 1 || (previous ? !isValidTransition$3(previous, record) : !isValidInitialRecord(record))) throw new Error(`invalid Feishu card delivery revision at line ${index + 1}`);
@@ -225,7 +230,7 @@ function isValidTransition$3(previous, next) {
225
230
  return next.terminalPublished ? next.sequence === previous.sequence : next.sequence === previous.sequence + 1;
226
231
  }
227
232
  function isCardDeliveryRecord(value) {
228
- return isRecord$4(value) && Number.isInteger(value.revision) && value.revision > 0 && typeof value.runId === "string" && value.runId.length > 0 && Number.isInteger(value.sequence) && value.sequence >= 0 && typeof value.terminalPublished === "boolean";
233
+ return isRecord$5(value) && Number.isInteger(value.revision) && value.revision > 0 && typeof value.runId === "string" && value.runId.length > 0 && Number.isInteger(value.sequence) && value.sequence >= 0 && typeof value.terminalPublished === "boolean";
229
234
  }
230
235
  //#endregion
231
236
  //#region src/domain/recovery-action.ts
@@ -298,6 +303,35 @@ function createFeishuInboxRepository(options = {}) {
298
303
  }),
299
304
  catch: (error) => error
300
305
  }),
306
+ claimById: (input) => Effect.tryPromise({
307
+ try: () => serial.run(async () => {
308
+ const current = deliveries.get(input.id);
309
+ if (!current) return void 0;
310
+ const available = current.state.status === "leased" && Date.parse(current.state.leaseExpiresAt) <= Date.parse(input.now) ? {
311
+ ...current,
312
+ revision: current.revision + 1,
313
+ state: {
314
+ availableAt: input.now,
315
+ status: "pending"
316
+ }
317
+ } : current;
318
+ if (available !== current) await save(available);
319
+ if (available.state.status !== "pending" || Date.parse(available.state.availableAt) > Date.parse(input.now)) return;
320
+ const claimed = {
321
+ ...available,
322
+ revision: available.revision + 1,
323
+ state: {
324
+ leaseExpiresAt: input.leaseExpiresAt,
325
+ leaseId: input.leaseId,
326
+ leasedAt: input.now,
327
+ status: "leased"
328
+ }
329
+ };
330
+ await save(claimed);
331
+ return structuredClone(claimed);
332
+ }),
333
+ catch: (error) => error
334
+ }),
301
335
  complete: (input) => mutateLeased(deliveries, serial, save, input.id, input.leaseId, (current) => ({
302
336
  ...current,
303
337
  revision: current.revision + 1,
@@ -349,7 +383,15 @@ function createFeishuInboxRepository(options = {}) {
349
383
  return structuredClone(requeued);
350
384
  }),
351
385
  catch: (error) => error
352
- })
386
+ }),
387
+ release: (input) => mutateLeased(deliveries, serial, save, input.id, input.leaseId, (current) => ({
388
+ ...current,
389
+ revision: current.revision + 1,
390
+ state: {
391
+ availableAt: input.availableAt,
392
+ status: "pending"
393
+ }
394
+ }))
353
395
  };
354
396
  }
355
397
  function validateNewDelivery(delivery) {
@@ -523,7 +565,7 @@ async function load$2(filePath) {
523
565
  for (const [index, line] of raw.split("\n").entries()) {
524
566
  if (line.trim().length === 0) continue;
525
567
  const envelope = JSON.parse(line);
526
- if (!isRecord$4(envelope) || envelope.version !== 1 || !isDelivery(envelope.delivery)) throw new Error(`invalid Feishu inbox snapshot at line ${index + 1}`);
568
+ if (!isRecord$5(envelope) || envelope.version !== 1 || !isDelivery(envelope.delivery)) throw new Error(`invalid Feishu inbox snapshot at line ${index + 1}`);
527
569
  const delivery = envelope.delivery;
528
570
  const previous = latest.get(delivery.id);
529
571
  if (delivery.revision !== (previous?.revision ?? 0) + 1) throw new Error(`invalid Feishu inbox revision at line ${index + 1}`);
@@ -535,14 +577,14 @@ async function load$2(filePath) {
535
577
  function isValidTransition$2(previous, next) {
536
578
  if (previous.id !== next.id || previous.acceptedAt !== next.acceptedAt || previous.laneKey !== next.laneKey || !isDeepStrictEqual(previous.payload, next.payload) || !isDeepStrictEqual(previous.options, next.options)) return false;
537
579
  switch (previous.state.status) {
538
- case "pending": return next.state.status === "leased" && next.attempts === previous.attempts + 1 && isDeepStrictEqual(previous.recovery, next.recovery);
580
+ case "pending": return next.state.status === "leased" && (next.attempts === previous.attempts || next.attempts === previous.attempts + 1) && isDeepStrictEqual(previous.recovery, next.recovery);
539
581
  case "leased": return (next.state.status === "pending" || next.state.status === "completed" || next.state.status === "dead") && next.attempts === previous.attempts && isDeepStrictEqual(previous.recovery, next.recovery);
540
582
  case "completed": return false;
541
583
  case "dead": return next.state.status === "pending" && next.attempts === 0 && next.recovery !== void 0 && isRecovery(next.recovery) && next.state.availableAt === next.recovery.at && !isDeepStrictEqual(previous.recovery, next.recovery);
542
584
  }
543
585
  }
544
586
  function isDelivery(value) {
545
- if (!isRecord$4(value) || !nonEmpty$1(value.acceptedAt) || !Number.isFinite(Date.parse(value.acceptedAt)) || !Number.isInteger(value.attempts) || value.attempts < 0 || !nonEmpty$1(value.id) || !nonEmpty$1(value.laneKey) || !Number.isInteger(value.revision) || value.revision < 1 || !isRecord$4(value.payload) || value.recovery !== void 0 && !isRecovery(value.recovery) || !isRecord$4(value.state)) return false;
587
+ if (!isRecord$5(value) || !nonEmpty$1(value.acceptedAt) || !Number.isFinite(Date.parse(value.acceptedAt)) || !Number.isInteger(value.attempts) || value.attempts < 0 || !nonEmpty$1(value.id) || !nonEmpty$1(value.laneKey) || !Number.isInteger(value.revision) || value.revision < 1 || !isRecord$5(value.payload) || value.recovery !== void 0 && !isRecovery(value.recovery) || !isRecord$5(value.state)) return false;
546
588
  switch (value.state.status) {
547
589
  case "pending": return timestamp(value.state.availableAt);
548
590
  case "leased": return timestamp(value.state.leasedAt) && timestamp(value.state.leaseExpiresAt) && nonEmpty$1(value.state.leaseId) && Date.parse(value.state.leaseExpiresAt) > Date.parse(value.state.leasedAt);
@@ -552,16 +594,111 @@ function isDelivery(value) {
552
594
  }
553
595
  }
554
596
  function isRecovery(value) {
555
- return isRecord$4(value) && nonEmpty$1(value.actorId) && timestamp(value.at) && nonEmpty$1(value.note);
597
+ return isRecord$5(value) && nonEmpty$1(value.actorId) && timestamp(value.at) && nonEmpty$1(value.note);
556
598
  }
557
599
  function timestamp(value) {
558
600
  return typeof value === "string" && Number.isFinite(Date.parse(value));
559
601
  }
560
602
  function nonEmpty$1(value, key) {
561
- const candidate = key === void 0 ? value : isRecord$4(value) ? value[key] : void 0;
603
+ const candidate = key === void 0 ? value : isRecord$5(value) ? value[key] : void 0;
562
604
  return typeof candidate === "string" && candidate.trim().length > 0;
563
605
  }
564
606
  //#endregion
607
+ //#region src/application/feishu/feishu-session-store.ts
608
+ function createFeishuSessionStore(options = {}) {
609
+ const generations = /* @__PURE__ */ new Map();
610
+ for (const record of options.initial ?? []) {
611
+ validateRecord(record);
612
+ const current = generations.get(record.baseSessionKey) ?? 0;
613
+ generations.set(record.baseSessionKey, Math.max(current, record.generation));
614
+ }
615
+ const serial = createSerialExecutor();
616
+ return {
617
+ current: (baseSessionKey) => {
618
+ validateBaseSessionKey(baseSessionKey);
619
+ return Effect.succeed(sessionKey(baseSessionKey, generations.get(baseSessionKey) ?? 0));
620
+ },
621
+ reset: (baseSessionKey) => Effect.tryPromise({
622
+ try: () => serial.run(async () => {
623
+ validateBaseSessionKey(baseSessionKey);
624
+ const previousGeneration = generations.get(baseSessionKey) ?? 0;
625
+ const generation = previousGeneration + 1;
626
+ const previousSessionKey = sessionKey(baseSessionKey, previousGeneration);
627
+ const nextSessionKey = sessionKey(baseSessionKey, generation);
628
+ const next = new Map(generations);
629
+ next.set(baseSessionKey, generation);
630
+ await options.persist?.([...next.entries()].map(([key, value]) => ({
631
+ baseSessionKey: key,
632
+ generation: value
633
+ })));
634
+ generations.clear();
635
+ for (const [key, value] of next) generations.set(key, value);
636
+ return {
637
+ generation,
638
+ previousSessionKey,
639
+ sessionKey: nextSessionKey
640
+ };
641
+ }),
642
+ catch: (error) => error instanceof Error ? error : new Error(String(error))
643
+ })
644
+ };
645
+ }
646
+ function sessionKey(baseSessionKey, generation) {
647
+ return generation === 0 ? baseSessionKey : `${baseSessionKey}:new-${generation}`;
648
+ }
649
+ function validateBaseSessionKey(value) {
650
+ if (value.trim() === "") throw new Error("Feishu base session key must not be empty");
651
+ }
652
+ function validateRecord(record) {
653
+ validateBaseSessionKey(record.baseSessionKey);
654
+ if (!Number.isSafeInteger(record.generation) || record.generation < 0) throw new Error("Feishu session generation must be a non-negative safe integer");
655
+ }
656
+ //#endregion
657
+ //#region src/infrastructure/persistence/write-persistence-file.ts
658
+ async function writePersistenceFile(filePath, value) {
659
+ await mkdir(dirname(filePath), { recursive: true });
660
+ const temporaryPath = `${filePath}.${randomUUID()}.tmp`;
661
+ try {
662
+ await writeFile(temporaryPath, `${JSON.stringify(value)}\n`, {
663
+ encoding: "utf8",
664
+ flag: "wx"
665
+ });
666
+ await rename(temporaryPath, filePath);
667
+ } catch (error) {
668
+ await unlink(temporaryPath).catch(() => void 0);
669
+ throw error;
670
+ }
671
+ }
672
+ //#endregion
673
+ //#region src/infrastructure/persistence/json-feishu-session-store.ts
674
+ const SNAPSHOT_VERSION = 1;
675
+ async function openJsonFeishuSessionStore(options) {
676
+ return createFeishuSessionStore({
677
+ initial: await readSnapshot$2(options.filePath),
678
+ persist: (records) => writeSnapshot$1(options.filePath, records)
679
+ });
680
+ }
681
+ async function readSnapshot$2(filePath) {
682
+ const raw = await readPersistenceFile(filePath);
683
+ if (raw === void 0) return [];
684
+ const value = JSON.parse(raw);
685
+ if (!isRecord$5(value) || value.version !== SNAPSHOT_VERSION || !Array.isArray(value.sessions)) throw new Error("Feishu session store must contain version 1 sessions");
686
+ return value.sessions.map(readRecord$2);
687
+ }
688
+ async function writeSnapshot$1(filePath, records) {
689
+ await writePersistenceFile(filePath, {
690
+ sessions: records,
691
+ version: SNAPSHOT_VERSION
692
+ });
693
+ }
694
+ function readRecord$2(value) {
695
+ if (!isRecord$5(value) || typeof value.baseSessionKey !== "string" || !Number.isSafeInteger(value.generation)) throw new Error("Feishu session record must contain a baseSessionKey and generation");
696
+ return {
697
+ baseSessionKey: value.baseSessionKey,
698
+ generation: value.generation
699
+ };
700
+ }
701
+ //#endregion
565
702
  //#region src/application/host/session-scheduler.ts
566
703
  var SessionSchedulerCapacityExceeded = class extends Error {
567
704
  name = "SessionSchedulerCapacityExceeded";
@@ -617,6 +754,10 @@ function createSessionScheduler(options) {
617
754
  const runtime = runtimes.get(input.sessionKey);
618
755
  return runtime ? (await runtime).cancel?.(input) ?? false : false;
619
756
  },
757
+ steer: async (input) => {
758
+ const runtime = runtimes.get(input.sessionKey);
759
+ return runtime ? (await runtime).steer?.(input) ?? false : false;
760
+ },
620
761
  dispose: async () => {
621
762
  if (disposed) return;
622
763
  disposed = true;
@@ -1096,6 +1237,18 @@ function createAgentRunUpdateHandler(handle) {
1096
1237
  catch: (cause) => cause
1097
1238
  });
1098
1239
  }
1240
+ function createSteeringChannel() {
1241
+ const pending = [];
1242
+ const waiters = [];
1243
+ return {
1244
+ next: () => pending.length > 0 ? Promise.resolve(pending.shift()) : new Promise((resolve) => waiters.push(resolve)),
1245
+ push: (text) => {
1246
+ const waiter = waiters.shift();
1247
+ if (waiter) waiter(text);
1248
+ else pending.push(text);
1249
+ }
1250
+ };
1251
+ }
1099
1252
  function createAgentHarness(options) {
1100
1253
  if (options.runTimeoutMs !== void 0 && (!Number.isSafeInteger(options.runTimeoutMs) || options.runTimeoutMs < 1)) throw new Error("Agent run timeout must be a positive integer");
1101
1254
  let activeRun;
@@ -1159,7 +1312,8 @@ function createAgentHarness(options) {
1159
1312
  const previousSessionTranscript = getSessionTranscript(command.sessionKey);
1160
1313
  const cancellation = {
1161
1314
  abortController: new AbortController(),
1162
- deferred: yield* Deferred.make()
1315
+ deferred: yield* Deferred.make(),
1316
+ steering: createSteeringChannel()
1163
1317
  };
1164
1318
  activeCancellation = cancellation;
1165
1319
  const events = [];
@@ -1236,6 +1390,7 @@ function createAgentHarness(options) {
1236
1390
  ...previousSessionTranscript.turnCount > 0 ? { previousSessionTranscript } : {},
1237
1391
  runId,
1238
1392
  sessionKey: command.sessionKey,
1393
+ ...options.loop.supportsSteering ? { steering: cancellation.steering } : {},
1239
1394
  text: command.text
1240
1395
  };
1241
1396
  const loop = Effect.try({
@@ -1335,10 +1490,20 @@ function createAgentHarness(options) {
1335
1490
  }
1336
1491
  return completed;
1337
1492
  });
1493
+ const requestSteering = (runId, text) => Effect.sync(() => {
1494
+ const normalized = text.trim();
1495
+ if (!normalized || !options.loop.supportsSteering || !activeRun || activeRun.runId !== runId || !activeCancellation || activeCancellation.requested) return false;
1496
+ activeCancellation.steering.push(normalized);
1497
+ return true;
1498
+ });
1338
1499
  const requestSessionCancellation = (sessionKey, runId, reason) => {
1339
1500
  if (!activeRun || activeRun.sessionKey !== sessionKey) return Effect.succeed(false);
1340
1501
  return requestCancellation(runId, reason);
1341
1502
  };
1503
+ const requestSessionSteering = (sessionKey, runId, text) => {
1504
+ if (!activeRun || activeRun.sessionKey !== sessionKey) return Effect.succeed(false);
1505
+ return requestSteering(runId, text);
1506
+ };
1342
1507
  const requestSessionActiveCancellation = (sessionKey, reason) => {
1343
1508
  if (!activeRun || activeRun.sessionKey !== sessionKey) return Effect.succeed(false);
1344
1509
  return requestCancellation(activeRun.runId, reason);
@@ -1436,9 +1601,11 @@ function createAgentHarness(options) {
1436
1601
  return yield* requestCancellation(activeRun.runId, reason);
1437
1602
  }),
1438
1603
  cancelRun: requestCancellation,
1604
+ steerRun: requestSteering,
1439
1605
  forSession: (sessionKey) => ({
1440
1606
  cancelActiveRun: (reason) => requestSessionActiveCancellation(sessionKey, reason),
1441
1607
  cancelRun: (runId, reason) => requestSessionCancellation(sessionKey, runId, reason),
1608
+ steerRun: (runId, text) => requestSessionSteering(sessionKey, runId, text),
1442
1609
  getActiveRun: () => getSessionActiveRun(sessionKey),
1443
1610
  getActiveRunState: () => getSessionActiveRunState(sessionKey),
1444
1611
  getAvailability: () => getSessionAvailability(sessionKey),
@@ -1866,7 +2033,7 @@ function createAgentCommandFromFeishuCardAction(payload) {
1866
2033
  const messageId = readNonEmptyString(event.context?.open_message_id);
1867
2034
  if (!messageId) return yield* Effect.fail(new InvalidFeishuCardAction("card action message id is missing"));
1868
2035
  const value = event.action?.value;
1869
- if (!isRecord$3(value)) return yield* Effect.fail(new UnsupportedFeishuCardAction("unsupported card action value"));
2036
+ if (!isRecord$4(value)) return yield* Effect.fail(new UnsupportedFeishuCardAction("unsupported card action value"));
1870
2037
  if (value.rivus_action === "resolve_interaction") {
1871
2038
  const interactionId = readNonEmptyString(value.interaction_id);
1872
2039
  if (!interactionId) return yield* Effect.fail(new InvalidFeishuCardAction("card interaction action requires an interaction id"));
@@ -1911,7 +2078,7 @@ function readInteractionAction(value) {
1911
2078
  function readNonEmptyString(value) {
1912
2079
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
1913
2080
  }
1914
- function isRecord$3(value) {
2081
+ function isRecord$4(value) {
1915
2082
  return typeof value === "object" && value !== null && !Array.isArray(value);
1916
2083
  }
1917
2084
  //#endregion
@@ -1948,10 +2115,7 @@ function encodeSegment(value) {
1948
2115
  return encodeURIComponent(value);
1949
2116
  }
1950
2117
  //#endregion
1951
- //#region src/application/feishu/feishu-message-intake.ts
1952
- function readTenantKey(payload) {
1953
- return payload.event.sender?.tenant_key ?? payload.header?.tenant_key ?? "";
1954
- }
2118
+ //#region src/application/feishu/feishu-message-content.ts
1955
2119
  var UnsupportedFeishuMessage = class extends Error {
1956
2120
  messageType;
1957
2121
  name = "UnsupportedFeishuMessage";
@@ -1970,21 +2134,107 @@ var InvalidFeishuMessageContent = class extends Error {
1970
2134
  this.reason = reason;
1971
2135
  }
1972
2136
  };
2137
+ function readFeishuMessageContent(input) {
2138
+ return input.messageType === "text" ? parseTextContent(input.content) : input.messageType === "post" ? parsePostContent(input.content) : Effect.fail(new UnsupportedFeishuMessage(input.messageType));
2139
+ }
2140
+ function parseTextContent(content) {
2141
+ return Effect.try({
2142
+ try: () => {
2143
+ const parsed = JSON.parse(content);
2144
+ if (typeof parsed.text !== "string") throw new Error("text content is missing");
2145
+ return parsed.text;
2146
+ },
2147
+ catch: (error) => new InvalidFeishuMessageContent(error instanceof Error ? error.message : "invalid JSON content")
2148
+ });
2149
+ }
2150
+ function parsePostContent(content) {
2151
+ return Effect.try({
2152
+ try: () => {
2153
+ const parsed = JSON.parse(content);
2154
+ const paragraphs = Array.isArray(parsed.content_v2) ? parsed.content_v2 : parsed.content;
2155
+ if (!Array.isArray(paragraphs)) throw new Error("post content is missing");
2156
+ let imageIndex = 0;
2157
+ const body = paragraphs.map((paragraph) => {
2158
+ if (!Array.isArray(paragraph)) throw new Error("post paragraph is invalid");
2159
+ return paragraph.map((element) => {
2160
+ if (element === null || typeof element !== "object") return "";
2161
+ if ("text" in element && typeof element.text === "string") return element.text;
2162
+ if ("user_name" in element && typeof element.user_name === "string") return `@${element.user_name}`;
2163
+ if ("tag" in element && element.tag === "img") {
2164
+ imageIndex += 1;
2165
+ return `[图片 ${imageIndex}]`;
2166
+ }
2167
+ return "";
2168
+ }).join("");
2169
+ }).filter((paragraph) => paragraph.length > 0).join("\n").trim();
2170
+ const text = [typeof parsed.title === "string" ? parsed.title.trim() : "", body].filter((part) => part.length > 0).join("\n");
2171
+ if (!text) throw new Error("post content has no text");
2172
+ return text;
2173
+ },
2174
+ catch: (error) => new InvalidFeishuMessageContent(error instanceof Error ? error.message : "invalid JSON content")
2175
+ });
2176
+ }
2177
+ //#endregion
2178
+ //#region src/application/feishu/feishu-message-intake.ts
2179
+ function readTenantKey(payload) {
2180
+ return payload.event.sender?.tenant_key ?? payload.header?.tenant_key ?? "";
2181
+ }
2182
+ /**
2183
+ * Classifies `/new` and `/reset` without advancing the persisted session.
2184
+ * The receive-path control lane uses this before the stateful intake parser so
2185
+ * a reset-and-prompt message is never applied twice when it falls through to
2186
+ * the durable prompt queue.
2187
+ */
2188
+ function readFeishuSessionResetDirective(payload, options) {
2189
+ return Effect.gen(function* () {
2190
+ const text = yield* readNormalizedMessageText(payload, options);
2191
+ const reset = parseNewSessionCommand(payload.event.message.message_id, text);
2192
+ if (!reset) return void 0;
2193
+ const sessionReference = toSessionReference(payload, options);
2194
+ const baseSessionKey = yield* createFeishuSessionKey(sessionReference);
2195
+ return {
2196
+ baseSessionKey,
2197
+ previousSessionKey: yield* resolveCurrentSessionKey(baseSessionKey, options.sessionStore),
2198
+ prompt: reset.prompt,
2199
+ sessionReference
2200
+ };
2201
+ });
2202
+ }
1973
2203
  function createAgentCommandFromFeishuMessage(payload, options) {
1974
2204
  return Effect.gen(function* () {
1975
2205
  const message = payload.event.message;
1976
- const normalizedText = normalizeTrustedBotMention(message.message_type === "text" ? yield* parseTextContent(message.content) : message.message_type === "post" ? yield* parsePostContent(message.content) : yield* Effect.fail(new UnsupportedFeishuMessage(message.message_type)), message.mentions, options.botOpenId);
1977
- yield* validateSkillCommand(normalizedText);
1978
- const cancel = yield* parseCancelRunCommand(message.message_id, normalizedText);
2206
+ const normalizedText = yield* readNormalizedMessageText(payload, options);
1979
2207
  const sessionReference = toSessionReference(payload, options);
1980
- const sessionKey = yield* createFeishuSessionKey(sessionReference);
2208
+ const baseSessionKey = yield* createFeishuSessionKey(sessionReference);
2209
+ const reset = parseNewSessionCommand(message.message_id, normalizedText);
2210
+ const cancel = yield* parseCancelRunCommand(message.message_id, normalizedText);
1981
2211
  if (cancel) return {
1982
2212
  ...cancel,
1983
- sessionKey
2213
+ sessionKey: yield* resolveCurrentSessionKey(baseSessionKey, options.sessionStore)
1984
2214
  };
2215
+ if (reset) {
2216
+ yield* validateSkillCommand(reset.prompt);
2217
+ const session = yield* resetSession(baseSessionKey, options.sessionStore);
2218
+ if (reset.prompt.length === 0) return {
2219
+ messageId: message.message_id,
2220
+ previousSessionKey: session.previousSessionKey,
2221
+ sessionKey: session.sessionKey,
2222
+ type: "new_session"
2223
+ };
2224
+ return {
2225
+ command: {
2226
+ sessionKey: session.sessionKey,
2227
+ text: reset.prompt
2228
+ },
2229
+ conversationId: yield* createFeishuConversationId(sessionReference),
2230
+ messageId: message.message_id,
2231
+ type: "prompt"
2232
+ };
2233
+ }
2234
+ yield* validateSkillCommand(normalizedText);
1985
2235
  return {
1986
2236
  command: {
1987
- sessionKey,
2237
+ sessionKey: yield* resolveCurrentSessionKey(baseSessionKey, options.sessionStore),
1988
2238
  text: normalizedText
1989
2239
  },
1990
2240
  conversationId: yield* createFeishuConversationId(sessionReference),
@@ -1993,32 +2243,62 @@ function createAgentCommandFromFeishuMessage(payload, options) {
1993
2243
  };
1994
2244
  });
1995
2245
  }
2246
+ function readNormalizedMessageText(payload, options) {
2247
+ const message = payload.event.message;
2248
+ return Effect.gen(function* () {
2249
+ return normalizeTrustedBotMention(yield* readFeishuMessageContent({
2250
+ content: message.content,
2251
+ messageType: message.message_type
2252
+ }), message.mentions, options.botOpenId);
2253
+ });
2254
+ }
1996
2255
  function normalizeTrustedBotMention(text, mentions, botOpenId) {
1997
2256
  if (!botOpenId || !mentions) return text.trim();
1998
- return mentions.filter((mention) => mention.id?.open_id === botOpenId && mention.key).map((mention) => mention.key).reduce((current, key) => current.replaceAll(key, ""), text).trim();
2257
+ const tokens = mentions.filter((mention) => mention.id?.open_id === botOpenId && mention.key).flatMap((mention) => [mention.key, ...mention.name ? [`@${mention.name}`] : []]);
2258
+ return [...new Set(tokens)].reduce((current, token) => current.replaceAll(token, ""), text).trim();
1999
2259
  }
2000
2260
  function validateSkillCommand(text) {
2001
2261
  if (!/^\/skill(?::|\s|$)/.test(text)) return Effect.void;
2002
2262
  if (/^\/skill:[a-z0-9][a-z0-9-]*(?:\s[\s\S]*)?$/.test(text)) return Effect.void;
2003
2263
  return Effect.fail(new InvalidFeishuMessageContent("skill command must use /skill:<lowercase-name> followed by optional arguments"));
2004
2264
  }
2265
+ function parseNewSessionCommand(messageId, text) {
2266
+ const match = /^(?:\/new|\/reset)(?:\s+([\s\S]*))?$/.exec(text.trim());
2267
+ return match ? {
2268
+ messageId,
2269
+ prompt: match[1]?.trim() ?? ""
2270
+ } : void 0;
2271
+ }
2272
+ function resolveCurrentSessionKey(baseSessionKey, store) {
2273
+ return store ? store.current(baseSessionKey).pipe(Effect.mapError((error) => new InvalidFeishuMessageContent(error.message))) : Effect.succeed(baseSessionKey);
2274
+ }
2275
+ function resetSession(baseSessionKey, store) {
2276
+ if (!store) return Effect.fail(new InvalidFeishuMessageContent("new session command is not configured"));
2277
+ return store.reset(baseSessionKey).pipe(Effect.mapError((error) => new InvalidFeishuMessageContent(error.message)));
2278
+ }
2005
2279
  function describeFeishuMessageIntake(payload, options) {
2006
2280
  return Effect.gen(function* () {
2007
2281
  const command = yield* createAgentCommandFromFeishuMessage(payload, options);
2008
2282
  const sessionReference = toSessionReference(payload, options);
2009
- const sessionKey = yield* createFeishuSessionKey(sessionReference);
2010
2283
  if (command.type === "cancel_run") return {
2011
2284
  commandType: "cancel_run",
2012
2285
  messageId: command.messageId,
2013
2286
  reason: command.reason,
2014
- runId: command.runId,
2015
- sessionKey,
2287
+ ...command.runId ? { runId: command.runId } : {},
2288
+ sessionKey: command.sessionKey ?? (yield* createFeishuSessionKey(sessionReference)),
2289
+ sessionReference
2290
+ };
2291
+ if (command.type === "new_session") return {
2292
+ commandType: "new_session",
2293
+ messageId: command.messageId,
2294
+ previousSessionKey: command.previousSessionKey,
2295
+ sessionKey: command.sessionKey,
2016
2296
  sessionReference
2017
2297
  };
2018
2298
  return {
2019
2299
  commandType: "prompt",
2020
2300
  messageId: command.messageId,
2021
- sessionKey,
2301
+ sessionKey: command.command.sessionKey,
2022
2302
  sessionReference,
2023
2303
  text: command.command.text
2024
2304
  };
@@ -2032,66 +2312,94 @@ function toSessionReference(payload, options) {
2032
2312
  ...payload.event.message.thread_id ? { threadId: payload.event.message.thread_id } : {}
2033
2313
  };
2034
2314
  }
2035
- function parseTextContent(content) {
2036
- return Effect.try({
2037
- try: () => {
2038
- const parsed = JSON.parse(content);
2039
- if (typeof parsed.text !== "string") throw new Error("text content is missing");
2040
- return parsed.text;
2041
- },
2042
- catch: (error) => new InvalidFeishuMessageContent(error instanceof Error ? error.message : "invalid JSON content")
2043
- });
2044
- }
2045
- function parsePostContent(content) {
2046
- return Effect.try({
2047
- try: () => {
2048
- const parsed = JSON.parse(content);
2049
- const paragraphs = Array.isArray(parsed.content_v2) ? parsed.content_v2 : parsed.content;
2050
- if (!Array.isArray(paragraphs)) throw new Error("post content is missing");
2051
- const body = paragraphs.map((paragraph) => {
2052
- if (!Array.isArray(paragraph)) throw new Error("post paragraph is invalid");
2053
- return paragraph.map((element) => {
2054
- if (element === null || typeof element !== "object") return "";
2055
- if ("text" in element && typeof element.text === "string") return element.text;
2056
- if ("user_name" in element && typeof element.user_name === "string") return `@${element.user_name}`;
2057
- return "";
2058
- }).join("");
2059
- }).join("\n").trim();
2060
- const text = [typeof parsed.title === "string" ? parsed.title.trim() : "", body].filter((part) => part.length > 0).join("\n");
2061
- if (!text) throw new Error("post content has no text");
2062
- return text;
2063
- },
2064
- catch: (error) => new InvalidFeishuMessageContent(error instanceof Error ? error.message : "invalid JSON content")
2065
- });
2066
- }
2067
2315
  function parseCancelRunCommand(messageId, text) {
2068
2316
  const trimmed = text.trim();
2069
2317
  if (!/^\/cancel(?:\s|$)/.test(trimmed)) return Effect.succeed(void 0);
2070
- const match = /^\/cancel\s+(\S+)\s*$/.exec(trimmed);
2071
- if (!match) return Effect.fail(new InvalidFeishuMessageContent("cancel command requires exactly one run id"));
2318
+ const match = /^\/cancel(?:\s+(\S+))?\s*$/.exec(trimmed);
2319
+ if (!match) return Effect.fail(new InvalidFeishuMessageContent("cancel command accepts at most one run id"));
2072
2320
  return Effect.succeed({
2073
2321
  messageId,
2074
2322
  reason: "Feishu cancel command",
2075
- runId: match[1],
2323
+ ...match[1] ? { runId: match[1] } : {},
2076
2324
  type: "cancel_run"
2077
2325
  });
2078
2326
  }
2079
2327
  //#endregion
2080
2328
  //#region src/application/feishu/feishu-agent-daemon.ts
2329
+ const STEERING_ACK_TEXT = "已收到这条消息,已加入当前任务的优先处理;不会新开任务,回答会继续写入当前任务卡。";
2330
+ const CANCELLED_ACK_TEXT = "已停止当前任务。";
2331
+ const NO_ACTIVE_RUN_ACK_TEXT = "当前没有正在运行的任务。";
2081
2332
  function createFeishuAgentDaemon(options) {
2333
+ const sessionStore = options.sessionStore ?? createFeishuSessionStore();
2082
2334
  const execution = options.execution ?? {
2335
+ cancelActiveRun: (input) => Effect.gen(function* () {
2336
+ const session = options.harness.forSession(input.sessionKey);
2337
+ const active = session.getActiveRun();
2338
+ if (!active) return void 0;
2339
+ return (yield* session.cancelRun(active.runId, input.reason)) ? active.runId : void 0;
2340
+ }),
2083
2341
  cancelRun: (input) => input.sessionKey === void 0 ? options.harness.cancelRun(input.runId, input.reason) : options.harness.forSession(input.sessionKey).cancelRun(input.runId, input.reason),
2342
+ steerRun: (input) => Effect.sync(() => typeof options.harness.forSession === "function" ? options.harness.forSession(input.sessionKey).getActiveRun() : void 0).pipe(Effect.flatMap((active) => active ? options.harness.forSession(input.sessionKey).steerRun(active.runId, input.text).pipe(Effect.map((steered) => steered ? {
2343
+ runId: active.runId,
2344
+ steered: true
2345
+ } : void 0)) : Effect.succeed(void 0))),
2084
2346
  promptWithUpdates: (command, onUpdate) => options.harness.promptWithUpdates(command, onUpdate)
2085
2347
  };
2086
2348
  const seenCardActionTokens = /* @__PURE__ */ new Set();
2087
2349
  const seenMessageIds = /* @__PURE__ */ new Set();
2088
2350
  const cancelRun = (command) => Effect.gen(function* () {
2089
- const cancelled = yield* execution.cancelRun(command);
2351
+ const activeRunId = command.runId ? void 0 : command.sessionKey ? yield* execution.cancelActiveRun({
2352
+ reason: command.reason,
2353
+ sessionKey: command.sessionKey
2354
+ }) : void 0;
2355
+ const cancelled = command.runId ? yield* execution.cancelRun({
2356
+ ...command,
2357
+ runId: command.runId
2358
+ }) : !!activeRunId;
2359
+ const runId = command.runId ?? activeRunId;
2090
2360
  return {
2091
2361
  cancelled,
2092
2362
  messageId: command.messageId,
2093
2363
  ...cancelled ? {} : { reason: "not_active" },
2094
- runId: command.runId
2364
+ ...runId ? { runId } : {}
2365
+ };
2366
+ });
2367
+ const replyToCancellation = (messageId, cancelled) => options.reply ? options.reply(messageId, cancelled ? CANCELLED_ACK_TEXT : NO_ACTIVE_RUN_ACK_TEXT) : Effect.void;
2368
+ const duplicateResult = (messageId) => options.dedupe && seenMessageIds.has(messageId) ? {
2369
+ messageId,
2370
+ reason: "duplicate",
2371
+ skipped: true
2372
+ } : void 0;
2373
+ const markSeen = (messageId) => {
2374
+ if (options.dedupe) seenMessageIds.add(messageId);
2375
+ };
2376
+ const resetSession = (inbound, replyEnabled) => Effect.gen(function* () {
2377
+ const cancelledRunId = yield* execution.cancelActiveRun({
2378
+ reason: "Feishu new session command",
2379
+ sessionKey: inbound.previousSessionKey
2380
+ });
2381
+ if (replyEnabled && options.reply) yield* options.reply(inbound.messageId, cancelledRunId ? "已停止当前任务并开启新会话。请发送下一条指令。" : "已开启新会话。请发送下一条指令。");
2382
+ return {
2383
+ messageId: inbound.messageId,
2384
+ previousSessionKey: inbound.previousSessionKey,
2385
+ reset: true,
2386
+ sessionKey: inbound.sessionKey
2387
+ };
2388
+ });
2389
+ const steerPrompt = (inbound, replyEnabled) => Effect.gen(function* () {
2390
+ const steered = yield* execution.steerRun({
2391
+ sessionKey: inbound.command.sessionKey,
2392
+ text: inbound.command.text
2393
+ });
2394
+ if (!steered) return void 0;
2395
+ if (replyEnabled) {
2396
+ if (options.acknowledgeSteering) yield* options.acknowledgeSteering(inbound.messageId).pipe(Effect.catchAll(() => options.reply ? options.reply(inbound.messageId, STEERING_ACK_TEXT) : Effect.void));
2397
+ else if (options.reply) yield* options.reply(inbound.messageId, STEERING_ACK_TEXT);
2398
+ }
2399
+ return {
2400
+ messageId: inbound.messageId,
2401
+ runId: steered.runId,
2402
+ steered: true
2095
2403
  };
2096
2404
  });
2097
2405
  return {
@@ -2106,6 +2414,43 @@ function createFeishuAgentDaemon(options) {
2106
2414
  if (inbound.type === "cancel_run" && inbound.token) seenCardActionTokens.add(inbound.token);
2107
2415
  return result;
2108
2416
  }),
2417
+ trySteerMessage: (payload) => Effect.gen(function* () {
2418
+ const resetDirective = yield* readFeishuSessionResetDirective(payload, {
2419
+ agentId: options.agentId,
2420
+ ...options.botOpenId ? { botOpenId: options.botOpenId } : {},
2421
+ sessionStore
2422
+ }).pipe(Effect.either);
2423
+ if (Either.isRight(resetDirective) && resetDirective.right?.prompt) {
2424
+ yield* execution.cancelActiveRun({
2425
+ reason: "Feishu new session command",
2426
+ sessionKey: resetDirective.right.previousSessionKey
2427
+ });
2428
+ return;
2429
+ }
2430
+ const parsed = yield* createAgentCommandFromFeishuMessage(payload, {
2431
+ agentId: options.agentId,
2432
+ ...options.botOpenId ? { botOpenId: options.botOpenId } : {},
2433
+ sessionStore
2434
+ }).pipe(Effect.either);
2435
+ if (Either.isLeft(parsed)) return void 0;
2436
+ const inbound = parsed.right;
2437
+ const duplicate = duplicateResult(inbound.messageId);
2438
+ if (duplicate) return duplicate;
2439
+ if (inbound.type === "cancel_run") {
2440
+ const result = yield* cancelRun(inbound);
2441
+ markSeen(inbound.messageId);
2442
+ yield* replyToCancellation(inbound.messageId, result.cancelled);
2443
+ return result;
2444
+ }
2445
+ if (inbound.type === "new_session") {
2446
+ markSeen(inbound.messageId);
2447
+ return yield* resetSession(inbound, true);
2448
+ }
2449
+ const steered = yield* steerPrompt(inbound, true);
2450
+ if (!steered) return void 0;
2451
+ markSeen(inbound.messageId);
2452
+ return steered;
2453
+ }),
2109
2454
  handleMessage: (payload, handleOptions) => {
2110
2455
  let dedupeMessageId;
2111
2456
  const sideEffectsDisabled = handleOptions?.sideEffects === "disabled";
@@ -2116,22 +2461,32 @@ function createFeishuAgentDaemon(options) {
2116
2461
  return Effect.gen(function* () {
2117
2462
  const inbound = yield* createAgentCommandFromFeishuMessage(payload, {
2118
2463
  agentId: options.agentId,
2119
- ...options.botOpenId ? { botOpenId: options.botOpenId } : {}
2464
+ ...options.botOpenId ? { botOpenId: options.botOpenId } : {},
2465
+ sessionStore
2120
2466
  });
2121
- if (options.dedupe && seenMessageIds.has(inbound.messageId)) return {
2122
- messageId: inbound.messageId,
2123
- reason: "duplicate",
2124
- skipped: true
2125
- };
2467
+ const duplicate = duplicateResult(inbound.messageId);
2468
+ if (duplicate) return duplicate;
2126
2469
  if (options.dedupe) {
2127
2470
  seenMessageIds.add(inbound.messageId);
2128
2471
  dedupeMessageId = inbound.messageId;
2129
2472
  }
2130
- if (inbound.type === "cancel_run") return yield* cancelRun(inbound);
2473
+ if (inbound.type === "cancel_run") {
2474
+ const result = yield* cancelRun(inbound);
2475
+ if (!sideEffectsDisabled) yield* replyToCancellation(inbound.messageId, result.cancelled);
2476
+ return result;
2477
+ }
2478
+ if (inbound.type === "new_session") return yield* resetSession(inbound, !sideEffectsDisabled);
2479
+ const steered = yield* steerPrompt(inbound, !sideEffectsDisabled);
2480
+ if (steered) return steered;
2131
2481
  const projector = createFeishuStreamProjector();
2132
2482
  const acceptedRunIds = /* @__PURE__ */ new Set();
2483
+ const promptText = options.promptContext ? yield* options.promptContext.resolve({
2484
+ payload,
2485
+ text: inbound.command.text
2486
+ }) : inbound.command.text;
2133
2487
  const command = {
2134
2488
  ...inbound.command,
2489
+ text: promptText,
2135
2490
  ...options.resolveInvocation ? { invocation: options.resolveInvocation(payload, inbound.conversationId) } : {}
2136
2491
  };
2137
2492
  const result = yield* execution.promptWithUpdates(command, (update) => Effect.gen(function* () {
@@ -2204,6 +2559,32 @@ function createFeishuMessageQueue(options) {
2204
2559
  pending: repository.pendingCount(),
2205
2560
  reason: admission
2206
2561
  };
2562
+ if (options.handleControl) {
2563
+ const claimedAt = now();
2564
+ const leaseId = randomUUID();
2565
+ const control = yield* repository.claimById({
2566
+ id: messageId,
2567
+ leaseExpiresAt: new Date(Date.parse(claimedAt) + leaseMs).toISOString(),
2568
+ leaseId,
2569
+ now: claimedAt
2570
+ });
2571
+ if (control) if (yield* options.handleControl(control.payload).pipe(Effect.catchAll((error) => repository.fail({
2572
+ failedAt: now(),
2573
+ id: control.id,
2574
+ leaseId,
2575
+ reason: error instanceof Error ? error.message : String(error),
2576
+ terminal: true
2577
+ }).pipe(Effect.flatMap(() => Effect.fail(error)))))) yield* repository.complete({
2578
+ completedAt: now(),
2579
+ id: control.id,
2580
+ leaseId
2581
+ });
2582
+ else yield* repository.release({
2583
+ availableAt: now(),
2584
+ id: control.id,
2585
+ leaseId
2586
+ });
2587
+ }
2207
2588
  return {
2208
2589
  accepted: true,
2209
2590
  messageId,
@@ -2222,6 +2603,22 @@ function createFeishuMessageQueue(options) {
2222
2603
  handled: false,
2223
2604
  reason: "empty"
2224
2605
  };
2606
+ if (options.shouldSkipReplay ? yield* options.shouldSkipReplay(message.payload) : false) {
2607
+ yield* repository.complete({
2608
+ completedAt: now(),
2609
+ id: message.id,
2610
+ leaseId
2611
+ });
2612
+ return {
2613
+ handled: true,
2614
+ messageId: message.id,
2615
+ result: {
2616
+ messageId: message.id,
2617
+ reason: "duplicate",
2618
+ skipped: true
2619
+ }
2620
+ };
2621
+ }
2225
2622
  const result = yield* options.handleMessage(message.payload, message.options).pipe(Effect.catchAll((error) => repository.fail({
2226
2623
  availableAt: new Date(Date.parse(now()) + retryDelayMs).toISOString(),
2227
2624
  failedAt: now(),
@@ -2644,7 +3041,10 @@ function toast(type, enUs, zhCn) {
2644
3041
  //#endregion
2645
3042
  //#region src/infrastructure/feishu/feishu-event-handlers.ts
2646
3043
  function createFeishuEventHandlers(options) {
2647
- const handlers = { "im.message.receive_v1": (payload) => Effect.runPromise(options.queue.accept(normalizeReceiveMessagePayload(payload))) };
3044
+ const handlers = { "im.message.receive_v1": (payload) => {
3045
+ const normalized = normalizeReceiveMessagePayload(payload);
3046
+ return Effect.runPromise(options.queue.accept(normalized));
3047
+ } };
2648
3048
  if (options.cardActions) return {
2649
3049
  ...handlers,
2650
3050
  "card.action.trigger": (payload) => Effect.runPromise(options.cardActions.handleCardAction(payload).pipe(Effect.map(createFeishuCardActionCallbackResponse), Effect.catchAll(() => Effect.succeed(createFeishuCardActionErrorResponse()))))
@@ -2664,6 +3064,7 @@ function normalizeReceiveMessagePayload(payload) {
2664
3064
  //#endregion
2665
3065
  //#region src/composition/feishu-agent-runtime.ts
2666
3066
  function createFeishuAgentRuntime(options) {
3067
+ const sessionStore = options.sessionStore ?? createFeishuSessionStore();
2667
3068
  const harness = createAgentHarness({
2668
3069
  clock: options.clock,
2669
3070
  ...options.eventSinks ? { eventSinks: options.eventSinks } : {},
@@ -2674,21 +3075,24 @@ function createFeishuAgentRuntime(options) {
2674
3075
  runIds: options.runIds
2675
3076
  });
2676
3077
  const daemon = createFeishuAgentDaemon({
3078
+ ...options.acknowledgeSteering ? { acknowledgeSteering: options.acknowledgeSteering } : {},
2677
3079
  agentId: options.agentId,
2678
3080
  ...options.botOpenId ? { botOpenId: options.botOpenId } : {},
3081
+ dedupe: true,
2679
3082
  ...options.finalizeRun ? { finalizeRun: options.finalizeRun } : {},
2680
3083
  harness,
2681
3084
  ...options.prepareRun ? { prepareRun: options.prepareRun } : {},
2682
- publish: options.publish
3085
+ ...options.promptContext ? { promptContext: options.promptContext } : {},
3086
+ publish: options.publish,
3087
+ ...options.reply ? { reply: options.reply } : {},
3088
+ sessionStore
2683
3089
  });
2684
3090
  let lastAccepted;
2685
3091
  let lastHandled;
2686
3092
  const queue = createFeishuMessageQueue({
3093
+ handleControl: (payload) => daemon.trySteerMessage(payload),
2687
3094
  handleMessage: (payload, handleOptions) => {
2688
- const effect = daemon.handleMessage(payload, handleOptions).pipe(Effect.tap((result) => describeFeishuMessageIntake(payload, {
2689
- agentId: options.agentId,
2690
- ...options.botOpenId ? { botOpenId: options.botOpenId } : {}
2691
- }).pipe(Effect.tap((intake) => Effect.gen(function* () {
3095
+ const effect = daemon.handleMessage(payload, handleOptions).pipe(Effect.tap((result) => describeHandledFeishuMessage(payload, result, harness, options.agentId, options.botOpenId, sessionStore).pipe(Effect.tap((intake) => Effect.gen(function* () {
2692
3096
  const observedAt = yield* options.clock.now;
2693
3097
  lastHandled = {
2694
3098
  intake,
@@ -2700,6 +3104,11 @@ function createFeishuAgentRuntime(options) {
2700
3104
  return options.periodicFlush ? options.periodicFlush.withPeriodicFlush(effect) : effect;
2701
3105
  },
2702
3106
  ...options.inboxRepository ? { repository: options.inboxRepository } : {},
3107
+ ...options.initialEvents ? { shouldSkipReplay: createInterruptedRunReplayGuard(options.initialEvents, {
3108
+ agentId: options.agentId,
3109
+ ...options.botOpenId ? { botOpenId: options.botOpenId } : {},
3110
+ sessionStore
3111
+ }) } : {},
2703
3112
  shouldRetryError: isRetryableFeishuMessageError
2704
3113
  });
2705
3114
  const worker = createFeishuMessageWorker({ queue });
@@ -2716,7 +3125,10 @@ function createFeishuAgentRuntime(options) {
2716
3125
  drainOne: () => queue.drainOne(),
2717
3126
  handlers: createFeishuEventHandlers({
2718
3127
  cardActions: daemon,
2719
- queue: observedQueue
3128
+ queue: {
3129
+ ...observedQueue,
3130
+ pending: () => queue.pending()
3131
+ }
2720
3132
  }),
2721
3133
  harness,
2722
3134
  pending: () => queue.pending(),
@@ -2726,21 +3138,75 @@ function createFeishuAgentRuntime(options) {
2726
3138
  ...lastHandled ? { lastHandled } : {}
2727
3139
  }),
2728
3140
  replayReceiveMessage: (payload, replayOptions) => Effect.gen(function* () {
2729
- const intake = yield* describeFeishuMessageIntake(payload, {
3141
+ const intakeOptions = {
2730
3142
  agentId: options.agentId,
2731
- ...options.botOpenId ? { botOpenId: options.botOpenId } : {}
2732
- });
3143
+ ...options.botOpenId ? { botOpenId: options.botOpenId } : {},
3144
+ sessionStore
3145
+ };
3146
+ const reset = yield* readFeishuSessionResetDirective(payload, intakeOptions);
3147
+ const initialIntake = reset ? void 0 : yield* describeFeishuMessageIntake(payload, intakeOptions);
3148
+ const accepted = yield* observedQueue.accept(payload, replayOptions);
3149
+ const drained = yield* worker.drainAvailable();
3150
+ const handledIntake = lastHandled?.messageId === payload.event.message.message_id ? lastHandled.intake : void 0;
3151
+ let intake;
3152
+ if (handledIntake) intake = handledIntake;
3153
+ else if (reset) {
3154
+ const sessionKey = yield* sessionStore.current(reset.baseSessionKey);
3155
+ intake = reset.prompt ? {
3156
+ commandType: "prompt",
3157
+ messageId: payload.event.message.message_id,
3158
+ sessionKey,
3159
+ sessionReference: reset.sessionReference,
3160
+ text: reset.prompt
3161
+ } : {
3162
+ commandType: "new_session",
3163
+ messageId: payload.event.message.message_id,
3164
+ previousSessionKey: reset.previousSessionKey,
3165
+ sessionKey,
3166
+ sessionReference: reset.sessionReference
3167
+ };
3168
+ } else intake = initialIntake;
2733
3169
  return {
2734
- accepted: yield* observedQueue.accept(payload, replayOptions),
2735
- drained: yield* worker.drainAvailable(),
3170
+ accepted,
3171
+ drained,
2736
3172
  intake
2737
3173
  };
2738
3174
  }),
2739
3175
  worker
2740
3176
  };
2741
3177
  }
3178
+ function describeHandledFeishuMessage(payload, result, harness, agentId, botOpenId, sessionStore) {
3179
+ const sessionReference = {
3180
+ agentId,
3181
+ chatId: payload.event.message.chat_id,
3182
+ tenantKey: readTenantKey(payload),
3183
+ ...payload.event.message.thread_id ? { threadId: payload.event.message.thread_id } : {}
3184
+ };
3185
+ if ("finalText" in result) {
3186
+ const state = harness.getRunState(result.runId);
3187
+ if (state?.sessionKey && state.prompt !== void 0) return Effect.succeed({
3188
+ commandType: "prompt",
3189
+ messageId: result.messageId,
3190
+ sessionKey: state.sessionKey,
3191
+ sessionReference,
3192
+ text: state.prompt
3193
+ });
3194
+ }
3195
+ if ("reset" in result) return Effect.succeed({
3196
+ commandType: "new_session",
3197
+ messageId: result.messageId,
3198
+ previousSessionKey: result.previousSessionKey,
3199
+ sessionKey: result.sessionKey,
3200
+ sessionReference
3201
+ });
3202
+ return describeFeishuMessageIntake(payload, {
3203
+ agentId,
3204
+ ...botOpenId ? { botOpenId } : {},
3205
+ sessionStore
3206
+ });
3207
+ }
2742
3208
  function isRetryableFeishuMessageError(error) {
2743
- return !(error instanceof AgentRunCancelled || error instanceof UnsupportedFeishuMessage || error instanceof InvalidFeishuMessageContent || error instanceof InvalidFeishuSessionReference);
3209
+ return !(error instanceof AgentEventHandlerFailed || error instanceof AgentEventSinkFailed || error instanceof AgentLoopFailed || error instanceof AgentRunCancelled || error instanceof UnsupportedFeishuMessage || error instanceof InvalidFeishuMessageContent || error instanceof InvalidFeishuSessionReference);
2744
3210
  }
2745
3211
  function summarizeReceiveMessage(payload) {
2746
3212
  const tenantKey = readTenantKey(payload);
@@ -2751,21 +3217,47 @@ function summarizeReceiveMessage(payload) {
2751
3217
  ...payload.event.message.thread_id ? { threadId: payload.event.message.thread_id } : {}
2752
3218
  };
2753
3219
  }
3220
+ function createInterruptedRunReplayGuard(events, intakeOptions, sessionNamespace) {
3221
+ const accepted = /* @__PURE__ */ new Map();
3222
+ const interrupted = [];
3223
+ for (const event of events) {
3224
+ if (event.type === "agent_run_accepted") {
3225
+ accepted.set(event.runId, {
3226
+ acceptedAt: event.occurredAt,
3227
+ prompt: event.prompt,
3228
+ sessionKey: event.sessionKey
3229
+ });
3230
+ continue;
3231
+ }
3232
+ if (event.type !== "agent_run_cancelled") continue;
3233
+ const run = accepted.get(event.runId);
3234
+ if (!run) continue;
3235
+ interrupted.push({
3236
+ ...run,
3237
+ cancelledAt: event.occurredAt
3238
+ });
3239
+ }
3240
+ return (payload) => describeFeishuMessageIntake(payload, intakeOptions).pipe(Effect.map((intake) => {
3241
+ if (intake.commandType !== "prompt") return false;
3242
+ const createdAt = readFeishuMessageCreatedAt(payload);
3243
+ if (!createdAt) return false;
3244
+ return interrupted.some((run) => (run.sessionKey === intake.sessionKey || sessionNamespace !== void 0 && run.sessionKey === `${sessionNamespace}:${intake.sessionKey}`) && run.prompt === intake.text && createdAt.getTime() <= run.cancelledAt.getTime() && createdAt.getTime() >= run.acceptedAt.getTime() - 5 * 6e4);
3245
+ }));
3246
+ }
3247
+ function readFeishuMessageCreatedAt(payload) {
3248
+ const raw = payload.event.message.create_time;
3249
+ if (raw === void 0) return void 0;
3250
+ const milliseconds = Number(raw);
3251
+ if (!Number.isFinite(milliseconds) || milliseconds <= 0) return void 0;
3252
+ const createdAt = new Date(milliseconds);
3253
+ return Number.isNaN(createdAt.getTime()) ? void 0 : createdAt;
3254
+ }
2754
3255
  //#endregion
2755
3256
  //#region src/application/agent/agent-history.ts
2756
3257
  function restoreAgentHistory(eventLog) {
2757
3258
  return eventLog.readAll().pipe(Effect.map(replayAgentHistory));
2758
3259
  }
2759
3260
  //#endregion
2760
- //#region src/infrastructure/feishu/feishu-periodic-flush.ts
2761
- function createFeishuPeriodicFlush(options) {
2762
- const loop = Effect.forever(Effect.gen(function* () {
2763
- yield* options.sleep(options.intervalMs);
2764
- yield* options.flush();
2765
- }));
2766
- return { withPeriodicFlush: (effect) => Effect.raceFirst(effect, loop) };
2767
- }
2768
- //#endregion
2769
3261
  //#region src/infrastructure/feishu/feishu-websocket-daemon.ts
2770
3262
  function createLazyFeishuWebSocketEventDispatcher(create) {
2771
3263
  let dispatcher;
@@ -2839,7 +3331,7 @@ function createFeishuAgentRunCard(input) {
2839
3331
  content: `_${presentation.note}_`,
2840
3332
  tag: "markdown"
2841
3333
  },
2842
- ...input.status === "running" ? [{
3334
+ ...input.status === "running" && input.cancelButton !== false ? [{
2843
3335
  behaviors: [{
2844
3336
  type: "callback",
2845
3337
  value: {
@@ -2874,15 +3366,15 @@ function resolveRunPresentation(input) {
2874
3366
  switch (input.status) {
2875
3367
  case "running": return {
2876
3368
  content: input.text || "正在思考…",
2877
- note: input.generation ? "接续上一条消息,回答将实时更新" : "回答将实时更新",
3369
+ note: input.cancelButton === false ? `回答将实时更新;如需停止,请发送 \`/cancel ${input.runId}\`` : input.generation ? "接续上一条消息,回答将实时更新" : "回答将实时更新",
2878
3370
  template: "blue",
2879
3371
  title: input.generation ? "正在处理(续)" : "正在处理"
2880
3372
  };
2881
3373
  case "handoff": return {
2882
- content: "任务仍在后台处理,进度将继续显示在下一条消息。",
2883
- note: "本卡片已停止更新;后续进度见下一条消息",
3374
+ content: "这是同一任务的续卡。任务仍在后台处理,进度将继续显示在下一条消息。",
3375
+ note: "本卡片已停止更新;请查看下一条消息,不会创建第二个任务",
2884
3376
  template: "blue",
2885
- title: "已转到新消息"
3377
+ title: "同一任务已续卡"
2886
3378
  };
2887
3379
  case "completed": return {
2888
3380
  content: input.text || "已完成。",
@@ -3190,6 +3682,7 @@ function createFeishuCardTargetPreparation(options) {
3190
3682
  function createConfiguredFeishuCardKitTargetCreator(options) {
3191
3683
  return createFeishuCardKitOpenApiTargetCreator({
3192
3684
  baseUrl: options.config.feishu.baseUrl,
3685
+ ...options.cancelButton === void 0 ? {} : { cancelButton: options.cancelButton },
3193
3686
  client: options.client,
3194
3687
  elementId: options.elementId ?? DEFAULT_ELEMENT_ID,
3195
3688
  ...options.initialContent === void 0 ? {} : { initialContent: options.initialContent },
@@ -3210,6 +3703,7 @@ function createFeishuCardKitOpenApiTargetCreator(options) {
3210
3703
  runId: run.runId,
3211
3704
  sessionKey: run.sessionKey,
3212
3705
  status: "running",
3706
+ ...options.cancelButton === void 0 ? {} : { cancelButton: options.cancelButton },
3213
3707
  text: createOptions?.initialContent ?? initialContent
3214
3708
  })),
3215
3709
  type: "card_json"
@@ -3236,10 +3730,10 @@ function createFeishuCardKitOpenApiTargetCreator(options) {
3236
3730
  }
3237
3731
  function readStringData(response, field) {
3238
3732
  const data = response.body.data;
3239
- if (!isRecord$2(data) || typeof data[field] !== "string" || data[field].length === 0) return Effect.fail(new FeishuOpenApiError(response.status, response.body.code, `Feishu OpenAPI response missing data.${field}`));
3733
+ if (!isRecord$3(data) || typeof data[field] !== "string" || data[field].length === 0) return Effect.fail(new FeishuOpenApiError(response.status, response.body.code, `Feishu OpenAPI response missing data.${field}`));
3240
3734
  return Effect.succeed(data[field]);
3241
3735
  }
3242
- function isRecord$2(value) {
3736
+ function isRecord$3(value) {
3243
3737
  return typeof value === "object" && value !== null && !Array.isArray(value);
3244
3738
  }
3245
3739
  //#endregion
@@ -3259,6 +3753,7 @@ function createConfiguredFeishuCardRolloverRuntime(options) {
3259
3753
  sleep: options.sleep
3260
3754
  });
3261
3755
  const creator = createConfiguredFeishuCardKitTargetCreator({
3756
+ ...options.cancelButton === void 0 ? {} : { cancelButton: options.cancelButton },
3262
3757
  client: options.client,
3263
3758
  config: options.config,
3264
3759
  ...options.elementId === void 0 ? {} : { elementId: options.elementId },
@@ -3284,6 +3779,12 @@ function createConfiguredFeishuCardRolloverRuntime(options) {
3284
3779
  rollover: cardRollover,
3285
3780
  sleep: options.sleep
3286
3781
  });
3782
+ const streaming = createPeriodicEffectLoop({
3783
+ intervalMs: options.flushIntervalMs ?? options.config.feishu.streamMinIntervalMs,
3784
+ ...options.onError ? { onError: options.onError } : {},
3785
+ run: () => publisher.flush(),
3786
+ sleep: options.sleep
3787
+ });
3287
3788
  return {
3288
3789
  prepareRun: createFeishuCardTargetPreparation({
3289
3790
  createTarget: (run) => creator.createTarget(run),
@@ -3292,15 +3793,20 @@ function createConfiguredFeishuCardRolloverRuntime(options) {
3292
3793
  rollover: cardRollover,
3293
3794
  supervisor,
3294
3795
  transport: {
3295
- running: () => supervisor.running(),
3796
+ running: () => supervisor.running() && streaming.running(),
3296
3797
  start: async () => {
3297
3798
  await Effect.runPromise(supervisor.recover().pipe(Effect.catchAll((error) => {
3298
3799
  const reported = options.onError?.(error);
3299
3800
  return reported instanceof Promise ? Effect.promise(() => reported) : Effect.void;
3300
3801
  })));
3301
3802
  supervisor.start();
3803
+ streaming.start();
3302
3804
  },
3303
- stop: () => supervisor.stop()
3805
+ stop: async () => {
3806
+ const errors = (await Promise.allSettled([streaming.stop(), supervisor.stop()])).filter((result) => result.status === "rejected").map((result) => result.reason);
3807
+ if (errors.length === 1) throw errors[0];
3808
+ if (errors.length > 1) throw new AggregateError(errors, "Feishu card runtime shutdown failed");
3809
+ }
3304
3810
  }
3305
3811
  };
3306
3812
  }
@@ -3308,9 +3814,74 @@ function resolveSupervisorInterval(leaseMs) {
3308
3814
  return Math.max(MIN_SUPERVISOR_INTERVAL_MS, Math.min(MAX_SUPERVISOR_INTERVAL_MS, Math.floor(leaseMs / 10)));
3309
3815
  }
3310
3816
  //#endregion
3817
+ //#region src/infrastructure/feishu/feishu-text-reply.ts
3818
+ function createConfiguredFeishuTextReplySender(options) {
3819
+ const baseUrl = options.config.feishu.baseUrl.replace(/\/$/, "");
3820
+ return { reply: (messageId, text) => Effect.gen(function* () {
3821
+ yield* options.client.request({
3822
+ body: {
3823
+ content: JSON.stringify({ text }),
3824
+ msg_type: "text"
3825
+ },
3826
+ method: "POST",
3827
+ url: `${baseUrl}/open-apis/im/v1/messages/${encodeURIComponent(messageId)}/reply`
3828
+ }, "Feishu reply failed");
3829
+ }) };
3830
+ }
3831
+ //#endregion
3832
+ //#region src/infrastructure/feishu/feishu-message-reaction.ts
3833
+ function createConfiguredFeishuMessageReactionSender(options) {
3834
+ const baseUrl = options.config.feishu.baseUrl.replace(/\/$/, "");
3835
+ return { add: (messageId, emojiType = "OK") => options.client.request({
3836
+ body: { reaction_type: { emoji_type: emojiType } },
3837
+ method: "POST",
3838
+ url: `${baseUrl}/open-apis/im/v1/messages/${encodeURIComponent(messageId)}/reactions`
3839
+ }, "Feishu message reaction failed").pipe(Effect.asVoid) };
3840
+ }
3841
+ //#endregion
3842
+ //#region src/application/feishu/feishu-prompt-context.ts
3843
+ function composeFeishuTopicPrompt(currentText, rootText) {
3844
+ return `${currentText.trim()}\n\n<feishu_topic_context>\n话题原消息:\n${rootText.trim()}\n</feishu_topic_context>`;
3845
+ }
3846
+ //#endregion
3847
+ //#region src/infrastructure/feishu/feishu-topic-context-resolver.ts
3848
+ var InvalidFeishuTopicContext = class extends Error {
3849
+ name = "InvalidFeishuTopicContext";
3850
+ };
3851
+ function createFeishuTopicContextResolver(options) {
3852
+ const baseUrl = (options.baseUrl ?? "https://open.feishu.cn").replace(/\/+$/u, "");
3853
+ return { resolve: ({ payload, text }) => {
3854
+ const message = payload.event.message;
3855
+ const rootId = message.root_id;
3856
+ if (!message.thread_id || !rootId || rootId === message.message_id) return Effect.succeed(text);
3857
+ return Effect.gen(function* () {
3858
+ const root = readRootMessage(yield* options.client.request({
3859
+ method: "GET",
3860
+ url: `${baseUrl}/open-apis/im/v1/messages/${encodeURIComponent(rootId)}`
3861
+ }, "Failed to read Feishu topic root message"), rootId);
3862
+ return composeFeishuTopicPrompt(text, yield* readFeishuMessageContent({
3863
+ content: root.content,
3864
+ messageType: root.messageType
3865
+ }));
3866
+ });
3867
+ } };
3868
+ }
3869
+ function readRootMessage(response, rootId) {
3870
+ const data = response.body.data;
3871
+ if (!isRecord$2(data) || !Array.isArray(data.items)) throw new InvalidFeishuTopicContext("Feishu topic root response is missing data.items");
3872
+ const item = data.items.find((candidate) => isRecord$2(candidate) && candidate.message_id === rootId);
3873
+ if (!isRecord$2(item) || typeof item.msg_type !== "string" || !isRecord$2(item.body) || typeof item.body.content !== "string") throw new InvalidFeishuTopicContext(`Feishu topic root message is invalid: ${rootId}`);
3874
+ return {
3875
+ content: item.body.content,
3876
+ messageType: item.msg_type
3877
+ };
3878
+ }
3879
+ function isRecord$2(value) {
3880
+ return value !== null && typeof value === "object";
3881
+ }
3882
+ //#endregion
3311
3883
  //#region src/composition/rivus-daemon-bootstrap.ts
3312
3884
  const DEFAULT_WORKER_INTERVAL_MS$1 = 250;
3313
- const DEFAULT_RUN_TIMEOUT_MS = 900 * 1e3;
3314
3885
  function restoreConfiguredRivusDaemonBootstrap(options) {
3315
3886
  return options.eventLog.readAll().pipe(Effect.map((events) => createConfiguredRivusDaemonBootstrap({
3316
3887
  ...options,
@@ -3324,19 +3895,33 @@ function createConfiguredRivusDaemonBootstrap(options) {
3324
3895
  });
3325
3896
  const cardRollover = createConfiguredFeishuCardRolloverRuntime({
3326
3897
  cardTargets: options.cardTargets,
3898
+ cancelButton: false,
3327
3899
  client: openApiClient,
3328
3900
  clock: options.clock,
3329
3901
  config: options.config,
3330
3902
  ...options.cardLedger ? { ledger: options.cardLedger } : {},
3903
+ ...options.flushIntervalMs === void 0 ? {} : { flushIntervalMs: options.flushIntervalMs },
3331
3904
  ...options.observeCardRollover ? { observe: options.observeCardRollover } : {},
3332
3905
  ...options.onWorkerError ? { onError: options.onWorkerError } : {},
3333
3906
  sleep: options.sleep,
3334
3907
  ...options.cardRolloverIntervalMs === void 0 ? {} : { supervisorIntervalMs: options.cardRolloverIntervalMs }
3335
3908
  });
3336
3909
  const publisher = cardRollover.rollover;
3910
+ const replies = createConfiguredFeishuTextReplySender({
3911
+ client: openApiClient,
3912
+ config: options.config
3913
+ });
3914
+ const reactions = createConfiguredFeishuMessageReactionSender({
3915
+ client: openApiClient,
3916
+ config: options.config
3917
+ });
3918
+ const promptContext = createFeishuTopicContextResolver({
3919
+ baseUrl: options.config.feishu.baseUrl,
3920
+ client: openApiClient
3921
+ });
3337
3922
  const prepareRun = (run) => cardRollover.prepareRun(run);
3338
- const periodicFlush = createPeriodicFlush(options, publisher);
3339
3923
  const runtime = createFeishuAgentRuntime({
3924
+ acknowledgeSteering: (messageId) => reactions.add(messageId),
3340
3925
  agentId: options.config.agentId,
3341
3926
  ...options.botOpenId ? { botOpenId: options.botOpenId } : {},
3342
3927
  clock: options.clock,
@@ -3346,10 +3931,11 @@ function createConfiguredRivusDaemonBootstrap(options) {
3346
3931
  ...options.initialEvents ? { initialEvents: options.initialEvents } : {},
3347
3932
  ...options.initialRunStates ? { initialRunStates: options.initialRunStates } : {},
3348
3933
  loop: options.loop,
3349
- periodicFlush,
3350
3934
  prepareRun,
3935
+ promptContext,
3351
3936
  publish: (action) => publisher.publish(action),
3352
- runTimeoutMs: options.runTimeoutMs ?? DEFAULT_RUN_TIMEOUT_MS,
3937
+ reply: (messageId, text) => replies.reply(messageId, text),
3938
+ ...options.runTimeoutMs === void 0 ? {} : { runTimeoutMs: options.runTimeoutMs },
3353
3939
  runIds: options.runIds
3354
3940
  });
3355
3941
  const websocketTransport = createFeishuWebSocketDaemon({
@@ -3404,13 +3990,6 @@ function createConfiguredRivusDaemonBootstrap(options) {
3404
3990
  workerLoop
3405
3991
  };
3406
3992
  }
3407
- function createPeriodicFlush(options, publisher) {
3408
- return createFeishuPeriodicFlush({
3409
- flush: () => publisher.flush(),
3410
- intervalMs: options.flushIntervalMs ?? options.config.feishu.streamMinIntervalMs,
3411
- sleep: options.sleep
3412
- });
3413
- }
3414
3993
  //#endregion
3415
3994
  //#region src/infrastructure/persistence/jsonl-agent-event-log.ts
3416
3995
  var AgentEventLogStoreError = class {
@@ -3447,7 +4026,7 @@ function parseJsonlEvents(raw) {
3447
4026
  return raw.split("\n").filter((line) => line.trim().length > 0).map((line, index) => parseAgentDomainEvent(JSON.parse(line), index + 1));
3448
4027
  }
3449
4028
  function parseAgentDomainEvent(value, lineNumber) {
3450
- if (!isRecord$4(value) || typeof value.type !== "string") throw new Error(`Invalid agent event at line ${lineNumber}.`);
4029
+ if (!isRecord$5(value) || typeof value.type !== "string") throw new Error(`Invalid agent event at line ${lineNumber}.`);
3451
4030
  const occurredAt = readDate(value, "occurredAt", lineNumber);
3452
4031
  switch (value.type) {
3453
4032
  case "agent_run_accepted": return {
@@ -3618,7 +4197,7 @@ function readOptionalNumber(value, field, lineNumber) {
3618
4197
  }
3619
4198
  function readRecord$1(value, field, lineNumber) {
3620
4199
  const fieldValue = value[field];
3621
- if (!isRecord$4(fieldValue)) throw new Error(`Invalid agent event field '${field}' at line ${lineNumber}.`);
4200
+ if (!isRecord$5(fieldValue)) throw new Error(`Invalid agent event field '${field}' at line ${lineNumber}.`);
3622
4201
  return fieldValue;
3623
4202
  }
3624
4203
  function readOptionalString(value, field, lineNumber) {
@@ -4626,13 +5205,13 @@ async function saveChains(filePath, chains) {
4626
5205
  }
4627
5206
  }
4628
5207
  function parseChains(value) {
4629
- if (!isRecord$4(value)) throw new Error("Feishu card presentation file must contain a JSON object.");
5208
+ if (!isRecord$5(value)) throw new Error("Feishu card presentation file must contain a JSON object.");
4630
5209
  if (value.version === void 0) return Object.entries(value).map(([runId, target]) => parseLegacyChain(runId, target));
4631
- if (value.version !== PRESENTATION_FILE_VERSION || !isRecord$4(value.runs)) throw new Error(`Unsupported Feishu card presentation file version: ${JSON.stringify(value.version)}`);
5210
+ if (value.version !== PRESENTATION_FILE_VERSION || !isRecord$5(value.runs)) throw new Error(`Unsupported Feishu card presentation file version: ${JSON.stringify(value.version)}`);
4632
5211
  return Object.entries(value.runs).map(([runId, chain]) => parseChain(runId, chain));
4633
5212
  }
4634
5213
  function parseLegacyChain(runId, target) {
4635
- if (!isRecord$4(target) || typeof target.cardId !== "string" || typeof target.elementId !== "string") throw new Error(`Invalid Feishu card target entry for run '${runId}'.`);
5214
+ if (!isRecord$5(target) || typeof target.cardId !== "string" || typeof target.elementId !== "string") throw new Error(`Invalid Feishu card target entry for run '${runId}'.`);
4636
5215
  return createCardPresentationChain({
4637
5216
  cardId: target.cardId,
4638
5217
  createdAt: LEGACY_PRESENTATION_TIMESTAMP,
@@ -4644,9 +5223,9 @@ function parseLegacyChain(runId, target) {
4644
5223
  });
4645
5224
  }
4646
5225
  function parseChain(runId, value) {
4647
- if (!isRecord$4(value) || value.runId !== runId || !Number.isInteger(value.activeGeneration) || typeof value.activePresentationId !== "string" || !Number.isInteger(value.revision) || !Array.isArray(value.presentations) || value.presentations.length === 0) throw new Error(`Invalid Feishu card presentation chain for run '${runId}'.`);
5226
+ if (!isRecord$5(value) || value.runId !== runId || !Number.isInteger(value.activeGeneration) || typeof value.activePresentationId !== "string" || !Number.isInteger(value.revision) || !Array.isArray(value.presentations) || value.presentations.length === 0) throw new Error(`Invalid Feishu card presentation chain for run '${runId}'.`);
4648
5227
  const chain = value;
4649
- for (const presentation of chain.presentations) if (!isRecord$4(presentation) || typeof presentation.cardId !== "string" || typeof presentation.presentationId !== "string" || typeof presentation.status !== "string" || !Number.isInteger(presentation.generation)) throw new Error(`Invalid Feishu card presentation entry for run '${runId}'.`);
5228
+ for (const presentation of chain.presentations) if (!isRecord$5(presentation) || typeof presentation.cardId !== "string" || typeof presentation.presentationId !== "string" || typeof presentation.status !== "string" || !Number.isInteger(presentation.generation)) throw new Error(`Invalid Feishu card presentation entry for run '${runId}'.`);
4650
5229
  activeCardPresentation(chain);
4651
5230
  return chain;
4652
5231
  }
@@ -5018,6 +5597,15 @@ function readRedactedString(serialized) {
5018
5597
  return typeof value === "string" ? value : serialized;
5019
5598
  }
5020
5599
  //#endregion
5600
+ //#region src/infrastructure/feishu/feishu-periodic-flush.ts
5601
+ function createFeishuPeriodicFlush(options) {
5602
+ const loop = Effect.forever(Effect.gen(function* () {
5603
+ yield* options.sleep(options.intervalMs);
5604
+ yield* options.flush();
5605
+ }));
5606
+ return { withPeriodicFlush: (effect) => Effect.raceFirst(effect, loop) };
5607
+ }
5608
+ //#endregion
5021
5609
  //#region src/testing/fakes.ts
5022
5610
  function createFixedClock(now) {
5023
5611
  return { now: Effect.succeed(now) };
@@ -5447,7 +6035,7 @@ async function load$1(filePath) {
5447
6035
  for (const [index, line] of raw.split("\n").entries()) {
5448
6036
  if (!line.trim()) continue;
5449
6037
  const envelope = JSON.parse(line);
5450
- if (!isRecord$4(envelope) || envelope.version !== 1 || !isOperationRecord(envelope.record)) throw new Error(`invalid tool operation snapshot at line ${index + 1}`);
6038
+ if (!isRecord$5(envelope) || envelope.version !== 1 || !isOperationRecord(envelope.record)) throw new Error(`invalid tool operation snapshot at line ${index + 1}`);
5451
6039
  const record = envelope.record;
5452
6040
  const previous = latest.get(record.operationId);
5453
6041
  if (record.revision !== (previous?.revision ?? 0) + 1) throw new Error(`invalid tool operation revision at line ${index + 1}`);
@@ -5470,7 +6058,7 @@ function isValidTransition$1(previous, next) {
5470
6058
  }
5471
6059
  }
5472
6060
  function isOperationRecord(value) {
5473
- if (!isRecord$4(value) || !isRecord$4(value.binding) || !isRecord$4(value.state)) return false;
6061
+ if (!isRecord$5(value) || !isRecord$5(value.binding) || !isRecord$5(value.state)) return false;
5474
6062
  const binding = value.binding;
5475
6063
  const state = value.state;
5476
6064
  if (typeof value.operationId !== "string" || !Number.isInteger(value.revision) || value.revision < 1) return false;
@@ -5501,7 +6089,7 @@ function isStableJson(value) {
5501
6089
  }
5502
6090
  }
5503
6091
  function isReconciliation(value) {
5504
- return isRecord$4(value) && typeof value.actorId === "string" && value.actorId.trim().length > 0 && typeof value.at === "string" && Number.isFinite(Date.parse(value.at)) && typeof value.note === "string" && value.note.trim().length > 0 && (value.outcome === "applied" || value.outcome === "not-applied");
6092
+ return isRecord$5(value) && typeof value.actorId === "string" && value.actorId.trim().length > 0 && typeof value.at === "string" && Number.isFinite(Date.parse(value.at)) && typeof value.note === "string" && value.note.trim().length > 0 && (value.outcome === "applied" || value.outcome === "not-applied");
5505
6093
  }
5506
6094
  //#endregion
5507
6095
  //#region src/infrastructure/persistence/jsonl-recovery-control.ts
@@ -5538,14 +6126,20 @@ function isMissingDirectory(error) {
5538
6126
  function createAgentHarnessPooledRuntime(harness) {
5539
6127
  return {
5540
6128
  cancel: (input) => Effect.runPromise(harness.forSession(input.sessionKey).cancelRun(input.runId, input.reason)),
5541
- run: (input) => Effect.runPromise(harness.promptWithUpdates({
5542
- ...input.invocation ? { invocation: input.invocation } : {},
5543
- sessionKey: input.sessionKey,
5544
- text: input.text
5545
- }, (update) => Effect.tryPromise({
5546
- try: async () => input.onUpdate?.(update),
5547
- catch: (error) => error
5548
- })))
6129
+ steer: (input) => Effect.runPromise(harness.forSession(input.sessionKey).steerRun(input.runId, input.text)),
6130
+ run: async (input) => {
6131
+ const exit = await Effect.runPromiseExit(harness.promptWithUpdates({
6132
+ ...input.invocation ? { invocation: input.invocation } : {},
6133
+ sessionKey: input.sessionKey,
6134
+ text: input.text
6135
+ }, (update) => Effect.tryPromise({
6136
+ try: async () => input.onUpdate?.(update),
6137
+ catch: (error) => error
6138
+ })));
6139
+ if (Exit.isSuccess(exit)) return exit.value;
6140
+ const failure = Cause.failureOption(exit.cause);
6141
+ throw Option.isSome(failure) ? failure.value : Cause.squash(exit.cause);
6142
+ }
5549
6143
  };
5550
6144
  }
5551
6145
  //#endregion
@@ -5558,20 +6152,31 @@ function createFeishuDeploymentEndpoint(options) {
5558
6152
  shutdownTimeoutMs: options.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS
5559
6153
  });
5560
6154
  const daemon = createFeishuAgentDaemon({
6155
+ ...options.acknowledgeSteering ? { acknowledgeSteering: options.acknowledgeSteering } : {},
5561
6156
  agentId: options.agentId,
5562
6157
  botOpenId: options.botOpenId,
6158
+ dedupe: true,
5563
6159
  execution: execution.port,
5564
6160
  ...options.finalizeRun ? { finalizeRun: options.finalizeRun } : {},
5565
6161
  ...options.interactions ? { interactions: options.interactions } : {},
5566
6162
  ...options.prepareRun ? { prepareRun: options.prepareRun } : {},
6163
+ ...options.promptContext ? { promptContext: options.promptContext } : {},
5567
6164
  publish: options.publish,
5568
6165
  ...options.publishRunUpdate ? { publishRunUpdate: options.publishRunUpdate } : {},
6166
+ ...options.reply ? { reply: options.reply } : {},
6167
+ ...options.sessionStore ? { sessionStore: options.sessionStore } : {},
5569
6168
  ...options.endpointId ? { resolveInvocation: (payload, conversationId) => createInvocation(payload, options.endpointId, options.memoryTenantId, conversationId, options.projectSpaceId) } : {}
5570
6169
  });
5571
6170
  const queue = createFeishuMessageQueue({
6171
+ handleControl: (payload) => daemon.trySteerMessage(payload),
5572
6172
  handleMessage: (payload, handleOptions) => daemon.handleMessage(payload, handleOptions),
5573
6173
  ...options.inboxRepository ? { repository: options.inboxRepository } : {},
5574
6174
  ...options.maxPendingMessages === void 0 ? {} : { maxPending: options.maxPendingMessages },
6175
+ ...options.initialEvents ? { shouldSkipReplay: createInterruptedRunReplayGuard(options.initialEvents, {
6176
+ agentId: options.agentId,
6177
+ botOpenId: options.botOpenId,
6178
+ ...options.sessionStore ? { sessionStore: options.sessionStore } : {}
6179
+ }, options.sessionNamespace) } : {},
5575
6180
  shouldRetryError: isRetryableFeishuMessageError
5576
6181
  });
5577
6182
  const worker = createFeishuMessageWorker({
@@ -5589,14 +6194,17 @@ function createFeishuDeploymentEndpoint(options) {
5589
6194
  eventDispatcher: options.eventDispatcher,
5590
6195
  handlers: createFeishuEventHandlers({
5591
6196
  cardActions: daemon,
5592
- queue: { accept: (payload) => shouldAcceptFeishuEndpointMessage(payload, options.groupPolicy, options.botOpenId) ? queue.accept(payload).pipe(Effect.tap((result) => result.accepted || result.reason !== "capacity" || !options.onCapacityExceeded ? Effect.void : Effect.sync(() => {
5593
- Effect.runPromise(options.onCapacityExceeded(payload)).catch((error) => options.onWorkerError?.(error)).catch(() => void 0);
5594
- }))) : Effect.succeed({
5595
- accepted: false,
5596
- messageId: payload.event.message.message_id,
5597
- pending: queue.pending(),
5598
- reason: "ignored"
5599
- }) }
6197
+ queue: {
6198
+ accept: (payload) => shouldAcceptFeishuEndpointMessage(payload, options.groupPolicy, options.botOpenId) ? queue.accept(payload).pipe(Effect.tap((result) => result.accepted || result.reason !== "capacity" || !options.onCapacityExceeded ? Effect.void : Effect.sync(() => {
6199
+ Effect.runPromise(options.onCapacityExceeded(payload)).catch((error) => options.onWorkerError?.(error)).catch(() => void 0);
6200
+ }))) : Effect.succeed({
6201
+ accepted: false,
6202
+ messageId: payload.event.message.message_id,
6203
+ pending: queue.pending(),
6204
+ reason: "ignored"
6205
+ }),
6206
+ pending: () => queue.pending()
6207
+ }
5600
6208
  })
5601
6209
  });
5602
6210
  return {
@@ -5637,6 +6245,10 @@ function createDeploymentExecution(options) {
5637
6245
  const activeHandles = /* @__PURE__ */ new Set();
5638
6246
  const activeRuns = /* @__PURE__ */ new Map();
5639
6247
  let stopping = false;
6248
+ const findActiveRun = (requestedSessionKey) => {
6249
+ const namespacedSessionKey = namespaceSessionKey(options.sessionNamespace, requestedSessionKey);
6250
+ return [...activeRuns].find(([, sessionKey]) => sessionKey === requestedSessionKey || sessionKey === namespacedSessionKey);
6251
+ };
5640
6252
  const requestShutdownCancellation = (runId, sessionKey) => {
5641
6253
  const current = cancellationRequests.get(runId);
5642
6254
  if (current) return current;
@@ -5681,6 +6293,19 @@ function createDeploymentExecution(options) {
5681
6293
  if (errors.length > 1) throw new AggregateError(errors, "Active Agent run cancellation failed");
5682
6294
  },
5683
6295
  port: {
6296
+ cancelActiveRun: (input) => Effect.tryPromise({
6297
+ try: async () => {
6298
+ const activeRun = findActiveRun(input.sessionKey);
6299
+ if (!activeRun) return void 0;
6300
+ const [runId, sessionKey] = activeRun;
6301
+ return await options.cancel({
6302
+ reason: input.reason,
6303
+ runId,
6304
+ sessionKey
6305
+ }) ? runId : void 0;
6306
+ },
6307
+ catch: (error) => error
6308
+ }),
5684
6309
  cancelRun: (input) => input.sessionKey === void 0 ? Effect.succeed(false) : Effect.tryPromise({
5685
6310
  try: () => options.cancel({
5686
6311
  reason: input.reason,
@@ -5689,6 +6314,23 @@ function createDeploymentExecution(options) {
5689
6314
  }),
5690
6315
  catch: (error) => error
5691
6316
  }),
6317
+ steerRun: (input) => Effect.tryPromise({
6318
+ try: async () => {
6319
+ if (!options.steer) return void 0;
6320
+ const activeRun = findActiveRun(input.sessionKey);
6321
+ if (!activeRun) return void 0;
6322
+ const [runId, sessionKey] = activeRun;
6323
+ return await options.steer({
6324
+ runId,
6325
+ sessionKey,
6326
+ text: input.text
6327
+ }) ? {
6328
+ runId,
6329
+ steered: true
6330
+ } : void 0;
6331
+ },
6332
+ catch: (error) => error
6333
+ }),
5692
6334
  promptWithUpdates: (command, onUpdate) => Effect.tryPromise({
5693
6335
  try: async () => {
5694
6336
  const sessionKey = namespaceSessionKey(options.sessionNamespace, command.sessionKey);
@@ -7104,8 +7746,8 @@ async function loadJsonlSnapshotStore(options) {
7104
7746
  }
7105
7747
  throw new JsonlSnapshotStoreCorrupted(`${options.errorLabel} JSONL at line ${index + 1} is not valid JSON`);
7106
7748
  }
7107
- if (!isRecord$4(parsed) || parsed.version !== options.version) throw new JsonlSnapshotStoreCorrupted(`unsupported ${options.errorLabel} version at line ${index + 1}`);
7108
- if (isRecord$4(parsed.snapshot)) {
7749
+ if (!isRecord$5(parsed) || parsed.version !== options.version) throw new JsonlSnapshotStoreCorrupted(`unsupported ${options.errorLabel} version at line ${index + 1}`);
7750
+ if (isRecord$5(parsed.snapshot)) {
7109
7751
  if (!Array.isArray(parsed.snapshot.records)) throw new JsonlSnapshotStoreCorrupted(`invalid ${options.errorLabel} snapshot at line ${index + 1}`);
7110
7752
  latest.clear();
7111
7753
  for (const value of parsed.snapshot.records) {
@@ -7169,7 +7811,7 @@ function validateSessionSequence(previous, record, _lineNumber) {
7169
7811
  return isValidBackgroundSessionTransition(previous, record);
7170
7812
  }
7171
7813
  function isBackgroundSessionState(value) {
7172
- if (!isRecord$4(value)) return false;
7814
+ if (!isRecord$5(value)) return false;
7173
7815
  const phases = [
7174
7816
  "queued",
7175
7817
  "running",
@@ -7187,14 +7829,14 @@ function isBackgroundSessionState(value) {
7187
7829
  if (!Number.isInteger(value.revision) || value.revision < 1) return false;
7188
7830
  if (typeof value.parentRunId !== "string" || value.parentRunId.trim() === "") return false;
7189
7831
  if (typeof value.sourceMessageId !== "string" || value.sourceMessageId.trim() === "") return false;
7190
- if (!isRecord$4(value.origin)) return false;
7832
+ if (!isRecord$5(value.origin)) return false;
7191
7833
  const origin = value.origin;
7192
7834
  if (typeof origin.endpointId !== "string" || origin.endpointId.trim() === "") return false;
7193
7835
  if (typeof origin.tenantKey !== "string") return false;
7194
7836
  if (!Array.isArray(origin.allowedActorOpenIds) || origin.allowedActorOpenIds.some((id) => typeof id !== "string")) return false;
7195
7837
  if (origin.conversationId !== void 0 && typeof origin.conversationId !== "string") return false;
7196
- if (origin.memory !== void 0 && !isRecord$4(origin.memory)) return false;
7197
- if (!isRecord$4(value.authority)) return false;
7838
+ if (origin.memory !== void 0 && !isRecord$5(origin.memory)) return false;
7839
+ if (!isRecord$5(value.authority)) return false;
7198
7840
  const authority = value.authority;
7199
7841
  if (typeof authority.agentId !== "string" || authority.agentId.trim() === "") return false;
7200
7842
  if (typeof authority.profileRevision !== "string" || authority.profileRevision.trim() === "") return false;
@@ -7217,17 +7859,17 @@ function isBackgroundSessionState(value) {
7217
7859
  if (typeof value.updatedAt !== "string" || !isIso(value.updatedAt)) return false;
7218
7860
  if (value.lease !== void 0 && !isLease(value.lease)) return false;
7219
7861
  if (value.cancellation !== void 0) {
7220
- if (!isRecord$4(value.cancellation)) return false;
7862
+ if (!isRecord$5(value.cancellation)) return false;
7221
7863
  if (typeof value.cancellation.reason !== "string" || !isIso(value.cancellation.requestedAt)) return false;
7222
7864
  }
7223
7865
  if (value.result !== void 0) {
7224
- if (!isRecord$4(value.result)) return false;
7866
+ if (!isRecord$5(value.result)) return false;
7225
7867
  if (typeof value.result.text !== "string" || typeof value.result.stepRunId !== "string" || !isIso(value.result.completedAt)) return false;
7226
7868
  }
7227
7869
  return true;
7228
7870
  }
7229
7871
  function isLease(value) {
7230
- if (!isRecord$4(value)) return false;
7872
+ if (!isRecord$5(value)) return false;
7231
7873
  return typeof value.owner === "string" && value.owner.trim() !== "" && Number.isInteger(value.epoch) && value.epoch >= 0 && isIso(value.expiresAt);
7232
7874
  }
7233
7875
  function isIso(value) {
@@ -7256,7 +7898,7 @@ function validateDeliverySequence(previous, record, _lineNumber) {
7256
7898
  return previous.state === "pending" && record.state === "delivered" ? void 0 : "transition";
7257
7899
  }
7258
7900
  function isDeliveryRecord(value) {
7259
- if (!isRecord$4(value)) return false;
7901
+ if (!isRecord$5(value)) return false;
7260
7902
  if (typeof value.deliveryId !== "string" || value.deliveryId.trim() === "" || typeof value.sessionId !== "string" || value.sessionId.trim() === "") return false;
7261
7903
  if (typeof value.kind !== "string" || ![
7262
7904
  "progress",
@@ -7586,10 +8228,13 @@ function createProjectMemoryPromptPreparer(options) {
7586
8228
  //#endregion
7587
8229
  //#region src/application/agent/agent-loop-prompt-transformer.ts
7588
8230
  function createAgentLoopPromptTransformer(options) {
7589
- return { run: (input) => Stream.unwrap(Effect.promise(() => Promise.resolve(options.transform(input))).pipe(Effect.map((text) => options.loop.run({
7590
- ...input,
7591
- text
7592
- })))) };
8231
+ return {
8232
+ run: (input) => Stream.unwrap(Effect.promise(() => Promise.resolve(options.transform(input))).pipe(Effect.map((text) => options.loop.run({
8233
+ ...input,
8234
+ text
8235
+ })))),
8236
+ ...options.loop.supportsSteering ? { supportsSteering: true } : {}
8237
+ };
7593
8238
  }
7594
8239
  //#endregion
7595
8240
  //#region src/application/project/project-skill-catalog.ts
@@ -7634,7 +8279,7 @@ async function load(filePath) {
7634
8279
  for (const [index, line] of raw.split("\n").entries()) {
7635
8280
  if (!line.trim()) continue;
7636
8281
  const envelope = JSON.parse(line);
7637
- if (!isRecord$4(envelope) || envelope.version !== 1 || !isMemorySnapshot(envelope.snapshot)) throw new Error(`invalid Agent Memory snapshot at line ${index + 1}`);
8282
+ if (!isRecord$5(envelope) || envelope.version !== 1 || !isMemorySnapshot(envelope.snapshot)) throw new Error(`invalid Agent Memory snapshot at line ${index + 1}`);
7638
8283
  const snapshot = envelope.snapshot;
7639
8284
  const key = snapshotKey(snapshot);
7640
8285
  const previous = latest.get(key);
@@ -7645,7 +8290,7 @@ async function load(filePath) {
7645
8290
  return [...latest.values()];
7646
8291
  }
7647
8292
  function isMemorySnapshot(value) {
7648
- if (!isRecord$4(value) || !isRecord$4(value.binding) || !isRecord$4(value.record)) return false;
8293
+ if (!isRecord$5(value) || !isRecord$5(value.binding) || !isRecord$5(value.record)) return false;
7649
8294
  const binding = value.binding;
7650
8295
  const record = value.record;
7651
8296
  return nonEmpty(binding.tenantId) && nonEmpty(binding.agentId) && nonEmpty(binding.subjectId) && isMemoryScope(binding.scope) && (binding.scope === "conversation" ? nonEmpty(binding.conversationId) && binding.projectId === void 0 : binding.scope === "project" ? nonEmpty(binding.projectId) && binding.conversationId === void 0 : binding.conversationId === void 0 && binding.projectId === void 0) && nonEmpty(record.id) && record.id.startsWith("memory:") && nonEmpty(record.content) && typeof record.conversationSafe === "boolean" && (record.tombstoneReason === void 0 || record.state === "tombstoned" && nonEmpty(record.tombstoneReason)) && Number.isInteger(record.revision) && record.revision > 0 && record.scope === binding.scope && isMemoryScope(record.scope) && [
@@ -8090,31 +8735,20 @@ async function readSnapshot$1(filePath) {
8090
8735
  version: 3
8091
8736
  });
8092
8737
  const value = JSON.parse(raw);
8093
- if (!isRecord$4(value) || value.version !== 2 && value.version !== 3 || !Array.isArray(value.records)) throw new Error("Automation Tick snapshot must contain version 2 or 3 records");
8738
+ if (!isRecord$5(value) || value.version !== 2 && value.version !== 3 || !Array.isArray(value.records)) throw new Error("Automation Tick snapshot must contain version 2 or 3 records");
8094
8739
  return Object.freeze({
8095
8740
  records: Object.freeze(value.records.map(readRecord)),
8096
8741
  version: value.version
8097
8742
  });
8098
8743
  }
8099
8744
  async function writeSnapshot(filePath, records) {
8100
- await mkdir(dirname(filePath), { recursive: true });
8101
- const temporaryPath = `${filePath}.${randomUUID()}.tmp`;
8102
- try {
8103
- await writeFile(temporaryPath, `${JSON.stringify({
8104
- records,
8105
- version: 3
8106
- })}\n`, {
8107
- encoding: "utf8",
8108
- flag: "wx"
8109
- });
8110
- await rename(temporaryPath, filePath);
8111
- } catch (error) {
8112
- await unlink(temporaryPath).catch(() => void 0);
8113
- throw error;
8114
- }
8745
+ await writePersistenceFile(filePath, {
8746
+ records,
8747
+ version: 3
8748
+ });
8115
8749
  }
8116
8750
  function readRecord(value) {
8117
- if (!isRecord$4(value)) throw new Error("Automation Tick record must be an object");
8751
+ if (!isRecord$5(value)) throw new Error("Automation Tick record must be an object");
8118
8752
  for (const field of [
8119
8753
  "automationId",
8120
8754
  "mandateId",
@@ -8528,21 +9162,6 @@ function createRoutedHumanInteractionToolApprovalService(registry) {
8528
9162
  } };
8529
9163
  }
8530
9164
  //#endregion
8531
- //#region src/infrastructure/feishu/feishu-text-reply.ts
8532
- function createConfiguredFeishuTextReplySender(options) {
8533
- const baseUrl = options.config.feishu.baseUrl.replace(/\/$/, "");
8534
- return { reply: (messageId, text) => Effect.gen(function* () {
8535
- yield* options.client.request({
8536
- body: {
8537
- content: JSON.stringify({ text }),
8538
- msg_type: "text"
8539
- },
8540
- method: "POST",
8541
- url: `${baseUrl}/open-apis/im/v1/messages/${encodeURIComponent(messageId)}/reply`
8542
- }, "Feishu reply failed");
8543
- }) };
8544
- }
8545
- //#endregion
8546
9165
  //#region src/infrastructure/feishu/feishu-automation-card-sender.ts
8547
9166
  function createConfiguredFeishuAutomationCardSender(options) {
8548
9167
  const baseUrl = options.config.feishu.baseUrl.replace(/\/$/, "");
@@ -8708,19 +9327,19 @@ async function loadSnapshots(filePath) {
8708
9327
  return interactions;
8709
9328
  }
8710
9329
  function readSnapshot(value, line) {
8711
- if (!isRecord$4(value) || value.version !== 1 || !isRecord$4(value.interaction)) throw new Error(`invalid human interaction envelope at line ${line}`);
9330
+ if (!isRecord$5(value) || value.version !== 1 || !isRecord$5(value.interaction)) throw new Error(`invalid human interaction envelope at line ${line}`);
8712
9331
  const interaction = value.interaction;
8713
9332
  if (!isHumanInteraction(interaction)) throw new Error(`invalid human interaction snapshot at line ${line}`);
8714
9333
  return structuredClone(interaction);
8715
9334
  }
8716
9335
  function isHumanInteraction(value) {
8717
- if (!nonEmptyStrings(value, "agentId", "createdAt", "expiresAt", "id", "instanceId", "runId", "sessionKey", "sourceMessageId", "summary", "tenantKey", "title") || !isTimestamp(value.createdAt) || !isTimestamp(value.expiresAt) || Date.parse(value.expiresAt) <= Date.parse(value.createdAt) || !Number.isInteger(value.revision) || value.revision < 1 || !isNonEmptyStringArray(value.allowedActorOpenIds) || !isFacts(value.facts) || !isRecord$4(value.state)) return false;
9336
+ if (!nonEmptyStrings(value, "agentId", "createdAt", "expiresAt", "id", "instanceId", "runId", "sessionKey", "sourceMessageId", "summary", "tenantKey", "title") || !isTimestamp(value.createdAt) || !isTimestamp(value.expiresAt) || Date.parse(value.expiresAt) <= Date.parse(value.createdAt) || !Number.isInteger(value.revision) || value.revision < 1 || !isNonEmptyStringArray(value.allowedActorOpenIds) || !isFacts(value.facts) || !isRecord$5(value.state)) return false;
8718
9337
  if (value.kind === "tool-approval") return isToolApprovalRequest(value.request) && isToolApprovalState(value.state) && stateRespectsInteraction(value.state, value);
8719
9338
  if (value.kind === "user-decision") return isUserDecisionOptions(value.options, value.recommendedOptionId) && isUserDecisionState(value.state, value.options) && stateRespectsInteraction(value.state, value);
8720
9339
  return false;
8721
9340
  }
8722
9341
  function isToolApprovalRequest(value) {
8723
- return isRecord$4(value) && nonEmptyStrings(value, "callId", "inputDigest", "operationId", "toolId", "toolVersion") && (value.risk === "observe" || value.risk === "mutate" || value.risk === "irreversible" || value.risk === "host-control");
9342
+ return isRecord$5(value) && nonEmptyStrings(value, "callId", "inputDigest", "operationId", "toolId", "toolVersion") && (value.risk === "observe" || value.risk === "mutate" || value.risk === "irreversible" || value.risk === "host-control");
8724
9343
  }
8725
9344
  function isToolApprovalState(state) {
8726
9345
  const common = isCommonInteractionState(state);
@@ -8734,7 +9353,7 @@ function isUserDecisionState(state, options) {
8734
9353
  const common = isCommonInteractionState(state);
8735
9354
  if (common !== void 0) return common;
8736
9355
  switch (state.status) {
8737
- case "selected": return isActor(state.actor) && isTimestamp(state.resolvedAt) && typeof state.optionId === "string" && Array.isArray(options) && options.some((option) => isRecord$4(option) && option.id === state.optionId);
9356
+ case "selected": return isActor(state.actor) && isTimestamp(state.resolvedAt) && typeof state.optionId === "string" && Array.isArray(options) && options.some((option) => isRecord$5(option) && option.id === state.optionId);
8738
9357
  default: return false;
8739
9358
  }
8740
9359
  }
@@ -8749,14 +9368,14 @@ function isCommonInteractionState(state) {
8749
9368
  }
8750
9369
  function isUserDecisionOptions(value, recommendedOptionId) {
8751
9370
  if (!Array.isArray(value) || value.length < 2 || value.length > 5) return false;
8752
- const ids = value.map((option) => isRecord$4(option) ? option.id : void 0);
8753
- return value.every((option) => isRecord$4(option) && nonEmptyStrings(option, "description", "id", "label")) && ids.every((id) => typeof id === "string") && new Set(ids).size === ids.length && (recommendedOptionId === void 0 || typeof recommendedOptionId === "string" && ids.includes(recommendedOptionId));
9371
+ const ids = value.map((option) => isRecord$5(option) ? option.id : void 0);
9372
+ return value.every((option) => isRecord$5(option) && nonEmptyStrings(option, "description", "id", "label")) && ids.every((id) => typeof id === "string") && new Set(ids).size === ids.length && (recommendedOptionId === void 0 || typeof recommendedOptionId === "string" && ids.includes(recommendedOptionId));
8754
9373
  }
8755
9374
  function isFacts(value) {
8756
- return Array.isArray(value) && value.every((fact) => isRecord$4(fact) && nonEmptyStrings(fact, "label", "value"));
9375
+ return Array.isArray(value) && value.every((fact) => isRecord$5(fact) && nonEmptyStrings(fact, "label", "value"));
8757
9376
  }
8758
9377
  function isActor(value) {
8759
- return isRecord$4(value) && nonEmptyStrings(value, "openId", "tenantKey");
9378
+ return isRecord$5(value) && nonEmptyStrings(value, "openId", "tenantKey");
8760
9379
  }
8761
9380
  function isNonEmptyStringArray(value) {
8762
9381
  return Array.isArray(value) && value.length > 0 && value.every((entry) => typeof entry === "string" && entry.length > 0);
@@ -8771,7 +9390,7 @@ function isTimestamp(value) {
8771
9390
  return typeof value === "string" && Number.isFinite(Date.parse(value));
8772
9391
  }
8773
9392
  function stateRespectsInteraction(state, interaction) {
8774
- if (isRecord$4(state.actor)) {
9393
+ if (isRecord$5(state.actor)) {
8775
9394
  if (state.actor.tenantKey !== interaction.tenantKey || !Array.isArray(interaction.allowedActorOpenIds) || !interaction.allowedActorOpenIds.includes(state.actor.openId)) return false;
8776
9395
  }
8777
9396
  const expiresAt = interaction.expiresAt;
@@ -8947,4 +9566,4 @@ function validateDecisionInput(input) {
8947
9566
  if (input.recommendedOptionId && !optionIds.includes(input.recommendedOptionId)) throw new Error("recommended user decision option must be available");
8948
9567
  }
8949
9568
  //#endregion
8950
- export { AgentContextBudgetExceeded, AgentEventHandlerFailed, AgentEventLogStoreError, AgentEventSinkFailed, AgentHarnessBusy, AgentInstanceBusy, AgentInstanceConflict, AgentLoopFailed, AgentMemoryError, AgentRunCancelled, AgentRuntimeDisposed, AutomationMandateError, BACKGROUND_SESSION_DELIVERY_JSONL_VERSION, BACKGROUND_SESSION_JSONL_VERSION, BACKGROUND_SESSION_SESSION_KEY_PREFIX, BACKGROUND_SESSION_START_TOOL_ID, BACKGROUND_SESSION_TOOL_IDS, BACKGROUND_SESSION_TOOL_PLUGIN_ID, BACKGROUND_SESSION_TOOL_VERSION, BackgroundSessionCallerDenied, BackgroundSessionDeliveryConflict, BackgroundSessionRepositoryConflict, BackgroundSessionRepositoryCorrupted, BackgroundSessionTransitionDenied, CardPresentationTransitionDenied, CompactionError, DEFAULT_BACKGROUND_SESSION_LEASE_MS, DEFAULT_BACKGROUND_SESSION_LEASE_RENEWAL_INTERVAL_MS, DEFAULT_BACKGROUND_SESSION_LIFETIME_MS, DEFAULT_BACKGROUND_SESSION_MAX_CONCURRENT_SESSIONS, DEFAULT_BACKGROUND_SESSION_MAX_CONSECUTIVE_FAILURES, DEFAULT_BACKGROUND_SESSION_RETRY_BACKOFF_MS, DEFAULT_BACKGROUND_SESSION_STEP_TIMEOUT_MS, DEFAULT_BACKGROUND_SESSION_SUPERVISOR_INTERVAL_MS, DEFAULT_CARD_STREAM_LEASE_MS, DelegationDenied, DeliveryOutboxError, FeishuCardPresentationNotFound, FeishuCardTargetNotFound, FeishuCardTargetRegistryStoreError, FeishuCotProtocolError, FeishuEndpointCredentialError, FeishuOpenApiError, FeishuTenantAccessTokenError, HumanInteractionRepositoryError, HumanInteractionTransitionDenied, InvalidFeishuCardAction, InvalidFeishuMessageContent, InvalidFeishuSessionReference, InvalidInvocationAuthority, InvalidProjectSkillCatalog, InvalidRecoveryAction, InvalidRivusEndpointBinding, InvalidRivusPlugin, InvalidRivusProjectSpace, InvalidStableJson, InvalidToolInput, InvalidWorkspaceRoot, LangfuseTelemetryConfigError, MEMORY_SCOPES, OpenClawEnvImportError, PluginStateConflict, RIVUS_MEMORY_TOOL_ID, RIVUS_MEMORY_TOOL_PLUGIN_ID, RIVUS_MEMORY_TOOL_VERSION, RIVUS_PLUGIN_API_VERSION, RivusDaemonConfigError, RivusDeploymentAutomationReadinessError, RivusDeploymentDaemonLifecycleError, RivusDeploymentManifestError, RivusDeploymentReadinessError, RivusPluginConformanceError, RivusPluginLoadError, RivusToolInputRejected, SessionSchedulerCapacityExceeded, SessionSchedulerDisposed, ToolInvocationDenied, UnsupportedFeishuCardAction, UnsupportedFeishuMessage, WorkspaceInstructionsSourceError, acceptsCardPresentationProgress, activeCardPresentation, appendBackgroundSessionInput, assembleAgentContext, assertRivusPluginConforms, backgroundSessionToolIds, claimBackgroundSession, commitAutomationOutcome, completeBackgroundSessionStep, completeBackgroundSessionStop, createAgentCommandFromFeishuCardAction, createAgentCommandFromFeishuMessage, createAgentConversationMessages, createAgentDomainEventHandler, createAgentDomainEventSink, createAgentDomainEventSinkFromCallback, createAgentHarness, createAgentHarnessClient, createAgentHarnessPooledRuntime, createAgentInstanceRegistry, createAgentLoopFromCallback, createAgentLoopModelExecutionEnd, createAgentLoopModelExecutionStart, createAgentLoopPromptTransformer, createAgentLoopSkillExecutionEnd, createAgentLoopSkillExecutionStart, createAgentLoopTextDelta, createAgentLoopThinkingDelta, createAgentLoopToolExecutionEnd, createAgentLoopToolExecutionStart, createAgentLoopToolExecutionUpdate, createAgentMemoryService, createAgentRunUpdateHandler, createAgentRuntime, createAgentRuntimePool, createAgentTranscriptMessages, createAgentTranscriptTurn, createAgentsMdInstructionsProvider, createAsyncIterableAgentLoop, createAutomationMandateStore, createBackgroundSession, createBackgroundSessionCard, createBackgroundSessionDeliveryStore, createBackgroundSessionHostTools, createBackgroundSessionKey, createBackgroundSessionRepository, createBackgroundSessionService, createBackgroundSessionStepSourceMessageId, createBackgroundSessionSupervisor, createBackgroundSessionToolContracts, createCoalescingFeishuPublisher, createCompactionService, createCompositeRivusDaemonTransport, createConfiguredFeishuAutomationCardSender, createConfiguredFeishuBackgroundSessionDelivery, createConfiguredFeishuCardKitPublisher, createConfiguredFeishuCardKitTargetCreator, createConfiguredFeishuCardRolloverRuntime, createConfiguredFeishuHumanInteractionPresenter, createConfiguredFeishuOpenApiClient, createConfiguredFeishuTextReplySender, createConfiguredRivusDaemonBootstrap, createConfiguredRivusDeploymentDaemon, createDailyAutomationSchedule, createDefaultAgentHarness, createDefaultAgentHarnessClient, createDefaultAgentHarnessClientFromCallback, createDefaultAgentHarnessClientFromTextCallback, createDefaultAgentHarnessFromCallback, createDefaultAgentHarnessFromTextCallback, createDefaultAgentRuntime, createDefaultAgentRuntimeFromCallback, createDefaultAgentRuntimeFromTextCallback, createDelegationService, createDeliveryOutbox, createEventAgentLoop, createFakeRivusPlugin, createFeishuAgentDaemon, createFeishuAgentRunCard, createFeishuAgentRuntime, createFeishuCardActionCallbackResponse, createFeishuCardActionErrorResponse, createFeishuCardDeliveryLedger, createFeishuCardDeliveryReconciler, createFeishuCardKitOpenApiClient, createFeishuCardKitOpenApiTargetCreator, createFeishuCardKitPublisher, createFeishuCardPresentationStore, createFeishuCardRollover, createFeishuCardRolloverSupervisor, createFeishuCardTargetPreparation, createFeishuConversationId, createFeishuCotPublisher, createFeishuDeploymentEndpoint, createFeishuEventHandlers, createFeishuHumanInteractionCard, createFeishuHumanInteractionPresenter, createFeishuInboxRepository, createFeishuMessageQueue, createFeishuMessageWorker, createFeishuOpenApiClient, createFeishuPeriodicFlush, createFeishuPresentationPreparation, createFeishuSessionKey, createFeishuStreamProjector, createFeishuTenantAccessTokenProvider, createFeishuWebSocketDaemon, createFeishuWorkerLoop, createFixedClock, createHumanInteractionEndpointRegistry, createHumanInteractionService, createHumanInteractionToolApprovalGateway, createInMemoryFeishuCardTargetRegistry, createInMemoryHumanInteractionRepository, createInvocationAuthority, createJsonFetchRequest, createJsonFileFeishuCardTargetRegistry, createJsonlAgentEventLog, createJsonlHumanInteractionRepository, createLangfuseAgentTelemetry, createLazyFeishuWebSocketEventDispatcher, createMemoryNamespace, createOpenTelemetryAgentEventSink, createOpenTelemetryAgentTelemetry, createPiAgentLoop, createPiSdkAgentLoop, createPiSessionRegistry, createPluginStateStore, createProjectMemoryPromptPreparer, createRateLimitedFeishuPublisher, createRecoveryAction, createRecoveryControl, createRivusAgentHost, createRivusDaemonProcess, createRivusDaemonShutdownController, createRivusDaemonStatusHttpServer, createRivusDaemonStatusReporter, createRivusDeploymentCliProcess, createRivusDeploymentDaemon, createRivusEnvFromOpenClawConfig, createRivusMemoryTool, createRivusMemoryToolContract, createRivusMemoryToolDescriptor, createRivusPluginCatalog, createRoutedHumanInteractionToolApprovalService, createScheduledAutomation, createSequenceRunIds, createSessionScheduler, createSubagentCoordinator, createSystemClock, createTelemetryContentRedactor, createTestClock, createTextAgentLoop, createTextAgentLoopFromCallback, createToolBroker, createToolInputDigest, createToolOperationLedger, createUuidRunIds, createWorkspaceRootHandle, describeFeishuMessageIntake, evolveAgentRun, extendBackgroundSessionDefinition, failBackgroundSessionStep, formatRivusEnvFile, initialAgentRunState, intersectToolIds, isAgentToolExecutionEvent, isAssistantTextDeltaEvent, isAssistantThinkingDeltaEvent, isBackgroundSessionDue, isBackgroundSessionLeaseExpired, isBackgroundSessionState, isBackgroundSessionTerminalPhase, isBackgroundSessionToolId, isCardPresentationHandoffDue, isTerminalAgentDomainEvent, isTerminalAgentRunPhase, loadNodeRivusPluginModule, loadRivusDaemonConfig, loadRivusDeployment, loadRivusDeploymentManifest, narrowBackgroundSessionDefinition, normalizeStableJson, openJsonAutomationTickRepository, openJsonlAgentMemoryService, openJsonlBackgroundSessionDeliveryStore, openJsonlBackgroundSessionRepository, openJsonlFeishuCardDeliveryLedger, openJsonlFeishuInboxRepository, openJsonlRecoveryControl, openJsonlToolOperationLedger, parkBackgroundSessionForReconciliation, progressDeliveryId, releaseBackgroundSessionLease, renewBackgroundSessionLease, replayAgentHistory, replayAgentTranscript, requestBackgroundSessionStop, requeueInterruptedBackgroundSessionStep, requiresToolApproval, resolveBackgroundSessionReconciliation, resolveBackgroundSessionSupervisorIntervalMs, resolveFeishuDeliveryChatId, resolveFeishuEndpointCredentials, resolveLangfuseTelemetryConfig, resolveRivusAgentDefinition, resolveRivusProjectSpace, restoreAgentHistory, restoreConfiguredRivusDaemonBootstrap, restrictMemoryScopesForAudience, runRivusDaemonCli, sessionIdFromSessionKey, shouldAcceptFeishuEndpointMessage, suspendBackgroundSession, terminalDeliveryId, transitionHumanInteraction, validateProjectSkillCatalog, validateProjectSkillCommand, validateRivusDeploymentManifest };
9569
+ export { AgentContextBudgetExceeded, AgentEventHandlerFailed, AgentEventLogStoreError, AgentEventSinkFailed, AgentHarnessBusy, AgentInstanceBusy, AgentInstanceConflict, AgentLoopFailed, AgentMemoryError, AgentRunCancelled, AgentRuntimeDisposed, AutomationMandateError, BACKGROUND_SESSION_DELIVERY_JSONL_VERSION, BACKGROUND_SESSION_JSONL_VERSION, BACKGROUND_SESSION_SESSION_KEY_PREFIX, BACKGROUND_SESSION_START_TOOL_ID, BACKGROUND_SESSION_TOOL_IDS, BACKGROUND_SESSION_TOOL_PLUGIN_ID, BACKGROUND_SESSION_TOOL_VERSION, BackgroundSessionCallerDenied, BackgroundSessionDeliveryConflict, BackgroundSessionRepositoryConflict, BackgroundSessionRepositoryCorrupted, BackgroundSessionTransitionDenied, CardPresentationTransitionDenied, CompactionError, DEFAULT_BACKGROUND_SESSION_LEASE_MS, DEFAULT_BACKGROUND_SESSION_LEASE_RENEWAL_INTERVAL_MS, DEFAULT_BACKGROUND_SESSION_LIFETIME_MS, DEFAULT_BACKGROUND_SESSION_MAX_CONCURRENT_SESSIONS, DEFAULT_BACKGROUND_SESSION_MAX_CONSECUTIVE_FAILURES, DEFAULT_BACKGROUND_SESSION_RETRY_BACKOFF_MS, DEFAULT_BACKGROUND_SESSION_STEP_TIMEOUT_MS, DEFAULT_BACKGROUND_SESSION_SUPERVISOR_INTERVAL_MS, DEFAULT_CARD_STREAM_LEASE_MS, DelegationDenied, DeliveryOutboxError, FeishuCardPresentationNotFound, FeishuCardTargetNotFound, FeishuCardTargetRegistryStoreError, FeishuCotProtocolError, FeishuEndpointCredentialError, FeishuOpenApiError, FeishuTenantAccessTokenError, HumanInteractionRepositoryError, HumanInteractionTransitionDenied, InvalidFeishuCardAction, InvalidFeishuMessageContent, InvalidFeishuSessionReference, InvalidFeishuTopicContext, InvalidInvocationAuthority, InvalidProjectSkillCatalog, InvalidRecoveryAction, InvalidRivusEndpointBinding, InvalidRivusPlugin, InvalidRivusProjectSpace, InvalidStableJson, InvalidToolInput, InvalidWorkspaceRoot, LangfuseTelemetryConfigError, MEMORY_SCOPES, OpenClawEnvImportError, PluginStateConflict, RIVUS_MEMORY_TOOL_ID, RIVUS_MEMORY_TOOL_PLUGIN_ID, RIVUS_MEMORY_TOOL_VERSION, RIVUS_PLUGIN_API_VERSION, RivusDaemonConfigError, RivusDeploymentAutomationReadinessError, RivusDeploymentDaemonLifecycleError, RivusDeploymentManifestError, RivusDeploymentReadinessError, RivusPluginConformanceError, RivusPluginLoadError, RivusToolInputRejected, SessionSchedulerCapacityExceeded, SessionSchedulerDisposed, ToolInvocationDenied, UnsupportedFeishuCardAction, UnsupportedFeishuMessage, WorkspaceInstructionsSourceError, acceptsCardPresentationProgress, activeCardPresentation, appendBackgroundSessionInput, assembleAgentContext, assertRivusPluginConforms, backgroundSessionToolIds, claimBackgroundSession, commitAutomationOutcome, completeBackgroundSessionStep, completeBackgroundSessionStop, composeFeishuTopicPrompt, createAgentCommandFromFeishuCardAction, createAgentCommandFromFeishuMessage, createAgentConversationMessages, createAgentDomainEventHandler, createAgentDomainEventSink, createAgentDomainEventSinkFromCallback, createAgentHarness, createAgentHarnessClient, createAgentHarnessPooledRuntime, createAgentInstanceRegistry, createAgentLoopFromCallback, createAgentLoopModelExecutionEnd, createAgentLoopModelExecutionStart, createAgentLoopPromptTransformer, createAgentLoopSkillExecutionEnd, createAgentLoopSkillExecutionStart, createAgentLoopTextDelta, createAgentLoopThinkingDelta, createAgentLoopToolExecutionEnd, createAgentLoopToolExecutionStart, createAgentLoopToolExecutionUpdate, createAgentMemoryService, createAgentRunUpdateHandler, createAgentRuntime, createAgentRuntimePool, createAgentTranscriptMessages, createAgentTranscriptTurn, createAgentsMdInstructionsProvider, createAsyncIterableAgentLoop, createAutomationMandateStore, createBackgroundSession, createBackgroundSessionCard, createBackgroundSessionDeliveryStore, createBackgroundSessionHostTools, createBackgroundSessionKey, createBackgroundSessionRepository, createBackgroundSessionService, createBackgroundSessionStepSourceMessageId, createBackgroundSessionSupervisor, createBackgroundSessionToolContracts, createCoalescingFeishuPublisher, createCompactionService, createCompositeRivusDaemonTransport, createConfiguredFeishuAutomationCardSender, createConfiguredFeishuBackgroundSessionDelivery, createConfiguredFeishuCardKitPublisher, createConfiguredFeishuCardKitTargetCreator, createConfiguredFeishuCardRolloverRuntime, createConfiguredFeishuHumanInteractionPresenter, createConfiguredFeishuMessageReactionSender, createConfiguredFeishuOpenApiClient, createConfiguredFeishuTextReplySender, createConfiguredRivusDaemonBootstrap, createConfiguredRivusDeploymentDaemon, createDailyAutomationSchedule, createDefaultAgentHarness, createDefaultAgentHarnessClient, createDefaultAgentHarnessClientFromCallback, createDefaultAgentHarnessClientFromTextCallback, createDefaultAgentHarnessFromCallback, createDefaultAgentHarnessFromTextCallback, createDefaultAgentRuntime, createDefaultAgentRuntimeFromCallback, createDefaultAgentRuntimeFromTextCallback, createDelegationService, createDeliveryOutbox, createEventAgentLoop, createFakeRivusPlugin, createFeishuAgentDaemon, createFeishuAgentRunCard, createFeishuAgentRuntime, createFeishuCardActionCallbackResponse, createFeishuCardActionErrorResponse, createFeishuCardDeliveryLedger, createFeishuCardDeliveryReconciler, createFeishuCardKitOpenApiClient, createFeishuCardKitOpenApiTargetCreator, createFeishuCardKitPublisher, createFeishuCardPresentationStore, createFeishuCardRollover, createFeishuCardRolloverSupervisor, createFeishuCardTargetPreparation, createFeishuConversationId, createFeishuCotPublisher, createFeishuDeploymentEndpoint, createFeishuEventHandlers, createFeishuHumanInteractionCard, createFeishuHumanInteractionPresenter, createFeishuInboxRepository, createFeishuMessageQueue, createFeishuMessageWorker, createFeishuOpenApiClient, createFeishuPeriodicFlush, createFeishuPresentationPreparation, createFeishuSessionKey, createFeishuSessionStore, createFeishuStreamProjector, createFeishuTenantAccessTokenProvider, createFeishuTopicContextResolver, createFeishuWebSocketDaemon, createFeishuWorkerLoop, createFixedClock, createHumanInteractionEndpointRegistry, createHumanInteractionService, createHumanInteractionToolApprovalGateway, createInMemoryFeishuCardTargetRegistry, createInMemoryHumanInteractionRepository, createInvocationAuthority, createJsonFetchRequest, createJsonFileFeishuCardTargetRegistry, createJsonlAgentEventLog, createJsonlHumanInteractionRepository, createLangfuseAgentTelemetry, createLazyFeishuWebSocketEventDispatcher, createMemoryNamespace, createOpenTelemetryAgentEventSink, createOpenTelemetryAgentTelemetry, createPiAgentLoop, createPiSdkAgentLoop, createPiSessionRegistry, createPluginStateStore, createProjectMemoryPromptPreparer, createRateLimitedFeishuPublisher, createRecoveryAction, createRecoveryControl, createRivusAgentHost, createRivusDaemonProcess, createRivusDaemonShutdownController, createRivusDaemonStatusHttpServer, createRivusDaemonStatusReporter, createRivusDeploymentCliProcess, createRivusDeploymentDaemon, createRivusEnvFromOpenClawConfig, createRivusMemoryTool, createRivusMemoryToolContract, createRivusMemoryToolDescriptor, createRivusPluginCatalog, createRoutedHumanInteractionToolApprovalService, createScheduledAutomation, createSequenceRunIds, createSessionScheduler, createSubagentCoordinator, createSystemClock, createTelemetryContentRedactor, createTestClock, createTextAgentLoop, createTextAgentLoopFromCallback, createToolBroker, createToolInputDigest, createToolOperationLedger, createUuidRunIds, createWorkspaceRootHandle, describeFeishuMessageIntake, evolveAgentRun, extendBackgroundSessionDefinition, failBackgroundSessionStep, formatRivusEnvFile, initialAgentRunState, intersectToolIds, isAgentToolExecutionEvent, isAssistantTextDeltaEvent, isAssistantThinkingDeltaEvent, isBackgroundSessionDue, isBackgroundSessionLeaseExpired, isBackgroundSessionState, isBackgroundSessionTerminalPhase, isBackgroundSessionToolId, isCardPresentationHandoffDue, isTerminalAgentDomainEvent, isTerminalAgentRunPhase, loadNodeRivusPluginModule, loadRivusDaemonConfig, loadRivusDeployment, loadRivusDeploymentManifest, narrowBackgroundSessionDefinition, normalizeStableJson, openJsonAutomationTickRepository, openJsonFeishuSessionStore, openJsonlAgentMemoryService, openJsonlBackgroundSessionDeliveryStore, openJsonlBackgroundSessionRepository, openJsonlFeishuCardDeliveryLedger, openJsonlFeishuInboxRepository, openJsonlRecoveryControl, openJsonlToolOperationLedger, parkBackgroundSessionForReconciliation, progressDeliveryId, readFeishuMessageContent, releaseBackgroundSessionLease, renewBackgroundSessionLease, replayAgentHistory, replayAgentTranscript, requestBackgroundSessionStop, requeueInterruptedBackgroundSessionStep, requiresToolApproval, resolveBackgroundSessionReconciliation, resolveBackgroundSessionSupervisorIntervalMs, resolveFeishuDeliveryChatId, resolveFeishuEndpointCredentials, resolveLangfuseTelemetryConfig, resolveRivusAgentDefinition, resolveRivusProjectSpace, restoreAgentHistory, restoreConfiguredRivusDaemonBootstrap, restrictMemoryScopesForAudience, runRivusDaemonCli, sessionIdFromSessionKey, shouldAcceptFeishuEndpointMessage, suspendBackgroundSession, terminalDeliveryId, transitionHumanInteraction, validateProjectSkillCatalog, validateProjectSkillCommand, validateRivusDeploymentManifest };